kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

commit 9ff956a2e8556a872bec0633637982a51b4b88e7
parent 85f4d72210d79fa1a6841c7d0fab48343a28620e
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Thu, 16 Jul 2026 10:12:48 -0700

dbg: propagate explicit script failures

Diffstat:
Mdriver/cmd/dbg.c | 79++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------
Mtest/dbg/run.sh | 43+++++++++++++++++++++++++++++++++++++++++++
2 files changed, 97 insertions(+), 25 deletions(-)

diff --git a/driver/cmd/dbg.c b/driver/cmd/dbg.c @@ -176,13 +176,9 @@ void driver_help_dbg(void) { " --batch non-interactive: suppress banner, exit after\n" " --script / --command sources drain; exit 1 on\n" " any command error\n" - " Note: multi-line jit{} / expr{} blocks in --script files must " - "fit\n" - " on a single line (continuation reads go to stdin, not the file).\n" - "\n" - " KNOWN LIMITATION: an unreadable --script file is diagnosed but\n" - " currently returns status 0. Check stderr/file existence rather\n" - " than relying on status alone until this is remediated.\n" + " Each script command must fit on one line. Missing, unreadable,\n" + " overlong, or malformed explicit scripts return status 1 without\n" + " falling through to an interactive prompt.\n" "\n" "SIGNALS\n" " Ctrl-C is forwarded into the running session as an interrupt; at\n" @@ -589,6 +585,7 @@ typedef struct DbgState { uint32_t nscript_entries; int batch_mode; int error_count; + int script_failed; } DbgState; /* Like driver_errf but increments s->error_count so --batch can propagate @@ -3245,47 +3242,74 @@ static void dbg_complete(void* user, const char* line, size_t cursor, * Script source helpers * ============================================================ */ -/* Execute a single command string as if typed at the REPL. - * Returns 1 if the REPL should exit (quit command), 0 otherwise. */ -static int dbg_run_script_cmd(DbgState* s, const char* cmd) { +typedef enum DbgScriptResult { + DBG_SCRIPT_CONTINUE = 0, + DBG_SCRIPT_QUIT, + DBG_SCRIPT_ERROR, +} DbgScriptResult; + +/* Execute a single command string as if typed at the REPL. */ +static DbgScriptResult dbg_run_script_cmd(DbgState* s, const char* cmd) { char line[LINE_CAP]; size_t n = driver_strlen(cmd); - if (n >= LINE_CAP) n = LINE_CAP - 1; + int errors = s->error_count; + int quit; + if (n >= LINE_CAP) { + dbg_errf(s, "script command exceeds %u bytes", (unsigned)(LINE_CAP - 1u)); + return DBG_SCRIPT_ERROR; + } driver_memcpy(line, cmd, n); line[n] = '\0'; - return dbg_dispatch(s, line); + quit = dbg_dispatch(s, line); + if (quit) return DBG_SCRIPT_QUIT; + return s->error_count != errors ? DBG_SCRIPT_ERROR : DBG_SCRIPT_CONTINUE; } -/* Execute debugger commands from a file, one per line. - * Returns 1 if the REPL should exit (quit command), 0 otherwise. */ -static int dbg_run_script_file(DbgState* s, const char* path) { +/* Execute debugger commands from a file, one per line. */ +static DbgScriptResult dbg_run_script_file(DbgState* s, const char* path) { DriverLoad load = {0}; KitSlice bytes; const char* p; const char* end; char line[LINE_CAP]; - if (driver_load_bytes(&s->env->file_io, DBG_TOOL, path, &load, &bytes) != 0) - return 0; /* driver_load_bytes already emitted an error */ + uint64_t line_no = 0; + if (driver_load_bytes(&s->env->file_io, DBG_TOOL, path, &load, &bytes) != 0) { + ++s->error_count; /* driver_load_bytes already emitted the diagnostic */ + return DBG_SCRIPT_ERROR; + } p = (const char*)bytes.data; end = p + bytes.len; while (p < end) { const char* nl = p; size_t len; + int errors = s->error_count; + int quit; + ++line_no; while (nl < end && *nl != '\n') nl++; len = (size_t)(nl - p); /* Strip trailing carriage return. */ if (len > 0 && p[len - 1] == '\r') len--; - if (len >= LINE_CAP) len = LINE_CAP - 1; + if (len >= LINE_CAP) { + dbg_errf(s, "%s:%llu: script line exceeds %u bytes", path, + (unsigned long long)line_no, (unsigned)(LINE_CAP - 1u)); + driver_release_bytes(&s->env->file_io, &load); + return DBG_SCRIPT_ERROR; + } driver_memcpy(line, p, len); line[len] = '\0'; p = (nl < end) ? nl + 1 : end; - if (dbg_dispatch(s, line)) { + quit = dbg_dispatch(s, line); + if (quit) { driver_release_bytes(&s->env->file_io, &load); - return 1; + return DBG_SCRIPT_QUIT; + } + if (s->error_count != errors) { + driver_release_bytes(&s->env->file_io, &load); + return DBG_SCRIPT_ERROR; } } driver_release_bytes(&s->env->file_io, &load); - return 0; + return DBG_SCRIPT_CONTINUE; } /* ============================================================ @@ -3301,9 +3325,14 @@ static void dbg_repl(DbgState* s) { /* Phase 1: drain --script / --command sources in order. */ for (i = 0; i < s->nscript_entries; i++) { DbgScriptEntry* e = &s->script_entries[i]; - int quit = (e->kind == DBG_ENTRY_FILE) ? dbg_run_script_file(s, e->value) - : dbg_run_script_cmd(s, e->value); - if (quit) return; + DbgScriptResult result = + (e->kind == DBG_ENTRY_FILE) ? dbg_run_script_file(s, e->value) + : dbg_run_script_cmd(s, e->value); + if (result == DBG_SCRIPT_QUIT) return; + if (result == DBG_SCRIPT_ERROR) { + s->script_failed = 1; + return; + } } /* Phase 2: in --batch mode, exit without falling through to stdin. */ @@ -3483,7 +3512,7 @@ int driver_dbg_ex(int argc, char** argv, const KitDriverExtension* ext) { kit_target_free(target); dbg_options_release(&o); driver_env_fini(&env); - return (st.batch_mode && st.error_count > 0) ? 1 : 0; + return (st.script_failed || (st.batch_mode && st.error_count > 0)) ? 1 : 0; } int driver_dbg(int argc, char** argv) { return driver_dbg_ex(argc, argv, NULL); } diff --git a/test/dbg/run.sh b/test/dbg/run.sh @@ -52,6 +52,49 @@ KIT_STRICT_XFAIL=${DBG_STRICT_XFAIL:-0} kit_report_init +# Explicit script sources have command-style status semantics independent of +# transcript contents. Exercise them outside the golden matrix so failures can +# never be hidden by prompt normalization or an interactive EOF fallback. +dbg_expect_status() { + des_name=$1 + des_want=$2 + shift 2 + "$@" </dev/null > "$KIT_WORK/$des_name.stdout" \ + 2> "$KIT_WORK/$des_name.stderr" + des_got=$? + if [ "$des_got" -eq "$des_want" ]; then + kit_pass "$des_name" + else + kit_fail "$des_name" "status=$des_got expected=$des_want" + sed 's/^/ stdout| /' "$KIT_WORK/$des_name.stdout" + sed 's/^/ stderr| /' "$KIT_WORK/$des_name.stderr" + fi +} + +printf 'set\n' > "$KIT_WORK/malformed.dbg" +awk 'BEGIN { for (i = 0; i < 1100; ++i) printf "x"; printf "\n" }' \ + > "$KIT_WORK/overlong.dbg" +printf 'q\n' > "$KIT_WORK/quit.dbg" + +dbg_expect_status missing-script-batch 1 \ + "$KIT" dbg --batch --script "$KIT_WORK/does-not-exist.dbg" +dbg_expect_status missing-script-interactive 1 \ + "$KIT" dbg --script "$KIT_WORK/does-not-exist.dbg" +dbg_expect_status unreadable-script-batch 1 \ + "$KIT" dbg --batch --script "$KIT_WORK" +dbg_expect_status malformed-script-batch 1 \ + "$KIT" dbg --batch --script "$KIT_WORK/malformed.dbg" +dbg_expect_status malformed-script-interactive 1 \ + "$KIT" dbg --script "$KIT_WORK/malformed.dbg" +dbg_expect_status overlong-script-batch 1 \ + "$KIT" dbg --batch --script "$KIT_WORK/overlong.dbg" +dbg_expect_status overlong-script-interactive 1 \ + "$KIT" dbg --script "$KIT_WORK/overlong.dbg" +dbg_expect_status explicit-quit-batch 0 \ + "$KIT" dbg --batch --script "$KIT_WORK/quit.dbg" +dbg_expect_status explicit-quit-interactive 0 \ + "$KIT" dbg --script "$KIT_WORK/quit.dbg" + # normalize_stdout RAW CASE_DIR : multi-stage stdout normalizer (lane-local). # Strips the "(kit) " prompt and continuation markers, re-tokenizes CASE_DIR # to @CASE@, masks run-to-run hex addresses to 0xADDR, trims trailing