diff options
| author | Miquel Sabaté Solà <msabate@suse.com> | 2023-12-21 00:02:51 +0100 |
|---|---|---|
| committer | Miquel Sabaté Solà <msabate@suse.com> | 2023-12-21 18:41:41 +0100 |
| commit | 9e22faffffa20aa256a05468157404006e2e4d1e (patch) | |
| tree | 04266bf24d93a5a11452c27c10dbca16e3a3a8d9 /test | |
| download | list.nes-9e22faffffa20aa256a05468157404006e2e4d1e.tar.gz list.nes-9e22faffffa20aa256a05468157404006e2e4d1e.zip | |
Initial commit
Implemented the List scope which brings some handy macros and functions
to manipulate big lists on the NES.
Signed-off-by: Miquel Sabaté Solà <msabate@suse.com>
Diffstat (limited to 'test')
| -rw-r--r-- | test/common.s | 83 | ||||
| -rw-r--r-- | test/run.sh | 56 | ||||
| -rw-r--r-- | test/suite.lua | 66 | ||||
| -rw-r--r-- | test/suite.s | 186 | ||||
| -rw-r--r-- | test/utils.lua | 80 |
5 files changed, 471 insertions, 0 deletions
diff --git a/test/common.s b/test/common.s new file mode 100644 index 0000000..f0e9fb9 --- /dev/null +++ b/test/common.s @@ -0,0 +1,83 @@ +.segment "HEADER" + .byte 'N', 'E', 'S', $1A + + .byte $02 + .byte $01 + + .byte $00 + .byte $00 + +.segment "VECTORS" + .addr nmi, reset, irq + +.segment "CHARS" +.segment "STARTUP" +.segment "CODE" + +.include "../list.s" + +reset: + sei + cld + + ldx #$40 + stx $4017 + + ldx #$ff + txs + + inx + stx $2000 + stx $2001 + stx $4010 + +@vblankwait1: + bit $2002 + bpl @vblankwait1 + + ldx #0 + lda #0 +@ram_reset_loop: + sta $000, x + sta $100, x + sta $300, x + sta $400, x + sta $500, x + sta $600, x + sta $700, x + inx + bne @ram_reset_loop + + lda #$ef +@sprite_reset_loop: + sta $200, x + inx + bne @sprite_reset_loop + + lda #$00 + sta $2003 + lda #$02 + sta $4014 + +@vblankwait2: + bit $2002 + bpl @vblankwait2 + + lda #$3F + sta $2006 + lda #$00 + sta $2006 + + lda #$0F + ldx #$20 +@palettes_reset_loop: + sta $2007 + dex + bne @palettes_reset_loop + + jmp main + +;; Unused +nmi: +irq: + rti diff --git a/test/run.sh b/test/run.sh new file mode 100644 index 0000000..6caef32 --- /dev/null +++ b/test/run.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash + +set -e + +ROOT="$( cd "$( dirname "$0" )/.." && pwd )" +cd "$ROOT" + +# Clean previous builds, prepare the test environment and call build again with +# a test setup. +sed -i 's/.ifdef RUN_TESTS/RUN_TESTS = 1\n.ifdef RUN_TESTS/g' "$ROOT/test/suite.s" +DEBUG=1 make test/suite.nes + +rm -f "$ROOT/tmp/test-results.txt" + +# Github Actions do not allow GUI programs to be run. This means that the code +# below will always fail (fceux won't be able to run). There is a way to emulate +# an X server with tools like xvfb-run or xvncserver, but so far I've had no +# luck on this front. +if [ ! -z "${GITHUB_ACTION}" ]; then + exit 0 +fi + +# Run all the tests that we have on Lua. +for file in test/*.lua; do + if [ -n "$(echo $file | grep -v utils.lua)" ]; then + echo $file + fceux --loadlua $file "$ROOT/test/suite.nes" + fi +done + +# +# Show the results. + +if [ ! -f "$ROOT/tmp/test-results.txt" ]; then + echo "Something went wrong: test results were not printed out!" + exit 1 +fi + +cat "$ROOT/tmp/test-results.txt" + +n=$(cat "$ROOT/tmp/test-results.txt" | grep FAIL | wc -l) +echo "" +case $n in + 0) + echo "All tests passed!" + exit 0 + ;; + 1) + echo "1 test failed!" + exit 1 + ;; + *) + echo "$n tests failed!" + exit 1 + ;; +esac diff --git a/test/suite.lua b/test/suite.lua new file mode 100644 index 0000000..4a5578c --- /dev/null +++ b/test/suite.lua @@ -0,0 +1,66 @@ +utils = require "utils" + +utils.StartRun("Unit tests") + +--- +-- We push three elements and we sum them up. We expect a proper sum value +-- stored at $90, the items on $040{0, 1, 2} and the List pointers at the last +-- position. + +utils.MemTest("@test_list_sum", { + {0x60, "03"}, {0x62, "03"}, -- List::{ptr, last} should be at the very end (one past the last written). + {0x90, "07"}, -- We pushed three elements: 2, 4, 1; and the sum is left in 0x90. + {0x400, "02"}, {0x401, "04"}, {0x402, "01"} -- The three elements pushed by this test. +}) + +--- +-- We initialize an empty list and we try to call `get` with no elements. The +-- pointers should not move and the test function should set a `1` on $90 if +-- `get` set $FF to `y` (which is what we want). + +utils.MemTest("@test_list_empty_get", { + {0x60, "00"}, {0x62, "00"}, -- List::{ptr, last} did not advance. + {0x90, "01"} -- $FF was simply returned. +}) + +--- +-- We initialize a large list ($200-sized) with the 8-bit index as a value +-- (hence 0-$FF twice). In $90-$91 we leave the sum of all the values. + +utils.MemTest("@test_large_list", { + {0x90, "00"}, {0x91, "FF"} -- (0..255) * 2 = 65280, which is 0xFF00 in hexadecimal. +}) + +--- +-- We run the same code of `@test_list_sum` so to get a new three-sized array, +-- but then we overwrite the contents. This test makes sure that perform the sum +-- again gives us a new value and, hence, the previous values were actually +-- overwritten by `List::set`. + +utils.MemTest("@test_list_set", { + {0x60, "03"}, {0x62, "03"}, -- List::{ptr, last} should be at the very end (one past the last written). + {0x90, "0A"}, -- We overwrote the three elements: 3, 5, 2; and the sum is left in 0x90. + {0x400, "03"}, {0x401, "05"}, {0x402, "02"} -- The three elements pushed by this test. +}) + +--- +-- We initialize an empty list and we try to call `set` with no elements. The +-- pointers should not move and the test function should set a `1` on $90 if +-- `get` set $FF to `y` (which is what we want). + +utils.MemTest("@test_list_empty_set", { + {0x60, "00"}, {0x62, "00"}, -- List::{ptr, last} did not advance. + {0x90, "01"} -- $FF was simply returned. +}) + +--- +-- We initialize a list with two elements, and then we try to call `List::set` +-- three times. The first two writes work, the third not. + +utils.MemTest("@test_list_overflow", { + {0x60, "02"}, {0x62, "02"}, -- List::{ptr, last} should be at the very end (one past the last written). + {0x90, "00"}, {0x91, "00"}, {0x92, "FF"}, -- We overwrote the three elements: 3, 5, 2; and the sum is left in 0x90. + {0x400, "02"}, {0x401, "04"}, {0x402, "00"} -- The three elements pushed by this test. +}) + +utils.EndRun() diff --git a/test/suite.s b/test/suite.s new file mode 100644 index 0000000..c302e0a --- /dev/null +++ b/test/suite.s @@ -0,0 +1,186 @@ +;;; +;; The definition of `RUN_TESTS` will be inserted automatically when running +;; tests. Do not insert the definition manually. + +.include "common.s" + + +main: +.ifdef RUN_TESTS + jsr suite +.endif +halt: + jmp halt + +;;; +;; Test suite + +suite: + jsr list_sum + jsr list_empty_get + jsr large_list + jsr list_set + jsr list_empty_set + jsr list_set_overflow + + rts + +;; This is re-used in both `list_sum` and `list_set`. +.macro LIST_SUM_AUX + lda #0 + sta $90 + + LIST_INIT $0400 + + lda #2 + jsr List::push + lda #4 + jsr List::push + lda #1 + jsr List::push + + LIST_IT_FROM $0400 +: + jsr List::get + cpy #$FF + beq :+ + clc + adc $90 + sta $90 + jmp :- +: + nop +.endmacro + +list_sum: + LIST_SUM_AUX +@test_list_sum: + rts + +list_empty_get: + LIST_INIT $0400 + jsr List::get + cpy #$FF + beq :+ + lda #0 + sta $90 + jmp @test_list_empty_get +: + lda #1 + sta $90 +@test_list_empty_get: + rts + +large_list: + lda #0 + sta $90 + sta $91 + sta $92 + + LIST_INIT $0400 + + ldx #0 +: + txa + jsr List::push + + ;; Are we about to overflow the `x` register? If so, check if this was the + ;; first time or not. If so, then we let it overflow and loop again $FF + ;; times. Otherwise we will stop the loop, since we want to have a + ;; $200-sized list. + cpx #$FF + bne :+ + inc $92 + lda #2 + cmp $92 + beq :++ +: + inx + jmp :-- +: + ;; At this point we have stored this big list, let's add things up. + lda #0 + sta $90 + sta $91 + LIST_IT_FROM $0400 +: + jsr List::get + cpy #$FF + beq @test_large_list + clc + adc $90 + sta $90 + lda #0 + adc $91 + sta $91 + jmp :- + +@test_large_list: + rts + +list_set: + LIST_SUM_AUX + + lda #0 + sta $90 + + LIST_IT_FROM $0400 + + lda #3 + jsr List::set + lda #5 + jsr List::set + lda #2 + jsr List::set + + LIST_IT_FROM $0400 +: + jsr List::get + cpy #$FF + beq @test_list_set + clc + adc $90 + sta $90 + jmp :- +@test_list_set: + rts + +list_empty_set: + LIST_INIT $0400 + jsr List::set + cpy #$FF + beq :+ + lda #0 + sta $90 + jmp @test_list_empty_set +: + lda #1 + sta $90 +@test_list_empty_set: + rts + +list_set_overflow: + ;; $0402 should not be set by this function, and this is to be checked; so + ;; zero it out before doing anything. + lda #0 + ldy #0 + sta $0402 + + LIST_INIT $0400 + lda #1 + jsr List::push + lda #2 + jsr List::push + + LIST_IT_FROM $0400 + lda #2 + jsr List::set + sty $90 + lda #4 + jsr List::set + sty $91 + lda #6 + jsr List::set + sty $92 +@test_list_overflow: + rts diff --git a/test/utils.lua b/test/utils.lua new file mode 100644 index 0000000..9405c8b --- /dev/null +++ b/test/utils.lua @@ -0,0 +1,80 @@ +utils = {} + +-- Returns the root path for the project. +function utils.RootPath() + local fullpath = debug.getinfo(1,"S").source:sub(2) + fullpath = io.popen("realpath '"..fullpath.."'", 'r'):read() + fullpath = fullpath:gsub('[\n\r]*$','') + + local dirname, filename = fullpath:match('^(.*/)([^/]-)$') + dirname = dirname or '' + if dirname == '' then + return '' + end + + return io.popen("realpath '"..dirname.."/..'", 'r'):read() +end + +-- At a `label` that exists on the assembly code grab the values for the given +-- addresses and write it all into the `test-results.txt` file. The `addresses` +-- array is made up of two-sized arrays, where the first element contains the +-- memory you are trying to test, and the second element is the value that we +-- are expecting. +function utils.MemTest(label, addresses) + local cmd = "cat ".. utils.RootPath() .. "/tmp/labels.txt | grep .".. label .." | awk '{ print $2; }' | cut -c3-" + local file = assert(io.popen(cmd, 'r')) + local result = file:read("*a") + + -- Double check that the address that we grabbed has at least a good format. + if string.len(result) ~= 5 then + error("Error on '" .. label .. "': got a bad address! (".. result ..")") + end + + -- Register a function to execute on the given test address. The function will + -- simply iterate over the given `addresses` and compare them with the + -- expected result. Everything will be saved into the `test-results.txt` file. + memory.registerexecute(tonumber(result, 16), function() + local expected = "" + local got = "" + + for _, vals in ipairs(addresses) do + expected = expected .. "$" .. string.format("%04X", vals[1]) .. " -> " .. vals[2] .. "; " + got = got .. "$" .. string.format("%04X", vals[1]) .. " -> " .. string.format("%02X", memory.readbyte(vals[1])) .. "; " + end + + file = io.open(utils.RootPath() .. "/tmp/test-results.txt", "a") + io.output(file) + + if expected == got then + io.write("-> Test '".. label .."': OK\n") + else + io.write("\n-> Test '".. label .."': FAIL\n") + io.write("Expected: ".. expected .. "\n") + io.write("Got: ".. got .. "\n") + io.write("\n") + end + io.close(file) + end) +end + +function utils.StartRun(title) + file = io.open(utils.RootPath() .. "/tmp/test-results.txt", "a") + io.output(file) + io.write("\n== " .. title .. " ==\n") + io.close(file) +end + +-- Ends the given test run. That is, it will exit from the emulator so we can +-- turn back to the runner. +function utils.EndRun() + -- I'm not entirely sure why this is needed, but if we don't advance for + -- several frames fceux won't exit. Thus, let's advance for some frames and + -- then quit. + for i = 0, 10, 1 do + emu.frameadvance(); + end + + emu.exit() +end + +return utils |
