From fe2778133f544369d1eef01398e1e8aa3f5af7b6 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Sat, 23 May 2026 18:31:02 -0700 Subject: [PATCH] rework file directive handling; remove T_Group; add SKIP to disable tests that are not passing; change command line syntax for easier test run/skip/force; updated help; shortened --dbg_graphical -> --gui; add 'skip' directive to the dbg scripts --- .github/workflows/builds.yml | 3 +- run.bat | 1 + run.sh | 4 + src/linker/hash_table.c | 4 +- src/torture/dbg_tests/step_into_for_goto.c | 25 ++ .../dbg_tests/step_into_lazy_module_func.c | 32 +- src/torture/torture.c | 323 ++++++++++-------- src/torture/torture.h | 54 ++- src/torture/torture_base.c | 3 - src/torture/torture_d2r.c | 7 +- src/torture/torture_dbg.c | 323 +++++++++++------- src/torture/torture_dbg.h | 41 ++- src/torture/torture_dwarf.c | 3 - src/torture/torture_md.c | 2 +- src/torture/torture_p2r.c | 4 - src/torture/torture_radlink.c | 5 +- 16 files changed, 494 insertions(+), 340 deletions(-) create mode 100644 src/torture/dbg_tests/step_into_for_goto.c diff --git a/.github/workflows/builds.yml b/.github/workflows/builds.yml index ffcc4f4d..31b8afbd 100644 --- a/.github/workflows/builds.yml +++ b/.github/workflows/builds.yml @@ -8,9 +8,8 @@ on: paths-ignore: - '**.md' -# TODO: fix skips env: - TORTURE_FLAGS: "--gen_crash_dump -s:d2r::d2r_line_table,d2r::d2r_checksums" + TORTURE_FLAGS: "* --gen_crash_dump" jobs: build-ubuntu-24-04: diff --git a/run.bat b/run.bat index 43a26831..a0f79b2e 100644 --- a/run.bat +++ b/run.bat @@ -27,6 +27,7 @@ if "%TORTURE_ARGS:~0,3%" == "-- " set "TORTURE_ARGS=%TORTURE_ARGS:~3%" && goto p set "TORTURE_ARGS=%TORTURE_ARGS:* -- =%" :parse_done +if not defined TORTURE_ARGS set "TORTURE_ARGS=* for %%m in (%MODE_VALUES%) do for %%c in (%CC_VALUES%) do ( setlocal diff --git a/run.sh b/run.sh index 39191afc..b7cb00c8 100755 --- a/run.sh +++ b/run.sh @@ -38,6 +38,10 @@ while [[ $# -gt 0 ]]; do esac done +if [[ "${#torture_args[@]}" == "0" ]]; then + torture_args=("*") +fi + for m in "${mode_values[@]}"; do for c in "${cc_values[@]}"; do # nuke artifacts from last run diff --git a/src/linker/hash_table.c b/src/linker/hash_table.c index a47e8929..48534e14 100644 --- a/src/linker/hash_table.c +++ b/src/linker/hash_table.c @@ -926,7 +926,9 @@ internal void * keys_from_hash_map_raw ( Arena *arena, HashMap *hm) { retur // values force_inline HASH_MAP_EXTRACT_FUNC(hash_map_extract_value_u64) { MemoryCopy(buffer, &kv->value.value_u64, sizeof(kv->value.value_u64)); } -internal U64 * values_from_hash_map_u64(Arena *arena, HashMap *hm) { return hash_map_extract(arena, hm, 0, 0, hash_map_extract_value_u64, sizeof(U64)).values; } +force_inline HASH_MAP_EXTRACT_FUNC(hash_map_extract_value_raw) { MemoryCopy(buffer, &kv->value.value_raw, sizeof(kv->value.value_raw)); } +internal U64 * values_from_hash_map_u64(Arena *arena, HashMap *hm) { return hash_map_extract(arena, hm, 0, 0, hash_map_extract_value_u64, sizeof(U64)).values; } +internal void * values_from_hash_map_raw(Arena *arena, HashMap *hm) {return hash_map_extract(arena, hm, 0, 0, hash_map_extract_value_raw, sizeof(void *)).values; } //////////////////////////////// diff --git a/src/torture/dbg_tests/step_into_for_goto.c b/src/torture/dbg_tests/step_into_for_goto.c new file mode 100644 index 00000000..5ff09613 --- /dev/null +++ b/src/torture/dbg_tests/step_into_for_goto.c @@ -0,0 +1,25 @@ +/// test: { +/// windows: { +/// skip: "" +/// compile: { +/// cc: "clang" +/// args: "-O0 -g -o main.exe %FILE%" +/// } +/// launch: "main.exe" +/// } +/// linux: { +/// skip: "" +/// compile: "-O0 -g -o main %FILE%" +/// launch: "./main" +/// } +/// } + +int main() +{ /// 1: { step_into step_into } + for (int i = 0; i < 1; /// 2: step_into + ++i, ({ goto exit; })) { /// 4: step_into + int x = 0; /// 3: step_into + } + exit:; +} /// 5: at + diff --git a/src/torture/dbg_tests/step_into_lazy_module_func.c b/src/torture/dbg_tests/step_into_lazy_module_func.c index 07a6b089..fe289ba1 100644 --- a/src/torture/dbg_tests/step_into_lazy_module_func.c +++ b/src/torture/dbg_tests/step_into_lazy_module_func.c @@ -1,20 +1,22 @@ // TODO: step into alg needs to detect that it is about to step into a thunk and place breakpoint, respective to OS, // where thunk calls into resolved function -// -// test: { -// windows: { -// compile: "/Od /Z7 /c main.c module.c" -// link: "/fixed /debug:full /dll module.obj" -// link: "/fixed /debug:full /entry:main kernel32.lib module.lib delayimp.lib main.obj /out:main.exe /delayload:module.dll /incremental:no" -// launch: "main.exe" -// } -// linux: { -// compile: "-O0 -g module.c -fPIC -shared -o libmodule.so" -// compile: "-O0 -g main.c -L. -lmodule -o main -Wl,-rpath,%CWD%" -// launch: "./main" -// } -// } -// + +/// test: { +/// windows: { +/// skip: "" +/// compile: "/Od /Z7 /c main.c module.c" +/// link: "/fixed /debug:full /dll module.obj" +/// link: "/fixed /debug:full /entry:main kernel32.lib module.lib delayimp.lib main.obj /out:main.exe /delayload:module.dll /incremental:no" +/// launch: "main.exe" +/// } +/// linux: { +/// skip: "" +/// compile: "-O0 -g module.c -fPIC -shared -o libmodule.so" +/// compile: "-O0 -g main.c -L. -lmodule -o main -Wl,-rpath,%CWD%" +/// launch: "./main" +/// } +/// } +/// /// file: "module.c" diff --git a/src/torture/torture.c b/src/torture/torture.c index 66e65d64..b399371f 100644 --- a/src/torture/torture.c +++ b/src/torture/torture.c @@ -23,19 +23,54 @@ global String8 g_output; global String8 g_errors; // tools -global B32 g_dbg_graphical; +global B32 g_gui; global String8 g_radbin_path; global String8 g_cl_path; global String8 g_clang_path; global String8 g_gcc_path; global String8 g_linker_path; +//////////////////////////////// + +internal String8 +t_group_from_test(T_Test *test) +{ + String8 file = str8_cstring(test->file); + String8 group = str8_chop_last_dot(str8_skip_last_slash(file)); + if (str8_match_wildcard(group, str8_lit("torture_*"), StringMatchFlag_CaseInsensitive)) { + group = str8_skip(group, str8_lit("torture_").size); + } + return group; +} + +internal String8 +t_group_from_test_idx(U64 test_idx) +{ + return t_group_from_test(g_torture_tests[test_idx]); +} + internal String8 t_test_name_from_idx(Arena *arena, U64 test_idx) { - return str8f(arena, "%s::%s", g_torture_tests[test_idx]->group, g_torture_tests[test_idx]->label); + return str8f(arena, "%.*s/%s", str8_varg(t_group_from_test_idx(test_idx)), g_torture_tests[test_idx]->label); } +internal String8List +t_test_group_from_name(Arena *arena, String8 pattern) +{ + Temp scratch = scratch_begin(&arena, 1); + String8List matches = {0}; + for EachIndex(i, g_torture_test_count) { + if (str8_match_wildcard(t_test_name_from_idx(scratch.arena, i), pattern, 0)) { + str8_list_push(arena, &matches, t_group_from_test_idx(i)); + } + } + scratch_end(scratch); + return matches; +} + +//////////////////////////////// + internal char * t_string_from_result(T_RunStatus v) { @@ -43,10 +78,24 @@ t_string_from_result(T_RunStatus v) case T_RunStatus_Fail: return "FAIL"; case T_RunStatus_Crash: return "CRASH"; case T_RunStatus_Pass: return "PASS"; + case T_RunStatus_Skip: return "SKIP"; + default: break; } return 0; } +internal char * +t_color_from_result(T_RunStatus v) +{ + switch (v) { +#define X(n,c,...) case T_RunStatus_##n: return c; + T_Run_XList +#undef X + default: break; + } + return "null"; +} + internal void t_break_if_debugger_present(void) { @@ -167,17 +216,22 @@ t_run_caller(void *raw_ctx) ctx->result.status = T_RunStatus_Pass; String8List test_out = {0}; - ctx->run(scratch.arena, ctx->user_data, &ctx->result, &test_out); + + if (ctx->test->skip) { + ctx->result.status = T_RunStatus_Skip; + } else { + ctx->test->r(scratch.arena, ctx->user_data, &ctx->result, &test_out); + } if (ctx->result.status == T_RunStatus_Fail || ctx->result.status == T_RunStatus_Crash) { for EachNode(n, String8Node, test_out.first) { t_errorf("%S", n->string); } if (g_errors.size) { - t_errorf("stderr: \"%S\"\n", g_errors); + t_errorf("%S\n", g_errors); } if (g_output.size) { - t_errorf("stdout: \"%S\"\n", g_output); + t_errorf("%S\n", g_output); } } @@ -185,9 +239,9 @@ t_run_caller(void *raw_ctx) } internal T_RunResult -t_run(T_Run run, String8 user_data) +t_run(T_Test *test, String8 user_data) { - T_RunCtx ctx = { .run = run, .user_data = user_data, .result.status = T_RunStatus_Fail }; + T_RunCtx ctx = { .test = test, .user_data = user_data, .result.status = T_RunStatus_Fail }; t_run_caller(&ctx); fflush(stdout); fflush(stderr); @@ -332,7 +386,7 @@ t_raddbg_path(void) local_persist U8 buffer[4096]; ArenaParams params = { .reserve_size = sizeof(buffer), .commit_size = sizeof(buffer), .optional_backing_buffer = buffer }; Arena *arena = arena_alloc_(¶ms); - String8 raddbg_base_name = g_dbg_graphical ? str8_lit("raddbg") : str8_lit("raddbg_non_graphical"); + String8 raddbg_base_name = g_gui ? str8_lit("raddbg") : str8_lit("raddbg_non_graphical"); #if OS_WINDOWS Temp scratch = scratch_begin(0, 0); path = full_path_from_path(arena, str8f(scratch.arena, "%S.exe", raddbg_base_name)); @@ -779,7 +833,7 @@ t_invoke_env(String8 exe_path, String8 cmdline, String8List env, U64 timeout_us) exit:; for EachElement(i, read_capture_handles) { file_close(read_capture_handles[i]); } for EachElement(i, write_capture_handles) { file_close(write_capture_handles[i]); } - AssertAlways(is_ok); + scratch_end(scratch); return is_ok; } @@ -883,7 +937,9 @@ internal int t_test_compar(const void *raw_a, const void *raw_b) { const T_Test *a = raw_a, *b = raw_b; - int cmp = str8_compar(str8_cstring(a->group), str8_cstring(b->group), 0); + String8 group_a = t_group_from_test((T_Test *)a); + String8 group_b = t_group_from_test((T_Test *)b); + int cmp = str8_compar(group_a, group_b, 0); if (cmp == 0) { cmp = u64_compar(&a->decl_line, &b->decl_line); } @@ -1014,10 +1070,37 @@ t_errorf(char *fmt, ...) scratch_end(scratch); } +internal void +t_help(void) +{ + fprintf(stderr, "--- Help -------------------------------------------------------\n"); + fprintf(stderr, " %s\n\n", BUILD_TITLE_STRING_LITERAL); + fprintf(stderr, " Usage: torture [Options] \n\n"); + fprintf(stderr, " Options:\n"); + fprintf(stderr, " -list Print available test targets\n"); + fprintf(stderr, " --gui Launch debugger with window\n"); + fprintf(stderr, " -cl: Override default cl path\n"); + fprintf(stderr, " -clang: Override default clang path\n"); + fprintf(stderr, " -gcc: Override default gcc path\n"); + fprintf(stderr, " -linker: Path to PE/COFF linker\n"); + fprintf(stderr, " -print_stdout Print to console stdout and stderr of a run\n"); + fprintf(stderr, " -out: Directory path for test outputs (default \"%.*s\")\n", str8_varg(g_out)); + fprintf(stderr, " -verbose Enable verbose mode\n"); + fprintf(stderr, " -help Print help menu and exit\n"); + fprintf(stderr, "\nInputs are wildcard expressions. Prefix with ! to skip matches, or + to force-run matches.\n"); + fprintf(stderr, "\nExamples:\n"); + fprintf(stderr, " torture * Run all tests\n"); + fprintf(stderr, " torture bit_array Run 'bit_array' test\n"); + fprintf(stderr, " torture !* Skip all tests\n"); + fprintf(stderr, " torture * !bit_array Run all tests but skip 'bit_array'\n"); + fprintf(stderr, " torture +* Force-run all tests\n"); +} + internal void t_entry_point(CmdLine *cmdline) { Temp scratch = scratch_begin(0,0); + U64 exit_code = max_U64; U64 dashes_size = 9999; U8 *dashes = push_array(scratch.arena, U8, dashes_size); @@ -1036,25 +1119,10 @@ t_entry_point(CmdLine *cmdline) // { B32 print_help = cmd_line_has_flag(cmdline, str8_lit("help")) || - cmd_line_has_flag(cmdline, str8_lit("h")); + cmd_line_has_flag(cmdline, str8_lit("h")); if (print_help) { - PrintHeader("Help"); - fprintf(stderr, " %s\n\n", BUILD_TITLE_STRING_LITERAL); - fprintf(stderr, " Usage: torture [Options] [Files]\n\n"); - fprintf(stderr, " Options:\n"); - fprintf(stderr, " -target:{name[,name]} Selects targets to test\n"); - fprintf(stderr, " -skip:{name[,name]} Selects targets to skip\n"); - fprintf(stderr, " -list Print available test targets and exit\n"); - fprintf(stderr, " -out:{path} Directory path for test outputs (default \"%.*s\")\n", str8_varg(g_out)); - fprintf(stderr, " -verbose Enable verbose mode\n"); - fprintf(stderr, " -cl:{path} Override default cl path\n"); - fprintf(stderr, " -clang:{path} Override default clang path\n"); - fprintf(stderr, " -gcc:{path} Override default gcc path\n"); - fprintf(stderr, " -linker:{path} Path to PE/COFF linker\n"); - fprintf(stderr, " --dbg_graphical Launch debugger with window\n"); - fprintf(stderr, " -print_stdout Print to console stdout and stderr of a run\n"); - fprintf(stderr, " -help Print help menu and exit\n"); - abort_self(0); + t_help(); + goto exit; } } @@ -1086,7 +1154,7 @@ t_entry_point(CmdLine *cmdline) // // Compiler overrides // - g_dbg_graphical = cmd_line_has_flag(cmdline, str8_lit("dbg_graphical")); + g_gui = cmd_line_has_flag(cmdline, str8_lit("gui")); g_cl_path = cmd_line_string(cmdline, str8_lit("cl")); g_clang_path = cmd_line_string(cmdline, str8_lit("clang")); g_gcc_path = cmd_line_string(cmdline, str8_lit("gcc")); @@ -1106,44 +1174,68 @@ t_entry_point(CmdLine *cmdline) // // Handle optional -target // - String8List target = cmdline->inputs; + U64List targets = {0}; { - CmdLineOpt *target_opt = cmd_line_opt_from_string(cmdline, str8_lit("target")); - if (target_opt == 0) { - target_opt = cmd_line_opt_from_string(cmdline, str8_lit("t")); - } + String8List inputs = {0}; + + CmdLineOpt *target_opt = 0; + if (target_opt == 0) { target_opt = cmd_line_opt_from_string(cmdline, str8_lit("target")); } + if (target_opt == 0) { target_opt = cmd_line_opt_from_string(cmdline, str8_lit("t")); } + + // handle explicit target switch if (target_opt) { - HashTable *ht = hash_table_init(scratch.arena, g_torture_test_count*2); + str8_list_concat_in_place(&inputs, &target_opt->value_strings); + } - String8List targets = target_opt->value_strings; - str8_list_concat_in_place(&targets, &cmdline->inputs); + // accept inputs from the command line as target tests to run + str8_list_concat_in_place(&inputs, &cmdline->inputs); - if (targets.node_count > 0) { - for EachNode(pattern_n, String8Node, targets.first) { - B32 do_namespace = str8_find_needle(pattern_n->string, 0, str8_lit("::"), 0) < pattern_n->string.size; - for EachIndex(test_idx, g_torture_test_count) { - String8 name = str8_cstring(g_torture_tests[test_idx]->label); - if (do_namespace) { - name = t_test_name_from_idx(scratch.arena, test_idx); - } - if (str8_match_wildcard(name, pattern_n->string, 0)) { - if ( ! hash_table_search_string(ht, name)) { - hash_table_push_string_raw(scratch.arena, ht, name, 0); - str8_list_push(scratch.arena, &target, str8_cstring(g_torture_tests[test_idx]->label)); - } - } + // no inputs -> print help and exit + if (inputs.node_count == 0) { + t_help(); + goto exit; + } + + HashMap hm = {0}; + for EachNode(input_n, String8Node, inputs.first) { + String8 t = input_n->string; + + // parse mode + typedef enum { Mode_Default, Mode_Skip, Mode_Force, } Mode; + Mode mode = Mode_Default; + if (str8_match_wildcard(t, str8_lit("+*"), 0)) { mode = Mode_Force; t = str8_skip(t, 1); } + else if (str8_match_wildcard(t, str8_lit("!*"), 0)) { mode = Mode_Skip; t = str8_skip(t, 1); } + + if (str8_find_needle(t, 0, str8_lit("/"), 0) >= t.size) { + t = str8f(scratch.arena, "*/%S", t); + } + + U64 match_count = 0; + + for EachIndex(test_idx, g_torture_test_count) { + // match test names + String8 test_name = t_test_name_from_idx(scratch.arena, test_idx); + + if (str8_match_wildcard(test_name, t, StringMatchFlag_CaseInsensitive)) { + // set skip flag + switch (mode) { + case Mode_Default: break; + case Mode_Skip: g_torture_tests[test_idx]->skip = 1; break; + case Mode_Force: g_torture_tests[test_idx]->skip = 0; break; } - } - - if (ht->count == 0) { - fprintf(stderr, "ERROR: -target matches not found for the following patterns: "); - for EachNode(n, String8Node, target_opt->value_strings.first) { - fprintf(stderr, "\"%.*s\"\n", str8_varg(n->string)); + + // append test when not in skipping mode + if ( ! hash_map_search_string_u64(&hm, test_name)) { + hash_map_push_string_u64(scratch.arena, &hm, test_name, 1); + u64_list_push(scratch.arena, &targets, test_idx); } - abort_self(1); + + match_count += 1; } - } else { - fprintf(stderr, "ERROR: -target has invalid number of arguments\n"); + } + + if (match_count == 0) { + fprintf(stderr, "WARNING: no matches found for input: %.*s\n", str8_varg(input_n->string)); } } } @@ -1199,62 +1291,17 @@ t_entry_point(CmdLine *cmdline) // Run tests // { - U64List target_indices_list = {0}; - if (target.node_count == 0) { - for EachIndex(i, g_torture_test_count) { u64_list_push(scratch.arena, &target_indices_list, i); } - } else { - for EachNode(target_n, String8Node, target.first) { - B32 is_target_unknown = 1; - for EachIndex(i, g_torture_test_count) { - if (str8_match(str8_cstring(g_torture_tests[i]->label), target_n->string, 0)) { - u64_list_push(scratch.arena, &target_indices_list, i); - is_target_unknown = 0; - break; - } - } - if (is_target_unknown) { - fprintf(stderr, "ERROR: unknown target \"%.*s\"\n", str8_varg(target_n->string)); - } - } - } - - // - // -skip - // - U64List final_target_list = {0}; - CmdLineOpt *skip_opt = cmd_line_opt_from_string(cmdline, str8_lit("skip")); - CmdLineOpt *s_opt = cmd_line_opt_from_string(cmdline, str8_lit("s")); - String8List skip_list = skip_opt ? skip_opt->value_strings : s_opt ? s_opt->value_strings : (String8List){0}; - for EachNode(n, U64Node, target_indices_list.first) { - // should test be skipped? - B32 include_test = 1; - String8 test_name = t_test_name_from_idx(scratch.arena, n->data); - for EachNode(pattern_n, String8Node, skip_list.first) { - if (str8_match_wildcard(test_name, pattern_n->string, 0)) { - include_test = 0; - break; - } - } - - if (include_test) { - u64_list_push(scratch.arena, &final_target_list, n->data); - } - } - - U64 skip_count = target_indices_list.count - final_target_list.count; - U64Array target_indices = u64_array_from_list(scratch.arena, &final_target_list); + U64Array target_indices = u64_array_from_list(scratch.arena, &targets); U64 max_label_size = 0; U64 max_group_size = 0; for EachIndex(i, target_indices.count) { U64 test_idx = target_indices.v[i]; max_label_size = Max(max_label_size, cstring8_length((U8*)g_torture_tests[test_idx]->label)); - max_group_size = Max(max_group_size, cstring8_length((U8*)g_torture_tests[test_idx]->group)); + max_group_size = Max(max_group_size, t_group_from_test_idx(test_idx).size); } - U64 pass_count = 0; - U64 fail_count = 0; - U64 crash_count = 0; + U64 run_counters[T_RunStatus_Count] = {0}; U64 max_digit_count = count_digits_u64(target_indices.count, 10); U64 total_time_start = now_time_us(); @@ -1262,6 +1309,8 @@ t_entry_point(CmdLine *cmdline) Slowest slowest[5] = {0}; for EachElement(i, slowest) { slowest[i].target_idx = max_U64; } + U64List skipped_tests = {0}; + for EachIndex(i, target_indices.count) { if (i == 0) { PrintHeader("Tests"); } @@ -1274,7 +1323,7 @@ t_entry_point(CmdLine *cmdline) U64 curr_digit_count = count_digits_u64(i+1, 10); int idx_align_space_count = (int)(max_digit_count - curr_digit_count); fprintf(stdout, "[%.*s%llu/%llu] ", idx_align_space_count, spaces, (unsigned long long)i+1, (unsigned long long)target_indices.count); - fprintf(stdout, "%s %.*s:: %s", test->group, (int)(max_group_size - cstring8_length((U8*)test->group)), spaces, test->label); + fprintf(stdout, "%.*s %.*s/ %s", str8_varg(t_group_from_test(test)), (int)(max_group_size - t_group_from_test(test).size), spaces, test->label); fprintf(stdout, " %.*s ", (int)dots_count, dots); fflush(stdout); @@ -1295,19 +1344,14 @@ t_entry_point(CmdLine *cmdline) // run test U64 run_start_time = now_time_us(); - T_RunResult result = t_run(test->r, test->user_data); + T_RunResult result = t_run(test, test->user_data); U64 run_end_time = now_time_us(); - // print result - if (result.status == T_RunStatus_Pass) { - fprintf(stdout, "\x1b[32m" "%s" "\x1b[0m", t_string_from_result(result.status)); - pass_count += 1; - } else if (result.status == T_RunStatus_Fail) { - fprintf(stdout, "\x1b[31m" "%s" "\x1b[0m", t_string_from_result(result.status)); - fail_count += 1; - } else if (result.status == T_RunStatus_Crash) { - fprintf(stdout, "\x1b[33m" "%s" "\x1b[0m", t_string_from_result(result.status)); - crash_count += 1; - } + + // update + run_counters[result.status] += 1; + + // print run status + fprintf(stdout, "%s%s" T_RESET, t_color_from_result(result.status), t_string_from_result(result.status)); if (result.status == T_RunStatus_Pass) { U64 d = run_end_time - run_start_time; @@ -1341,18 +1385,23 @@ t_entry_point(CmdLine *cmdline) if (result.status == T_RunStatus_Fail || result.status == T_RunStatus_Crash) { if (g_stop_on_first_fail_or_crash) { goto exit; } } + + if (result.status == T_RunStatus_Skip) { + u64_list_push(scratch.arena, &skipped_tests, target_idx); + } } U64 total_time_end = now_time_us(); - if (target_indices.count > 0 && (pass_count > 0 || fail_count > 0 || crash_count > 0 || skip_count > 0)) { + if (target_indices.count > 0 && sum_array_u64(ArrayCount(run_counters), run_counters) > 0) { + U64 total_time_dt = total_time_end - total_time_start; + String8 total_time_str = string_from_elapsed_time(scratch.arena, date_time_from_micro_seconds(total_time_dt)); + fprintf(stderr, "\n"); PrintHeader("Summary"); - U64 total_time_dt = total_time_end - total_time_start; - String8 total_time_str = string_from_elapsed_time(scratch.arena, date_time_from_micro_seconds(total_time_dt)); - fprintf(stderr, " Passed %llu\n", (unsigned long long)pass_count); - fprintf(stderr, " Failed %llu\n", (unsigned long long)fail_count); - fprintf(stderr, " Crashed %llu\n", (unsigned long long)crash_count); - fprintf(stderr, " Skipped %llu\n", (unsigned long long)skip_count); + fprintf(stderr, " Passed %llu\n", (unsigned long long)run_counters[T_RunStatus_Pass]); + fprintf(stderr, " Failed %llu\n", (unsigned long long)run_counters[T_RunStatus_Fail]); + fprintf(stderr, " Crashed %llu\n", (unsigned long long)run_counters[T_RunStatus_Crash]); + fprintf(stderr, " Skipped %llu\n", (unsigned long long)run_counters[T_RunStatus_Skip]); fprintf(stderr, " Time %.*s\n", str8_varg(total_time_str)); U64 slow_count = 0; @@ -1368,17 +1417,17 @@ t_entry_point(CmdLine *cmdline) for EachElement(i, slowest) { Slowest s = slowest[i]; label_max = Max(strlen(g_torture_tests[s.target_idx]->label), label_max); - group_max = Max(strlen(g_torture_tests[s.target_idx]->group), group_max); + group_max = Max(t_group_from_test_idx(s.target_idx).size, group_max); } - fprintf(stderr, " Slow Tests\n"); + fprintf(stderr, " \nSlow Tests\n"); for EachElement(i, slowest) { Slowest s = slowest[i]; if (s.target_idx >= g_torture_test_count) { break; } String8 elapsed_time = string_from_elapsed_time(scratch.arena, date_time_from_micro_seconds(s.d)); - fprintf(stderr, " %s %.*s:: %s %.*s %.*s\n", - g_torture_tests[s.target_idx]->group, - (int)(group_max - strlen(g_torture_tests[s.target_idx]->group)), spaces, + fprintf(stderr, " %.*s %.*s/ %s %.*s %.*s\n", + str8_varg(t_group_from_test_idx(s.target_idx)), + (int)(group_max - t_group_from_test_idx(s.target_idx).size), spaces, g_torture_tests[s.target_idx]->label, (int)(label_max - strlen(g_torture_tests[s.target_idx]->label)) + 4, dots, str8_varg(elapsed_time)); @@ -1386,12 +1435,10 @@ t_entry_point(CmdLine *cmdline) } } + exit_code = run_counters[T_RunStatus_Fail] + run_counters[T_RunStatus_Crash]; exit:; - if (fail_count + crash_count != 0) { - fflush(stdout); - abort_self(fail_count + crash_count); - } } - + scratch_end(scratch); + // TODO: return exit_code; } diff --git a/src/torture/torture.h b/src/torture/torture.h index 80a5995d..fff10f90 100644 --- a/src/torture/torture.h +++ b/src/torture/torture.h @@ -5,11 +5,25 @@ //////////////////////////////// +#define T_RESET "\x1b[0m" +#define T_RED "\x1b[31m" +#define T_GREEN "\x1b[32m" +#define T_YELLOW "\x1b[33m" +#define T_BLUE "\x1b[34m" + +//#define X(n, c) +#define T_Run_XList \ + X(Fail, T_RED) \ + X(Crash, T_RED) \ + X(Pass, T_GREEN) \ + X(Skip, T_RESET) + typedef enum { - T_RunStatus_Fail, - T_RunStatus_Crash, - T_RunStatus_Pass, +#define X(n,...) T_RunStatus_##n, +T_Run_XList +#undef X + T_RunStatus_Count } T_RunStatus; typedef struct @@ -25,20 +39,21 @@ typedef void (*T_Run)(Arena *arena, String8 user_data, T_RunResul typedef struct { - T_Run run; - String8 user_data; - T_RunResult result; -} T_RunCtx; - -typedef struct -{ - char *group; + char *file; char *label; int decl_line; T_Run r; + B32 skip; String8 user_data; } T_Test; +typedef struct +{ + T_Test *test; + String8 user_data; + T_RunResult result; +} T_RunCtx; + typedef enum { T_Compiler_Null, @@ -61,25 +76,28 @@ extern T_Test g_torture_tests_[0xffffff]; internal void t_break_if_debugger_present(void); -#define T_AddTest(name, l, ...) \ +#define T_AddTest(name, f, l, skip, ...) \ T_RunSig(name); \ __VA_ARGS__ void t_add_test_##name(void) \ { \ - g_torture_tests_[g_torture_test_count++] = (T_Test){ .group = T_Group, .label = Stringify(name), .r = &t_##name, .decl_line = l }; \ + g_torture_tests_[g_torture_test_count++] = (T_Test){ f, Stringify(name), l, &t_##name, skip }; \ } #if COMPILER_MSVC # pragma section(".CRT$XCU", read) -# define TEST_(name) \ - T_AddTest(name, __LINE__) \ +# define TEST_(name, skip) \ + T_AddTest(name, __FILE__, __LINE__, skip) \ __declspec(allocate(".CRT$XCU")) void(*r_##name)(void) = t_add_test_##name; \ __pragma(comment(linker, "/include:" Stringify(r_##name))) #else -# define TEST_(name) T_AddTest(name, __LINE__, __attribute__((constructor))) +# define TEST_(name, skip) T_AddTest(name, __FILE__, __LINE__, skip, __attribute__((constructor))) #endif #define TEST(name) \ - TEST_(name) \ + TEST_(name, 0) \ + T_RunSig(name) +#define SKIP(name) \ + TEST_(name, 1) \ T_RunSig(name) #define T_Ok(c) do { if (!(c)) { \ @@ -113,7 +131,7 @@ internal String8 t_make_file_path(Arena *arena, String8 name); // test runner internal void t_run_caller(void *raw_ctx); internal void t_run_fail_handler(void *raw_ctx); -internal T_RunResult t_run(T_Run run, String8 user_data); +internal T_RunResult t_run(T_Test *test, String8 user_data); // tools internal String8 t_radbin_path(void); diff --git a/src/torture/torture_base.c b/src/torture/torture_base.c index 12929bf1..988378d8 100644 --- a/src/torture/torture_base.c +++ b/src/torture/torture_base.c @@ -1,8 +1,6 @@ // Copyright (c) Epic Games Tools // Licensed under the MIT license (https://opensource.org/license/mit/) -#define T_Group "Base" - TEST(str8_list_substr) { String8List zero_list = {0}; @@ -341,4 +339,3 @@ TEST(hash_map) } } -#undef T_Group diff --git a/src/torture/torture_d2r.c b/src/torture/torture_d2r.c index bdc43c3d..5576f7ea 100644 --- a/src/torture/torture_d2r.c +++ b/src/torture/torture_d2r.c @@ -5,8 +5,6 @@ // // [ ] d2r_types alias size and byte size on __float80 typedef mismatch -#define T_Group "d2r" - internal RDI_Parsed * d2r_rdi_from_dwarf_writer(Arena *arena, DW_Writer *writer) { @@ -232,7 +230,7 @@ T_Ok(str8_match(str8_from_rdi_string_idx(rdi, type->built_in.name_string_idx), s dw_writer_end(&writer); } -TEST(d2r_line_table) +SKIP(d2r_line_table) { DW_Writer *writer = dw_writer_begin(DW_Format_32Bit, DW_Version_5, DW_CompUnitKind_Compile, Arch_x64); String8 comp_dir = str8_lit("c:/DEVEL/"); @@ -303,7 +301,7 @@ TEST(d2r_line_table) dw_writer_end(&writer); } -TEST(d2r_checksums) +SKIP(d2r_checksums) { DW_Writer *writer = dw_writer_begin(DW_Format_32Bit, DW_Version_5, DW_CompUnitKind_Compile, Arch_x64); @@ -553,4 +551,3 @@ TEST(d2r_general) dw_writer_end(&writer); } -#undef T_Group diff --git a/src/torture/torture_dbg.c b/src/torture/torture_dbg.c index 9b8b9bc0..e92c6e51 100644 --- a/src/torture/torture_dbg.c +++ b/src/torture/torture_dbg.c @@ -1,32 +1,54 @@ // Copyright (c) Epic Games Tools // Licensed under the MIT license (https://opensource.org/license/mit/) -#define T_Group "Dbg" - #define T_Dbg_DefaultTimeout TIMEOUT_SEC(5) extern B32 g_stop_on_first_fail_or_crash; +//////////////////////////////// + +internal void +t_find_line_and_col(String8 source, MD_Node *n, U64 *line_out, U64 *col_out) +{ + U64 line = 1; + U64 col = 1; + for (U64 cursor = 0; cursor < source.size && cursor < n->src_offset; cursor += 1) { + if (source.str[cursor] == '\n') { + line += 1; + col = 0; + } + col += 1; + } + if (line_out) { *line_out = line; } + if (col_out) { *col_out = col; } +} + +internal void +t_errorf_md(String8 file_name, String8 source, MD_Node *n, char *fmt, ...) +{ + Temp scratch = scratch_begin(0,0); + va_list args; + va_start(args, fmt); + String8 result = push_str8fv(scratch.arena, fmt, args); + U64 line = 0, col = 0; + t_find_line_and_col(source, n, &line, &col); + t_errorf("ERROR: %S:%llu%llu: %S\n", file_name, (unsigned long long)line, (unsigned long long)col, result); + va_end(args); + scratch_end(scratch); +} + //////////////////////////////// // Debugger IPC Replies -typedef struct RD_IpcReply RD_IpcReply; -struct RD_IpcReply +typedef struct { MD_ParseResult parse; MD_Node *root; MD_Node *msg; -}; - -internal RD_IpcReply -rd_ipc_mdesk_reply_from_string(Arena *arena, String8 string) -{ - MD_ParseResult parse = md_parse_from_text(arena, str8_lit("ipc_reply"), string); - return (RD_IpcReply){ .parse = parse, .root = parse.root, parse.root->first }; -} +} T_IpcReply; internal B32 -rd_ipc_parse_string(MD_Node *node, String8 child_name, String8 *out) +t_ipc_parse_string(MD_Node *node, String8 child_name, String8 *out) { MD_Node *child = md_child_from_string(node, child_name, 0); if (!md_node_is_nil(child) && !md_node_is_nil(child->first)) @@ -38,10 +60,10 @@ rd_ipc_parse_string(MD_Node *node, String8 child_name, String8 *out) } internal B32 -rd_ipc_parse_u32(MD_Node *node, String8 child_name, U32 *out) +t_ipc_parse_u32(MD_Node *node, String8 child_name, U32 *out) { String8 value = {0}; - if (rd_ipc_parse_string(node, child_name, &value)) + if (t_ipc_parse_string(node, child_name, &value)) { U64 v64 = 0; if (try_u64_from_str8_c_rules(value, &v64)) @@ -53,12 +75,12 @@ rd_ipc_parse_u32(MD_Node *node, String8 child_name, U32 *out) return 0; } -#define rd_ipc_parse_int(n, c, ptr) rd_ipc_parse_int_(n, c, sizeof(*ptr), ptr) +#define t_ipc_parse_int(n, c, ptr) t_ipc_parse_int_(n, c, sizeof(*ptr), ptr) internal B32 -rd_ipc_parse_int_(MD_Node *node, String8 child_name, U64 out_size, void *out) +t_ipc_parse_int_(MD_Node *node, String8 child_name, U64 out_size, void *out) { String8 value = {0}; - if (rd_ipc_parse_string(node, child_name, &value)) + if (t_ipc_parse_string(node, child_name, &value)) { U64 v64 = 0; if (try_u64_from_str8_c_rules(value, &v64)) @@ -71,12 +93,12 @@ rd_ipc_parse_int_(MD_Node *node, String8 child_name, U64 out_size, void *out) } internal B32 -rd_ipc_parse_b32(MD_Node *node, String8 child_name, B32 *out) +t_ipc_parse_b32(MD_Node *node, String8 child_name, B32 *out) { B32 is_ok = 0; String8 s = {0}; U64 value = 0; - if (rd_ipc_parse_string(node, child_name, &s)) + if (t_ipc_parse_string(node, child_name, &s)) { if (str8_matchi(s, str8_lit("true"))) { value = 1; is_ok = 1; } else if (str8_matchi(s, str8_lit("false"))) { value = 0; is_ok = 1; } @@ -172,9 +194,9 @@ t_dbg_state(Arena *arena, U64 timeout_us) MD_Node *threads_md = md_child_from_string(state_md, str8_lit("threads"), 0); MD_Node *modules_md = md_child_from_string(state_md, str8_lit("modules"), 0); T_DbgState v = {0}; - if (!rd_ipc_parse_int(state_md, str8_lit("running"), &v.running)) { t_infof("INFO: 'state' is missing 'running'\n"); goto exit; } - if (!rd_ipc_parse_int(state_md, str8_lit("run_gen"), &v.run_gen)) { t_infof("INFO: 'state' is missing 'run_gen'\n"); goto exit; } - if (!rd_ipc_parse_int(state_md, str8_lit("ip"), &v.ip)) { t_infof("INFO: 'state' is missing 'ip'\n"); goto exit; } + if (!t_ipc_parse_int(state_md, str8_lit("running"), &v.running)) { t_infof("INFO: 'state' is missing 'running'\n"); goto exit; } + if (!t_ipc_parse_int(state_md, str8_lit("run_gen"), &v.run_gen)) { t_infof("INFO: 'state' is missing 'run_gen'\n"); goto exit; } + if (!t_ipc_parse_int(state_md, str8_lit("ip"), &v.ip)) { t_infof("INFO: 'state' is missing 'ip'\n"); goto exit; } result = push_array(arena, T_DbgState, 1); *result = v; @@ -201,11 +223,11 @@ t_dbg_src_line(Arena *arena, U64 vaddr, T_DbgLineArray *lines_out, U64 timeout_u U64 line_count = 0; for MD_EachNode(n, lines_md->first) { T_DbgLine line = {0}; - if ( ! rd_ipc_parse_string(n, str8_lit("file_path"), &line.file_path)) { t_infof("INFO: 'lines' is missing 'file_path'\n"); goto exit; } - if ( ! rd_ipc_parse_int(n, str8_lit("line_num"), &line.line_num)) { t_infof("INFO: 'lines' is missing 'line_num'\n"); goto exit; } - if ( ! rd_ipc_parse_int(n, str8_lit("column_num"), &line.column_num)) { t_infof("INFO: 'lines' is missing 'column_num'\n"); goto exit; } - if ( ! rd_ipc_parse_int(n, str8_lit("voff_range_min"), &line.voff_range.min)) { t_infof("INFO: 'lines' is missing 'voff_range_min'\n"); goto exit; } - if ( ! rd_ipc_parse_int(n, str8_lit("voff_range_max"), &line.voff_range.max)) { t_infof("INFO: 'lines' is missing 'voff_range_max'\n"); goto exit; } + if ( ! t_ipc_parse_string(n, str8_lit("file_path"), &line.file_path)) { t_infof("INFO: 'lines' is missing 'file_path'\n"); goto exit; } + if ( ! t_ipc_parse_int(n, str8_lit("line_num"), &line.line_num)) { t_infof("INFO: 'lines' is missing 'line_num'\n"); goto exit; } + if ( ! t_ipc_parse_int(n, str8_lit("column_num"), &line.column_num)) { t_infof("INFO: 'lines' is missing 'column_num'\n"); goto exit; } + if ( ! t_ipc_parse_int(n, str8_lit("voff_range_min"), &line.voff_range.min)) { t_infof("INFO: 'lines' is missing 'voff_range_min'\n"); goto exit; } + if ( ! t_ipc_parse_int(n, str8_lit("voff_range_max"), &line.voff_range.max)) { t_infof("INFO: 'lines' is missing 'voff_range_max'\n"); goto exit; } Node *n = push_array(scratch.arena, Node, 1); n->v = line; @@ -393,10 +415,10 @@ t_dbg_eval(Arena *arena, String8 expr, T_Eval *eval_out) B32 is_ok = t_dbg_send_cmd(cmd, T_Dbg_DefaultTimeout, arena, &reply); T_Eval e = {0}; - if ( ! rd_ipc_parse_string(reply.root, str8_lit("expr"), &e.expr)) { t_errorf("ERROR: failed to parse reply member: expr\n"); Assert(0); goto exit; } - if ( ! rd_ipc_parse_string(reply.root, str8_lit("value"), &e.value)) { t_errorf("ERROR: failed to parse reply member: value\n"); Assert(0); goto exit; } - if ( ! rd_ipc_parse_string(reply.root, str8_lit("type"), &e.type)) { t_errorf("ERROR: failed to parse reply member: type\n"); Assert(0); goto exit; } - if ( ! rd_ipc_parse_string(reply.root, str8_lit("error"), &e.error)) { t_errorf("ERROR: failed to parse reply member: error\n"); Assert(0); goto exit; } + if ( ! t_ipc_parse_string(reply.root, str8_lit("expr"), &e.expr)) { t_errorf_md(str8_lit("IPC"), str8_zero(), reply.root, "ERROR: failed to parse reply member: expr\n"); Assert(0); goto exit; } + if ( ! t_ipc_parse_string(reply.root, str8_lit("value"), &e.value)) { t_errorf_md(str8_lit("IPC"), str8_zero(), reply.root, "ERROR: failed to parse reply member: value\n"); Assert(0); goto exit; } + if ( ! t_ipc_parse_string(reply.root, str8_lit("type"), &e.type)) { t_errorf_md(str8_lit("IPC"), str8_zero(), reply.root, "ERROR: failed to parse reply member: type\n"); Assert(0); goto exit; } + if ( ! t_ipc_parse_string(reply.root, str8_lit("error"), &e.error)) { t_errorf_md(str8_lit("IPC"), str8_zero(), reply.root, "ERROR: failed to parse reply member: error\n"); Assert(0); goto exit; } if (eval_out) { *eval_out = e; } exit:; @@ -424,22 +446,11 @@ internal String8 t_string_from_dbg_script_cmd_kind(T_DbgScriptCmdKind v) { switch (v) { - case T_DbgScriptCmdKind_Null: return str8_zero(); - case T_DbgScriptCmdKind_Breakpoint: return str8_lit("bp"); - case T_DbgScriptCmdKind_ClearBreakpoints: return str8_lit("bp_clear"); - case T_DbgScriptCmdKind_Run: return str8_lit("run"); - case T_DbgScriptCmdKind_Halt: return str8_lit("halt"); - case T_DbgScriptCmdKind_StepOver: return str8_lit("step_over"); - case T_DbgScriptCmdKind_StepInto: return str8_lit("step_into"); - case T_DbgScriptCmdKind_StepOut: return str8_lit("step_out"); - case T_DbgScriptCmdKind_StepOverInst: return str8_lit("step_over_inst"); - case T_DbgScriptCmdKind_StepIntoInst: return str8_lit("step_into_inst"); - case T_DbgScriptCmdKind_StepOverLine: return str8_lit("step_over_line"); - case T_DbgScriptCmdKind_StepIntoLine: return str8_lit("step_into_line"); - case T_DbgScriptCmdKind_KillAll: return str8_lit("kill_all"); - case T_DbgScriptCmdKind_At: return str8_lit("at"); - case T_DbgScriptCmdKind_Eval: return str8_lit("eval"); - default: InvalidPath; +#define X(n,v) case T_DbgScriptCmdKind_##n: return str8_lit(v); + T_DbgScriptCmdKind_XList +#undef X + case T_DbgScriptCmdKind_Null: return str8_zero(); + default: InvalidPath; break; } return str8_zero(); } @@ -473,9 +484,9 @@ t_dbg_script_from_source(Arena *arena, String8 file_path, String8 source, T_DbgS String8 comment = str8_skip_chop_whitespace(token_string); String8 prefix = str8_lit("///"); if (str8_matchi(str8_prefix(comment, prefix.size), prefix)) { - String8 script_part = str8_skip(comment, prefix.size); - U64 script_part_base_off = (U64)(script_part.str - source.str); - MD_TokenizeResult script_part_tokenize = md_tokenize_from_text(scratch.arena, script_part); + String8 script_part = str8_skip(comment, prefix.size); + U64 script_part_base_off = (U64)(script_part.str - source.str); + MD_TokenizeResult script_part_tokenize = md_tokenize_from_text(scratch.arena, script_part); for EachIndex(script_token_idx, script_part_tokenize.tokens.count) { MD_Token script_token = script_part_tokenize.tokens.v[script_token_idx]; script_token.range.min += script_part_base_off; @@ -508,28 +519,65 @@ t_dbg_script_from_source(Arena *arena, String8 file_path, String8 source, T_DbgS if (str8_matchi(field->string, str8_lit("compile"))) { kind = T_DbgScriptDirectiveKind_Compile; } else if (str8_matchi(field->string, str8_lit("link"))) { kind = T_DbgScriptDirectiveKind_Link; } else if (str8_matchi(field->string, str8_lit("launch"))) { kind = T_DbgScriptDirectiveKind_Launch; } - AssertAlways(kind != T_DbgScriptDirectiveKind_Null); - - // syntax check - AssertAlways( !md_node_is_nil(field->first)); - AssertAlways(md_node_is_nil(field->first->next)); - Assert(field->first->flags & MD_NodeFlag_StringLiteral); - - // src_offset -> line - // - // TODO: super silly!! mdesk should export line numbers - U64 line = 1; - String8 text_before_src = str8_prefix(source, field->src_offset); - for EachIndex(idx, text_before_src.size) { line += (text_before_src.str[idx] == '\n'); } - - T_DbgScriptDirective *n = push_array(arena, T_DbgScriptDirective, 1); - n->kind = kind; - n->line = line; - n->args = str8_copy(arena, field->first->string); - // TODO: expand % in compile: and link: to current source file name and esacpe with %% - + else if (str8_matchi(field->string, str8_lit("skip"))) { kind = T_DbgScriptDirectiveKind_Skip; } + + if (kind == T_DbgScriptDirectiveKind_Null) { + t_errorf_md(file_path, source, n, "unknown field in test header \"%S\"\n", field->string); + goto exit; + } + + T_DbgScriptDirective *dir = push_array(arena, T_DbgScriptDirective, 1); + dir->kind = kind; + t_find_line_and_col(source, field, &dir->line, 0); + + if (field->flags & MD_NodeFlag_HasBraceLeft) { + if (kind == T_DbgScriptDirectiveKind_Compile) { + for MD_EachNode(sub_field, field->first) { + if (str8_matchi(sub_field->string, str8_lit("cc"))) { + MD_Node *cc = sub_field->first; + if (cc->flags & MD_NodeFlag_StringLiteral) { + if (str8_matchi(cc->string, str8_lit("clang"))) { dir->compile.cc = T_Compiler_Clang; } + else if (str8_matchi(cc->string, str8_lit("cl"))) { dir->compile.cc = T_Compiler_Cl; } + else { + t_errorf_md(file_path, source, cc, "unknown compiler name: \"%S\"\n", sub_field->string); + goto exit; + } + } else { + t_errorf_md(file_path, source, sub_field, "value of CC must be a string literal e.g. CC: \"clang\"\n"); + goto exit; + } + } else if (str8_matchi(sub_field->string, str8_lit("args"))) { + MD_Node *args = sub_field->first; + if (args->flags & MD_NodeFlag_StringLiteral) { + dir->args = str8_copy(arena, args->string); + } else { + t_errorf_md(file_path, source, args, "value of ARGS must be a string literal\n"); + goto exit; + } + } else { + t_errorf_md(file_path, source, sub_field, "unknown field \"%S\"\n", sub_field->string); + goto exit; + } + } + } + } else { + if (md_node_is_nil(field->first)) { + t_errorf_md(file_path, source, field, "missing value on field %S\n", field->string); + goto exit; + } + if ( ! md_node_is_nil(field->first->next)) { + t_errorf_md(file_path, source, field, "field %S accepts only one value\n", field->string); + goto exit; + } + if (~field->first->flags & MD_NodeFlag_StringLiteral) { + t_errorf_md(file_path, source, field, "field %S accepts only strings\n", field->string); + goto exit; + } + dir->args = str8_copy(arena, field->first->string); + } + T_DbgScriptDirectiveList *list = &script.directives[os][kind]; - SLLQueuePush(list->first, list->last, n); + SLLQueuePush(list->first, list->last, dir); list->count += 1; } } @@ -541,52 +589,59 @@ t_dbg_script_from_source(Arena *arena, String8 file_path, String8 source, T_DbgS // file { - MD_Node *last_file = 0; + MD_NodePtrList files = {0}; for MD_EachNode(n, script_parse.root->first->next) { - B32 is_end = (md_node_is_nil(n->next) && last_file != 0); - if (str8_matchi(n->string, str8_lit("file")) || is_end) { - if (!is_end) { - AssertAlways( ! md_node_is_nil(n->first)); - AssertAlways(md_node_is_nil(n->first->next)); + if (str8_matchi(n->string, str8_lit("file"))) { + AssertAlways( ! md_node_is_nil(n->first)); + + if ( ! md_node_is_nil(n->first->next) || ! (n->first->flags & MD_NodeFlag_StringLiteral)) { + U32 line = 0; + for EachIndex(idx, n->first->src_offset) { line += (source.str[idx] == '\n'); } + t_errorf_md(file_path, source, n, "value of the 'file' must be a string, (e.g. file: \"main.c\")\n", file_path, line); + goto exit; } - - if (last_file) { - // src_offset -> base_line - // - // TODO: super silly!! mdesk should export line numbers - U64 line = 1; - for EachIndex(idx, last_file->src_offset) { line += (source.str[idx] == '\n'); } - - String8 sub_source = str8_substr(source, r1u64(last_file->src_offset, is_end ? source.size : n->src_offset)); - U64 file_dir_end = str8_find_needle(sub_source, 0, str8_lit("\n"), 0); - U64 next_file_dir_begin = str8_find_needle_reverse(sub_source, 0, str8_lit("\n"), n->src_offset); - sub_source = str8_substr(sub_source, r1u64(file_dir_end + 1, next_file_dir_begin)); - - T_DbgScriptFile *file = push_array(arena, T_DbgScriptFile, 1); - file->path = t_make_file_path(arena, last_file->first->string); - file->source = sub_source; - file->line = line; - SLLQueuePush(script.files.first, script.files.last, file); - script.files.count += 1; - } - - last_file = n; + + md_node_ptr_list_push(scratch.arena, &files, n); } } - - // no file directives? assume script file as main source file - if (last_file == 0) { + + if (files.count == 0) { + MD_Node *whole_file = push_array(scratch.arena, MD_Node, 1); + whole_file->first = push_array(scratch.arena, MD_Node, 1); + whole_file->first->next = push_array(scratch.arena, MD_Node, 1); + whole_file->src_offset = 0; + whole_file->first->string = str8_skip_last_slash(file_path); + md_node_ptr_list_push(scratch.arena, &files, whole_file); + } + + HashMap files_hm = {0}; + for EachNode(n_ptr, MD_NodePtrNode, files.first) { + MD_Node *n = n_ptr->v; + + U64 src_opl = source.size; + if (n_ptr->next) { + src_opl = n_ptr->next->v->src_offset; + } + + String8 sub_source = str8_substr(source, r1u64(n->src_offset, src_opl)); + U64 file_min = str8_find_needle(sub_source, 0, str8_lit("\n"), 0) + 1; + U64 file_max = str8_find_needle_reverse(sub_source, 0, str8_lit("\n"), 0); + sub_source = str8_substr(sub_source, r1u64(file_min, file_max)); + T_DbgScriptFile *file = push_array(arena, T_DbgScriptFile, 1); - file->path = file_path; - file->source = source; + file->path = t_make_file_path(arena, n->first->string); + file->source = sub_source; + t_find_line_and_col(source, n, &file->line, 0); SLLQueuePush(script.files.first, script.files.last, file); script.files.count += 1; + + hash_map_push_raw_raw(scratch.arena, &files_hm, n, file); } } // programs { - HashTable *ht = hash_table_init(scratch.arena, 256); // + HashMap hm = {0}; // for MD_EachNode(n, script_parse.root->first->next) { U64 order = 0; if (try_u64_from_str8_c_rules(n->string, &order)) { @@ -598,20 +653,14 @@ t_dbg_script_from_source(Arena *arena, String8 file_path, String8 source, T_DbgS } AssertAlways(file != 0); - // src_offset -> line - // - // TODO: super silly!! mdesk should export line numbers - U64 line = 1; - for EachIndex(i, n->src_offset) { line += (source.str[i] == '\n'); } - - T_DbgScriptProgram *p = hash_table_search_u64_raw(ht, order); + T_DbgScriptProgram *p = hash_map_search_u64_raw(&hm, order); if (p == 0) { p = push_array(arena, T_DbgScriptProgram, 1); - p->line = line; + t_find_line_and_col(source, n, &p->line, 0); p->order = order; p->os = OperatingSystem_CURRENT; p->file = file; - hash_table_push_u64_raw(scratch.arena, ht, order, p); + hash_map_push_u64_raw(scratch.arena, &hm, order, p); } else { t_errorf("ERROR: duplicate order number %llu found on line %llu\n", (unsigned long long)order, (unsigned long long)p->line); } @@ -620,7 +669,7 @@ t_dbg_script_from_source(Arena *arena, String8 file_path, String8 source, T_DbgS // push new cmd T_DbgScriptCmd *cmd = push_array(arena, T_DbgScriptCmd, 1); cmd->kind = t_dbg_script_cmd_kind_from_string(cmd_n->string); - cmd->line = line; + t_find_line_and_col(source, n, &cmd->line_num, &cmd->col_num); Assert(cmd->kind != T_DbgScriptCmdKind_Null); SLLQueuePush(p->first, p->last, cmd); p->count += 1; @@ -640,8 +689,8 @@ t_dbg_script_from_source(Arena *arena, String8 file_path, String8 source, T_DbgS } } - script.program_count = ht->count; - script.programs = values_from_hash_table_raw(arena, ht); + script.program_count = hm.count; + script.programs = values_from_hash_map_raw(arena, &hm); radsort(script.programs, script.program_count, t_dbg_script_program_is_before); } @@ -666,7 +715,7 @@ t_dbg_script_invoke(T_DbgScript *script, U64 timeout_us) if (program->os == OperatingSystem_CURRENT) { for EachNode(cmd, T_DbgScriptCmd, program->first) { - t_infof("[%llu] Command: %S:%llu %S\n", program->order, script->file_path, (unsigned long long)cmd->line, t_string_from_dbg_script_cmd_kind(cmd->kind)); + t_infof("[%llu] Command: %S:%llu %S\n", program->order, script->file_path, (unsigned long long)cmd->line_num, t_string_from_dbg_script_cmd_kind(cmd->kind)); switch (cmd->kind) { case T_DbgScriptCmdKind_Null: break; @@ -694,7 +743,7 @@ t_dbg_script_invoke(T_DbgScript *script, U64 timeout_us) #else T_DbgState *temp_status = t_dbg_state(scratch.arena, T_Dbg_DefaultTimeout); if (temp_status == 0) { - t_errorf("ERROR: %S:%llu: failed to query IP\n", script->file_path, (unsigned long long)cmd->line); + t_errorf("ERROR: %S:%llu: failed to query IP\n", script->file_path, (unsigned long long)cmd->line_num); goto exit; } U64 ip = temp_status->ip; @@ -703,7 +752,7 @@ t_dbg_script_invoke(T_DbgScript *script, U64 timeout_us) // map IP -> source location T_DbgLineArray lines = {0}; if (t_dbg_src_line(scratch.arena, ip, &lines, T_Dbg_DefaultTimeout) == 0) { - t_errorf("ERROR: %S:%llu: IP (0x%llx) does not map to a source line\n", script->file_path, (unsigned long long)cmd->line, (unsigned long long)ip); + t_errorf("ERROR: %S:%llu: IP (0x%llx) does not map to a source line\n", script->file_path, (unsigned long long)cmd->line_num, (unsigned long long)ip); goto exit; } @@ -711,14 +760,18 @@ t_dbg_script_invoke(T_DbgScript *script, U64 timeout_us) S64 at_line_s64 = (S64)(program->line - program->file->line) + cmd->at.delta; U64 at_line_u64 = at_line_s64 >= 0 ? (U64)at_line_s64 : 0; AssertAlways(at_line_u64 > 0); + + if (lines.count == 0) { + t_errorf("ERROR: %S:%llu:%llu: no source location maps for vaddr: 0x%llx\n", script->file_path, (unsigned long long)cmd->line_num, (unsigned long long)cmd->col_num, (unsigned long long)ip); + goto exit; + } // match expected vs current debugger locations - for EachIndex(i, lines.count) { B32 mismatch = lines.v[i].line_num != at_line_u64 || !str8_match(lines.v[i].file_path, program->file->path, StringMatchFlag_CaseInsensitive|StringMatchFlag_SlashInsensitive); if (mismatch) { - t_errorf("ERROR: %S:%llu: location check did not pass:\n", script->file_path, (unsigned long long)cmd->line); + t_errorf("ERROR: %S:%llu: location check did not pass:\n", script->file_path, (unsigned long long)cmd->line_num); t_errorf(" Expected: %S:%llu\n", program->file->path, (unsigned long long)at_line_u64); t_errorf(" Got : %S:%llu\n", lines.v[i].file_path, (unsigned long long)lines.v[i].line_num); t_errorf(" IP : 0x%llx\n", (unsigned long long)ip); @@ -758,6 +811,7 @@ T_RunSig(dbg_script_runner) // source -> script T_DbgScript script = {0}; if ( ! t_dbg_script_from_source(arena, user_data, source, &script)) { + result_out->status = T_RunStatus_Fail; goto exit; } @@ -768,17 +822,23 @@ T_RunSig(dbg_script_runner) T_Ok(0); } } - + + if (script.directives[OperatingSystem_CURRENT][T_DbgScriptDirectiveKind_Skip].count) { + result_out->status = T_RunStatus_Skip; + goto exit; + } + // compiler vars HashTable *script_vars = hash_table_init(arena, 1000); + hash_table_push_path_string(arena, script_vars, str8_lit("FILE"), user_data); hash_table_push_path_string(arena, script_vars, str8_lit("CWD"), g_wdir); // run compilers for EachNode(directive, T_DbgScriptDirective, script.directives[OperatingSystem_CURRENT][T_DbgScriptDirectiveKind_Compile].first) { - T_Compiler compiler = directive->compile.compiler; - + T_Compiler compiler = directive->compile.cc; + // pick default compiler if none selected - if (compiler == T_Compiler_Null) { + if (directive->compile.cc == T_Compiler_Null) { switch (OperatingSystem_CURRENT) { case OperatingSystem_Windows: { compiler = T_Compiler_Cl; } break; case OperatingSystem_Linux: { compiler = T_Compiler_Clang; } break; @@ -796,10 +856,16 @@ T_RunSig(dbg_script_runner) // invoke compiler with arguments from directive String8 expanded_args = lnk_expand_env_vars_windows(arena, script_vars, directive->args); + + if (compiler == T_Compiler_Cl) { expanded_args = str8f(arena, "/nologo %S", expanded_args); } + if (t_invoke(compiler_path, expanded_args, max_U64) == 0) { t_errorf("ERROR: failed to launch compiler: \"%S %S\"\n", compiler_path, expanded_args); T_Ok(0); } + + if (compiler == T_Compiler_Cl) { g_output = str8_skip(g_output, str8_chop_line(&g_output).size); } // file name print + if (g_last_exit_code) { t_errorf("ERROR: %S:%llu: %S\n", script.file_path, (unsigned long long)directive->line, g_errors); if (g_stop_on_first_fail_or_crash) { @@ -875,7 +941,7 @@ t_dbg_register_script_tests(Arena *arena, String8 folder_path) String8 file_name_escaped = str8_list_join(arena, &file_name_parts, &(StringJoin){.sep=str8_lit("-"), .post = str8_lit("\0") }); g_torture_tests_[g_torture_test_count++] = (T_Test){ - .group = T_Group, + .file = "raddbg", .label = (char*)file_name_escaped.str, .r = t_dbg_script_runner, .user_data = str8_copy(arena, file_path), @@ -885,4 +951,3 @@ t_dbg_register_script_tests(Arena *arena, String8 folder_path) scratch_end(scratch); } -#undef T_Group diff --git a/src/torture/torture_dbg.h b/src/torture/torture_dbg.h index 1f22a318..39e73b05 100644 --- a/src/torture/torture_dbg.h +++ b/src/torture/torture_dbg.h @@ -21,31 +21,37 @@ typedef struct T_DbgScriptFile *last; } T_DbgScriptFileList; +#define T_DbgScriptCmdKind_XList \ + X(Breakpoint, "bp") \ + X(ClearBreakpoints, "bp_clear") \ + X(Run, "run") \ + X(Halt, "halt") \ + X(StepOver, "step_over") \ + X(StepInto, "step_into") \ + X(StepOut, "step_out") \ + X(StepOverInst, "step_over_inst") \ + X(StepIntoInst, "step_over_inst") \ + X(StepOverLine, "step_over_line") \ + X(StepIntoLine, "step_into_line") \ + X(KillAll, "kll_all") \ + X(At, "at") \ + X(Eval, "eval") + typedef enum { T_DbgScriptCmdKind_Null, - T_DbgScriptCmdKind_Breakpoint, - T_DbgScriptCmdKind_ClearBreakpoints, - T_DbgScriptCmdKind_Run, - T_DbgScriptCmdKind_Halt, - T_DbgScriptCmdKind_StepOver, - T_DbgScriptCmdKind_StepInto, - T_DbgScriptCmdKind_StepOut, - T_DbgScriptCmdKind_StepOverInst, - T_DbgScriptCmdKind_StepIntoInst, - T_DbgScriptCmdKind_StepOverLine, - T_DbgScriptCmdKind_StepIntoLine, - T_DbgScriptCmdKind_KillAll, - T_DbgScriptCmdKind_At, - T_DbgScriptCmdKind_Eval, +#define X(n,...) T_DbgScriptCmdKind_##n, + T_DbgScriptCmdKind_XList +#undef X T_DbgScriptCmdKind_Count } T_DbgScriptCmdKind; typedef struct T_DbgScriptCmd { struct T_DbgScriptCmd *next; - T_DbgScriptCmdKind kind; - U64 line; + T_DbgScriptCmdKind kind; + U64 line_num; + U64 col_num; union { struct { S64 delta; @@ -78,6 +84,7 @@ typedef enum T_DbgScriptDirectiveKind_Compile, T_DbgScriptDirectiveKind_Link, T_DbgScriptDirectiveKind_Launch, + T_DbgScriptDirectiveKind_Skip, T_DbgScriptDirectiveKind_Count, } T_DbgScriptDirectiveKind; @@ -89,7 +96,7 @@ typedef struct T_DbgScriptDirective String8 args; union { struct { - T_Compiler compiler; + T_Compiler cc; } compile; }; } T_DbgScriptDirective; diff --git a/src/torture/torture_dwarf.c b/src/torture/torture_dwarf.c index 46d2d185..0649731a 100644 --- a/src/torture/torture_dwarf.c +++ b/src/torture/torture_dwarf.c @@ -1,8 +1,6 @@ // Copyright (c) Epic Games Tools // Licensed under the MIT license (https://opensource.org/license/mit/) -#define T_Group "Dwarf" - internal U64 t_dw_test_uleb128(U64 v, U64 expected_length) { @@ -1040,4 +1038,3 @@ TEST(reg_split_spill) } #endif -#undef T_Group diff --git a/src/torture/torture_md.c b/src/torture/torture_md.c index 7b87e7ed..5eabb69f 100644 --- a/src/torture/torture_md.c +++ b/src/torture/torture_md.c @@ -1,6 +1,6 @@ #define T_Group "MD" -TEST(md_tokenizer) +SKIP(md_tokenizer) { MD_TokenizeResult result; diff --git a/src/torture/torture_p2r.c b/src/torture/torture_p2r.c index a7804059..86ac00e1 100644 --- a/src/torture/torture_p2r.c +++ b/src/torture/torture_p2r.c @@ -1,5 +1,3 @@ -#define T_Group "p2r" - #if OS_WINDOWS // TODO: location is not baked consistently @@ -127,5 +125,3 @@ TEST(p2r_determ) #endif #endif -#undef T_Group - diff --git a/src/torture/torture_radlink.c b/src/torture/torture_radlink.c index 56dcd802..f7ec015f 100644 --- a/src/torture/torture_radlink.c +++ b/src/torture/torture_radlink.c @@ -433,8 +433,6 @@ t_write_entry_obj(void) //////////////////////////////// -#define T_Group "Linker" - TEST(machine_compat_check) { // unknown.obj @@ -4712,7 +4710,7 @@ TEST(relocate_undefined_section_symbol) T_Ok(t_write_def_obj(obj_name, (T_COFF_DefObj){ .machine = T_COFF_DefSetMachine(X64), .sections = (T_COFF_DefSection[]){ - { target_name, sect_name, target_name, .flags = "r:data@1", .raw_flags = COFF_SectionFlag_LnkCOMDAT }, + { target_name, sect_name, str8_cstring(target_name), .flags = "r:data@1", .raw_flags = COFF_SectionFlag_LnkCOMDAT }, {0} }, .symbols = (T_COFF_DefSymbol[]){ @@ -7203,4 +7201,3 @@ TEST(lib_member_reloc_apply_off_out_of_bounds) } #endif -#undef T_Group