From bf604b99b39aaa281a696184f8241cd524b1bed6 Mon Sep 17 00:00:00 2001 From: Bill Currie Date: Mon, 3 Jan 2022 14:41:29 +0900 Subject: [PATCH] [gamecode] Add automated tests for store ops They even found a bug in the addressing mode functions :) (I'd forgotten that I wanted signed offsets from the pointer and thus forgot to cast st->b to short in order to get the sign extension) --- libs/gamecode/Makemodule.am | 2 + libs/gamecode/pr_exec.c | 18 ++-- libs/gamecode/test/Makemodule.am | 17 ++++ libs/gamecode/test/main.c | 166 +++++++++++++++++++++++++++++++ libs/gamecode/test/test-store.c | 132 ++++++++++++++++++++++++ 5 files changed, 326 insertions(+), 9 deletions(-) create mode 100644 libs/gamecode/test/Makemodule.am create mode 100644 libs/gamecode/test/main.c create mode 100644 libs/gamecode/test/test-store.c diff --git a/libs/gamecode/Makemodule.am b/libs/gamecode/Makemodule.am index dfae7c388..b962f60ad 100644 --- a/libs/gamecode/Makemodule.am +++ b/libs/gamecode/Makemodule.am @@ -1,3 +1,5 @@ +include libs/gamecode/test/Makemodule.am + gc_deps=libs/util/libQFutil.la noinst_LTLIBRARIES += libs/gamecode/libQFgamecode.la diff --git a/libs/gamecode/pr_exec.c b/libs/gamecode/pr_exec.c index 8a014b469..e8c186505 100644 --- a/libs/gamecode/pr_exec.c +++ b/libs/gamecode/pr_exec.c @@ -1753,11 +1753,11 @@ pr_entity_mode (progs_t *pr, const dstatement_t *st, int shift) mm_offs = OPA(uint); break; case 2: - // constant indexed pointer: *a + b - mm_offs = OPA(uint) + st->b; + // constant indexed pointer: *a + b (supports -ve offset) + mm_offs = OPA(uint) + (short) st->b; break; case 3: - // verible indexed pointer: *a + *b (supports -ve offset) + // variable indexed pointer: *a + *b (supports -ve offset) mm_offs = OPA(uint) + OPB(int); break; } @@ -1782,11 +1782,11 @@ pr_address_mode (progs_t *pr, const dstatement_t *st, int shift) mm_offs = OPA(uint); break; case 2: - // constant indexed pointer: *a + b - mm_offs = OPA(uint) + st->b; + // constant indexed pointer: *a + b (supports -ve offset) + mm_offs = OPA(uint) + (short) st->b; break; case 3: - // verible indexed pointer: *a + *b (supports -ve offset) + // variable indexed pointer: *a + *b (supports -ve offset) mm_offs = OPA(uint) + OPB(int); break; } @@ -1811,11 +1811,11 @@ pr_jump_mode (progs_t *pr, const dstatement_t *st) jump_offs = OPA(uint); break; case 2: - // constant indexed pointer: *a + b - jump_offs = OPA(uint) + st->b; + // constant indexed pointer: *a + b (supports -ve offset) + jump_offs = OPA(uint) + (short) st->b; break; case 3: - // verible indexed pointer: *a + *b (supports -ve offset) + // variable indexed pointer: *a + *b (supports -ve offset) jump_offs = OPA(uint) + OPB(int); break; } diff --git a/libs/gamecode/test/Makemodule.am b/libs/gamecode/test/Makemodule.am new file mode 100644 index 000000000..9a0bac68a --- /dev/null +++ b/libs/gamecode/test/Makemodule.am @@ -0,0 +1,17 @@ +libs_gamecode_tests = \ + libs/gamecode/test/test-store + +TESTS += $(libs_gamecode_tests) + +check_PROGRAMS += $(libs_gamecode_tests) + +EXTRA_DIST += main.c + +test_gamecode_libs= \ + libs/gamecode/libQFgamecode.la \ + libs/util/libQFutil.la + +libs_gamecode_test_test_store_SOURCES= \ + libs/gamecode/test/test-store.c +libs_gamecode_test_test_store_LDADD= $(test_gamecode_libs) +libs_gamecode_test_test_store_DEPENDENCIES= $(test_gamecode_libs) diff --git a/libs/gamecode/test/main.c b/libs/gamecode/test/main.c new file mode 100644 index 000000000..e861d6e46 --- /dev/null +++ b/libs/gamecode/test/main.c @@ -0,0 +1,166 @@ +#include +#include +#include +#include + +#include "QF/va.h" + +#define num_tests (sizeof (tests) / sizeof (tests[0])) +static int test_enabled[num_tests] = { 0 }; + +#include "getopt.h" + +#include "QF/cmd.h" +#include "QF/cvar.h" + +static bfunction_t test_functions[] = { + {}, // null function + { .first_statement = 0 } +}; +static dprograms_t test_progs = { + .version = PROG_VERSION, +}; +static progs_t test_pr; + +static jmp_buf jump_buffer; + +static void +test_debug_handler (prdebug_t event, void *param, void *data) +{ + switch (event) { + case prd_breakpoint: + if (verbose > 0) { + printf ("debug: %s\n", prdebug_names[event]); + } + longjmp (jump_buffer, 1); + case prd_subenter: + if (verbose > 0) { + printf ("debug: subenter %d\n", *(func_t *) param); + } + case prd_subexit: + break; + case prd_trace: + dstatement_t *st = test_pr.pr_statements + test_pr.pr_xstatement; + if (verbose > 0) { + printf ("debug: trace %05x %04x %04x %04x %04x\n", + test_pr.pr_xstatement, st->op, st->a, st->b, st->c); + } + break; + case prd_runerror: + printf ("debug: %s: %s\n", prdebug_names[event], (char *)param); + longjmp (jump_buffer, 3); + case prd_watchpoint: + case prd_begin: + case prd_terminate: + case prd_error: + case prd_none: + printf ("debug: unexpected:%s %p\n", prdebug_names[event], param); + longjmp (jump_buffer, 2); + } +} + +static void +setup_test (test_t *test) +{ + memset (&test_pr, 0, sizeof (test_pr)); + test_pr.progs = &test_progs; + test_pr.debug_handler = test_debug_handler; + test_pr.pr_trace = 1; + test_pr.pr_trace_depth = -1; + test_pr.function_table = test_functions; + test_pr.globals_size = test->num_globals; + pr_uint_t num_globals = test->num_globals + test->extra_globals; + test_pr.pr_globals = malloc (num_globals * sizeof (pr_type_t)); + memcpy (test_pr.pr_globals, test->init_globals, + test->num_globals * sizeof (pr_type_t)); + memset (test_pr.pr_globals + test->num_globals, 0, + test->extra_globals * sizeof (pr_type_t)); + test_pr.pr_statements + = malloc ((test->num_statements + 1) * sizeof (dstatement_t)); + memcpy (test_pr.pr_statements, test->statements, + (test->num_statements + 1) * sizeof (dstatement_t)); + test_pr.pr_statements[test->num_statements] = + (dstatement_t) { OP_BREAK, 0, 0, 0 }; +} + +static int +run_test (test_t *test) +{ + int jump_ret; + int ret = 0; + + setup_test (test); + + if (!(jump_ret = setjmp (jump_buffer))) { + PR_ExecuteProgram (&test_pr, 1); + printf ("returned from progs\n"); + } + if (jump_ret == 1) { + if (memcmp (test_pr.pr_globals, test->expect_globals, + test->num_globals * sizeof (pr_int_t)) == 0) { + ret = 1; + printf ("test #%zd: %s: OK\n", test - tests, test->desc); + } else { + printf ("test #%zd: %s: bytes differ\n", test - tests, test->desc); + } + } else { + printf ("test #%zd: %s: critical failure\n", test - tests, test->desc); + } + free (test_pr.pr_globals); + free (test_pr.pr_statements); + return ret; +} + +int +main (int argc, char **argv) +{ + int c; + size_t i, test; + int pass = 1; + + Cmd_Init_Hash (); + Cvar_Init_Hash (); + Cmd_Init (); + Cvar_Init (); + PR_Init_Cvars (); + + while ((c = getopt (argc, argv, "qvt:")) != EOF) { + switch (c) { + case 'q': + verbose--; + break; + case 'v': + verbose++; + break; + case 't': + test = atoi (optarg); + if (test < num_tests) { + test_enabled[test] = 1; + } else { + fprintf (stderr, "Bad test number (0 - %zd)\n", num_tests); + return 1; + } + break; + default: + fprintf (stderr, "-q (quiet) -v (verbose) and/or -t TEST " + "(test number)\n"); + return 1; + } + } + + for (i = 0; i < num_tests; i++) + if (test_enabled[i]) + break; + if (i == num_tests) { + for (i = 0; i < num_tests; i++) + test_enabled[i] = 1; + } + + for (i = 0; i < num_tests; i++) { + if (!test_enabled[i]) + continue; + pass &= run_test (&tests[i]); + } + + return !pass; +} diff --git a/libs/gamecode/test/test-store.c b/libs/gamecode/test/test-store.c new file mode 100644 index 000000000..aacddc4e8 --- /dev/null +++ b/libs/gamecode/test/test-store.c @@ -0,0 +1,132 @@ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "QF/progs.h" + +static int verbose = 0; + +// both calculates the number of globals in the test, and ensures that both +// init and expect are the same size (will product a "void value not ignored" +// error if the sizes differ) +#define num_globals(init, expect) \ + __builtin_choose_expr ( \ + sizeof (init) == sizeof (expect), sizeof (init) / sizeof (init[0]), \ + (void) 0\ + ) + +// calculate the numver of statements in the test +#define num_statements(statements) \ + (sizeof (statements) / sizeof (statements[0])) + +typedef struct { + const char *desc; + pr_uint_t extra_globals; + pr_uint_t num_globals; + pr_uint_t num_statements; + dstatement_t *statements; + pr_int_t *init_globals; + pr_int_t *expect_globals; +} test_t; + +static pr_int_t test_globals_init[] = { + // pointers + 24, 26, 28, 29, + 32, -4, -2, 0, + 1, 4, 0xdeadbeef, 0xfeedf00d, + // source data + 1, 2, 3, 4, + 5, 6, 7, 8, + 9, 10, 11, 12, + // destination data + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, +}; + +static pr_int_t test_globals_expect[] = { + // pointers + 24, 26, 28, 29, + 32, -4, -2, 0, + 1, 4, 0xdeadbeef, 0xfeedf00d, + // source data + 1, 2, 3, 4, + 5, 6, 7, 8, + 9, 10, 11, 12, + // destination data + 11, 12, 9, 10, + 8, 5, 6, 7, + 1, 2, 3, 4, +}; + +#define BASE(b, base) (((base) & 3) << OP_##b##_SHIFT) +#define OP(a, b, c, op) ((op) | BASE(A, a) | BASE(B, b) | BASE(C, c)) + +static dstatement_t store_A_statements[] = { + {OP(0, 0, 0, OP_STORE_A_4), 32, 0, 12}, + {OP(0, 0, 0, OP_STORE_A_3), 29, 0, 16}, + {OP(0, 0, 0, OP_STORE_A_1), 28, 0, 19}, + {OP(0, 0, 0, OP_STORE_A_2), 26, 0, 20}, + {OP(0, 0, 0, OP_STORE_A_2), 24, 0, 22}, +}; + +static dstatement_t store_B_statements[] = { + {OP(0, 0, 0, OP_STORE_B_4), 4, 0, 12}, + {OP(0, 0, 0, OP_STORE_B_3), 3, 0, 16}, + {OP(0, 0, 0, OP_STORE_B_1), 2, 0, 19}, + {OP(0, 0, 0, OP_STORE_B_2), 1, 0, 20}, + {OP(0, 0, 0, OP_STORE_B_2), 0, 0, 22}, +}; + +static dstatement_t store_C_statements[] = { + {OP(0, 0, 0, OP_STORE_C_4), 2, 4, 12}, + {OP(0, 0, 0, OP_STORE_C_3), 2, 1, 16}, + {OP(0, 0, 0, OP_STORE_C_1), 2, 0, 19}, + {OP(0, 0, 0, OP_STORE_C_2), 2, -2, 20}, + {OP(0, 0, 0, OP_STORE_C_2), 2, -4, 22}, +}; + +static dstatement_t store_D_statements[] = { + {OP(0, 0, 0, OP_STORE_D_4), 2, 9, 12}, + {OP(0, 0, 0, OP_STORE_D_3), 2, 8, 16}, + {OP(0, 0, 0, OP_STORE_D_1), 2, 7, 19}, + {OP(0, 0, 0, OP_STORE_D_2), 2, 6, 20}, + {OP(0, 0, 0, OP_STORE_D_2), 2, 5, 22}, +}; + +test_t tests[] = { + { + .desc = "store A", + .num_globals = num_globals (test_globals_init, test_globals_expect), + .num_statements = num_statements (store_A_statements), + .statements = store_A_statements, + .init_globals = test_globals_init, + .expect_globals = test_globals_expect, + }, + { + .desc = "store B", + .num_globals = num_globals (test_globals_init, test_globals_expect), + .num_statements = num_statements (store_B_statements), + .statements = store_B_statements, + .init_globals = test_globals_init, + .expect_globals = test_globals_expect, + }, + { + .desc = "store C", + .num_globals = num_globals (test_globals_init, test_globals_expect), + .num_statements = num_statements (store_C_statements), + .statements = store_C_statements, + .init_globals = test_globals_init, + .expect_globals = test_globals_expect, + }, + { + .desc = "store D", + .num_globals = num_globals (test_globals_init, test_globals_expect), + .num_statements = num_statements (store_D_statements), + .statements = store_D_statements, + .init_globals = test_globals_init, + .expect_globals = test_globals_expect, + }, +}; + +#include "main.c"