diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 249acd1..2596e4c 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -17,11 +17,12 @@ jobs: - name: Install Deps run: | sudo apt update - sudo apt install libsdl2-dev libsdl2-ttf-dev librtmidi-dev nlohmann-json3-dev + sudo apt install libsdl2-dev libsdl2-ttf-dev librtmidi-dev nlohmann-json3-dev ninja-build - name: Build run: | - mkdir -p out/${{ matrix.build-type }} - cd out/${{ matrix.build-type }} - cmake -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} ../../ - make -j4 + cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} + cmake --build build + + - name: Test + run: ctest --output-on-failure --test-dir build diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index fee7652..73d8445 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -51,6 +51,9 @@ jobs: cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_COMPILE_WARNING_AS_ERROR=YES cmake --build build + - name: Test + run: ctest --output-on-failure --test-dir build + - name: Package run: | cp build/system3.exe COPYING.txt dist/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 6b3bc40..9746d2a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,6 +7,7 @@ endif () project(System3 LANGUAGES CXX) set(CMAKE_CXX_STANDARD 17) +include(CTest) # Generates a static library from pkg_check_modules() result function(add_static_library name pkg) @@ -250,3 +251,7 @@ endif() target_compile_definitions(system3 PRIVATE RESOURCE_PATH="${RESOURCE_PATH}") target_include_directories(system3 PRIVATE src src/sys) + +if (BUILD_TESTING AND NOT ANDROID AND NOT EMSCRIPTEN AND NOT NINTENDO_SWITCH) + add_subdirectory(test) +endif() diff --git a/src/sys/game_id.cpp b/src/sys/game_id.cpp index dcff0fd..27c5c24 100644 --- a/src/sys/game_id.cpp +++ b/src/sys/game_id.cpp @@ -96,6 +96,7 @@ const struct CRCTable { uint32 crc32_a; uint32 crc32_b; } crc_table[] = { + {GameId::SYSTEM1_GENERIC, "system1_generic", 1, "System 1", JAPANESE}, {GameId::BUNKASAI, "bunkasai", 1, "あぶない文化祭前夜", JAPANESE, CRC32_BUNKASAI}, {GameId::CRESCENT, "crescent", 1, "クレセントムーンがぁる", JAPANESE, CRC32_CRESCENT}, {GameId::DPS, "dps", 1, "D.P.S - Dream Program System", JAPANESE, CRC32_DPS}, @@ -122,6 +123,7 @@ const struct CRCTable { {GameId::GAKUEN, "gakuen", 1, "学園戦記", JAPANESE, CRC32_GAKUEN}, {GameId::GAKUEN, "gakuen_eng", 1, "Gakuen Senki", ENGLISH, CRC32_GAKUEN_ENG}, + {GameId::SYSTEM2_GENERIC, "system2_generic", 2, "System 2", JAPANESE}, {GameId::AYUMI_FD, "ayumi_fd", 2, "あゆみちゃん物語", JAPANESE, CRC32_AYUMI_FD}, {GameId::AYUMI_HINT, "ayumi_hint", 2, "あゆみちゃん物語 ヒントディスク", JAPANESE, CRC32_AYUMI_HINT}, {GameId::AYUMI_PROTO, "ayumi_proto", 2, "あゆみちゃん物語 PROTO", JAPANESE, CRC32_AYUMI_PROTO}, @@ -138,6 +140,7 @@ const struct CRCTable { {GameId::SDPS_KAIZOKU, "sdps_kaizoku", 2, "Super D.P.S - うれしたのし海賊稼業", JAPANESE, CRC32_SDPS, CRC32_SDPS_KAIZOKU}, {GameId::YAKATA2, "yakata2", 2, "ALICEの館II", JAPANESE, CRC32_YAKATA2}, + {GameId::SYSTEM3_GENERIC, "system3_generic", 3, "System 3", JAPANESE}, {GameId::AMBIVALENZ_FD, "ambivalenz_fd", 3, "AmbivalenZ −二律背反−", JAPANESE, CRC32_AMBIVALENZ_FD}, {GameId::AMBIVALENZ_CD, "ambivalenz_cd", 3, "AmbivalenZ −二律背反−", JAPANESE, CRC32_AMBIVALENZ_CD}, {GameId::DPS_ALL, "dps_all", 3, "D.P.S. 全部", JAPANESE, CRC32_DPSALL}, @@ -172,7 +175,7 @@ const struct CRCTable { const CRCTable* lookup(uint32 crc32_a, uint32 crc32_b) { for (const CRCTable* t = crc_table; t->id; t++) { - if (crc32_a == t->crc32_a && (!t->crc32_b || crc32_b == t->crc32_b)) + if (t->crc32_a && crc32_a == t->crc32_a && (!t->crc32_b || crc32_b == t->crc32_b)) return t; } return NULL; diff --git a/src/sys/game_id.h b/src/sys/game_id.h index 56dfce6..c6bf125 100644 --- a/src/sys/game_id.h +++ b/src/sys/game_id.h @@ -15,6 +15,7 @@ struct GameId { UNKNOWN, // System 1 + SYSTEM1_GENERIC, BUNKASAI, CRESCENT, DPS, @@ -40,6 +41,7 @@ struct GameId { GAKUEN, // System 2 + SYSTEM2_GENERIC, AYUMI_FD, AYUMI_HINT, AYUMI_PROTO, @@ -57,6 +59,7 @@ struct GameId { YAKATA2, // System 3 + SYSTEM3_GENERIC, AMBIVALENZ_FD, AMBIVALENZ_CD, DPS_ALL, diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..c9933d8 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,25 @@ +function(add_scenario_test system) + set(TEST_DIR ${CMAKE_CURRENT_BINARY_DIR}/scenario/${system}) + file(MAKE_DIRECTORY ${TEST_DIR}) + configure_file(scenario/${system}/ADISK.DAT ${TEST_DIR}/ADISK.DAT COPYONLY) + + add_test( + NAME scenario_${system} + COMMAND $ + -gamedir ${TEST_DIR} + -game ${system}_generic + -encoding utf8 + -fontfile ${PROJECT_SOURCE_DIR}/resources/fonts/MTLc3m.ttf + -texthook print + ) + set_tests_properties(scenario_${system} PROPERTIES + ENVIRONMENT "SDL_VIDEODRIVER=dummy;SDL_AUDIODRIVER=dummy" + FAIL_REGULAR_EXPRESSION "\\[FAIL\\]" + PASS_REGULAR_EXPRESSION "(^|[^0-9])0 tests failed" + TIMEOUT 5 + ) +endfunction() + +add_scenario_test(system1) +add_scenario_test(system2) +add_scenario_test(system3) diff --git a/test/README.md b/test/README.md new file mode 100644 index 0000000..f29367a --- /dev/null +++ b/test/README.md @@ -0,0 +1,16 @@ +# Scenario tests + +The scenario tests run compiled System 1, 2, and 3 scenarios through the +normal engine with SDL's dummy video and audio drivers. CTest checks both the +process exit status and the `0 tests failed` summary printed through the text +hook. + +`ADISK.DAT` is checked in so that building and running the tests does not +require [sys3c](https://github.com/kichikuou/sys3c). After installing sys3c +and changing the scenario sources, regenerate it with: + +```sh +for system in system1 system2 system3; do + (cd test/scenario/$system && sys3c) +done +``` diff --git a/test/scenario/common/arithmetic.adv b/test/scenario/common/arithmetic.adv new file mode 100644 index 0000000..dc0f485 --- /dev/null +++ b/test/scenario/common/arithmetic.adv @@ -0,0 +1,31 @@ + ; Addition and saturating overflow. + {3 + 4 \ 7: !tests_failed : tests_failed + 1! '[FAIL] addition' R} + {50000 + 50000 \ 65535: !tests_failed : tests_failed + 1! '[FAIL] addition overflow' R} + + ; Subtraction saturates at zero. + {10 - 3 \ 7: !tests_failed : tests_failed + 1! '[FAIL] subtraction' R} + {3 - 10 \ 0: !tests_failed : tests_failed + 1! '[FAIL] subtraction underflow' R} + + ; Multiplication and precedence. + {3 * 4 \ 12: !tests_failed : tests_failed + 1! '[FAIL] multiplication' R} + {1000 * 1000 \ 65535: !tests_failed : tests_failed + 1! '[FAIL] multiplication overflow' R} + {2 + 3 * 4 \ 14: !tests_failed : tests_failed + 1! '[FAIL] precedence' R} + {(2 + 3) * 4 \ 20: !tests_failed : tests_failed + 1! '[FAIL] parentheses' R} + + ; Comparisons. + {3 < 4 \ 1: !tests_failed : tests_failed + 1! '[FAIL] less than' R} + {4 < 3 \ 0: !tests_failed : tests_failed + 1! '[FAIL] false less than' R} + {4 > 3 \ 1: !tests_failed : tests_failed + 1! '[FAIL] greater than' R} + {3 \ 3: !tests_failed : tests_failed + 1! '[FAIL] equality' R} + {3 = 4: !tests_failed : tests_failed + 1! '[FAIL] inequality' R} + + ; Variables use the same expression evaluator. + !actual : 40! + !expected : 42! + !actual : actual + 2! + {actual \ expected: !tests_failed : tests_failed + 1! '[FAIL] variables' R} + + %0: + +*default: + EOF diff --git a/test/scenario/common/division.adv b/test/scenario/common/division.adv new file mode 100644 index 0000000..c410b77 --- /dev/null +++ b/test/scenario/common/division.adv @@ -0,0 +1,7 @@ + {10 / 3 \ 3: !tests_failed : tests_failed + 1! '[FAIL] division' R} + {10 / 0 \ 0: !tests_failed : tests_failed + 1! '[FAIL] division by zero' R} + + %0: + +*default: + EOF diff --git a/test/scenario/system1/ADISK.DAT b/test/scenario/system1/ADISK.DAT new file mode 100644 index 0000000..1cff70f Binary files /dev/null and b/test/scenario/system1/ADISK.DAT differ diff --git a/test/scenario/system1/sys3c.cfg b/test/scenario/system1/sys3c.cfg new file mode 100644 index 0000000..951dd3b --- /dev/null +++ b/test/scenario/system1/sys3c.cfg @@ -0,0 +1,4 @@ +game = system1_generic +hed = test.hed +encoding = utf8 +unicode = true diff --git a/test/scenario/system1/test.adv b/test/scenario/system1/test.adv new file mode 100644 index 0000000..8797da2 --- /dev/null +++ b/test/scenario/system1/test.adv @@ -0,0 +1,14 @@ + !RND : 0! + !tests_failed : 0! + + Y 240, 1: + + %#arithmetic.adv: + + ; System 1 cannot return the failure count as its process exit status. + ; Print the success marker only when every assertion passed. + {tests_failed = 0: '0 tests failed' R} + Y 253, 0: + +*default: + EOF diff --git a/test/scenario/system1/test.hed b/test/scenario/system1/test.hed new file mode 100644 index 0000000..0a16e60 --- /dev/null +++ b/test/scenario/system1/test.hed @@ -0,0 +1,2 @@ +test.adv +../common/arithmetic.adv diff --git a/test/scenario/system2/ADISK.DAT b/test/scenario/system2/ADISK.DAT new file mode 100644 index 0000000..6e6c468 Binary files /dev/null and b/test/scenario/system2/ADISK.DAT differ diff --git a/test/scenario/system2/sys3c.cfg b/test/scenario/system2/sys3c.cfg new file mode 100644 index 0000000..93f2927 --- /dev/null +++ b/test/scenario/system2/sys3c.cfg @@ -0,0 +1,4 @@ +game = system2_generic +hed = test.hed +encoding = utf8 +unicode = true diff --git a/test/scenario/system2/test.adv b/test/scenario/system2/test.adv new file mode 100644 index 0000000..ca8af75 --- /dev/null +++ b/test/scenario/system2/test.adv @@ -0,0 +1,14 @@ + !RND : 0! + !tests_failed : 0! + + Y 240, 1: + + %#arithmetic.adv: + %#division.adv: + + H 0, tests_failed: ' tests failed' R + !RND : tests_failed! + Y 255, 0: + +*default: + EOF diff --git a/test/scenario/system2/test.hed b/test/scenario/system2/test.hed new file mode 100644 index 0000000..a8128a0 --- /dev/null +++ b/test/scenario/system2/test.hed @@ -0,0 +1,3 @@ +test.adv +../common/arithmetic.adv +../common/division.adv diff --git a/test/scenario/system3/ADISK.DAT b/test/scenario/system3/ADISK.DAT new file mode 100644 index 0000000..523c908 Binary files /dev/null and b/test/scenario/system3/ADISK.DAT differ diff --git a/test/scenario/system3/sys3c.cfg b/test/scenario/system3/sys3c.cfg new file mode 100644 index 0000000..4a51aa6 --- /dev/null +++ b/test/scenario/system3/sys3c.cfg @@ -0,0 +1,4 @@ +game = system3_generic +hed = test.hed +encoding = utf8 +unicode = true diff --git a/test/scenario/system3/test.adv b/test/scenario/system3/test.adv new file mode 100644 index 0000000..1d3817c --- /dev/null +++ b/test/scenario/system3/test.adv @@ -0,0 +1,16 @@ + !RND : 0! + !tests_failed : 0! + + ; System3-sdl2 extension: draw ASCII as half-width characters so the + ; text-hook output is easy for CTest and humans to read. + Y 240, 1: + + %#arithmetic.adv: + %#division.adv: + + H 0, tests_failed: ' tests failed' R + !RND : tests_failed! + Y 255, 0: + +*default: + EOF diff --git a/test/scenario/system3/test.hed b/test/scenario/system3/test.hed new file mode 100644 index 0000000..a8128a0 --- /dev/null +++ b/test/scenario/system3/test.hed @@ -0,0 +1,3 @@ +test.adv +../common/arithmetic.adv +../common/division.adv