Add the concept of unit tests

Will be much much easier to tests new core routines without having to
re-create their context first.

Also, extract parse.asm from core.asm
This commit is contained in:
Virgil Dupras 2019-05-17 09:33:20 -04:00
parent e4ffe669a1
commit 013a3b74c8
14 changed files with 224 additions and 76 deletions

View File

@ -167,70 +167,6 @@ fmtHexPair:
pop af
ret
; Parse the hex char at A and extract it's 0-15 numerical value. Put the result
; in A.
;
; On success, the carry flag is reset. On error, it is set.
parseHex:
; First, let's see if we have an easy 0-9 case
cp '0'
jr c, .error ; if < '0', we have a problem
cp '9'+1
jr nc, .alpha ; if >= '9'+1, we might have alpha
; We are in the 0-9 range
sub '0' ; C is clear
ret
.alpha:
call upcase
cp 'A'
jr c, .error ; if < 'A', we have a problem
cp 'F'+1
jr nc, .error ; if >= 'F', we have a problem
; We have alpha.
sub 'A'-10 ; C is clear
ret
.error:
scf
ret
; Parses 2 characters of the string pointed to by HL and returns the numerical
; value in A. If the second character is a "special" character (<0x21) we don't
; error out: the result will be the one from the first char only.
; HL is set to point to the last char of the pair.
;
; On success, the carry flag is reset. On error, it is set.
parseHexPair:
push bc
ld a, (hl)
call parseHex
jr c, .end ; error? goto end, keeping the C flag on
rla \ rla \ rla \ rla ; let's push this in MSB
ld b, a
inc hl
ld a, (hl)
cp 0x21
jr c, .single ; special char? single digit
call parseHex
jr c, .end ; error?
or b ; join left-shifted + new. we're done!
; C flag was set on parseHex and is necessarily clear at this point
jr .end
.single:
; If we have a single digit, our result is already stored in B, but
; we have to right-shift it back.
ld a, b
and 0xf0
rra \ rra \ rra \ rra
dec hl
.end:
pop bc
ret
; Compares strings pointed to by HL and DE up to A count of characters. If
; equal, Z is set. If not equal, Z is reset.
strncmp:

63
parts/z80/parse.asm Normal file
View File

@ -0,0 +1,63 @@
; Parse the hex char at A and extract it's 0-15 numerical value. Put the result
; in A.
;
; On success, the carry flag is reset. On error, it is set.
parseHex:
; First, let's see if we have an easy 0-9 case
cp '0'
jr c, .error ; if < '0', we have a problem
cp '9'+1
jr nc, .alpha ; if >= '9'+1, we might have alpha
; We are in the 0-9 range
sub '0' ; C is clear
ret
.alpha:
call upcase
cp 'A'
jr c, .error ; if < 'A', we have a problem
cp 'F'+1
jr nc, .error ; if >= 'F', we have a problem
; We have alpha.
sub 'A'-10 ; C is clear
ret
.error:
scf
ret
; Parses 2 characters of the string pointed to by HL and returns the numerical
; value in A. If the second character is a "special" character (<0x21) we don't
; error out: the result will be the one from the first char only.
; HL is set to point to the last char of the pair.
;
; On success, the carry flag is reset. On error, it is set.
parseHexPair:
push bc
ld a, (hl)
call parseHex
jr c, .end ; error? goto end, keeping the C flag on
rla \ rla \ rla \ rla ; let's push this in MSB
ld b, a
inc hl
ld a, (hl)
cp 0x21
jr c, .single ; special char? single digit
call parseHex
jr c, .end ; error?
or b ; join left-shifted + new. we're done!
; C flag was set on parseHex and is necessarily clear at this point
jr .end
.single:
; If we have a single digit, our result is already stored in B, but
; we have to right-shift it back.
ld a, b
and 0xf0
rra \ rra \ rra \ rra
dec hl
.end:
pop bc
ret

View File

@ -14,6 +14,7 @@
; hexadecimal form, without prefix or suffix.
; *** REQUIREMENTS ***
; parse
; stdio
; blkdev

View File

@ -12,6 +12,7 @@ jp init
jp aciaInt
#include "core.asm"
#include "parse.asm"
ACIA_RAMSTART .equ RAMSTART
#include "acia.asm"

View File

@ -25,6 +25,7 @@ jp init
jp aciaInt
#include "core.asm"
#include "parse.asm"
ACIA_RAMSTART .equ RAMSTART
#include "acia.asm"
BLOCKDEV_RAMSTART .equ ACIA_RAMEND

11
tools/emul/.gitignore vendored
View File

