Browse Source

Merge branch 'feature/asm_tests' into develop

master
Ben Kurtovic 8 years ago
parent
commit
08b958f971
29 changed files with 625 additions and 31 deletions
  1. +2
    -0
      .gitignore
  2. +1
    -1
      LICENSE
  3. +11
    -4
      README.md
  4. +25
    -16
      makefile
  5. +0
    -2
      src/assembler/instructions.c
  6. +19
    -1
      src/assembler/tokenizer.c
  7. +1
    -1
      src/config.c
  8. +3
    -2
      src/logging.h
  9. +1
    -1
      src/rom.c
  10. +1
    -1
      src/z80_ops.inc.c
  11. +0
    -0
     
  12. +16
    -0
      tests/asm/02-headers1.asm
  13. +16
    -0
      tests/asm/03-headers2.asm
  14. +21
    -0
      tests/asm/04-basic.asm
  15. +17
    -0
      tests/asm/05-includes.asm
  16. +13
    -0
      tests/asm/05.inc1.asm
  17. +3
    -0
      tests/asm/05.inc2.asm
  18. +2
    -0
      tests/asm/05.inc3.asm
  19. +3
    -0
      tests/asm/05.inc4.asm
  20. +16
    -0
      tests/asm/06-formatting.asm
  21. +13
    -0
      tests/asm/06.inc.asm
  22. +31
    -0
      tests/asm/07-data.asm
  23. +103
    -0
      tests/asm/08-instructions.asm
  24. +8
    -0
      tests/asm/manifest
  25. BIN
     
  26. +0
    -0
      tests/cpu/01_basic_math.asm
  27. +0
    -0
      tests/cpu/_header.asm
  28. +27
    -0
      tests/makefile
  29. +272
    -2
      tests/runner.c

+ 2
- 0
.gitignore View File

@@ -3,3 +3,5 @@ roms/*
!roms/README
crater
crater-dev
tests/runner
tests/asm/*.gg

+ 1
- 1
LICENSE View File

@@ -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
of this software and associated documentation files (the "Software"), to deal


+ 11
- 4
README.md View File

@@ -24,12 +24,19 @@ Installing
----------

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
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
-----


+ 25
- 16
makefile View File

@@ -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.

PROGRAM = crater
SOURCES = src
BUILD = build
DEVEXT = -dev
TESTS = cpu vdp psg asm dis integrate

CC = clang
FLAGS = -O2 -Wall -Wextra -pedantic -std=c11
CFLAGS = $(shell sdl2-config --cflags)
LIBS = $(shell sdl2-config --libs)
DFLAGS = -g -DDEBUG_MODE
MKDIR = mkdir -p
RM = rm -rf
ASM_UP = scripts/update_asm_instructions.py

MODE = release
BNRY = $(PROGRAM)
FLGS = $(FLAGS)
SDRS = $(shell find $(SOURCES) -type d | xargs echo)
SRCS = $(filter-out %.inc.c,$(foreach d,. $(SDRS),$(wildcard $(addprefix $(d)/*,.c))))
OBJS = $(patsubst %.c,%.o,$(addprefix $(BUILD)/$(MODE)/,$(SRCS)))
DEPS = $(OBJS:%.o=%.d)
DIRS = $(sort $(dir $(OBJS)))
TCPS = $(addprefix test-,$(TESTS))

ifdef DEBUG
BNRY := $(BNRY)$(DEVEXT)
FLAGS += -g -DDEBUG_MODE
MODE = debug
BNRY := $(BNRY)$(DEVEXT)
FLGS += $(DFLAGS)
MODE = debug
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)

clean:
$(RM) $(BUILD) $(PROGRAM) $(PROGRAM)$(DEVEXT)
@$(MAKE) -C tests clean

$(DIRS):
$(MKDIR) $@

$(BNRY): $(OBJS)
$(CC) $(FLAGS) $(LIBS) $(OBJS) -o $@
$(CC) $(FLGS) $(LIBS) $(OBJS) -o $@

$(OBJS): | $(DIRS)

$(BUILD)/$(MODE)/%.o: %.c
$(CC) $(FLAGS) $(CFLAGS) -MMD -MP -c $< -o $@
$(CC) $(FLGS) $(CFLAGS) -MMD -MP -c $< -o $@

-include $(DEPS)

@@ -52,16 +61,16 @@ ASM_INST = $(SOURCES)/assembler/instructions
$(ASM_INST).inc.c: $(ASM_INST).yml $(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-,,$@)

+ 0
- 2
src/assembler/instructions.c View File

@@ -98,8 +98,6 @@ static ASMErrorDesc parse_inst_##mnemonic( \

#define INST_INDEX_PREFIX(n) INST_PREFIX_(INST_INDEX(n).reg)

/* ----------------------------- END WORK BLOCK ---------------------------- */

