improve error handling in the debugger harness (source file + line

number for error reports and allow scripts without header)
This commit is contained in:
Nikita Smith
2026-06-02 12:10:44 -07:00
committed by Ryan Fleury
parent 56ee3bbf7b
commit 09afff7957
4 changed files with 193 additions and 103 deletions
+51 -8
View File
@@ -14,6 +14,7 @@ global String8 g_test_data;
U64 g_torture_test_count;
T_Test **g_torture_tests;
T_Test g_torture_tests_[0xffffff];
B32 g_is_first_print;
// invoke
global U64 g_last_exit_code;
@@ -159,21 +160,27 @@ internal void
t_run_caller(void *raw_ctx)
{
Temp scratch = scratch_begin(0,0);
g_is_first_print = 1;
T_RunCtx *ctx = raw_ctx;
String8List test_out = {0};
ctx->result.status = T_RunStatus_Pass;
String8List test_out = {0};
ctx->run(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) {
fprintf(stderr, "%.*s", str8_varg(n->string));
t_errorf("%S", n->string);
}
if (g_errors.size) {
fprintf(stderr, "stderr: \"%.*s\"\n", str8_varg(g_errors));
t_errorf("stderr: \"%S\"\n", g_errors);
}
if (g_output.size) {
fprintf(stderr, "stdout: \"%.*s\"\n", str8_varg(g_output));
t_errorf("stdout: \"%S\"\n", g_output);
}
}
scratch_end(scratch);
}
@@ -479,11 +486,7 @@ t_invoke_env(String8 exe_path, String8 cmdline, String8List env, U64 timeout_us)
};
str8_list_push_front(scratch.arena, &launch_opts.cmd_line, exe_path);
if (g_verbose) {
String8 full_cmd_line = str8_list_join(scratch.arena, &launch_opts.cmd_line, &(StringJoin){ .sep = str8_lit(" ") });
fprintf(stdout, "Command Line: %.*s\n", str8_varg(full_cmd_line));
fprintf(stdout, "Working Dir: %.*s\n", str8_varg(g_wdir));
}
// invoke exe
Process process_handle = process_launch(&launch_opts);
@@ -757,6 +760,12 @@ t_invoke_env(String8 exe_path, String8 cmdline, String8List env, U64 timeout_us)
}
#endif
t_infof("Invoke: {\n");
t_infof(" CMDL: %S\n", full_cmd_line);
t_infof(" WDIR: %S\n", g_wdir);
t_infof(" Exit: %u\n", g_last_exit_code);
t_infof("}\n");
// update output global
g_output = str8_list_join(g_output_arena, &stdout_parts, 0);
g_errors = str8_list_join(g_output_arena, &stderr_parts, 0);
@@ -971,6 +980,40 @@ t_match_folders(String8 a, String8 b)
return is_match;
}
internal void
t_infof(char *fmt, ...)
{
if (g_verbose) {
Temp scratch = scratch_begin(0,0);
va_list args;
va_start(args, fmt);
String8 result = push_str8fv(scratch.arena, fmt, args);
if (g_is_first_print) {
g_is_first_print = 0;
fprintf(stderr, "\n");
}
fprintf(stderr, "%.*s", str8_varg(result));
va_end(args);
scratch_end(scratch);
}
}
internal void
t_errorf(char *fmt, ...)
{
Temp scratch = scratch_begin(0,0);
va_list args;
va_start(args, fmt);
String8 result = push_str8fv(scratch.arena, fmt, args);
if (g_is_first_print) {
g_is_first_print = 0;
fprintf(stderr, "\n");
}
fprintf(stderr, "%.*s", str8_varg(result));
va_end(args);
scratch_end(scratch);
}
internal void
t_entry_point(CmdLine *cmdline)
{
+16 -2
View File
@@ -101,7 +101,7 @@ internal void t_break_if_debugger_present(void);
#define ENDT_MS(x) TIMEOUT_US((x)*1000ull)
#define ENDT_SEC(x) TIMEOUT_MS((x)*1000ull)
////////////////////////////////
////////////////////////////////////////////////////////////////
// output directory
internal B32 t_write_file_list(String8 name, String8List data);
@@ -115,6 +115,7 @@ 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);
// tools
internal String8 t_radbin_path(void);
internal String8 t_cl_path(void);
internal String8 t_clang_path(void);
@@ -128,7 +129,20 @@ internal B32 t_invoke_cl(char *fmt, ...);
internal B32 t_invoke_linkerf(char *fmt, ...);
internal B32 t_invoke_radbin(char *fmt, ...);
internal void t_kill_all(String8 pattern);
#define t_invoke_linker_timeout(c, t) T_Ok(t_invoke(t_radlink_path(), c, t))
#define t_invoke_linker_timeoutf(t, f, ...) t_invoke_linker_timeout(push_str8f(arena, f, ##__VA_ARGS__), t)
#define t_invoke_linker(c) t_invoke_linker_timeout(c, max_U64)
internal void t_kill_all(String8 pattern);
internal String8 t_chop_line(String8 *string);
internal B32 t_match_line(String8 *output, String8 expected_line);
internal B32 t_match_linef(String8 *output, char *fmt, ...);
// files helper
internal String8List t_file_paths_from_dir(Arena *arena, String8 dir);
// printer
internal void t_infof(char *fmt, ...);
internal void t_errorf(char *fmt, ...);
+85 -54
View File
@@ -86,15 +86,15 @@ t_dbg_status(T_DbgStatus *status_out, U64 timeout_us)
if ( ! t_dbg_send_cmd(str8_lit("status"), timeout_us, scratch.arena, &reply)) { goto exit; }
// parse reply
if ( ! rd_ipc_parse_b32(reply.msg, str8_lit("ok"), &is_ok)) { fprintf(stderr, "ERROR: failed to parse reply member: ok\n"); Assert(0); goto exit; }
if ( ! rd_ipc_parse_b32(reply.msg, str8_lit("running"), &status.running)) { fprintf(stderr, "ERROR: failed to parse reply member: running\n"); Assert(0); goto exit; }
if ( ! rd_ipc_parse_int(reply.msg, str8_lit("run_gen"), &status.run_gen)) { fprintf(stderr, "ERROR: failed to parse reply member: run_gen\n"); Assert(0); goto exit; }
if ( ! rd_ipc_parse_int(reply.msg, str8_lit("ip"), &status.ip)) { fprintf(stderr, "ERROR: failed to parse reply member: ip\n"); Assert(0); goto exit; }
if ( ! rd_ipc_parse_b32(reply.msg, str8_lit("ok"), &is_ok)) { t_errorf("ERROR: failed to parse reply member: ok\n"); Assert(0); goto exit; }
if ( ! rd_ipc_parse_b32(reply.msg, str8_lit("running"), &status.running)) { t_errorf("ERROR: failed to parse reply member: running\n"); Assert(0); goto exit; }
if ( ! rd_ipc_parse_int(reply.msg, str8_lit("run_gen"), &status.run_gen)) { t_errorf("ERROR: failed to parse reply member: run_gen\n"); Assert(0); goto exit; }
if ( ! rd_ipc_parse_int(reply.msg, str8_lit("ip"), &status.ip)) { t_errorf("ERROR: failed to parse reply member: ip\n"); Assert(0); goto exit; }
if (status_out != 0) { *status_out = status; }
exit:;
if ( ! is_ok && reply.parse.root) {
fprintf(stderr, "\tReply: %.*s\n", str8_varg(reply.parse.root->raw_string));
t_errorf("\tReply: %S\n", reply.parse.root->raw_string);
}
scratch_end(scratch);
return is_ok;
@@ -319,10 +319,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.msg, str8_lit("expr"), &e.expr)) { fprintf(stderr, "ERROR: failed to parse reply member: expr\n"); Assert(0); goto exit; }
if ( ! rd_ipc_parse_string(reply.msg, str8_lit("value"), &e.value)) { fprintf(stderr, "ERROR: failed to parse reply member: value\n"); Assert(0); goto exit; }
if ( ! rd_ipc_parse_string(reply.msg, str8_lit("type"), &e.type)) { fprintf(stderr, "ERROR: failed to parse reply member: type\n"); Assert(0); goto exit; }
if ( ! rd_ipc_parse_string(reply.msg, str8_lit("error"), &e.error)) { fprintf(stderr, "ERROR: failed to parse reply member: error\n"); Assert(0); goto exit; }
if ( ! rd_ipc_parse_string(reply.msg, 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.msg, 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.msg, 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.msg, str8_lit("error"), &e.error)) { t_errorf("ERROR: failed to parse reply member: error\n"); Assert(0); goto exit; }
if (eval_out) { *eval_out = e; }
exit:;
@@ -346,29 +346,44 @@ t_dbg_script_program_is_before(void *raw_a, void *raw_b)
return t_dbg_script_program_compar(raw_a, raw_b) < 0;
}
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;
}
return str8_zero();
}
internal T_DbgScriptCmdKind
t_dbg_script_cmd_kind_from_string(String8 cmd)
{
if (str8_matchi(cmd, str8_lit("bp"))) { return T_DbgScriptCmdKind_Breakpoint; }
else if (str8_matchi(cmd, str8_lit("bp_clear"))) { return T_DbgScriptCmdKind_ClearBreakpoints; }
else if (str8_matchi(cmd, str8_lit("run"))) { return T_DbgScriptCmdKind_Run; }
else if (str8_matchi(cmd, str8_lit("halt"))) { return T_DbgScriptCmdKind_Halt; }
else if (str8_matchi(cmd, str8_lit("step_over"))) { return T_DbgScriptCmdKind_StepOver; }
else if (str8_matchi(cmd, str8_lit("step_into"))) { return T_DbgScriptCmdKind_StepInto; }
else if (str8_matchi(cmd, str8_lit("step_out"))) { return T_DbgScriptCmdKind_StepOut; }
else if (str8_matchi(cmd, str8_lit("step_over_inst"))) { return T_DbgScriptCmdKind_StepOverInst; }
else if (str8_matchi(cmd, str8_lit("step_into_inst"))) { return T_DbgScriptCmdKind_StepIntoInst; }
else if (str8_matchi(cmd, str8_lit("step_over_line"))) { return T_DbgScriptCmdKind_StepOverLine; }
else if (str8_matchi(cmd, str8_lit("step_into_line"))) { return T_DbgScriptCmdKind_StepIntoLine; }
else if (str8_matchi(cmd, str8_lit("at"))) { return T_DbgScriptCmdKind_At; }
else if (str8_matchi(cmd, str8_lit("eval"))) { return T_DbgScriptCmdKind_Eval; }
for EachIndex(i, T_DbgScriptCmdKind_Count) {
if (str8_matchi(t_string_from_dbg_script_cmd_kind(i), cmd)) { return i; }
}
return T_DbgScriptCmdKind_Null;
}
internal T_DbgScript
t_dbg_script_from_source(Arena *arena, String8 file_path, String8 source)
internal B32
t_dbg_script_from_source(Arena *arena, String8 file_path, String8 source, T_DbgScript *script_out)
{
Temp scratch = scratch_begin(&arena, 1);
B32 is_ok = 0;
T_DbgScript script = { .file_path = push_str8_copy(arena, file_path) };
@@ -409,10 +424,7 @@ t_dbg_script_from_source(Arena *arena, String8 file_path, String8 source)
{
MD_Node *test = script_parse.root->first;
AssertAlways( ! md_node_is_nil(test));
AssertAlways(str8_matchi(test->string, str8_lit("test")));
String8 os_name = string_from_operating_system(OperatingSystem_CURRENT);
if (str8_matchi(test->string, str8_lit("test"))) {
for MD_EachNode(n, test->first) {
OperatingSystem os = operating_system_from_string(n->string);
AssertAlways(os != OperatingSystem_Null);
@@ -447,6 +459,10 @@ t_dbg_script_from_source(Arena *arena, String8 file_path, String8 source)
list->count += 1;
}
}
} else {
t_errorf("ERROR: %S: missing test header\n", file_path);
goto exit;
}
}
// file
@@ -523,21 +539,20 @@ t_dbg_script_from_source(Arena *arena, String8 file_path, String8 source)
p->file = file;
hash_table_push_u64_raw(scratch.arena, ht, order, p);
} else {
fprintf(stderr, "ERROR: duplicate order number %llu found on line %llu\n", (unsigned long long)order, (unsigned long long)p->line);
t_errorf("ERROR: duplicate order number %llu found on line %llu\n", (unsigned long long)order, (unsigned long long)p->line);
}
MD_Node *cmd_name = n->first;
MD_Node *cmd_arg = cmd_name->first;
AssertAlways(!md_node_is_nil(cmd_name));
for MD_EachNode(cmd_n, n->first) {
// push new cmd
T_DbgScriptCmd *cmd = push_array(arena, T_DbgScriptCmd, 1);
cmd->kind = t_dbg_script_cmd_kind_from_string(cmd_name->string);
AssertAlways(cmd->kind != T_DbgScriptCmdKind_Null);
SLLQueuePush(p->first, p->first, cmd);
cmd->kind = t_dbg_script_cmd_kind_from_string(cmd_n->string);
cmd->line = line;
Assert(cmd->kind != T_DbgScriptCmdKind_Null);
SLLQueuePush(p->first, p->last, cmd);
p->count += 1;
// parse cmd args
MD_Node *cmd_arg = cmd_n->first;
if ( ! md_node_is_nil(cmd_arg)) {
if (cmd->kind == T_DbgScriptCmdKind_At) {
AssertAlways(try_s64_from_str8_c_rules(cmd_arg->string, &cmd->at.delta));
@@ -549,14 +564,20 @@ t_dbg_script_from_source(Arena *arena, String8 file_path, String8 source)
}
}
}
}
script.program_count = ht->count;
script.programs = values_from_hash_table_raw(arena, ht);
radsort(script.programs, script.program_count, t_dbg_script_program_is_before);
}
is_ok = 1;
exit:;
if (script_out) {
*script_out = script;
}
scratch_end(scratch);
return script;
return is_ok;
}
internal B32
@@ -571,6 +592,8 @@ 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));
switch (cmd->kind) {
case T_DbgScriptCmdKind_Null: break;
case T_DbgScriptCmdKind_Halt: t_dbg_send_cmd_and_wait_stop(str8_lit("halt"), timeout_us); break; // NOTE: does not auto-magically select main thread on stop
@@ -597,7 +620,7 @@ t_dbg_script_invoke(T_DbgScript *script, U64 timeout_us)
#else
T_DbgStatus temp_status = {0};
if ( ! t_dbg_status(&temp_status, T_Dbg_DefaultTimeout)) {
fprintf(stderr, "ERROR: failed to query IP\n");
t_errorf("ERROR: %S:%llu: failed to query IP\n", script->file_path, (unsigned long long)cmd->line);
goto exit;
}
U64 ip = temp_status.ip;
@@ -606,7 +629,7 @@ t_dbg_script_invoke(T_DbgScript *script, U64 timeout_us)
// map IP -> source location
T_DbgSourceLocation loc = {0};
if (t_dbg_src_line(scratch.arena, ip, &loc, T_Dbg_DefaultTimeout) == 0) {
fprintf(stderr, "ERROR: failed to map IP (0x%llx) to source location\n", (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, (unsigned long long)ip);
goto exit;
}
@@ -619,11 +642,10 @@ t_dbg_script_invoke(T_DbgScript *script, U64 timeout_us)
B32 mismatch = loc.pt.line != at_line_u64 ||
!str8_match(loc.file_path, program->file->path, StringMatchFlag_CaseInsensitive|StringMatchFlag_SlashInsensitive);
if (mismatch) {
fprintf(stderr, "ERROR: location check did not pass:\n");
fprintf(stderr, " Script : %.*s\n", str8_varg(script->file_path));
fprintf(stderr, " Expected: %.*s:%llu\n", str8_varg(program->file->path), (unsigned long long)at_line_u64);
fprintf(stderr, " Got : %.*s:%llu\n", str8_varg(loc.file_path), (unsigned long long)loc.pt.line);
fprintf(stderr, " IP : 0x%llx\n", (unsigned long long)ip);
t_errorf("ERROR: %S:%llu: location check did not pass:\n", script->file_path, (unsigned long long)cmd->line);
t_errorf(" Expected: %S:%llu\n", program->file->path, (unsigned long long)at_line_u64);
t_errorf(" Got : %S:%llu\n", loc.file_path, (unsigned long long)loc.pt.line);
t_errorf(" IP : 0x%llx\n", (unsigned long long)ip);
goto exit;
}
} break;
@@ -645,24 +667,27 @@ internal
T_RunSig(dbg_script_runner)
{
if ( ! file_path_exists(t_raddbg_path())) {
fprintf(stderr, "ERROR: failed to find debugger \"%.*s\"\n", str8_varg(t_raddbg_path()));
t_errorf("ERROR: failed to find debugger \"%S\"\n", t_raddbg_path());
T_Ok(0);
}
// read source file
String8 source = data_from_file_path(arena, user_data);
if (source.size == 0) {
fprintf(stderr, "ERROR: failed to read script: \"%.*s\"\n", str8_varg(user_data));
t_errorf("ERROR: failed to read script: \"%S\"\n", user_data);
T_Ok(0);
}
// source -> script
T_DbgScript script = t_dbg_script_from_source(arena, user_data, source);
T_DbgScript script = {0};
if ( ! t_dbg_script_from_source(arena, user_data, source, &script)) {
goto exit;
}
// write source files to test folder
for EachNode(file, T_DbgScriptFile, script.files.first) {
if (write_data_to_file_path(file->path, file->source) == 0) {
fprintf(stderr, "ERROR: %.*s:%llu: failed to write: \"%.*s\"\n", str8_varg(user_data), (unsigned long long)file->line, str8_varg(file->path));
t_errorf("ERROR: %S:%llu: failed to write: \"%S\"\n", user_data, (unsigned long long)file->line, file->path);
T_Ok(0);
}
}
@@ -695,11 +720,11 @@ 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 (t_invoke(compiler_path, expanded_args, max_U64) == 0) {
fprintf(stderr, "ERROR: failed to launch compiler: \"%.*s %.*s\"\n", str8_varg(compiler_path), str8_varg(expanded_args));
t_errorf("ERROR: failed to launch compiler: \"%S %S\"\n", compiler_path, expanded_args);
T_Ok(0);
}
if (g_last_exit_code) {
fprintf(stderr, "ERROR: %.*s\n", str8_varg(g_output));
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) {
T_Ok(0);
}
@@ -711,7 +736,11 @@ T_RunSig(dbg_script_runner)
for EachNode(directive, T_DbgScriptDirective, script.directives[OperatingSystem_CURRENT][T_DbgScriptDirectiveKind_Link].first) {
String8 expanded_args = lnk_expand_env_vars_windows(arena, script_vars, directive->args);
if (t_invoke(linker_path, expanded_args, max_U64) == 0) {
fprintf(stderr, "ERROR: failed to launch linker: \"%.*s %.*s\"\n", str8_varg(linker_path), str8_varg(expanded_args));
t_errorf("ERROR: failed to launch linker: \"%S %S\"\n", linker_path, expanded_args);
T_Ok(0);
}
if (g_last_exit_code != 0) {
t_errorf("ERROR: %S:%llu: %S\n", script.file_path, (unsigned long long)directive->line, g_errors);
T_Ok(0);
}
}
@@ -721,14 +750,14 @@ T_RunSig(dbg_script_runner)
String8 expanded_args = lnk_expand_env_vars_windows(arena, script_vars, directive->args);
String8 cmdl = str8f(arena, "--user:%S.raddbg_user %S", t_make_file_path(arena, str8_lit("temp")), expanded_args);
if (t_dbg_launch(cmdl, T_Dbg_DefaultTimeout) == 0) {
fprintf(stderr, "ERROR: failed to launch debugger with command line \"%.*s %.*s\"; work dir \"%.*s\"\n", str8_varg(t_raddbg_path()), str8_varg(cmdl), str8_varg(g_wdir));
t_errorf("ERROR: failed to launch debugger with command line \"%S %S\"; work dir \"%S\"\n", t_raddbg_path(), cmdl, g_wdir);
T_Ok(0);
}
}
// debugger is ready -- now invoke script
if (t_dbg_script_invoke(&script, T_Dbg_DefaultTimeout) == 0) {
fprintf(stderr, "ERROR: %.*s: failed to run to completion\n", str8_varg(user_data));
t_errrof("ERROR: %S: failed to run to completion\n", user_data);
T_Ok(0);
}
@@ -742,6 +771,8 @@ T_RunSig(dbg_script_runner)
#else
kill(g_dbg_pid, SIGKILL);
#endif
exit:;
g_dbg_pid = 0;
}
@@ -751,7 +782,7 @@ t_dbg_register_script_tests(Arena *arena, String8 folder_path)
Temp scratch = scratch_begin(&arena, 1);
if ( ! folder_path_exists(folder_path)) {
fprintf(stderr, "ERROR: this folder does not exists: %.*s\n", str8_varg(folder_path));
t_errof("ERROR: this folder does not exists: %S\n", folder_path);
return;
}
+2
View File
@@ -38,6 +38,7 @@ typedef enum
T_DbgScriptCmdKind_KillAll,
T_DbgScriptCmdKind_At,
T_DbgScriptCmdKind_Eval,
T_DbgScriptCmdKind_Count
} T_DbgScriptCmdKind;
typedef struct T_DbgScriptCmd
@@ -107,6 +108,7 @@ typedef struct
T_DbgScriptFileList files;
U64 program_count;
T_DbgScriptProgram **programs;
B32 skip;
} T_DbgScript;
////////////////////////////////