aboutsummaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authorMiquel Sabaté Solà <msabate@suse.com>2023-12-21 00:02:51 +0100
committerMiquel Sabaté Solà <msabate@suse.com>2023-12-21 18:41:41 +0100
commit9e22faffffa20aa256a05468157404006e2e4d1e (patch)
tree04266bf24d93a5a11452c27c10dbca16e3a3a8d9 /test
downloadlist.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.s83
-rw-r--r--test/run.sh56
-rw-r--r--test/suite.lua66
-rw-r--r--test/suite.s186
-rw-r--r--test/utils.lua80
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