/*
Fill an instruction's byte array with the given data.



+ 19
- 1
src/assembler/tokenizer.c View File

@@ -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.

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 char *directive;
parser_func parser = (parser_func) parse_string;
parser_func parser;

if (IS_DIRECTIVE(line, DIR_BYTE)) {
directive = DIR_BYTE;
@@ -295,10 +310,13 @@ static ErrorInfo* parse_data(
parser = parse_space;
} else if (IS_DIRECTIVE(line, DIR_ASCII)) {
directive = DIR_ASCII;
parser = (parser_func) parse_string;
} else if (IS_DIRECTIVE(line, DIR_ASCIZ)) {
directive = DIR_ASCIZ;
parser = (parser_func) parse_cstring;
} else if (IS_DIRECTIVE(line, DIR_ASCIIZ)) {
directive = DIR_ASCIIZ;
parser = (parser_func) parse_cstring;
} else {
return error_info_create(line, ET_PREPROC, ED_PP_UNKNOWN);
}


+ 1
- 1
src/config.c View File

@@ -90,7 +90,7 @@ static int get_rom_paths(char ***path_ptr)
}
closedir(dirp);
} else {
WARN_ERRNO("couldn't open 'roms/'")
WARN_ERRNO("couldn't open '" ROMS_DIR "/'")
}
*path_ptr = paths;
return npaths;


+ 3
- 2
src/logging.h View File

@@ -10,12 +10,13 @@

/* Internal usage only */

#define LOG_MSG_(dest, level, extra, after, ...) { \
#define LOG_MSG_(dest, level, extra, after, ...) \
do { \
fprintf(dest, level ": " __VA_ARGS__); \
extra; \
fprintf(dest, "\n"); \
after; \
}
} while (0);

#define LOG_ERR_(...) LOG_MSG_(stderr, __VA_ARGS__)
#define LOG_OUT_(...) LOG_MSG_(stdout, __VA_ARGS__)


+ 1
- 1
src/rom.c View File

@@ -32,7 +32,7 @@ static void print_header(const uint8_t *header)
snprintf(&header_chr[3 * i], 3, "%2c", header[i]);
else {
header_chr[3 * i] = ' ';
header_chr[3 * i + 1] = '?';
header_chr[3 * i + 1] = '.';
}
header_hex[3 * i + 2] = header_chr[3 * i + 2] = ' ';
}


+ 1
- 1
src/z80_ops.inc.c View File

@@ -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. */

/*


+ 0
- 0
View File


+ 16
- 0
tests/asm/02-headers1.asm View File

@@ -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

+ 16
- 0
tests/asm/03-headers2.asm View File

@@ -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

+ 21
- 0
tests/asm/04-basic.asm View File

@@ -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

+ 17
- 0
tests/asm/05-includes.asm View File

@@ -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

+ 13
- 0
tests/asm/05.inc1.asm View File

@@ -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

+ 3
- 0
tests/asm/05.inc2.asm View File

@@ -0,0 +1,3 @@
testfunc:
sub b
ret

+ 2
- 0
tests/asm/05.inc3.asm View File

@@ -0,0 +1,2 @@
.include "05.inc4.asm"
.rom_product 57005

+ 3
- 0
tests/asm/05.inc4.asm View File

@@ -0,0 +1,3 @@
blah:
ei
ret

+ 16
- 0
tests/asm/06-formatting.asm View File

@@ -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

+ 13
- 0
tests/asm/06.inc.asm View File

@@ -0,0 +1,13 @@
test1:test2: test3: test4:



inc a

inc b
inc c

ret




+ 31
- 0
tests/asm/07-data.asm View File

@@ -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

+ 103
- 0
tests/asm/08-instructions.asm View File

@@ -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

+ 8
- 0
tests/asm/manifest View File

@@ -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

BIN
View File


tests/z80/01_basic_math.asm → tests/cpu/01_basic_math.asm View File


tests/z80/_header.asm → tests/cpu/_header.asm View File


+ 27
- 0
tests/makefile View File

@@ -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

+ 272
- 2
tests/runner.c View File

@@ -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. */

// ...
#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;
}

Loading…
Cancel
Save