@ -1,7 +1,8 @@
/shell
/zasm
/zasm-includes.h
/*-kernel.h
/*-user.h
/shell/shell
/zasm/zasm
/runbin/runbin
/zasm/includes.*
/*/kernel.h
/*/user.h
/cfsin
/cfsout

View File

@ -1,4 +1,4 @@
TARGETS = shell/shell zasm/zasm
TARGETS = shell/shell zasm/zasm runbin/runbin
KERNEL_HEADERS = shell/kernel.h zasm/kernel.h
USER_HEADERS = zasm/user.h
CFSPACK = ../cfspack/cfspack
@ -25,6 +25,7 @@ zasm/includes.h: zasm/includes.cfs
shell/shell: shell/shell.c libz80/libz80.o shell/kernel.h $(CFSPACK)
zasm/zasm: zasm/zasm.c libz80/libz80.o zasm/kernel.h zasm/user.h zasm/includes.h
runbin/runbin: runbin/runbin.c libz80/libz80.o
$(TARGETS):
cc $< libz80/libz80.o -o $@

View File

@ -0,0 +1,56 @@
#include <stdint.h>
#include <stdio.h>
#include "../libz80/z80.h"
/* runbin loads binary from stdin directly in memory address 0 then runs it
* until it halts. The return code is the value of the register A at halt time.
*/
static Z80Context cpu;
static uint8_t mem[0x10000];
static uint8_t io_read(int unused, uint16_t addr)
{
addr &= 0xff;
fprintf(stderr, "Out of bounds I/O read: %d\n", addr);
return 0;
}
static void io_write(int unused, uint16_t addr, uint8_t val)
{
addr &= 0xff;
fprintf(stderr, "Out of bounds I/O write: %d / %d\n", addr, val);
}
static uint8_t mem_read(int unused, uint16_t addr)
{
return mem[addr];
}
static void mem_write(int unused, uint16_t addr, uint8_t val)
{
mem[addr] = val;
}
int main()
{
// read stdin in mem
int i = 0;
int c = getchar();
while (c != EOF) {
mem[i] = c & 0xff;
i++;
c = getchar();
}
Z80RESET(&cpu);
cpu.ioRead = io_read;
cpu.ioWrite = io_write;
cpu.memRead = mem_read;
cpu.memWrite = mem_write;
while (!cpu.halted) {
Z80Execute(&cpu);
}
return cpu.R1.br.A;
}

View File

@ -9,6 +9,7 @@ FS_SEEKH_PORT .equ 0x03
jp init
#include "core.asm"
#include "parse.asm"
BLOCKDEV_RAMSTART .equ RAMSTART
BLOCKDEV_COUNT .equ 4

View File

@ -25,6 +25,7 @@ jp fsSeek
jp fsTell
#include "core.asm"
#include "parse.asm"
.equ BLOCKDEV_RAMSTART RAMSTART
.equ BLOCKDEV_COUNT 3
#include "blockdev.asm"

7
tools/tests/Makefile Normal file
View File

@ -0,0 +1,7 @@
EMULDIR = ../../tools/emul
.PHONY: run
run:
make -C $(EMULDIR) zasm runbin
cd unit && ./runtests.sh
cd zasm && ./runtests.sh

18
tools/tests/unit/runtests.sh Executable file
View File

@ -0,0 +1,18 @@
#!/bin/sh
set -e
set -o pipefail
SCAS=scas
PARTS=../../../parts/z80
RUNBIN=../../emul/runbin/runbin
for fn in *.asm; do
echo "Running test ${fn}"
if ! ${SCAS} -I ${PARTS} -o - ${fn} | ${RUNBIN}; then
echo "failed with code ${PIPESTATUS[1]}"
exit 1
fi
done
echo "All tests passed!"

View File

@ -0,0 +1,67 @@
jp test
#include "core.asm"
#include "parse.asm"
testNum: .db 1
sFoo: .db "Foo", 0
saB: .db "aB", 0
s99: .db "99", 0
test:
ld hl, 0xffff
ld sp, hl
ld a, '8'
call parseHex
jp c, fail
cp 8
jp nz, fail
call nexttest
ld a, 'e'
call parseHex
jp c, fail
cp 0xe
jp nz, fail
call nexttest
ld a, 'x'
call parseHex
jp nc, fail
call nexttest
ld hl, s99
call parseHexPair
jp c, fail
cp 0x99
jp nz, fail
call nexttest
ld hl, sab
call parseHexPair
jp c, fail
cp 0xab
jp nz, fail
call nexttest
ld hl, sfoo
call parseHexPair
jp nc, fail
call nexttest
; success
xor a
halt
nexttest:
ld a, (testNum)
inc a
ld (testNum), a
ret
fail:
ld a, (testNum)
halt

View File

@ -1,6 +0,0 @@
EMULDIR = ../../../tools/emul
.PHONY: run
run:
make -C $(EMULDIR) zasm
./runtests.sh