diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c171ee147..d39c5764c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -78,6 +78,53 @@ years). If you are adding new files, you need to use the 8. Be prepared to get some comments about your code and to modify it. Tip: Use `git rebase -i origin/master` to modify chains of commits. +## Running tests + +### Using CTest (recommended — CMake build) + +Each test case is registered as an individual CTest test, enabling parallel +execution, filtering, and ordered dependencies. + +```sh +# Build RGBDS first +cmake -S . -B build +cmake --build build + +# Run all internal tests in parallel (defaults to nproc) +cd build && ctest -j$(nproc) + +# Run only a specific suite +ctest -L asm +ctest -L link +ctest -L fix +ctest -L gfx + +# Run only internal tests (skip downstream projects) +ctest -L internal + +# Run only external/downstream tests +ctest -L external + +# Filter by test name +ctest -R "asm/charmap" +ctest -R "link/overlay" + +# Show output from failing tests +ctest -j$(nproc) --output-on-failure +``` + +Smoke tests run first for each suite. If a smoke test fails, the rest of that +suite is skipped. External/downstream tests only run after all smoke tests pass. + +### Using run-tests.sh (Makefile build) + +The legacy `test/run-tests.sh` script still works for Makefile-based builds: + +```sh +make +cd test && ./run-tests.sh +``` + ## Adding a test The test suite is a little ad-hoc, so the way tests work is different for each @@ -86,6 +133,9 @@ program being tested. Feel free to modify how the test scripts work, if the thing you want to test doesn't fit the existing scheme(s). +When adding new test files, re-run `cmake` to pick them up (CTest discovers +tests at configure time via `file(GLOB ...)`). + ### RGBASM There are two kinds of test. diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index b054f5861..58351a379 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,7 +1,20 @@ # SPDX-License-Identifier: MIT +# +# CTest-based test suite for RGBDS. +# +# Each test case is registered as an individual CTest test, enabling: +# - Parallel execution: ctest -j$(nproc) +# - Filtering: ctest -R "asm/charmap" or ctest -L gfx +# - Ordering: Smoke tests run first via FIXTURES +# - Minimal output: CTest only shows failures by default +# +# The original test.sh scripts are preserved for Makefile-based builds. -option(USE_NONFREE_TESTS "run tests that build nonfree codebases" ON) -option(USE_EXTERNAL_TESTS "run tests that build external codebases" ON) +# Allow arbitrary characters (e.g. #) in test names +cmake_policy(SET CMP0110 NEW) + +option(USE_NONFREE_TESTS "Run tests that build nonfree codebases" ON) +option(USE_EXTERNAL_TESTS "Run tests that build external codebases" ON) if(NOT USE_NONFREE_TESTS) set(ONLY_FREE "--only-free") @@ -13,6 +26,9 @@ if(DEFINED OS) set(OS_NAME "--os" "${OS}") endif() +# ========================================================================= +# Build test helper executables (gfx suite) +# ========================================================================= add_executable(randtilegen gfx/randtilegen.cpp) add_executable(rgbgfx_test gfx/rgbgfx_test.cpp) set_target_properties(randtilegen rgbgfx_test PROPERTIES @@ -33,7 +49,379 @@ foreach(TARGET randtilegen rgbgfx_test) endif() endforeach() -add_test(NAME all - COMMAND ./run-tests.sh ${ONLY_FREE} ${ONLY_INTERNAL} ${OS_NAME} - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} -) +# ========================================================================= +# Helpers +# ========================================================================= +set(TEST_DIR "${CMAKE_CURRENT_SOURCE_DIR}") + +# Default per-test timeout (seconds) +set(INTERNAL_TEST_TIMEOUT 60) +set(EXTERNAL_TEST_TIMEOUT 600) + +# Macro: register a test that runs a per-suite runner script. +# Sets TEST_SRCDIR so the runner can find test data. +macro(add_rgbds_test TEST_NAME WORKING_DIR) + add_test(NAME "${TEST_NAME}" + COMMAND ${ARGN} + WORKING_DIRECTORY "${WORKING_DIR}") + set_tests_properties("${TEST_NAME}" PROPERTIES + ENVIRONMENT "TEST_SRCDIR=${WORKING_DIR}" + TIMEOUT ${INTERNAL_TEST_TIMEOUT} + ) +endmacro() + +# ========================================================================= +# ASM TESTS +# ========================================================================= + +# --- Simple .asm tests --- +file(GLOB _asm_tests RELATIVE "${TEST_DIR}/asm" "${TEST_DIR}/asm/*.asm") +set(_all_asm_tests "") +foreach(_test_file ${_asm_tests}) + string(REGEX REPLACE "\\.asm$" "" _test_name "${_test_file}") + set(_full_name "asm/${_test_name}") + add_rgbds_test("${_full_name}" "${TEST_DIR}/asm" + bash run-one-test.sh simple "${_test_name}") + set_tests_properties("${_full_name}" PROPERTIES LABELS "asm;internal") + list(APPEND _all_asm_tests "${_full_name}") +endforeach() + +# notexist.asm — intentionally missing file test (not caught by glob) +add_rgbds_test("asm/notexist" "${TEST_DIR}/asm" + bash run-one-test.sh simple notexist) +set_tests_properties("asm/notexist" PROPERTIES LABELS "asm;internal") +list(APPEND _all_asm_tests "asm/notexist") + +# --- CLI tests --- +file(GLOB _asm_cli_tests RELATIVE "${TEST_DIR}/asm/cli" "${TEST_DIR}/asm/cli/*.flags") +foreach(_test_file ${_asm_cli_tests}) + string(REGEX REPLACE "\\.flags$" "" _test_name "${_test_file}") + set(_full_name "asm/cli/${_test_name}") + add_rgbds_test("${_full_name}" "${TEST_DIR}/asm" + bash run-one-test.sh cli "${_test_name}") + set_tests_properties("${_full_name}" PROPERTIES LABELS "asm;internal") + list(APPEND _all_asm_tests "${_full_name}") +endforeach() + +# --- Dependency tracking tests --- +foreach(_dep_test + continues-after-missing-include + exits-after-missing-include + continues-after-missing-preinclude + exits-after-missing-preinclude) + set(_full_name "asm/dep/${_dep_test}") + add_rgbds_test("${_full_name}" "${TEST_DIR}/asm" + bash run-one-test.sh dep "${_dep_test}") + set_tests_properties("${_full_name}" PROPERTIES LABELS "asm;internal") + list(APPEND _all_asm_tests "${_full_name}") +endforeach() + +# --- State-file test --- +add_rgbds_test("asm/state-file" "${TEST_DIR}/asm" + bash run-one-test.sh state state-file) +set_tests_properties("asm/state-file" PROPERTIES LABELS "asm;internal") +list(APPEND _all_asm_tests "asm/state-file") + +# ========================================================================= +# LINK TESTS +# ========================================================================= + +set(_all_link_tests "") + +# --- Simple .asm tests (generic loop) --- +file(GLOB _link_simple_tests RELATIVE "${TEST_DIR}/link" "${TEST_DIR}/link/*.asm") +foreach(_test_file ${_link_simple_tests}) + string(REGEX REPLACE "\\.asm$" "" _test_name "${_test_file}") + set(_full_name "link/${_test_name}") + add_rgbds_test("${_full_name}" "${TEST_DIR}/link" + bash run-one-test.sh simple "${_test_name}") + set_tests_properties("${_full_name}" PROPERTIES LABELS "link;internal") + list(APPEND _all_link_tests "${_full_name}") +endforeach() + +# --- Special sub-directory tests --- +# Tests with sub-sub-directories (fragment-align/*) +file(GLOB _fragment_align_tests RELATIVE "${TEST_DIR}/link" + "${TEST_DIR}/link/fragment-align/*") +foreach(_test_dir ${_fragment_align_tests}) + set(_full_name "link/${_test_dir}") + add_rgbds_test("${_full_name}" "${TEST_DIR}/link" + bash run-one-test.sh special "${_test_dir}") + set_tests_properties("${_full_name}" PROPERTIES LABELS "link;internal") + list(APPEND _all_link_tests "${_full_name}") +endforeach() + +# Flat special tests (single sub-directory) +foreach(_special_test + bank-const + constant-parent + export-all + fragment-literals + high-low + load-fragment/base + load-fragment/multiple-objects + load-fragment/section-fragment + map-file + overlay/smaller + overlay/unfixed + overlay/tiny + pipeline + rept-trace + same-consts + scramble-invalid + scramble-romx + script-include + sdcc/good + sdcc/no-script + section-conflict/different-mod + section-fragment/good + section-fragment/jr-offset + section-fragment/jr-offset-load + section-normal/same-name + section-union/compat + section-union/good + section-union/same-export + section-union/same-label + symbols/conflict + symbols/good + symbols/unknown + truncation/level1 + truncation/level2) + set(_full_name "link/${_special_test}") + add_rgbds_test("${_full_name}" "${TEST_DIR}/link" + bash run-one-test.sh special "${_special_test}") + set_tests_properties("${_full_name}" PROPERTIES LABELS "link;internal") + list(APPEND _all_link_tests "${_full_name}") +endforeach() + +# --- Error tests (section-union/*.asm and section-fragment/*.asm) --- +file(GLOB _link_su_error_tests RELATIVE "${TEST_DIR}/link" + "${TEST_DIR}/link/section-union/*.asm") +file(GLOB _link_sf_error_tests RELATIVE "${TEST_DIR}/link" + "${TEST_DIR}/link/section-fragment/*.asm") +foreach(_test_file ${_link_su_error_tests} ${_link_sf_error_tests}) + string(REGEX REPLACE "\\.asm$" "" _test_name "${_test_file}") + set(_full_name "link/error/${_test_name}") + add_rgbds_test("${_full_name}" "${TEST_DIR}/link" + bash run-one-test.sh error "${_test_name}") + set_tests_properties("${_full_name}" PROPERTIES LABELS "link;internal") + list(APPEND _all_link_tests "${_full_name}") +endforeach() + +# ========================================================================= +# FIX TESTS +# ========================================================================= + +set(_all_fix_tests "") + +# --- Normal .flags tests --- +file(GLOB _fix_tests RELATIVE "${TEST_DIR}/fix" "${TEST_DIR}/fix/*.flags") +foreach(_test_file ${_fix_tests}) + string(REGEX REPLACE "\\.flags$" "" _test_name "${_test_file}") + set(_full_name "fix/${_test_name}") + add_rgbds_test("${_full_name}" "${TEST_DIR}/fix" + bash run-one-test.sh normal "${_test_name}") + set_tests_properties("${_full_name}" PROPERTIES LABELS "fix;internal") + list(APPEND _all_fix_tests "${_full_name}") +endforeach() + +# --- Special tests --- +foreach(_special no-exist no-input multiple-to-one) + set(_full_name "fix/${_special}") + add_rgbds_test("${_full_name}" "${TEST_DIR}/fix" + bash run-one-test.sh special "${_special}") + set_tests_properties("${_full_name}" PROPERTIES LABELS "fix;internal") + list(APPEND _all_fix_tests "${_full_name}") +endforeach() + +# --- Padding tests (10 iterations with deterministic byte values) --- +foreach(_i RANGE 0 9) + math(EXPR _padding "${_i} * 25 + 7") # deterministic spread: 7, 32, 57, ... + set(_full_name "fix/padding-${_i}") + add_rgbds_test("${_full_name}" "${TEST_DIR}/fix" + bash run-one-test.sh padding padding "${_padding}") + set_tests_properties("${_full_name}" PROPERTIES LABELS "fix;internal") + list(APPEND _all_fix_tests "${_full_name}") +endforeach() + +# ========================================================================= +# GFX TESTS +# ========================================================================= + +set(_all_gfx_tests "") + +# --- PNG tests --- +file(GLOB _gfx_png_files "${TEST_DIR}/gfx/*.png") +foreach(_png_file ${_gfx_png_files}) + get_filename_component(_fname "${_png_file}" NAME) + + # Skip result.png, *.pal.png, and out*.png (transient seed test output) + if(_fname STREQUAL "result.png") + continue() + endif() + string(FIND "${_fname}" ".pal.png" _pal_pos) + if(NOT _pal_pos EQUAL -1) + continue() + endif() + string(REGEX MATCH "^out[0-9]*\\.png$" _out_match "${_fname}") + if(_out_match) + continue() + endif() + + string(REGEX REPLACE "\\.png$" "" _test_name "${_fname}") + set(_full_name "gfx/${_test_name}") + add_rgbds_test("${_full_name}" "${TEST_DIR}/gfx" + bash run-one-test.sh png "${_test_name}") + set_tests_properties("${_full_name}" PROPERTIES LABELS "gfx;internal") + list(APPEND _all_gfx_tests "${_full_name}") +endforeach() + +# --- Seed tests --- +file(GLOB _gfx_seed_files RELATIVE "${TEST_DIR}/gfx" "${TEST_DIR}/gfx/seed*.bin") +foreach(_seed_file ${_gfx_seed_files}) + set(_full_name "gfx/${_seed_file}") + add_rgbds_test("${_full_name}" "${TEST_DIR}/gfx" + bash run-one-test.sh seed "${_seed_file}") + set_tests_properties("${_full_name}" PROPERTIES LABELS "gfx;internal") + list(APPEND _all_gfx_tests "${_full_name}") +endforeach() + +# --- Reverse tests --- +file(GLOB _gfx_reverse_files "${TEST_DIR}/gfx/*.[12]bpp") +foreach(_rev_file ${_gfx_reverse_files}) + get_filename_component(_fname "${_rev_file}" NAME) + + # Skip result.* and *.out.* + string(FIND "${_fname}" "result." _res_pos) + if(NOT _res_pos EQUAL -1) + continue() + endif() + string(FIND "${_fname}" ".out." _out_pos) + if(NOT _out_pos EQUAL -1) + continue() + endif() + + set(_full_name "gfx/reverse/${_fname}") + add_rgbds_test("${_full_name}" "${TEST_DIR}/gfx" + bash run-one-test.sh reverse "${_fname}") + set_tests_properties("${_full_name}" PROPERTIES LABELS "gfx;internal") + list(APPEND _all_gfx_tests "${_full_name}") +endforeach() + +# --- Write-stdout test --- +add_rgbds_test("gfx/write-stdout" "${TEST_DIR}/gfx" + bash run-one-test.sh stdout write_stdout) +set_tests_properties("gfx/write-stdout" PROPERTIES LABELS "gfx;internal") +list(APPEND _all_gfx_tests "gfx/write-stdout") + +# ========================================================================= +# EXTERNAL / DOWNSTREAM TESTS +# ========================================================================= + +set(_all_external_tests "") + +if(USE_EXTERNAL_TESTS) + set(_external_projects ucity libbet SameBoy gb-starter-kit) + if(USE_NONFREE_TESTS) + list(PREPEND _external_projects pokecrystal pokered LADX-Disassembly) + endif() + + foreach(_project ${_external_projects}) + set(_full_name "external/${_project}") + add_test(NAME "${_full_name}" + COMMAND bash run-one-external.sh "${_project}" + WORKING_DIRECTORY "${TEST_DIR}") + set_tests_properties("${_full_name}" PROPERTIES + ENVIRONMENT "TEST_SRCDIR=${TEST_DIR};OS_NAME=${OS}" + TIMEOUT ${EXTERNAL_TEST_TIMEOUT} + LABELS "external" + ) + list(APPEND _all_external_tests "${_full_name}") + endforeach() +endif() + +# ========================================================================= +# FIXTURES — ordering / dependency control +# ========================================================================= + +# Pick one smoke test per suite — if it fails, the rest of that suite +# is skipped (CTest FIXTURES_REQUIRED). + +# ASM smoke: use "assert" if available as a basic sanity test +if("asm/assert" IN_LIST _all_asm_tests) + set(_asm_smoke "asm/assert") +elseif(_all_asm_tests) + list(GET _all_asm_tests 0 _asm_smoke) +else() + set(_asm_smoke "") +endif() + +if(_asm_smoke) + set_tests_properties("${_asm_smoke}" PROPERTIES FIXTURES_SETUP ASM_SMOKE) + foreach(_t ${_all_asm_tests}) + if(NOT "${_t}" STREQUAL "${_asm_smoke}") + set_tests_properties("${_t}" PROPERTIES FIXTURES_REQUIRED ASM_SMOKE) + endif() + endforeach() +endif() + +# Slow ASM tests that need a longer timeout +if("asm/section-unsigned-overflow" IN_LIST _all_asm_tests) + set_tests_properties("asm/section-unsigned-overflow" PROPERTIES TIMEOUT 300) +endif() + +# LINK smoke +if(_all_link_tests) + list(GET _all_link_tests 0 _link_smoke) + set_tests_properties("${_link_smoke}" PROPERTIES FIXTURES_SETUP LINK_SMOKE) + foreach(_t ${_all_link_tests}) + if(NOT "${_t}" STREQUAL "${_link_smoke}") + set_tests_properties("${_t}" PROPERTIES FIXTURES_REQUIRED LINK_SMOKE) + endif() + endforeach() +endif() + +# FIX smoke +if(_all_fix_tests) + list(GET _all_fix_tests 0 _fix_smoke) + set_tests_properties("${_fix_smoke}" PROPERTIES FIXTURES_SETUP FIX_SMOKE) + foreach(_t ${_all_fix_tests}) + if(NOT "${_t}" STREQUAL "${_fix_smoke}") + set_tests_properties("${_t}" PROPERTIES FIXTURES_REQUIRED FIX_SMOKE) + endif() + endforeach() +endif() + +# GFX smoke +if(_all_gfx_tests) + list(GET _all_gfx_tests 0 _gfx_smoke) + set_tests_properties("${_gfx_smoke}" PROPERTIES FIXTURES_SETUP GFX_SMOKE) + foreach(_t ${_all_gfx_tests}) + if(NOT "${_t}" STREQUAL "${_gfx_smoke}") + set_tests_properties("${_t}" PROPERTIES FIXTURES_REQUIRED GFX_SMOKE) + endif() + endforeach() +endif() + +# External tests require all smoke fixtures to pass +if(_all_external_tests) + set(_required_fixtures "") + if(_asm_smoke) + list(APPEND _required_fixtures "ASM_SMOKE") + endif() + if(_all_link_tests) + list(APPEND _required_fixtures "LINK_SMOKE") + endif() + if(_all_fix_tests) + list(APPEND _required_fixtures "FIX_SMOKE") + endif() + if(_all_gfx_tests) + list(APPEND _required_fixtures "GFX_SMOKE") + endif() + if(_required_fixtures) + foreach(_t ${_all_external_tests}) + set_tests_properties("${_t}" PROPERTIES + FIXTURES_REQUIRED "${_required_fixtures}") + endforeach() + endif() +endif() diff --git a/test/asm/run-one-test.sh b/test/asm/run-one-test.sh new file mode 100755 index 000000000..b74f2eea0 --- /dev/null +++ b/test/asm/run-one-test.sh @@ -0,0 +1,237 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MIT +# +# Run a single RGBASM test case in an isolated temp directory. +# +# Usage: +# run-one-test.sh +# +# test-type is one of: +# simple — a .asm file in the asm test directory +# cli — a .flags file in the asm/cli/ subdirectory +# dep — one of the dependency-tracking test directories +# state — the state-file test +# +# TEST_SRCDIR must point to the test/asm source directory. + +set -euo pipefail + +# shellcheck source=../helpers.sh +source "$(dirname "$0")/../helpers.sh" + +TEST_SRCDIR="${TEST_SRCDIR:?TEST_SRCDIR must be set}" +setup_tools +setup_tmpdir + +test_type="${1:?missing test-type argument}" +test_name="${2:?missing test-name argument}" + +rc=0 + +cd "$TEST_SRCDIR" + +case "$test_type" in + +# ========================================================================= +# Simple .asm tests — runs normal + piped variants +# ========================================================================= +simple) + i="${test_name}.asm" + + # Generate version.asm dynamically if needed + if [[ "$test_name" = "version" ]]; then + if ! git -c safe.directory='*' describe --tags --abbrev=0 >"$TMPWORKDIR/version.out" 2>/dev/null; then + echo "${bold}${orange}Warning: cannot run version test!${rescolors}${resbold}" >&2 + exit 0 # Skip gracefully + fi + "$RGBASM" --version >>"$TMPWORKDIR/version.out" + cat >"$TMPWORKDIR/version.asm" <<'EOF' +IF !DEF(__RGBDS_RC__) + PRINTLN "v{d:__RGBDS_MAJOR__}.{d:__RGBDS_MINOR__}.{d:__RGBDS_PATCH__}" +ELSE + PRINTLN "v{d:__RGBDS_MAJOR__}.{d:__RGBDS_MINOR__}.{d:__RGBDS_PATCH__}-rc{d:__RGBDS_RC__}" +ENDC + PRINTLN "rgbasm {__RGBDS_VERSION__}" +EOF + # Redirect to use the tmpdir copies + i="$TMPWORKDIR/version.asm" + fi + + RGBASMFLAGS="-Weverything -Bcollapse" + if [ -f "${test_name}.flags" ]; then + RGBASMFLAGS="$RGBASMFLAGS @${test_name}.flags" + fi + + o="$TMPWORKDIR/out.o" + gb="$TMPWORKDIR/out.gb" + output="$TMPWORKDIR/stdout" + errput="$TMPWORKDIR/stderr" + + for variant in '' ' piped'; do + echo "${bold}${green}${test_name}${variant}...${rescolors}${resbold}" + + if [ -e "${test_name}.out" ]; then + desired_outname="${test_name}.out" + elif [[ "$test_name" = "version" ]]; then + desired_outname="$TMPWORKDIR/version.out" + else + desired_outname=/dev/null + fi + if [ -e "${test_name}.err" ]; then + desired_errname="${test_name}.err" + else + desired_errname=/dev/null + fi + + if [ -z "$variant" ]; then + "$RGBASM" $RGBASMFLAGS -o "$o" "$i" >"$output" 2>"$errput" || true + desired_output="$desired_outname" + desired_errput="$desired_errname" + else + # Skip piped variant for certain tests + if [[ "$test_name" = include-recursion || "$test_name" = make-deps || "$test_name" = notexist ]]; then + continue + fi + + # Stop! This is not a Useless Use Of Cat. Using cat instead of + # stdin redirection makes the input an unseekable pipe. + # shellcheck disable=SC2002 + cat "$i" | "$RGBASM" $RGBASMFLAGS -o "$o" - >"$output" 2>"$errput" || true + + desired_output="$TMPWORKDIR/desired_out" + desired_errput="$TMPWORKDIR/desired_err" + # Escape regex metacharacters + subst="$(printf '%s\n' "$i" | sed 's:[][\/.^$*]:\\&:g')" + # Replace the file name with "" to match changed output + sed "s/$subst//g" "$desired_outname" >"$desired_output" + sed "s/$subst//g" "$desired_errname" >"$desired_errput" + fi + + our_rc=0 + tryDiff "$desired_output" "$output" "${test_name}${variant}.out" || our_rc=1 + tryDiff "$desired_errput" "$errput" "${test_name}${variant}.err" || (( our_rc = 1 )) + + desired_binname="${test_name}.out.bin" + if [[ -f "$desired_binname" && $our_rc -eq 0 ]]; then + if ! "$RGBLINK" -x -o "$gb" "$o"; then + echo "${bold}${red}\`rgblink -x\` failed!${rescolors}${resbold}" >&2 + our_rc=1 + else + tryCmp "$desired_binname" "$gb" "${test_name}${variant}.gb" || our_rc=1 + fi + fi + + if [[ $our_rc -ne 0 ]]; then + rc=1 + break + fi + done + ;; + +# ========================================================================= +# CLI tests — .flags files in cli/ subdirectory +# ========================================================================= +cli) + i="cli/${test_name}.flags" + output="$TMPWORKDIR/stdout" + errput="$TMPWORKDIR/stderr" + + echo "${bold}${green}cli/${test_name}...${rescolors}${resbold}" + + if [ -e "cli/${test_name}.out" ]; then + desired_output="cli/${test_name}.out" + else + desired_output=/dev/null + fi + if [ -e "cli/${test_name}.err" ]; then + desired_errput="cli/${test_name}.err" + else + desired_errput=/dev/null + fi + + "$RGBASM" "@$i" >"$output" 2>"$errput" || true + + our_rc=0 + tryDiff "$desired_output" "$output" "cli/${test_name}.out" || our_rc=1 + tryDiff "$desired_errput" "$errput" "cli/${test_name}.err" || (( our_rc = 1 )) + + rc=$our_rc + ;; + +# ========================================================================= +# Dependency tracking tests +# ========================================================================= +dep) + o="$TMPWORKDIR/out.o" + output="$TMPWORKDIR/stdout" + errput="$TMPWORKDIR/stderr" + fixed_output="$TMPWORKDIR/fixed_out" + + RGBASMFLAGS="-Weverything -Bcollapse -M -" + if [ -f "$test_name/a.flags" ]; then + RGBASMFLAGS="$RGBASMFLAGS @$test_name/a.flags" + fi + + echo "${bold}${green}${test_name}...${rescolors}${resbold}" + "$RGBASM" $RGBASMFLAGS -o "$o" "${test_name}"/a.asm >"$output" 2>"$errput" || true + + if command -v cygpath &>/dev/null; then + subst1="$(printf '%s\n' "$o" | sed 's:[][\/.^$*]:\\&:g')" + subst2="$(printf '%s\n' "$(cygpath -w "$o")" | sed -e 's:\\:/:g' -e 's:[][\/.^$*]:\\&:g')" + sed -e "s/$subst1/a.o/g" -e "s/$subst2/a.o/g" "$output" >"$fixed_output" + else + subst="$(printf '%s\n' "$o" | sed 's:[][\/.^$*]:\\&:g')" + sed "s/$subst/a.o/g" "$output" >"$fixed_output" + fi + + our_rc=0 + tryDiff "${test_name}"/a.out "$fixed_output" "${test_name}.out" || our_rc=1 + tryDiff "${test_name}"/a.err "$errput" "${test_name}.err" || (( our_rc = 1 )) + + rc=$our_rc + ;; + +# ========================================================================= +# State-file test — runs normal + piped variants +# ========================================================================= +state) + o="$TMPWORKDIR/out.o" + output="$TMPWORKDIR/stdout" + errput="$TMPWORKDIR/stderr" + + if command -v cygpath &>/dev/null; then + state_outname="$(cygpath -w "$o")" + else + state_outname="$o" + fi + state_features=" all " # Test trimming whitespace + RGBASMFLAGS="-Weverything -Bcollapse" + + for variant in '' '.pipe'; do + echo "${bold}${green}state-file${variant}...${rescolors}${resbold}" + if [ -z "$variant" ]; then + "$RGBASM" $RGBASMFLAGS -s "$state_features:$state_outname" state-file/a.asm >"$output" 2>"$errput" || true + else + # shellcheck disable=SC2002 + cat state-file/a.asm | "$RGBASM" $RGBASMFLAGS -s "$state_features:$state_outname" - >"$output" 2>"$errput" || true + fi + + our_rc=0 + tryDiff /dev/null "$output" "state-file${variant}.out" || our_rc=1 + tryDiff /dev/null "$errput" "state-file${variant}.err" || (( our_rc = 1 )) + tryDiff state-file/a.dump.asm "$o" "state-file${variant}.dump" || (( our_rc = 1 )) + + if [[ $our_rc -ne 0 ]]; then + rc=1 + break + fi + done + ;; + +*) + echo "Unknown test type: $test_type" >&2 + exit 2 + ;; +esac + +exit $rc diff --git a/test/fix/run-one-test.sh b/test/fix/run-one-test.sh new file mode 100755 index 000000000..91366c96c --- /dev/null +++ b/test/fix/run-one-test.sh @@ -0,0 +1,207 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MIT +# +# Run a single RGBFIX test case in an isolated temp directory. +# +# Usage: +# run-one-test.sh [extra-args...] +# +# test-type: +# normal — a .flags file test (runs 3 variants: direct, piped, output) +# special — one of the special-case tests (no-exist, no-input, multiple-to-one) +# padding — a padding test iteration (test-name = padding index, extra = suffix) +# +# TEST_SRCDIR must point to the test/fix source directory. + +set -euo pipefail + +# shellcheck source=../helpers.sh +source "$(dirname "$0")/../helpers.sh" + +TEST_SRCDIR="${TEST_SRCDIR:?TEST_SRCDIR must be set}" +setup_tools +setup_tmpdir + +test_type="${1:?missing test-type argument}" +test_name="${2:?missing test-name argument}" +shift 2 + +rc=0 + +# Copy rgbfix and gbdiff into the tmpdir (matching original test.sh pattern) +cp "$RGBFIX" "$TMPWORKDIR/rgbfix" +cp "$GBDIFF" "$TMPWORKDIR/gbdiff.bash" + +cd "$TMPWORKDIR" +RGBFIX=./rgbfix +src="$TEST_SRCDIR" + +# Set up src path substitution for error message normalization +if command -v cygpath &>/dev/null; then + subst1="$(printf '%s\n' "$src" | sed 's:[][\/.^$*]:\\&:g')" + subst2="$(printf '%s\n' "$(cygpath -w "$src")" | sed -e 's:\\:/:g' -e 's:[][\/.^$*]:\\&:g')" + src_subst="$src/\\|$subst1/\\|$subst2/" +else + src_subst="$src/" +fi + +case "$test_type" in + +# ========================================================================= +# Normal .flags tests — 3 variants (direct, piped, output) +# ========================================================================= +normal) + test_base="$test_name" + + if grep -qF ' ./' "$src/$test_base.flags"; then + flags=$( + head -n 1 "$src/$test_base.flags" | + sed "s# ./# ${src//#/\\#}/#g" + ) + else + flags="@$src/$test_base.flags" + fi + + for variant in '' ' piped' ' output'; do + our_rc=0 + echo "${bold}${green}${test_base}${variant}...${rescolors}${resbold}" + + if [[ -r "$src/$test_base.bin" ]]; then + desired_input="$src/$test_base.bin" + else + desired_input="$src/default-input.bin" + fi + + if [[ -z "$variant" ]]; then + cp "$desired_input" out.gb + eval "$RGBFIX" $flags out.gb '>out.out' '2>out.err' || true + subst=out.gb + elif [[ "$variant" = ' piped' ]]; then + # shellcheck disable=SC2002 + cat "$desired_input" | eval "$RGBFIX" $flags - '>out.gb' '2>out.err' || true + subst='' + elif [[ "$variant" = ' output' ]]; then + cp "$desired_input" input.gb + eval "$RGBFIX" $flags -o out.gb input.gb '>out.out' '2>out.err' || true + subst=input.gb + fi + + if [[ -r "$src/$test_base.out" ]]; then + desired_outname="$src/$test_base.out" + else + desired_outname=/dev/null + fi + if [[ -r "$src/$test_base.err" ]]; then + desired_errname="$src/$test_base.err" + else + desired_errname=/dev/null + fi + + sed -e "s/$subst//g" -e "s#$src_subst##g" out.out | + tryDiff "$desired_outname" - "$test_base.out${variant}" || our_rc=1 + sed -e "s/$subst//g" -e "s#$src_subst##g" out.err | + tryDiff "$desired_errname" - "$test_base.err${variant}" || our_rc=1 + + if [[ -r "$src/$test_base.gb" ]]; then + tryCmp "$src/$test_base.gb" out.gb "$test_base.gb${variant}" || our_rc=1 + fi + + if [[ $our_rc -ne 0 ]]; then + rc=1 + break + fi + done + ;; + +# ========================================================================= +# Special tests +# ========================================================================= +special) + echo "${bold}${green}${test_name}...${rescolors}${resbold}" + case "$test_name" in + no-exist) + eval "$RGBFIX" no-exist '2>out.err' || true + tryDiff "$src/no-exist.err" out.err "${test_name}.err" || rc=1 + ;; + no-input) + eval "$RGBFIX" '2>out.err' || true + tryDiff "$src/no-input.err" out.err "${test_name}.err" || rc=1 + ;; + multiple-to-one) + eval "$RGBFIX" one two three -o multiple-to-one '2>out.err' || true + tryDiff "$src/multiple-to-one.err" out.err "${test_name}.err" || rc=1 + ;; + *) + echo "Unknown special test: $test_name" >&2 + exit 2 + ;; + esac + ;; + +# ========================================================================= +# Padding tests — test_name is the index, $1 is the random padding byte +# ========================================================================= +padding) + padding="${1:?missing padding byte argument}" + + echo "${bold}Checking padding byte $padding...${resbold}" + + cp "$src"/padding{,-large,-larger}.bin . + touch padding{,-large,-larger}.err + + for suffix in '' -large -larger; do + cat <<<" -p $padding" >padding$suffix.flags + tr '\377' \\$((padding / 64))$(((padding / 8) % 8))$((padding % 8)) <"$src/padding$suffix.gb" >padding$suffix.gb + + # Run all 3 variants for this padding test + test_base="padding${suffix}" + flags="@./padding$suffix.flags" + + for variant in '' ' piped' ' output'; do + our_rc=0 + + desired_input="./padding${suffix}.bin" + + if [[ -z "$variant" ]]; then + cp "$desired_input" out.gb + eval "$RGBFIX" $flags out.gb '>out.out' '2>out.err' || true + subst=out.gb + elif [[ "$variant" = ' piped' ]]; then + # shellcheck disable=SC2002 + cat "$desired_input" | eval "$RGBFIX" $flags - '>out.gb' '2>out.err' || true + subst='' + elif [[ "$variant" = ' output' ]]; then + cp "$desired_input" input.gb + eval "$RGBFIX" $flags -o out.gb input.gb '>out.out' '2>out.err' || true + subst=input.gb + fi + + desired_outname=/dev/null + desired_errname="./padding${suffix}.err" + + sed -e "s/$subst//g" out.out | + tryDiff "$desired_outname" - "padding${suffix}.out${variant}" || our_rc=1 + sed -e "s/$subst//g" out.err | + tryDiff "$desired_errname" - "padding${suffix}.err${variant}" || our_rc=1 + + tryCmp "./padding${suffix}.gb" out.gb "padding${suffix}.gb${variant}" || our_rc=1 + + if [[ $our_rc -ne 0 ]]; then + rc=1 + break + fi + done + + if [[ $rc -ne 0 ]]; then + break + fi + done + ;; + +*) + echo "Unknown test type: $test_type" >&2 + exit 2 + ;; +esac + +exit $rc diff --git a/test/gfx/run-one-test.sh b/test/gfx/run-one-test.sh new file mode 100755 index 000000000..410797b00 --- /dev/null +++ b/test/gfx/run-one-test.sh @@ -0,0 +1,183 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MIT +# +# Run a single RGBGFX test case in an isolated temp directory. +# +# Usage: +# run-one-test.sh [extra-args...] +# +# test-type: +# png — a .png file test (runs normal + piped variants) +# seed — a seed*.bin test (runs 4 flag variants) +# reverse — a .1bpp/.2bpp round-trip test +# stdout — the write-stdout test +# +# TEST_SRCDIR must point to the test/gfx source directory. + +set -euo pipefail + +# shellcheck source=../helpers.sh +source "$(dirname "$0")/../helpers.sh" + +TEST_SRCDIR="${TEST_SRCDIR:?TEST_SRCDIR must be set}" +setup_tools +setup_tmpdir + +test_type="${1:?missing test-type argument}" +test_name="${2:?missing test-name argument}" +shift 2 + +rc=0 + +cd "$TEST_SRCDIR" + +RGBGFX="${RGBGFX}" + +case "$test_type" in + +# ========================================================================= +# PNG tests — normal + piped variants +# ========================================================================= +png) + f="${test_name}.png" + base="${test_name}" + + flags="$([[ -e "${base}.flags" ]] && echo "@${base}.flags" || true)" + + # Build output flags — write result files to tmpdir + for f_ext in o_1bpp o_2bpp p_pal t_tilemap a_attrmap q_palmap; do + if [[ -e "${base}.out.${f_ext#*_}" ]]; then + flags="$flags -${f_ext%_*} $TMPWORKDIR/result.${f_ext#*_}" + fi + done + + errtmp="$TMPWORKDIR/stderr" + + # --- normal variant --- + echo "${bold}${green}Testing: $RGBGFX $flags $f${rescolors}${resbold}" + if [[ -e "${base}.err" ]]; then + "$RGBGFX" $flags "$f" 2>"$errtmp" || true + diff -au --strip-trailing-cr "${base}.err" "$errtmp" || { rc=1; echo "${bold}${red}Test $RGBGFX $flags $f failed!${rescolors}${resbold}" >&2; } + else + if ! "$RGBGFX" $flags "$f" 2>"$errtmp"; then + rc=1 + echo "${bold}${red}Test $RGBGFX $flags $f failed!${rescolors}${resbold}" >&2 + cat "$errtmp" >&2 + else + # Check outputs + for ext in 1bpp 2bpp pal tilemap attrmap palmap; do + if [[ -e "${base}.out.$ext" ]]; then + tryCmp "${base}.out.$ext" "$TMPWORKDIR/result.$ext" "${base}.out.$ext" || rc=1 + fi + done + fi + fi + + # --- piped variant --- + # Re-build flags for tmpdir + flags="$([[ -e "${base}.flags" ]] && echo "@${base}.flags" || true)" + for f_ext in o_1bpp o_2bpp p_pal t_tilemap a_attrmap q_palmap; do + if [[ -e "${base}.out.${f_ext#*_}" ]]; then + flags="$flags -${f_ext%_*} $TMPWORKDIR/result.${f_ext#*_}" + fi + done + + echo "${bold}${green}Testing: $RGBGFX $flags - <$f${rescolors}${resbold}" + if [[ -e "${base}.err" ]]; then + "$RGBGFX" $flags - <"$f" 2>"$errtmp" || true + diff -au --strip-trailing-cr <(sed "s/$f//g" "${base}.err") "$errtmp" || { rc=1; echo "${bold}${red}Test $RGBGFX $flags - <$f failed!${rescolors}${resbold}" >&2; } + else + if ! "$RGBGFX" $flags - <"$f" 2>"$errtmp"; then + rc=1 + echo "${bold}${red}Test $RGBGFX $flags - <$f failed!${rescolors}${resbold}" >&2 + cat "$errtmp" >&2 + else + for ext in 1bpp 2bpp pal tilemap attrmap palmap; do + if [[ -e "${base}.out.$ext" ]]; then + tryCmp "${base}.out.$ext" "$TMPWORKDIR/result.$ext" "${base}.out.$ext (piped)" || rc=1 + fi + done + fi + fi + ;; + +# ========================================================================= +# Seed tests — 4 flag combinations +# Run in an isolated tmpdir because rgbgfx_test writes out0.png, result.* +# to CWD and calls ../../rgbgfx relative to CWD. +# ========================================================================= +seed) + f="${test_name}" + + # Set up tmpdir with directory structure so ../../rgbgfx resolves: + # $TMPWORKDIR/rgbgfx → symlink to real rgbgfx binary + # $TMPWORKDIR/a/b/rgbgfx_test → symlink to real rgbgfx_test + # $TMPWORKDIR/a/b/randtilegen → symlink to real randtilegen + # $TMPWORKDIR/a/b/seed*.bin → symlink to test input + # CWD = $TMPWORKDIR/a/b/ so ../../rgbgfx = $TMPWORKDIR/rgbgfx ✓ + seed_workdir="$TMPWORKDIR/a/b" + mkdir -p "$seed_workdir" + ln -sf "$(cd "$TEST_SRCDIR/../.." && pwd)/rgbgfx" "$TMPWORKDIR/rgbgfx" + ln -sf "$TEST_SRCDIR/rgbgfx_test" "$seed_workdir/rgbgfx_test" + ln -sf "$TEST_SRCDIR/randtilegen" "$seed_workdir/randtilegen" + ln -sf "$TEST_SRCDIR/$f" "$seed_workdir/$f" + cd "$seed_workdir" + + # Draw a random tile offset and VRAM0 size + ofs=0 + size=0 + while [[ "$ofs" -eq 0 ]]; do (( ofs = RANDOM % 256 )); done + while [[ "$size" -eq 0 ]]; do (( size = RANDOM % 256 )); done + + for flags in ""{," -b $ofs"}{," -N $size,256"}; do + echo "${bold}${green}Testing: ./rgbgfx_test $f $flags${rescolors}${resbold}" + if ! ./rgbgfx_test "$f" $flags; then + rc=1 + echo "${bold}${red}Test ./rgbgfx_test $f $flags failed!${rescolors}${resbold}" >&2 + fi + done + ;; + +# ========================================================================= +# Reverse tests — round-trip +# ========================================================================= +reverse) + f="${test_name}" + base="${f%.[12]bpp}" + + flags="$([[ -e "${base}.flags" ]] && echo "@${base}.flags" || true) $([[ "${f}" = *.1bpp ]] && echo "-d 1" || true)" + + result_png="$TMPWORKDIR/result.png" + result_2bpp="$TMPWORKDIR/result.2bpp" + + echo "${bold}${green}Testing: $RGBGFX $flags -o $f -r 1 result.png && $RGBGFX $flags -o result.2bpp result.png${rescolors}${resbold}" + if ! ($RGBGFX $flags -o "$f" -r 1 "$result_png" && $RGBGFX $flags -o "$result_2bpp" "$result_png"); then + rc=1 + echo "${bold}${red}Reverse test for $f failed!${rescolors}${resbold}" >&2 + else + tryCmp "$f" "$result_2bpp" "$f round-trip" || rc=1 + fi + ;; + +# ========================================================================= +# Write-stdout test +# ========================================================================= +stdout) + result="$TMPWORKDIR/result.2bpp" + + echo "${bold}${green}Testing: $RGBGFX -m -o - write_stdout.bin > result.2bpp${rescolors}${resbold}" + if ! "$RGBGFX" -m -o - write_stdout.bin > "$result"; then + rc=1 + echo "${bold}${red}Write-stdout test failed!${rescolors}${resbold}" >&2 + else + tryCmp write_stdout.out.2bpp "$result" "write_stdout round-trip" || rc=1 + fi + ;; + +*) + echo "Unknown test type: $test_type" >&2 + exit 2 + ;; +esac + +exit $rc diff --git a/test/helpers.sh b/test/helpers.sh new file mode 100755 index 000000000..4b27ddfea --- /dev/null +++ b/test/helpers.sh @@ -0,0 +1,105 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MIT +# +# Common helpers for per-test runner scripts. +# Source this file, don't execute it. + +export LC_ALL=C +# Game Boy release date, 1989-04-21T12:34:56Z (for reproducible test results) +export SOURCE_DATE_EPOCH=609165296 + +# --------------------------------------------------------------------------- +# Terminal colours (safe for non-TTY — tput returns empty strings) +# --------------------------------------------------------------------------- +bold="$(tput bold 2>/dev/null || true)" +resbold="$(tput sgr0 2>/dev/null || true)" +red="$(tput setaf 1 2>/dev/null || true)" +green="$(tput setaf 2 2>/dev/null || true)" +orange="$(tput setaf 3 2>/dev/null || true)" +rescolors="$(tput op 2>/dev/null || true)" + +# --------------------------------------------------------------------------- +# Resolve tool paths — honour env vars, fall back to build-tree locations +# relative to the *test source directory* (passed via TEST_SRCDIR or $1). +# --------------------------------------------------------------------------- +setup_tools () { + local srcdir="${TEST_SRCDIR:?TEST_SRCDIR must be set}" + local root + root="$(cd "$srcdir/../.." && pwd)" + + RGBASM="${RGBASM:-$root/rgbasm}" + RGBLINK="${RGBLINK:-$root/rgblink}" + RGBFIX="${RGBFIX:-$root/rgbfix}" + RGBGFX="${RGBGFX:-$root/rgbgfx}" + GBDIFF="${GBDIFF:-$root/contrib/gbdiff.bash}" + + export RGBASM RGBLINK RGBFIX RGBGFX GBDIFF +} + +# --------------------------------------------------------------------------- +# Per-test temporary directory — completely isolated working space. +# Cleaned up automatically on EXIT. +# Sets: TMPWORKDIR +# --------------------------------------------------------------------------- +setup_tmpdir () { + TMPWORKDIR="$(mktemp -d)" + # Immediate expansion is the desired behavior. + # shellcheck disable=SC2064 + trap "rm -rf ${TMPWORKDIR@Q}" EXIT + export TMPWORKDIR +} + +# --------------------------------------------------------------------------- +# Comparison helpers +# --------------------------------------------------------------------------- + +# Text diff. Returns 0 on match, 1 on mismatch (with output to stderr). +tryDiff () { + if ! diff -au --strip-trailing-cr "$1" "$2"; then + echo "${bold}${red}${3:-$1} mismatch!${rescolors}${resbold}" >&2 + return 1 + fi +} + +# Binary diff. Falls back to gbdiff.bash for human-readable output. +tryCmp () { + if ! cmp "$1" "$2"; then + "$GBDIFF" "$1" "$2" 2>/dev/null || true + echo "${bold}${red}${3:-$1} mismatch!${rescolors}${resbold}" >&2 + return 1 + fi +} + +# Compare a generated ROM (in $1) against an expected binary ($2). +# The ROM is truncated to the expected file's size before comparison. +tryCmpRom () { + local expected="$1" + local rom="$2" + local tmprom="$TMPWORKDIR/_rom_trunc" + local rom_size + rom_size=$(printf %s "$(wc -c <"$expected")") + dd if="$rom" count=1 bs="$rom_size" of="$tmprom" 2>/dev/null + tryCmp "$expected" "$tmprom" "${3:-ROM binary}" +} + +# Check that a file's size matches an expected value. +tryCmpRomSize () { + local file="$1" + local expected_size="$2" + local actual_size + actual_size=$(printf %s "$(wc -c <"$file")") + if [ "$actual_size" -ne "$expected_size" ]; then + echo "${bold}${red}Binary size mismatch! Expected $expected_size, got $actual_size${rescolors}${resbold}" >&2 + return 1 + fi +} + +# Run rgblink, fail if it produces anything on stdout. +rgblinkQuiet () { + local out + out="$(env "$RGBLINK" -Weverything -Bcollapse "$@")" || return $? + if [[ -n "$out" ]]; then + echo "${bold}${red}Linking shouldn't produce anything on stdout!${rescolors}${resbold}" >&2 + return 1 + fi +} diff --git a/test/link/run-one-test.sh b/test/link/run-one-test.sh new file mode 100755 index 000000000..701b52e1c --- /dev/null +++ b/test/link/run-one-test.sh @@ -0,0 +1,457 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MIT +# +# Run a single RGBLINK test case in an isolated temp directory. +# +# Usage: +# run-one-test.sh +# +# test-type: +# simple — a top-level .asm file (generic loop: flag/script/simple variants) +# special — a hardcoded special sub-directory test +# error — section-union/*.asm / section-fragment/*.asm error tests +# +# TEST_SRCDIR must point to the test/link source directory. + +set -euo pipefail +set -o pipefail + +# shellcheck source=../helpers.sh +source "$(dirname "$0")/../helpers.sh" + +TEST_SRCDIR="${TEST_SRCDIR:?TEST_SRCDIR must be set}" +setup_tools +setup_tmpdir + +test_type="${1:?missing test-type argument}" +test_name="${2:?missing test-name argument}" + +rc=0 + +cd "$TEST_SRCDIR" + +# Temp file aliases inside the isolated tmpdir +otemp="$TMPWORKDIR/a.o" +gbtemp="$TMPWORKDIR/out.gb" +gbtemp2="$TMPWORKDIR/b.o" +outtemp="$TMPWORKDIR/out1" +outtemp2="$TMPWORKDIR/out2" +outtemp3="$TMPWORKDIR/out3" + +case "$test_type" in + +# ========================================================================= +# Simple .asm tests — the generic loop from test.sh +# ========================================================================= +simple) + test="$test_name" + + echo "${bold}${green}${test} assembling...${rescolors}${resbold}" + "$RGBASM" -o "$otemp" "${test}.asm" + + our_rc=0 + + # ---------- variant flag tests ---------- + ran_flag=false + for flag in '-d' '-t' '-w'; do + if [ -f "${test}-no${flag}.out" ]; then + echo "${bold}${green}${test}-no${flag}...${rescolors}${resbold}" + rgblinkQuiet -o "$gbtemp" "$otemp" 2>"$outtemp" || true + tryDiff "${test}-no${flag}.out" "$outtemp" "${test}-no${flag}.out" || our_rc=1 + ran_flag=true + fi + if [ -f "${test}${flag}.out" ]; then + echo "${bold}${green}${test}${flag}...${rescolors}${resbold}" + rgblinkQuiet ${flag} -o "$gbtemp" "$otemp" 2>"$outtemp" || true + tryDiff "${test}${flag}.out" "$outtemp" "${test}${flag}.out" || our_rc=1 + ran_flag=true + fi + done + if "$ran_flag"; then + rc=$our_rc + exit $rc + fi + + # ---------- linker script tests ---------- + ran_flag=false + for script in "$test"*.link; do + [[ -e "$script" ]] || break + echo "${bold}${green}${test} ${script#${test}}...${rescolors}${resbold}" + rgblinkQuiet -l "$script" -o "$gbtemp" "$otemp" 2>"$outtemp" || true + tryDiff "${script%.link}.out" "$outtemp" "${script%.link}.out" || our_rc=1 + ran_flag=true + done + if "$ran_flag"; then + rc=$our_rc + exit $rc + fi + + # ---------- simple link + compare ---------- + echo "${bold}${green}${test}...${rescolors}${resbold}" + rgblinkQuiet -o "$gbtemp" "$otemp" 2>"$outtemp" || true + tryDiff "${test}.out" "$outtemp" "${test}.out" || our_rc=1 + bin="${test}.out.bin" + if [ -f "$bin" ]; then + tryCmpRom "$bin" "$gbtemp" "${test}.out.bin" || our_rc=1 + fi + rc=$our_rc + ;; + +# ========================================================================= +# Special sub-directory tests +# ========================================================================= +special) + test="$test_name" + our_rc=0 + + case "$test" in + + bank-const) + echo "${bold}${green}${test} assembling...${rescolors}${resbold}" + "$RGBASM" -o "$otemp" "$test"/a.asm + "$RGBASM" -o "$gbtemp2" "$test"/b.asm + echo "${bold}${green}${test}...${rescolors}${resbold}" + rgblinkQuiet -o "$gbtemp" "$otemp" "$gbtemp2" 2>"$outtemp" || true + tryDiff "$test"/out.err "$outtemp" || our_rc=1 + ;; + + constant-parent) + echo "${bold}${green}${test} assembling...${rescolors}${resbold}" + "$RGBASM" -o "$otemp" "$test"/a.asm + "$RGBASM" -o "$gbtemp2" "$test"/b.asm + echo "${bold}${green}${test}...${rescolors}${resbold}" + rgblinkQuiet -o "$gbtemp" -n "$outtemp2" "$otemp" "$gbtemp2" 2>"$outtemp" || true + tryDiff "$test"/out.err "$outtemp" || our_rc=1 + tryDiff "$test"/ref.out.sym "$outtemp2" || our_rc=1 + tryCmpRom "$test"/ref.out.bin "$gbtemp" || our_rc=1 + ;; + + export-all) + echo "${bold}${green}${test} assembling...${rescolors}${resbold}" + "$RGBASM" -E -o "$otemp" "$test"/a.asm + "$RGBASM" -o "$gbtemp2" "$test"/b.asm + echo "${bold}${green}${test}...${rescolors}${resbold}" + rgblinkQuiet -o "$gbtemp" -n "$outtemp2" "$otemp" "$gbtemp2" 2>"$outtemp" || true + tryDiff "$test"/out.err "$outtemp" || our_rc=1 + tryDiff "$test"/ref.out.sym "$outtemp2" || our_rc=1 + tryCmpRom "$test"/ref.out.bin "$gbtemp" || our_rc=1 + ;; + + fragment-align/*) + echo "${bold}${green}${test} assembling...${rescolors}${resbold}" + "$RGBASM" -o "$otemp" "$test"/a.asm + "$RGBASM" -o "$gbtemp2" "$test"/b.asm + echo "${bold}${green}${test}...${rescolors}${resbold}" + rgblinkQuiet -o "$gbtemp" "$otemp" "$gbtemp2" 2>"$outtemp" || true + tryDiff "$test"/out.err "$outtemp" || our_rc=1 + if [[ -f "$test"/out.gb ]]; then + tryCmpRom "$test"/out.gb "$gbtemp" || our_rc=1 + fi + ;; + + fragment-literals) + echo "${bold}${green}${test} assembling...${rescolors}${resbold}" + "$RGBASM" -o "$otemp" "$test"/a.asm + echo "${bold}${green}${test}...${rescolors}${resbold}" + rgblinkQuiet -o "$gbtemp" -m "$outtemp" -n "$outtemp2" "$otemp" || true + tryCmpRom "$test"/ref.out.bin "$gbtemp" || our_rc=1 + tryDiff "$test"/ref.out.map "$outtemp" || our_rc=1 + tryDiff "$test"/ref.out.sym "$outtemp2" || our_rc=1 + ;; + + high-low) + echo "${bold}${green}${test} assembling...${rescolors}${resbold}" + "$RGBASM" -o "$otemp" "$test"/a.asm + "$RGBASM" -o "$outtemp" "$test"/b.asm + echo "${bold}${green}${test}...${rescolors}${resbold}" + rgblinkQuiet -o "$gbtemp" "$otemp" || true + rgblinkQuiet -o "$gbtemp2" "$outtemp" || true + tryCmp "$gbtemp" "$gbtemp2" || our_rc=1 + ;; + + load-fragment/base) + echo "${bold}${green}${test} assembling...${rescolors}${resbold}" + "$RGBASM" -o "$otemp" "$test"/a.asm + echo "${bold}${green}${test}...${rescolors}${resbold}" + rgblinkQuiet -o "$gbtemp" -n "$outtemp" "$otemp" || true + tryCmpRom "$test"/ref.out.bin "$gbtemp" || our_rc=1 + tryDiff "$test"/ref.out.sym "$outtemp" || our_rc=1 + ;; + + load-fragment/multiple-objects) + echo "${bold}${green}${test} assembling...${rescolors}${resbold}" + "$RGBASM" -o "$otemp" "$test"/a.asm + "$RGBASM" -o "$gbtemp2" "$test"/b.asm + echo "${bold}${green}${test}...${rescolors}${resbold}" + rgblinkQuiet -o "$gbtemp" "$otemp" "$gbtemp2" || true + tryCmpRom "$test"/ref.out.bin "$gbtemp" || our_rc=1 + ;; + + load-fragment/section-fragment) + echo "${bold}${green}${test} assembling...${rescolors}${resbold}" + "$RGBASM" -o "$otemp" "$test"/a.asm + "$RGBASM" -o "$outtemp" "$test"/b.asm + "$RGBASM" -o "$outtemp2" "$test"/c.asm + echo "${bold}${green}${test}...${rescolors}${resbold}" + rgblinkQuiet -o "$gbtemp" -m "$outtemp3" -n "$gbtemp2" "$otemp" "$outtemp" "$outtemp2" || true + tryCmpRom "$test"/ref.out.bin "$gbtemp" || our_rc=1 + tryDiff "$test"/ref.out.map "$outtemp3" || our_rc=1 + tryDiff "$test"/ref.out.sym "$gbtemp2" || our_rc=1 + ;; + + map-file) + echo "${bold}${green}${test} assembling...${rescolors}${resbold}" + "$RGBASM" -o "$otemp" "$test"/a.asm + echo "${bold}${green}${test}...${rescolors}${resbold}" + rgblinkQuiet -o "$gbtemp" -m "$outtemp2" "$otemp" 2>"$outtemp" || true + tryDiff "$test"/out.err "$outtemp" || our_rc=1 + tryDiff "$test"/ref.out.map "$outtemp2" || our_rc=1 + tryCmpRom "$test"/ref.out.bin "$gbtemp" || our_rc=1 + ;; + + overlay/smaller) + echo "${bold}${green}${test} assembling...${rescolors}${resbold}" + "$RGBASM" -o "$otemp" "$test"/a.asm + echo "${bold}${green}${test}...${rescolors}${resbold}" + rgblinkQuiet -o "$gbtemp" -p 0x42 -O "$test"/overlay.gb "$otemp" 2>"$outtemp" || true + tryDiff "$test"/out.err "$outtemp" || our_rc=1 + tryCmp "$test"/out.gb "$gbtemp" || our_rc=1 + ;; + + overlay/unfixed) + echo "${bold}${green}${test} assembling...${rescolors}${resbold}" + "$RGBASM" -o "$otemp" "$test"/a.asm + echo "${bold}${green}${test}...${rescolors}${resbold}" + rgblinkQuiet -o "$gbtemp" -O "$test"/overlay.gb "$otemp" 2>"$outtemp" || true + tryDiff "$test"/out.err "$outtemp" || our_rc=1 + ;; + + overlay/tiny) + echo "${bold}${green}${test} assembling...${rescolors}${resbold}" + "$RGBASM" -o "$otemp" "$test"/a.asm + echo "${bold}${green}${test}...${rescolors}${resbold}" + rgblinkQuiet -o "$gbtemp" -t -O "$test"/overlay.gb "$otemp" 2>"$outtemp" || true + tryDiff "$test"/out.err "$outtemp" || our_rc=1 + tryCmp "$test"/out.gb "$gbtemp" || our_rc=1 + ;; + + pipeline) + echo "${bold}${green}${test}...${rescolors}${resbold}" + ("$RGBASM" -o - - | "$RGBLINK" -o - - | "$RGBFIX" -v -p 0xff -) < "$test"/a.asm > "$gbtemp" + tryCmp "$test"/out.gb "$gbtemp" || our_rc=1 + ;; + + rept-trace) + echo "${bold}${green}${test} assembling...${rescolors}${resbold}" + "$RGBASM" -o "$otemp" "$test"/a.asm + echo "${bold}${green}${test}...${rescolors}${resbold}" + rgblinkQuiet -Bno-collapse -o "$gbtemp" "$otemp" 2>"$outtemp" || true + tryDiff "$test"/out.err "$outtemp" || our_rc=1 + ;; + + same-consts) + echo "${bold}${green}${test} assembling...${rescolors}${resbold}" + "$RGBASM" -o "$otemp" "$test"/a.asm + "$RGBASM" -o "$gbtemp2" "$test"/b.asm + echo "${bold}${green}${test}...${rescolors}${resbold}" + rgblinkQuiet -o "$gbtemp" "$otemp" "$gbtemp2" 2>"$outtemp" || true + tryDiff "$test"/out.err "$outtemp" || our_rc=1 + ;; + + scramble-invalid) + echo "${bold}${green}${test} assembling...${rescolors}${resbold}" + "$RGBASM" -o "$otemp" "$test"/a.asm + echo "${bold}${green}${test}...${rescolors}${resbold}" + rgblinkQuiet -o "$gbtemp" -S "romx := 4" "$otemp" 2>"$outtemp" || true + tryDiff "$test"/out.err "$outtemp" || our_rc=1 + ;; + + scramble-romx) + echo "${bold}${green}${test} assembling...${rescolors}${resbold}" + "$RGBASM" -o "$otemp" "$test"/a.asm + echo "${bold}${green}${test}...${rescolors}${resbold}" + rgblinkQuiet -o "$gbtemp" -S "romx=3,wramx=4,sram=4" "$otemp" 2>"$outtemp" || true + tryDiff "$test"/out.err "$outtemp" || our_rc=1 + tryCmpRomSize "$gbtemp" 65536 || our_rc=1 + ;; + + script-include) + echo "${bold}${green}${test} assembling...${rescolors}${resbold}" + "$RGBASM" -o "$otemp" "$test"/a.asm + "$RGBASM" -o "$gbtemp2" "$test"/b.asm + echo "${bold}${green}${test}...${rescolors}${resbold}" + rgblinkQuiet -o "$gbtemp" -l "$test"/script.link "$otemp" "$gbtemp2" 2>"$outtemp" || true + tryDiff "$test"/out.err "$outtemp" || our_rc=1 + tryCmpRom "$test"/ref.out.bin "$gbtemp" || our_rc=1 + ;; + + sdcc/good) + echo "${bold}${green}${test} assembling...${rescolors}${resbold}" + "$RGBASM" -o "$otemp" "$test"/a.asm + echo "${bold}${green}${test}...${rescolors}${resbold}" + rgblinkQuiet -o "$gbtemp" -n "$outtemp2" -l "$test"/script.link "$otemp" "$test"/b.rel "$test"/c.rel 2>"$outtemp" || true + tryDiff "$test"/out.err "$outtemp" || our_rc=1 + tryDiff "$test"/ref.out.sym "$outtemp2" || our_rc=1 + tryCmpRom "$test"/ref.out.bin "$gbtemp" || our_rc=1 + ;; + + sdcc/no-script) + echo "${bold}${green}${test} assembling...${rescolors}${resbold}" + "$RGBASM" -o "$otemp" "$test"/a.asm + echo "${bold}${green}${test}...${rescolors}${resbold}" + rgblinkQuiet "$otemp" "$test"/b.rel 2>"$outtemp" || true + tryDiff "$test"/out.err "$outtemp" || our_rc=1 + ;; + + section-conflict/different-mod) + echo "${bold}${green}${test} assembling...${rescolors}${resbold}" + "$RGBASM" -o "$otemp" "$test"/a.asm + "$RGBASM" -o "$gbtemp" "$test"/b.asm + echo "${bold}${green}${test}...${rescolors}${resbold}" + rgblinkQuiet "$otemp" "$gbtemp" 2>"$outtemp" || true + tryDiff "$test"/out.err "$outtemp" || our_rc=1 + ;; + + section-fragment/good) + echo "${bold}${green}${test} assembling...${rescolors}${resbold}" + "$RGBASM" -o "$otemp" "$test"/a.asm + "$RGBASM" -o "$gbtemp2" "$test"/b.asm + echo "${bold}${green}${test}...${rescolors}${resbold}" + rgblinkQuiet -o "$gbtemp" "$otemp" "$gbtemp2" || true + tryCmpRom "$test"/ref.out.bin "$gbtemp" || our_rc=1 + ;; + + section-fragment/jr-offset|section-fragment/jr-offset-load) + echo "${bold}${green}${test} assembling...${rescolors}${resbold}" + "$RGBASM" -o "$otemp" "$test"/a.asm + "$RGBASM" -o "$gbtemp2" "$test"/b.asm + echo "${bold}${green}${test}...${rescolors}${resbold}" + rgblinkQuiet -o "$gbtemp" "$otemp" "$gbtemp2" || true + tryCmpRom "$test"/ref.out.bin "$gbtemp" || our_rc=1 + ;; + + section-normal/same-name) + echo "${bold}${green}${test} assembling...${rescolors}${resbold}" + "$RGBASM" -o "$otemp" "$test"/a.asm + "$RGBASM" -o "$gbtemp" "$test"/b.asm + echo "${bold}${green}${test}...${rescolors}${resbold}" + rgblinkQuiet "$otemp" "$gbtemp" 2>"$outtemp" || true + tryDiff "$test"/out.err "$outtemp" || our_rc=1 + ;; + + section-union/compat|section-union/good) + echo "${bold}${green}${test} assembling...${rescolors}${resbold}" + "$RGBASM" -o "$otemp" "$test"/a.asm + "$RGBASM" -o "$gbtemp2" "$test"/b.asm + echo "${bold}${green}${test}...${rescolors}${resbold}" + rgblinkQuiet -o "$gbtemp" -l "$test"/script.link "$otemp" "$gbtemp2" || true + tryCmpRom "$test"/ref.out.bin "$gbtemp" || our_rc=1 + ;; + + section-union/same-export) + echo "${bold}${green}${test} assembling...${rescolors}${resbold}" + "$RGBASM" -o "$otemp" "$test"/a.asm + "$RGBASM" -o "$gbtemp2" "$test"/b.asm + echo "${bold}${green}${test}...${rescolors}${resbold}" + rgblinkQuiet "$otemp" "$gbtemp2" 2>"$outtemp" || true + tryDiff "$test"/out.err "$outtemp" || our_rc=1 + ;; + + section-union/same-label) + echo "${bold}${green}${test} assembling...${rescolors}${resbold}" + "$RGBASM" -o "$otemp" "$test"/a.asm + "$RGBASM" -o "$gbtemp2" "$test"/b.asm + echo "${bold}${green}${test}...${rescolors}${resbold}" + rgblinkQuiet -o "$gbtemp" "$otemp" "$gbtemp2" 2>"$outtemp" || true + tryDiff "$test"/out.err "$outtemp" || our_rc=1 + tryCmpRom "$test"/ref.out.bin "$gbtemp" || our_rc=1 + ;; + + symbols/conflict) + echo "${bold}${green}${test} assembling...${rescolors}${resbold}" + "$RGBASM" -o "$otemp" "$test"/a.asm + "$RGBASM" -o "$gbtemp" "$test"/b.asm + echo "${bold}${green}${test}...${rescolors}${resbold}" + rgblinkQuiet "$otemp" "$gbtemp" 2>"$outtemp" || true + tryDiff "$test"/out.err "$outtemp" || our_rc=1 + ;; + + symbols/good) + echo "${bold}${green}${test} assembling...${rescolors}${resbold}" + "$RGBASM" -o "$otemp" "$test"/a.asm + "$RGBASM" -o "$gbtemp2" "$test"/b.asm + echo "${bold}${green}${test}...${rescolors}${resbold}" + rgblinkQuiet -o "$gbtemp" -n "$outtemp2" "$otemp" "$gbtemp2" 2>"$outtemp" || true + tryDiff "$test"/out.err "$outtemp" || our_rc=1 + tryDiff "$test"/ref.out.sym "$outtemp2" || our_rc=1 + tryCmpRom "$test"/ref.out.bin "$gbtemp" || our_rc=1 + ;; + + symbols/unknown) + echo "${bold}${green}${test} assembling...${rescolors}${resbold}" + "$RGBASM" -o "$otemp" "$test"/a.asm + "$RGBASM" -o "$gbtemp" "$test"/b.asm + "$RGBASM" -o "$gbtemp2" "$test"/c.asm + "$RGBASM" -o "$outtemp" "$test"/d.asm + "$RGBASM" -o "$outtemp2" "$test"/e.asm + echo "${bold}${green}${test}...${rescolors}${resbold}" + rgblinkQuiet "$otemp" "$gbtemp" "$gbtemp2" "$outtemp" "$outtemp2" 2>"$outtemp3" || true + tryDiff "$test"/out.err "$outtemp3" || our_rc=1 + ;; + + truncation/level1) + echo "${bold}${green}${test} assembling...${rescolors}${resbold}" + "$RGBASM" -o "$otemp" "$test"/a.asm + echo "${bold}${green}${test}...${rescolors}${resbold}" + rgblinkQuiet -Wtruncation=1 -o "$gbtemp" "$otemp" 2>"$outtemp" || true + tryDiff "$test"/out.err "$outtemp" || our_rc=1 + ;; + + truncation/level2) + echo "${bold}${green}${test} assembling...${rescolors}${resbold}" + "$RGBASM" -o "$otemp" "$test"/a.asm + echo "${bold}${green}${test}...${rescolors}${resbold}" + rgblinkQuiet -Wtruncation=2 -o "$gbtemp" "$otemp" 2>"$outtemp" || true + tryDiff "$test"/out.err "$outtemp" || our_rc=1 + ;; + + *) + echo "Unknown special test: $test" >&2 + exit 2 + ;; + esac + + rc=$our_rc + ;; + +# ========================================================================= +# Error tests — section-union/*.asm and section-fragment/*.asm +# ========================================================================= +error) + test="$test_name" + + echo "${bold}${green}${test} assembling...${rescolors}${resbold}" + "$RGBASM" -o "$otemp" "${test}.asm" + "$RGBASM" -o "$gbtemp2" "${test}.asm" -DSECOND + + echo "${bold}${green}${test}...${rescolors}${resbold}" + our_rc=0 + if rgblinkQuiet "$otemp" "$gbtemp2" 2>"$outtemp"; then + echo "${bold}${red}${test}.asm didn't fail to link!${rescolors}${resbold}" >&2 + our_rc=1 + fi + echo --- >>"$outtemp" + # Ensure RGBASM also errors out + cat "${test}.asm" - "${test}.asm" <<<'def SECOND equs "1"' | "$RGBASM" - 2>>"$outtemp" || true + tryDiff "${test}.out" "$outtemp" || our_rc=1 + + rc=$our_rc + ;; + +*) + echo "Unknown test type: $test_type" >&2 + exit 2 + ;; +esac + +exit $rc diff --git a/test/run-one-external.sh b/test/run-one-external.sh new file mode 100755 index 000000000..9593db250 --- /dev/null +++ b/test/run-one-external.sh @@ -0,0 +1,82 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MIT +# +# Run a single external/downstream project test. +# +# Usage: +# run-one-external.sh +# +# Environment: +# TEST_SRCDIR — must point to the test/ source directory +# RGBDS_PATH — override for RGBDS= make variable (default: path to built binaries) +# OS_NAME — for platform-specific skips + +set -euo pipefail + +# shellcheck source=helpers.sh +source "$(dirname "$0")/helpers.sh" + +TEST_SRCDIR="${TEST_SRCDIR:?TEST_SRCDIR must be set}" +# Do NOT call setup_tools — external projects use RGBDS= make variable, +# not individual RGBASM/RGBLINK/… env vars. Unset them so that downstream +# Makefiles (which use ?=) are free to construct paths from the RGBDS prefix. +unset RGBASM RGBLINK RGBFIX RGBGFX + +project="${1:?missing project name argument}" + +cd "$TEST_SRCDIR" + +# Default RGBDS_PATH to point at the built binaries two levels up +RGBDS_PATH="${RGBDS_PATH:-RGBDS=../../}" +OS_NAME="${OS_NAME:-}" + +test_downstream() { # make-target build-file build-hash + local target="$1" build_file="$2" expected_hash="$3" + + if ! pushd "$project"; then + echo >&2 "Please run fetch-test-deps.sh before running external tests" + return 1 + fi + make clean $RGBDS_PATH + make -j4 "$target" $RGBDS_PATH + hash="$(sha1sum -b "$build_file" | head -c 40)" + if [ "$hash" != "$expected_hash" ]; then + echo >&2 "SHA-1 hash of $build_file did not match: $hash" + popd + return 1 + fi + popd +} + +case "$project" in + pokecrystal) + test_downstream compare pokecrystal.gbc f4cd194bdee0d04ca4eac29e09b8e4e9d818c133 + ;; + pokered) + test_downstream compare pokered.gbc ea9bcae617fdf159b045185467ae58b2e4a48b9a + ;; + LADX-Disassembly) + test_downstream default azle.gbc d90ac17e9bf17b6c61624ad9f05447bdb5efc01a + ;; + ucity) + test_downstream all ucity.gbc 5f026649611c9606ce0bf70dc1552e054e7df5bc + ;; + libbet) + test_downstream all libbet.gb f117089aa056600e2d404bbcbac96b016fc64611 + ;; + SameBoy) + test_downstream bootroms build/bin/BootROMs/cgb_boot.bin 113903775a9d34b798c2f8076672da6626815a91 + ;; + gb-starter-kit) + # gb-starter-kit fails on Windows and macOS/BSD make + if [[ "${OS_NAME%-*}" = "windows" || "${OS_NAME%-*}" = "macos" || "${OS_NAME%-*}" = "bsd" ]]; then + echo "Skipping gb-starter-kit on ${OS_NAME}" >&2 + exit 0 + fi + test_downstream all bin/boilerplate.gb b4f130169ba73284e0d0e71b53e7baa4eca2f7fe + ;; + *) + echo "Unknown downstream project: $project" >&2 + exit 2 + ;; +esac