@@ -3,3 +3,5 @@ roms/* | |||||
!roms/README | !roms/README | ||||
crater | crater | ||||
crater-dev | crater-dev | ||||
tests/runner | |||||
tests/asm/*.gg |
@@ -1,4 +1,4 @@ | |||||
Copyright (C) 2014-2015 Ben Kurtovic <ben.kurtovic@gmail.com> | |||||
Copyright (C) 2014-2016 Ben Kurtovic <ben.kurtovic@gmail.com> | |||||
Permission is hereby granted, free of charge, to any person obtaining a copy | Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
of this software and associated documentation files (the "Software"), to deal | of this software and associated documentation files (the "Software"), to deal | ||||
@@ -24,12 +24,19 @@ Installing | |||||
---------- | ---------- | ||||
Only OS X and Linux are tested. You'll need a modern compiler that supports C11 | Only OS X and Linux are tested. You'll need a modern compiler that supports C11 | ||||
(clang preferred) and SDL 2. Using Homebrew, you can `brew install sdl2`; using | |||||
apt, you can `apt-get install libsdl2-dev`. | |||||
([clang][clang] preferred) and [SDL 2][sdl2]. Using Homebrew, you can | |||||
`brew install sdl2`; using apt, you can `apt-get install libsdl2-dev`. | |||||
Run `make` to create `./crater`. To build the development version with debug | Run `make` to create `./crater`. To build the development version with debug | ||||
symbols (they can exist simultaneously), run `make DEBUG=1`, which creates | |||||
`./crater-dev`. This also enables the printing of debugging info to stdout. | |||||
symbols and extra diagnostic info (they can exist simultaneously), run | |||||
`make DEBUG=1`, which creates `./crater-dev`. | |||||
crater has a number of test cases. Run the entire suite with `make test`; | |||||
individual components can be tested by doing `make test-{component}`, where | |||||
`{component}` is one of `cpu`, `vdp`, `psg`, `asm`, `dis`, or `integrate`. | |||||
[clang]: http://clang.llvm.org/ | |||||
[sdl2]: https://www.libsdl.org/ | |||||
Usage | Usage | ||||
----- | ----- | ||||
@@ -1,50 +1,59 @@ | |||||
# Copyright (C) 2014 Ben Kurtovic <ben.kurtovic@gmail.com> | |||||
# Copyright (C) 2014-2016 Ben Kurtovic <ben.kurtovic@gmail.com> | |||||
# Released under the terms of the MIT License. See LICENSE for details. | # Released under the terms of the MIT License. See LICENSE for details. | ||||
PROGRAM = crater | PROGRAM = crater | ||||
SOURCES = src | SOURCES = src | ||||
BUILD = build | BUILD = build | ||||
DEVEXT = -dev | DEVEXT = -dev | ||||
TESTS = cpu vdp psg asm dis integrate | |||||
CC = clang | CC = clang | ||||
FLAGS = -O2 -Wall -Wextra -pedantic -std=c11 | FLAGS = -O2 -Wall -Wextra -pedantic -std=c11 | ||||
CFLAGS = $(shell sdl2-config --cflags) | CFLAGS = $(shell sdl2-config --cflags) | ||||
LIBS = $(shell sdl2-config --libs) | LIBS = $(shell sdl2-config --libs) | ||||
DFLAGS = -g -DDEBUG_MODE | |||||
MKDIR = mkdir -p | MKDIR = mkdir -p | ||||
RM = rm -rf | RM = rm -rf | ||||
ASM_UP = scripts/update_asm_instructions.py | ASM_UP = scripts/update_asm_instructions.py | ||||
MODE = release | MODE = release | ||||
BNRY = $(PROGRAM) | BNRY = $(PROGRAM) | ||||
FLGS = $(FLAGS) | |||||
SDRS = $(shell find $(SOURCES) -type d | xargs echo) | SDRS = $(shell find $(SOURCES) -type d | xargs echo) | ||||
SRCS = $(filter-out %.inc.c,$(foreach d,. $(SDRS),$(wildcard $(addprefix $(d)/*,.c)))) | SRCS = $(filter-out %.inc.c,$(foreach d,. $(SDRS),$(wildcard $(addprefix $(d)/*,.c)))) | ||||
OBJS = $(patsubst %.c,%.o,$(addprefix $(BUILD)/$(MODE)/,$(SRCS))) | OBJS = $(patsubst %.c,%.o,$(addprefix $(BUILD)/$(MODE)/,$(SRCS))) | ||||
DEPS = $(OBJS:%.o=%.d) | DEPS = $(OBJS:%.o=%.d) | ||||
DIRS = $(sort $(dir $(OBJS))) | DIRS = $(sort $(dir $(OBJS))) | ||||
TCPS = $(addprefix test-,$(TESTS)) | |||||
ifdef DEBUG | ifdef DEBUG | ||||
BNRY := $(BNRY)$(DEVEXT) | |||||
FLAGS += -g -DDEBUG_MODE | |||||
MODE = debug | |||||
BNRY := $(BNRY)$(DEVEXT) | |||||
FLGS += $(DFLAGS) | |||||
MODE = debug | |||||
endif | endif | ||||
.PHONY: all clean test test-all test-z80 test-asm test-dasm | |||||
export CC | |||||
export FLAGS | |||||
export RM | |||||
.PHONY: all clean test tests test-prereqs test-make-prereqs $(TCPS) | |||||
all: $(BNRY) | all: $(BNRY) | ||||
clean: | clean: | ||||
$(RM) $(BUILD) $(PROGRAM) $(PROGRAM)$(DEVEXT) | $(RM) $(BUILD) $(PROGRAM) $(PROGRAM)$(DEVEXT) | ||||
@$(MAKE) -C tests clean | |||||
$(DIRS): | $(DIRS): | ||||
$(MKDIR) $@ | $(MKDIR) $@ | ||||
$(BNRY): $(OBJS) | $(BNRY): $(OBJS) | ||||
$(CC) $(FLAGS) $(LIBS) $(OBJS) -o $@ | |||||
$(CC) $(FLGS) $(LIBS) $(OBJS) -o $@ | |||||
$(OBJS): | $(DIRS) | $(OBJS): | $(DIRS) | ||||
$(BUILD)/$(MODE)/%.o: %.c | $(BUILD)/$(MODE)/%.o: %.c | ||||
$(CC) $(FLAGS) $(CFLAGS) -MMD -MP -c $< -o $@ | |||||
$(CC) $(FLGS) $(CFLAGS) -MMD -MP -c $< -o $@ | |||||
-include $(DEPS) | -include $(DEPS) | ||||
@@ -52,16 +61,16 @@ ASM_INST = $(SOURCES)/assembler/instructions | |||||
$(ASM_INST).inc.c: $(ASM_INST).yml $(ASM_UP) | $(ASM_INST).inc.c: $(ASM_INST).yml $(ASM_UP) | ||||
python $(ASM_UP) | python $(ASM_UP) | ||||
test: test-all test-z80 test-asm test-dasm | |||||
test-prereqs: $(PROGRAM) | |||||
@: # No-op; prevents make from cluttering output with "X is up to date" | |||||
test-all: | |||||
@echo "running all tests" | |||||
test-make-prereqs: | |||||
@$(MAKE) test-prereqs DEBUG= | |||||
test-z80: | |||||
@echo "running Z80 CPU tests" | |||||
test: test-make-prereqs | |||||
@$(MAKE) -C tests -s all | |||||
test-asm: | |||||
@echo "running assembler tests" | |||||
tests: test | |||||
test-dasm: | |||||
@echo "running disassembler tests" | |||||
$(TCPS): test-make-prereqs | |||||
@$(MAKE) -C tests -s $(subst test-,,$@) |
@@ -98,8 +98,6 @@ static ASMErrorDesc parse_inst_##mnemonic( \ | |||||
#define INST_INDEX_PREFIX(n) INST_PREFIX_(INST_INDEX(n).reg) | #define INST_INDEX_PREFIX(n) INST_PREFIX_(INST_INDEX(n).reg) | ||||
/* ----------------------------- END WORK BLOCK ---------------------------- */ | |||||
/* | /* | ||||
Fill an instruction's byte array with the given data. | Fill an instruction's byte array with the given data. | ||||
@@ -276,6 +276,21 @@ static bool parse_space( | |||||
} | } | ||||
/* | /* | ||||
Parse a string like parse_string(), but null-terminate it. | |||||
*/ | |||||
static bool parse_cstring( | |||||
char **result, size_t *length, const char *arg, ssize_t size) | |||||
{ | |||||
if (!parse_string(result, length, arg, size)) | |||||
return false; | |||||
(*length)++; | |||||
*result = cr_realloc(*result, sizeof(char) * (*length)); | |||||
(*result)[*length - 1] = '\0'; | |||||
return true; | |||||
} | |||||
/* | |||||
Parse data encoded in a line into an ASMData object. | Parse data encoded in a line into an ASMData object. | ||||
On success, return NULL and store the instruction in *data_ptr. On failure, | On success, return NULL and store the instruction in *data_ptr. On failure, | ||||
@@ -285,7 +300,7 @@ static ErrorInfo* parse_data( | |||||
const ASMLine *line, ASMData **data_ptr, size_t offset) | const ASMLine *line, ASMData **data_ptr, size_t offset) | ||||
{ | { | ||||
const char *directive; | const char *directive; | ||||
parser_func parser = (parser_func) parse_string; | |||||
parser_func parser; | |||||
if (IS_DIRECTIVE(line, DIR_BYTE)) { | if (IS_DIRECTIVE(line, DIR_BYTE)) { | ||||
directive = DIR_BYTE; | directive = DIR_BYTE; | ||||
@@ -295,10 +310,13 @@ static ErrorInfo* parse_data( | |||||
parser = parse_space; | parser = parse_space; | ||||
} else if (IS_DIRECTIVE(line, DIR_ASCII)) { | } else if (IS_DIRECTIVE(line, DIR_ASCII)) { | ||||
directive = DIR_ASCII; | directive = DIR_ASCII; | ||||
parser = (parser_func) parse_string; | |||||
} else if (IS_DIRECTIVE(line, DIR_ASCIZ)) { | } else if (IS_DIRECTIVE(line, DIR_ASCIZ)) { | ||||
directive = DIR_ASCIZ; | directive = DIR_ASCIZ; | ||||
parser = (parser_func) parse_cstring; | |||||
} else if (IS_DIRECTIVE(line, DIR_ASCIIZ)) { | } else if (IS_DIRECTIVE(line, DIR_ASCIIZ)) { | ||||
directive = DIR_ASCIIZ; | directive = DIR_ASCIIZ; | ||||
parser = (parser_func) parse_cstring; | |||||
} else { | } else { | ||||
return error_info_create(line, ET_PREPROC, ED_PP_UNKNOWN); | return error_info_create(line, ET_PREPROC, ED_PP_UNKNOWN); | ||||
} | } | ||||
@@ -90,7 +90,7 @@ static int get_rom_paths(char ***path_ptr) | |||||
} | } | ||||
closedir(dirp); | closedir(dirp); | ||||
} else { | } else { | ||||
WARN_ERRNO("couldn't open 'roms/'") | |||||
WARN_ERRNO("couldn't open '" ROMS_DIR "/'") | |||||
} | } | ||||
*path_ptr = paths; | *path_ptr = paths; | ||||
return npaths; | return npaths; | ||||
@@ -10,12 +10,13 @@ | |||||
/* Internal usage only */ | /* Internal usage only */ | ||||
#define LOG_MSG_(dest, level, extra, after, ...) { \ | |||||
#define LOG_MSG_(dest, level, extra, after, ...) \ | |||||
do { \ | |||||
fprintf(dest, level ": " __VA_ARGS__); \ | fprintf(dest, level ": " __VA_ARGS__); \ | ||||
extra; \ | extra; \ | ||||
fprintf(dest, "\n"); \ | fprintf(dest, "\n"); \ | ||||
after; \ | after; \ | ||||
} | |||||
} while (0); | |||||
#define LOG_ERR_(...) LOG_MSG_(stderr, __VA_ARGS__) | #define LOG_ERR_(...) LOG_MSG_(stderr, __VA_ARGS__) | ||||
#define LOG_OUT_(...) LOG_MSG_(stdout, __VA_ARGS__) | #define LOG_OUT_(...) LOG_MSG_(stdout, __VA_ARGS__) | ||||
@@ -32,7 +32,7 @@ static void print_header(const uint8_t *header) | |||||
snprintf(&header_chr[3 * i], 3, "%2c", header[i]); | snprintf(&header_chr[3 * i], 3, "%2c", header[i]); | ||||
else { | else { | ||||
header_chr[3 * i] = ' '; | header_chr[3 * i] = ' '; | ||||
header_chr[3 * i + 1] = '?'; | |||||
header_chr[3 * i + 1] = '.'; | |||||
} | } | ||||
header_hex[3 * i + 2] = header_chr[3 * i + 2] = ' '; | header_hex[3 * i + 2] = header_chr[3 * i + 2] = ' '; | ||||
} | } | ||||
@@ -1,4 +1,4 @@ | |||||
/* Copyright (C) 2014-2015 Ben Kurtovic <ben.kurtovic@gmail.com> | |||||
/* Copyright (C) 2014-2016 Ben Kurtovic <ben.kurtovic@gmail.com> | |||||
Released under the terms of the MIT License. See LICENSE for details. */ | Released under the terms of the MIT License. See LICENSE for details. */ | ||||
/* | /* | ||||
@@ -0,0 +1,16 @@ | |||||
;; Copyright (C) 2016 Ben Kurtovic <ben.kurtovic@gmail.com> | |||||
;; Released under the terms of the MIT License. See LICENSE for details. | |||||
; ----- CRATER UNIT TESTING SUITE --------------------------------------------- | |||||
; 02-headers1.asm | |||||
; Basic test for headers and other directives, mostly using default values | |||||
.rom_size auto | |||||
.rom_header auto | |||||
.rom_product 0 | |||||
.rom_version 0 | |||||
.rom_region "GG Export" | |||||
.rom_checksum off | |||||
.rom_declsize auto | |||||
.cross_blocks auto |
@@ -0,0 +1,16 @@ | |||||
;; Copyright (C) 2016 Ben Kurtovic <ben.kurtovic@gmail.com> | |||||
;; Released under the terms of the MIT License. See LICENSE for details. | |||||
; ----- CRATER UNIT TESTING SUITE --------------------------------------------- | |||||
; 03-headers2.asm | |||||
; Header/directive test using non-default values | |||||
.rom_size "64 KB" | |||||
.rom_header $7FF0 | |||||
.rom_product 101893 | |||||
.rom_version 3 | |||||
.rom_region "GG International" | |||||
.rom_checksum on | |||||
.rom_declsize "32 KB" | |||||
.cross_blocks off |
@@ -0,0 +1,21 @@ | |||||
;; Copyright (C) 2016 Ben Kurtovic <ben.kurtovic@gmail.com> | |||||
;; Released under the terms of the MIT License. See LICENSE for details. | |||||
; ----- CRATER UNIT TESTING SUITE --------------------------------------------- | |||||
; 04-basic.asm | |||||
; Basic instruction test | |||||
.org $0000 | |||||
main: | |||||
di | |||||
ld a, $23 | |||||
inc a | |||||
call foo | |||||
foo: | |||||
push bc | |||||
xor c | |||||
ret z | |||||
rrca | |||||
ret |
@@ -0,0 +1,17 @@ | |||||
;; Copyright (C) 2016 Ben Kurtovic <ben.kurtovic@gmail.com> | |||||
;; Released under the terms of the MIT License. See LICENSE for details. | |||||
; ----- CRATER UNIT TESTING SUITE --------------------------------------------- | |||||
; 05-includes.asm | |||||
; Source file inclusion test, involving cross-file label references and complex | |||||
; origins | |||||
.include "05.inc1.asm" | |||||
.org $0000 | |||||
main: | |||||
di | |||||
ld c, $FA | |||||
inc c | |||||
call bar |
@@ -0,0 +1,13 @@ | |||||
.org $0100 | |||||
.include "05.inc2.asm" | |||||
.include "05.inc3.asm" | |||||
bar: | |||||
push de | |||||
call blah | |||||
xor d | |||||
ret pe | |||||
call testfunc | |||||
exx | |||||
ret |
@@ -0,0 +1,3 @@ | |||||
testfunc: | |||||
sub b | |||||
ret |
@@ -0,0 +1,2 @@ | |||||
.include "05.inc4.asm" | |||||
.rom_product 57005 |
@@ -0,0 +1,3 @@ | |||||
blah: | |||||
ei | |||||
ret |
@@ -0,0 +1,16 @@ | |||||
;; Copyright (C) 2016 Ben Kurtovic <ben.kurtovic@gmail.com> | |||||
;; Released under the terms of the MIT License. See LICENSE for details. | |||||
; ----- CRATER UNIT TESTING SUITE --------------------------------------------- | |||||
; 06-formatting.asm | |||||
; Complex formatting test, involving strange indentation and newline usage | |||||
foo: .inCLUde "06.inc.asm" | |||||
.ORG $0100 | |||||
ayy: lmao: | |||||
di | |||||
InC a | |||||
ret |
@@ -0,0 +1,13 @@ | |||||
test1:test2: test3: test4: | |||||
inc a | |||||
inc b | |||||
inc c | |||||
ret | |||||
@@ -0,0 +1,31 @@ | |||||
;; Copyright (C) 2016 Ben Kurtovic <ben.kurtovic@gmail.com> | |||||
;; Released under the terms of the MIT License. See LICENSE for details. | |||||
; ----- CRATER UNIT TESTING SUITE --------------------------------------------- | |||||
; 07-data.asm | |||||
; Test of data directives to fill ROM with non-code bytes | |||||
.org $1000 | |||||
str1: .ascii "Hello, world!" | |||||
str2: .asciz "World, hello!" | |||||
str3: .asciiz "foobar" | |||||
arr1: .byte 1 2 3 4 5 6 | |||||
arr2: .byte 14 $14 $FF 255 $F0 | |||||
arr3: .byte 8, $10, 4 5 6, 8, 10 | |||||
void1: .space 3 | |||||
void2: .space 6 $D0 | |||||
void3: .space 128 $DE | |||||
.org $0000 | |||||
main: | |||||
di | |||||
loop: | |||||
jp loop | |||||
.org $0066 | |||||
nmi: | |||||
retn |
@@ -0,0 +1,103 @@ | |||||
;; Copyright (C) 2016 Ben Kurtovic <ben.kurtovic@gmail.com> | |||||
;; Released under the terms of the MIT License. See LICENSE for details. | |||||
; ----- CRATER UNIT TESTING SUITE --------------------------------------------- | |||||
; 08-instructions.asm | |||||
; Exhaustive test of instruction syntax | |||||
.define COUNTER $C010 | |||||
.define OFFSET 6 | |||||
.define OFFSET2 157 | |||||
.org $1000 | |||||
str1: .ascii "Hello, world!" | |||||
.org $0000 | |||||
main: | |||||
di | |||||
call inst | |||||
halt | |||||
.org $0066 | |||||
nmi: | |||||
retn | |||||
.org $2000 | |||||
inst: | |||||
inc a | |||||
inc c | |||||
inc hl | |||||
inc sp | |||||
inc (hl) | |||||
inc ( hl ) | |||||
inc ix | |||||
inc iyl | |||||
inc (ix) | |||||
inc ( ix ) | |||||
inc (iy+0) | |||||
inc (ix+3) | |||||
inc (ix+OFFSET) | |||||
inc (ix-OFFSET) | |||||
inc ( ix + 7 ) | |||||
inc (ix-8) | |||||
inc (ix - +9) | |||||
inc (iy+127) | |||||
inc (iy-128) | |||||
add a, e | |||||
add a, 5 | |||||
add a, $12 | |||||
add a, (hl) | |||||
add a, (ix+4) | |||||
add hl, bc | |||||
add ix, de | |||||
adc a, e | |||||
adc a, 5 | |||||
adc a, $12 | |||||
adc a, (hl) | |||||
adc a, (ix+4) | |||||
adc hl, bc | |||||
rlca | |||||
rrca | |||||
rla | |||||
rra | |||||
daa | |||||
cpl | |||||
scf | |||||
ccf | |||||
halt | |||||
exx | |||||
ei | |||||
di | |||||
cpd | |||||
cpdr | |||||
cpi | |||||
cpir | |||||
ind | |||||
indr | |||||
ini | |||||
inir | |||||
ldd | |||||
lddr | |||||
ldi | |||||
ldir | |||||
otdr | |||||
otir | |||||
outd | |||||
outi | |||||
rrd | |||||
rld | |||||
ld hl, COUNTER ; H contains $C0, L contains $10 | |||||
ld d, (hl) ; D contains $FF (garbage) | |||||
ld c, $3B ; C contains $3B | |||||
ld (hl), c ; memory address $C010 contains $3B | |||||
ld hl, str1 | |||||
ld b, (hl) ; B contains 'H' | |||||
ld hl, (str1) ; H contains 'e', L contains 'H' | |||||
ld (hl), b ; error, writing to cartridge ROM |
@@ -0,0 +1,8 @@ | |||||
01-empty.asm 01-empty.gg | |||||
02-headers1.asm 02-headers1.gg | |||||
03-headers2.asm 03-headers2.gg | |||||
04-basic.asm 04-basic.gg | |||||
05-includes.asm 05-includes.gg | |||||
06-formatting.asm 06-formatting.gg | |||||
07-data.asm 07-data.gg | |||||
08-instructions.asm 08-instructions.gg |
@@ -0,0 +1,27 @@ | |||||
# Copyright (C) 2014-2016 Ben Kurtovic <ben.kurtovic@gmail.com> | |||||
# Released under the terms of the MIT License. See LICENSE for details. | |||||
RUNNER = runner | |||||
COMPONENTS = cpu vdp psg asm dis integrate | |||||
.PHONY: all clean $(COMPONENTS) | |||||
all: $(COMPONENTS) | |||||
clean: | |||||
$(RM) $(RUNNER) | |||||
$(RM) asm/*.gg | |||||
$(RUNNER): $(RUNNER).c | |||||
$(CC) $(FLAGS) $< -o $@ | |||||
$(COMPONENTS): $(RUNNER) | |||||
./$(RUNNER) $@ | |||||
asm: asm-unarchive | |||||
asm-archive: | |||||
tar -czf asm/roms.tar.gz asm/*.gg | |||||
asm-unarchive: | |||||
tar -xf asm/roms.tar.gz |
@@ -1,4 +1,274 @@ | |||||
/* Copyright (C) 2014-2015 Ben Kurtovic <ben.kurtovic@gmail.com> | |||||
/* Copyright (C) 2014-2016 Ben Kurtovic <ben.kurtovic@gmail.com> | |||||
Released under the terms of the MIT License. See LICENSE for details. */ | Released under the terms of the MIT License. See LICENSE for details. */ | ||||
// ... | |||||
#include <stdbool.h> | |||||
#include <stdio.h> | |||||
#include <stdlib.h> | |||||
#include <string.h> | |||||
#include <unistd.h> | |||||
#include "../src/logging.h" | |||||
#include "../src/util.h" | |||||
#define ASM_PREFIX "asm/" | |||||
#define ASM_OUTFILE ASM_PREFIX ".output.gg" | |||||
/* Helper macros for reporting test passings/failures */ | |||||
#define PASS_TEST() \ | |||||
do { \ | |||||
printf("."); \ | |||||
fflush(stdout); \ | |||||
passed_tests++; \ | |||||
pending_nl = true; \ | |||||
} while(0); | |||||
#define FAIL_MSG "***** FAILURE *****" | |||||
#define FAIL_TEST(format, ...) \ | |||||
do { \ | |||||
printf("F\n"); \ | |||||
fprintf(stderr, FAIL_MSG "\n" format "\n", __VA_ARGS__); \ | |||||
failed_tests++; \ | |||||
pending_nl = false; \ | |||||
} while(0); | |||||
#define READY_STDOUT() \ | |||||
do { \ | |||||
if (pending_nl) { \ | |||||
printf("\n"); \ | |||||
pending_nl = false; \ | |||||
} \ | |||||
} while(0); | |||||
static int passed_tests = 0, failed_tests = 0; | |||||
static bool pending_nl = false; | |||||
/* | |||||
Prints out the test report. Called before exiting using atexit(). | |||||
*/ | |||||
static void finalize() { | |||||
READY_STDOUT() | |||||
if (failed_tests) | |||||
printf("fail (%d/%d)\n", passed_tests, passed_tests + failed_tests); | |||||
else | |||||
printf("pass (%d/%d)\n", passed_tests, passed_tests); | |||||
} | |||||
/* | |||||
Compare two files. If they are identical, then return true. Otherwise, | |||||
return false and print an error message showing the difference. | |||||
*/ | |||||
static bool diff_files(const char *expected_path, const char *actual_path) | |||||
{ | |||||
bool same = false; | |||||
FILE *expected = NULL, *actual = NULL; | |||||
if (!(actual = fopen(actual_path, "rb"))) { | |||||
FAIL_TEST("missing output file: %s", actual_path) | |||||
goto cleanup; | |||||
} | |||||
if (!(expected = fopen(expected_path, "rb"))) { | |||||
FAIL_TEST("missing reference file: %s", expected_path) | |||||
goto cleanup; | |||||
} | |||||
size_t len = 0; | |||||
int e, a; | |||||
while ((e = fgetc(expected)) != EOF) { | |||||
a = fgetc(actual); | |||||
if (a == EOF) { | |||||
FAIL_TEST("files differ: output file too short (index %zu)", len) | |||||
goto cleanup; | |||||
} | |||||
if (e != a) { | |||||
FAIL_TEST("files differ: 0x%02X != 0x%02X (expected vs. actual at " | |||||
"index %zu)", e, a, len) | |||||
goto cleanup; | |||||
} | |||||
len++; | |||||
} | |||||
if (fgetc(actual) != EOF) { | |||||
FAIL_TEST("files differ: junk at end of output file (index %zu)", len) | |||||
goto cleanup; | |||||
} | |||||
same = true; | |||||
cleanup: | |||||
if (expected) | |||||
fclose(expected); | |||||
if (actual) | |||||
fclose(actual); | |||||
return same; | |||||
} | |||||
/* | |||||
Return whether the given character is valid within a filename. | |||||
*/ | |||||
static bool is_valid_filename_char(char c) { | |||||
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || | |||||
(c >= '0' && c <= '9') || c == '.' || c == '_' || c == '-'; | |||||
} | |||||
/* | |||||
Run a single ASM->ROM test, converting the given source file to a temporary | |||||
output file, compared against the reference file. | |||||
*/ | |||||
static bool run_asm_test(const char *src_file, const char *ref_file) | |||||
{ | |||||
char *cmd_prefix = "../crater --assemble " ASM_PREFIX; | |||||
char *cmd = cr_malloc(sizeof(char) * | |||||
(strlen(cmd_prefix) + strlen(ASM_OUTFILE) + strlen(src_file)) + 2); | |||||
// Construct the command by concatenating: | |||||
// ../crater --assemble asm/<src_file> asm/.output.gg | |||||
stpcpy(stpcpy(stpcpy(cmd, cmd_prefix), src_file), " " ASM_OUTFILE); | |||||
unlink(ASM_OUTFILE); | |||||
system(cmd); | |||||
free(cmd); | |||||
// Construct the full reference file path in a temporary variable and diff | |||||
// it with the output file: | |||||
char *ref_path = malloc(sizeof(char) * | |||||
(strlen(ASM_PREFIX) + strlen(ref_file) + 1)); | |||||
stpcpy(stpcpy(ref_path, ASM_PREFIX), ref_file); | |||||
bool diff = diff_files(ref_path, ASM_OUTFILE); | |||||
free(ref_path); | |||||
return diff; | |||||
} | |||||
/* --------------------------- Main test runners --------------------------- */ | |||||
/* | |||||
Run tests for the Z80 CPU. | |||||
*/ | |||||
static bool test_cpu() | |||||
{ | |||||
// TODO | |||||
return true; | |||||
} | |||||
/* | |||||
Run tests for the VDP. | |||||
*/ | |||||
static bool test_vdp() | |||||
{ | |||||
// TODO | |||||
return true; | |||||
} | |||||
/* | |||||
Run tests for the SN76489 PSG. | |||||
*/ | |||||
static bool test_psg() | |||||
{ | |||||
// TODO | |||||
return true; | |||||
} | |||||
/* | |||||
Run tests for the assembler. | |||||
*/ | |||||
static bool test_asm() | |||||
{ | |||||
FILE *fp = fopen(ASM_PREFIX "manifest", "r"); | |||||
if (!fp) { | |||||
ERROR_ERRNO("couldn't open manifest file") | |||||
return false; | |||||
} | |||||
char *line = NULL, *split, c; | |||||
size_t cap = 0, lineno = 0, i; | |||||
ssize_t len; | |||||
while ((len = getline(&line, &cap, fp)) > 0) { | |||||
lineno++; | |||||
line[--len] = '\0'; | |||||
if (!len) | |||||
continue; | |||||
i = 0; | |||||
while ((c = line[i++])) { | |||||
if (!is_valid_filename_char(c) && c != ' ') { | |||||
READY_STDOUT() | |||||
ERROR("bad character in manifest file on line %zu", lineno) | |||||
return false; | |||||
} | |||||
} | |||||
split = strchr(line, ' '); | |||||
if (!split || strchr(split + 1, ' ')) { | |||||
READY_STDOUT() | |||||
ERROR("bad format in manifest file on line %zu", lineno) | |||||
return false; | |||||
} | |||||
*(split++) = '\0'; | |||||
if (!run_asm_test(line, split)) { | |||||
fprintf(stderr, "in test: %s -> %s\n", line, split); | |||||
return false; | |||||
} | |||||
PASS_TEST() | |||||
} | |||||
unlink(ASM_OUTFILE); | |||||
free(line); | |||||
return true; | |||||
} | |||||
/* | |||||
Run tests for the disassembler. | |||||
*/ | |||||
static bool test_dis() | |||||
{ | |||||
// TODO | |||||
return true; | |||||
} | |||||
/* | |||||
Run integration tests (i.e., multiple components working together). | |||||
*/ | |||||
static bool test_integrate() | |||||
{ | |||||
// TODO | |||||
return true; | |||||
} | |||||
/* | |||||
Main function. | |||||
*/ | |||||
int main(int argc, char *argv[]) | |||||
{ | |||||
if (argc != 2) | |||||
FATAL("a single component name is required") | |||||
const char *component = argv[1], *name; | |||||
bool (*func)(); | |||||
if (!strcmp(component, "cpu")) { | |||||
name = "Z80 CPU"; | |||||
func = test_cpu; | |||||
} else if (!strcmp(component, "vdp")) { | |||||
name = "VDP"; | |||||
func = test_vdp; | |||||
} else if (!strcmp(component, "psg")) { | |||||
name = "SN76489 PSG"; | |||||
func = test_psg; | |||||
} else if (!strcmp(component, "asm")) { | |||||
name = "assembler"; | |||||
func = test_asm; | |||||
} else if (!strcmp(component, "dis")) { | |||||
name = "disassembler"; | |||||
func = test_dis; | |||||
} else if (!strcmp(component, "integrate")) { | |||||
name = "integration"; | |||||
func = test_integrate; | |||||
} else { | |||||
FATAL("unknown component: %s", component) | |||||
} | |||||
printf("crater: running %s tests\n", name); | |||||
atexit(finalize); | |||||
return func() ? EXIT_SUCCESS : EXIT_FAILURE; | |||||
} |