kit

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

commit 9fb1e48ba4c1b8dcd5ab830faa2ccdc9ab6ab285
parent 784af4539fe0903af8fae52182337b712c366f7a
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Sat,  9 May 2026 16:15:58 -0700

test/cg: scaffold Group P (set_loc/debug) + W path

Group P drives CGTarget.set_loc and is the first group whose oracle is
metadata rather than test_main's exit code. The new W path reads the
emitted obj back through cfree_dwarf_open and asserts the registered
line/subprogram facts; cases without registered checks skip silently.

p01_line_one_inst returns 42 (so D/R/E/J still pass) and registers
"line p01.c 10" + "subprogram test_main" for W. W fails today because
debug_emit and the cfree_dwarf_* consumers are stubs — once those land
the same case starts passing W with no test changes.

Diffstat:
Mtest/cg/CORPUS.md | 25++++++++++++++++++++++++-
Mtest/cg/harness/cases.c | 21+++++++++++++++++++++
Atest/cg/harness/cases_p.c | 38++++++++++++++++++++++++++++++++++++++
Atest/cg/harness/cg_check_dwarf.c | 279+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtest/cg/harness/cg_runner.c | 16++++++++++++++++
Mtest/cg/harness/cg_test.h | 24++++++++++++++++++++++++
Mtest/cg/run.sh | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++------
7 files changed, 455 insertions(+), 7 deletions(-)

diff --git a/test/cg/CORPUS.md b/test/cg/CORPUS.md @@ -14,6 +14,11 @@ Test paths per case (run.sh): `link-exe-runner` → run. - **J** jit-via-file (aarch64 host only) — `cg-runner --emit` → `jit-runner`. +- **W** DWARF check (Group P only) — `cg-runner --emit` + + `cg-runner --dwarf-checks NAME | cg_check_dwarf OBJ`. Opens the obj + via `cfree_dwarf_open` and asserts the line program / subprograms + registered for the case. Cases without registered checks skip + silently. `O` (opt-wrapped) lands once `opt_cgtarget` is implemented. @@ -395,12 +400,30 @@ forward-declared helpers defined later in the TU. | `q10_global_and_static_mix` | · | one SB_GLOBAL + two SB_LOCAL helpers; sum = 12+15+15 | 42 | | `q11_addr_of_helper_through_global` | · | function ptr stored in `.data` (R_ABS64); indirect call | 42 | +## Group P — set_loc / debug + +Drives `CGTarget.set_loc` (which forwards to `MCEmitter.set_loc` and, once +wired, to `Debug` for the line program). The case body still returns 42 +so D/R/E/J keep working; the **W** path is the metadata oracle and reads +the emitted obj back through `cfree_dwarf_open` / +`cfree_dwarf_addr_to_line` / `cfree_dwarf_subprogram_at`. + +Today every W check fails by design: `debug_new`/`debug_emit` and the +`cfree_dwarf_*` consumers are stubs (src/api/stubs.c), and +`MCEmitter::set_loc` does not yet propagate to `Debug`. Once those land +the same case bodies start producing real DWARF and the W path flips +green. This matches the harness preamble's "fail at runtime until deps +land" pattern. + +| Case | Status | Body | Expected (D/E/J / W) | +|---|---|---|---| +| `p01_line_one_inst` | · | `set_loc(p01.c:10)` before single `load_imm 42; ret`; W asserts addr↔line round-trip and `subprogram test_main` | 42 / line p01.c:10 + subprogram test_main | + ## Deferred groups | Group | Theme | |---|---| | M | inline asm | -| P | set_loc / debug | | R | opt-wrapped equivalence | See `doc/cg_testing.md` for the strategy and group definitions. diff --git a/test/cg/harness/cases.c b/test/cg/harness/cases.c @@ -208,6 +208,8 @@ void build_o10_global_addend(CgTestCtx*); void build_o11_text_section_named(CgTestCtx*); void build_o12_global_across_call(CgTestCtx*); +void build_p01_line_one_inst(CgTestCtx*); + void build_q01_three_helpers(CgTestCtx*); void build_q02_static_internal_linkage(CgTestCtx*); void build_q03_intra_tu_call_chain(CgTestCtx*); @@ -464,6 +466,13 @@ const CgCase cg_cases[] = { {"o12_global_across_call", build_o12_global_across_call, 42, CG_CASE_DEFAULT}, + /* Group P — set_loc / debug. Today these fail at runtime because the + * Debug subsystem and the cfree_dwarf_* consumers are stubbed; once + * those land, the same case bodies start producing real DWARF and + * path W flips green. The exit-code oracle (D/E/J) is 42; the W path + * checks the line program. See cases_p.c for the contract. */ + {"p01_line_one_inst", build_p01_line_one_inst, 42, CG_CASE_DEFAULT}, + /* Group Q — multi-function */ {"q01_three_helpers", build_q01_three_helpers, 42, CG_CASE_DEFAULT}, {"q02_static_internal_linkage", build_q02_static_internal_linkage, 42, @@ -488,3 +497,15 @@ const CgCase cg_cases[] = { }; const unsigned cg_cases_count = sizeof(cg_cases) / sizeof(cg_cases[0]); + +/* ---- DWARF check registry (path W) ---- + * Only Group P has entries today. See cg_test.h for the directive + * vocabulary. */ +const CgDwarfCheck cg_dwarf_checks[] = { + {"p01_line_one_inst", + "subprogram test_main\n" + "line p01.c 10\n"}, +}; + +const unsigned cg_dwarf_checks_count = + sizeof(cg_dwarf_checks) / sizeof(cg_dwarf_checks[0]); diff --git a/test/cg/harness/cases_p.c b/test/cg/harness/cases_p.c @@ -0,0 +1,38 @@ +/* Group P — set_loc / debug. + * See CORPUS.md for the case list and expected values. + * + * Group P's oracle is metadata, not exit code: the case still returns 42 + * (so D/E/J keep passing once the line program is wired) but the *real* + * assertion runs through path W, which opens the emitted obj with + * cfree_dwarf_open and checks the line program against the + * (file, line) pairs the case set via cg_set_loc / target->set_loc. + * + * Today these cases fail by design: debug_new and debug_emit are stubs + * (src/api/stubs.c), MCEmitter::set_loc only stores the loc on the impl + * and does not propagate to Debug, and the cfree_dwarf_* consumers are + * stubbed. The W path will start passing once those land. The harness + * preamble in cg_test.h documents this "fail at runtime until deps land" + * pattern. */ + +#include "cg_test.h" +#include "core/core.h" + +/* p01_line_one_inst — one instruction at a known SrcLoc. + * + * Registers a synthetic source file "p01.c" with the SourceManager, + * stamps line 10 onto a single load_imm via target->set_loc, and returns + * 42. Path W asserts that the emitted obj's .debug_line maps some PC + * inside test_main back to (p01.c, 10). */ +void build_p01_line_one_inst(CgTestCtx* ctx) { + const Type* I32 = T_i32(ctx); + + u32 file_id = source_add_memory(ctx->c->sources, "p01.c"); + SrcLoc loc = {file_id, 10, 0}; + + CgTestFn* tf = cgtest_begin_main(ctx, I32); + ctx->target->set_loc(ctx->target, loc); + Reg r = ctx->target->alloc_reg(ctx->target, RC_INT, I32); + ctx->target->load_imm(ctx->target, REG_op(r, I32), 42); + cgtest_ret_reg(tf, r, I32); + cgtest_end(tf); +} diff --git a/test/cg/harness/cg_check_dwarf.c b/test/cg/harness/cg_check_dwarf.c @@ -0,0 +1,279 @@ +/* cg_check_dwarf — path W oracle for the test/cg harness. + * + * cg_check_dwarf <obj_path> # reads directives from stdin, one per line + * + * Directives (see cg_test.h for the contract): + * + * line FILE LINE + * Some PC inside the object's text must map to (FILE, LINE) via + * cfree_dwarf_addr_to_line, and cfree_dwarf_line_to_addr(FILE, LINE) + * must return a PC that maps back to (FILE, LINE). + * + * subprogram NAME + * cfree_dwarf_subprogram_at must report a non-empty pc range whose + * name equals NAME. + * + * Exit code: 0 if every directive passes; 1 if any directive fails or the + * object cannot be opened. Blank lines and lines beginning with '#' are + * ignored. */ + +#include <cfree.h> +#include <fcntl.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <unistd.h> + +/* ---- env ---- */ + +static void* h_alloc(CfreeHeap* h, size_t n, size_t a) { + (void)h; + (void)a; + return n ? malloc(n) : NULL; +} +static void* h_realloc(CfreeHeap* h, void* p, size_t o, size_t n, size_t a) { + (void)h; + (void)o; + (void)a; + return realloc(p, n); +} +static void h_free(CfreeHeap* h, void* p, size_t n) { + (void)h; + (void)n; + free(p); +} +static CfreeHeap g_heap = {h_alloc, h_realloc, h_free, NULL}; + +static void diag_emit(CfreeDiagSink* s, CfreeDiagKind k, CfreeSrcLoc loc, + const char* fmt, va_list ap) { + static const char* names[] = {"note", "warning", "error", "fatal"}; + (void)s; + (void)loc; + fprintf(stderr, "%s: ", names[k]); + vfprintf(stderr, fmt, ap); + fputc('\n', stderr); +} +static CfreeDiagSink g_diag = {diag_emit, NULL, 0, 0}; + +/* ---- file slurp ---- */ + +static int slurp(const char* path, uint8_t** out, size_t* n_out) { + int fd = open(path, O_RDONLY); + if (fd < 0) { + perror(path); + return 1; + } + struct stat st; + if (fstat(fd, &st) != 0) { + perror("fstat"); + close(fd); + return 1; + } + size_t n = (size_t)st.st_size; + uint8_t* buf = (uint8_t*)malloc(n); + if (!buf) { + close(fd); + return 1; + } + size_t off = 0; + while (off < n) { + ssize_t k = read(fd, buf + off, n - off); + if (k <= 0) { + perror("read"); + close(fd); + free(buf); + return 1; + } + off += (size_t)k; + } + close(fd); + *out = buf; + *n_out = n; + return 0; +} + +/* ---- directive checks ---- */ + +typedef struct Ctx { + CfreeCompiler* cc; + CfreeDebugInfo* di; + int fails; +} Ctx; + +static void fail(Ctx* c, const char* fmt, ...) { + va_list ap; + va_start(ap, fmt); + fputs("FAIL ", stdout); + vfprintf(stdout, fmt, ap); + fputc('\n', stdout); + va_end(ap); + c->fails++; +} + +static void pass(const char* fmt, ...) { + va_list ap; + va_start(ap, fmt); + fputs("PASS ", stdout); + vfprintf(stdout, fmt, ap); + fputc('\n', stdout); + va_end(ap); +} + +static void check_line(Ctx* c, const char* file, uint32_t line) { + uint64_t pc = 0; + if (cfree_dwarf_line_to_addr(c->di, file, line, &pc) != 0) { + fail(c, "line %s:%u — line_to_addr returned no PC", file, line); + return; + } + const char* got_file = NULL; + uint32_t got_line = 0, got_col = 0; + if (cfree_dwarf_addr_to_line(c->di, pc, &got_file, &got_line, &got_col) != + 0) { + fail(c, "line %s:%u — addr_to_line(0x%llx) returned no entry", file, line, + (unsigned long long)pc); + return; + } + if (!got_file || strcmp(got_file, file) != 0 || got_line != line) { + fail(c, "line %s:%u — round-tripped to %s:%u (pc=0x%llx)", file, line, + got_file ? got_file : "(null)", got_line, (unsigned long long)pc); + return; + } + pass("line %s:%u (pc=0x%llx)", file, line, (unsigned long long)pc); +} + +static void check_subprogram(Ctx* c, const char* name) { + /* No "find subprogram by name" entry exists in cfree_dwarf_*; we have + * subprogram_at(pc, ...). Walk a small probe range starting at 0 and + * accept the first hit whose name matches. This is a stopgap that + * keeps the directive vocabulary stable; once a name-keyed query + * lands we'll switch to it. */ + CfreeDwarfSubprogram sp; + for (uint64_t pc = 0; pc < 0x10000ull; pc += 4) { + if (cfree_dwarf_subprogram_at(c->di, pc, &sp) != 0) continue; + if (sp.name && strcmp(sp.name, name) == 0) { + if (sp.high_pc <= sp.low_pc) { + fail(c, "subprogram %s — empty pc range [0x%llx, 0x%llx)", name, + (unsigned long long)sp.low_pc, (unsigned long long)sp.high_pc); + return; + } + pass("subprogram %s [0x%llx, 0x%llx)", name, + (unsigned long long)sp.low_pc, (unsigned long long)sp.high_pc); + return; + } + } + fail(c, "subprogram %s — not found in first 64KB of text", name); +} + +static void run_directive(Ctx* c, char* line) { + while (*line == ' ' || *line == '\t') line++; + if (*line == '\0' || *line == '\n' || *line == '#') return; + + /* strip trailing newline */ + size_t n = strlen(line); + while (n > 0 && (line[n - 1] == '\n' || line[n - 1] == '\r')) line[--n] = 0; + + char* sp = strchr(line, ' '); + if (!sp) { + fail(c, "bad directive: %s", line); + return; + } + *sp = 0; + const char* op = line; + char* rest = sp + 1; + + if (strcmp(op, "line") == 0) { + char* sp2 = strchr(rest, ' '); + if (!sp2) { + fail(c, "line: expected FILE LINE"); + return; + } + *sp2 = 0; + const char* file = rest; + long ln = strtol(sp2 + 1, NULL, 10); + if (ln <= 0) { + fail(c, "line: bad line number"); + return; + } + check_line(c, file, (uint32_t)ln); + } else if (strcmp(op, "subprogram") == 0) { + check_subprogram(c, rest); + } else { + fail(c, "unknown directive: %s", op); + } +} + +/* ---- main ---- */ + +int main(int argc, char** argv) { + if (argc != 2) { + fprintf(stderr, "usage: cg_check_dwarf <obj_path>\n"); + return 2; + } + const char* obj_path = argv[1]; + + /* Slurp the obj. */ + uint8_t* bytes = NULL; + size_t nbytes = 0; + if (slurp(obj_path, &bytes, &nbytes) != 0) return 1; + + CfreeTarget target; + memset(&target, 0, sizeof target); + target.arch = CFREE_ARCH_ARM_64; + target.os = CFREE_OS_LINUX; + target.obj = CFREE_OBJ_ELF; + target.ptr_size = 8; + target.ptr_align = 8; + CfreeEnv env; + memset(&env, 0, sizeof env); + env.heap = &g_heap; + env.diag = &g_diag; + env.now = -1; + + CfreeCompiler* cc = cfree_compiler_new(target, &env); + if (!cc) { + fprintf(stderr, "cg_check_dwarf: compiler_new failed\n"); + free(bytes); + return 1; + } + + CfreeBytesInput in; + memset(&in, 0, sizeof in); + in.name = obj_path; + in.data = bytes; + in.len = nbytes; + CfreeObjFile* obj = cfree_obj_open(&env, &in); + if (!obj) { + fprintf(stderr, "cg_check_dwarf: cannot open %s as object\n", obj_path); + cfree_compiler_free(cc); + free(bytes); + return 1; + } + + CfreeDebugInfo* di = cfree_dwarf_open(cc, obj); + if (!di) { + fprintf(stderr, + "cg_check_dwarf: %s has no DWARF (cfree_dwarf_open returned " + "NULL)\n", + obj_path); + cfree_obj_close(obj); + cfree_compiler_free(cc); + free(bytes); + return 1; + } + + Ctx ctx = {cc, di, 0}; + + /* Stream directives from stdin. */ + char buf[1024]; + while (fgets(buf, sizeof buf, stdin)) { + run_directive(&ctx, buf); + } + + cfree_dwarf_close(di); + cfree_obj_close(obj); + cfree_compiler_free(cc); + free(bytes); + return ctx.fails ? 1 : 0; +} diff --git a/test/cg/harness/cg_runner.c b/test/cg/harness/cg_runner.c @@ -299,6 +299,19 @@ static int mode_expected(const char* name) { return 0; } +/* --dwarf-checks NAME — print the W-path directive blob registered for + * NAME, or nothing if the case has no DWARF checks. The shell harness + * pipes this into cg_check_dwarf <obj>. */ +static int mode_dwarf_checks(const char* name) { + for (unsigned i = 0; i < cg_dwarf_checks_count; ++i) { + if (strcmp(cg_dwarf_checks[i].case_name, name) == 0) { + fputs(cg_dwarf_checks[i].directives, stdout); + return 0; + } + } + return 0; /* not registered → empty stdout, harness skips W */ +} + static int mode_emit(const char* name, const char* out_path) { const CgCase* cc = find_case(name); if (!cc) { @@ -462,6 +475,7 @@ static int usage(void) { fprintf(stderr, "usage: cg-runner --list\n" " cg-runner --expected NAME\n" + " cg-runner --dwarf-checks NAME\n" " cg-runner --emit NAME OUT.o\n" " cg-runner --jit NAME\n"); return 2; @@ -477,6 +491,8 @@ int main(int argc, char** argv) { return mode_list(); else if (!strcmp(argv[1], "--expected") && argc == 3) return mode_expected(argv[2]); + else if (!strcmp(argv[1], "--dwarf-checks") && argc == 3) + return mode_dwarf_checks(argv[2]); else if (!strcmp(argv[1], "--emit") && argc == 4) return mode_emit(argv[2], argv[3]); else if (!strcmp(argv[1], "--jit") && argc == 3) diff --git a/test/cg/harness/cg_test.h b/test/cg/harness/cg_test.h @@ -60,6 +60,30 @@ typedef struct CgCase { extern const CgCase cg_cases[]; extern const unsigned cg_cases_count; +/* ---- DWARF checks (path W) ---- + * Optional per-case directives consumed by test/cg/harness/cg_check_dwarf + * after --emit. Each entry pairs a case name with a directive blob: one + * directive per line, blank lines ignored. Cases not listed here are + * skipped on path W. Supported directives: + * + * line FILE LINE + * Some PC inside the obj's text must map to (FILE, LINE) and the + * inverse line_to_addr must round-trip. + * + * subprogram NAME + * cfree_dwarf_subprogram_at must report a non-empty pc range for + * the named symbol. + * + * The cfree_dwarf_* consumers are stubbed today (src/api/stubs.c), so + * every directive currently fails — that's intentional. */ +typedef struct CgDwarfCheck { + const char* case_name; + const char* directives; +} CgDwarfCheck; + +extern const CgDwarfCheck cg_dwarf_checks[]; +extern const unsigned cg_dwarf_checks_count; + /* ---- pre-interned type accessors ---- * Resolved once per ctx via type_prim/type_void/type_ptr against * ctx->pool. Sugar so cases don't repeat the lookup. */ diff --git a/test/cg/run.sh b/test/cg/run.sh @@ -10,6 +10,11 @@ # E exec via qemu — cg-runner --emit + start.o → link-exe-runner → qemu/ # podman → exit code. Cross-host friendly. # J jit-via-file — cg-runner --emit + jit-runner. aarch64 host. +# W DWARF check — cg-runner --emit + cg-runner --dwarf-checks NAME | +# cg_check_dwarf OBJ. Group P only; cases that don't +# register checks are silently skipped. Today every +# check fails by design — debug_emit and the +# cfree_dwarf_* consumers are stubs. # # Reuses the existing test/link harness binaries (link-exe-runner, # jit-runner, cfree-roundtrip) verbatim. @@ -20,7 +25,7 @@ # Filtering: # ./run.sh [name_filter] [paths] # name_filter substring match against case name (e.g. "a01", "add") -# paths subset of "DREJ" (default "DREJ") +# paths subset of "DREJW" (default "DREJW") # Equivalent env vars: CFREE_TEST_FILTER, CFREE_TEST_PATHS. set -u @@ -35,6 +40,7 @@ CG_RUNNER="$BUILD_DIR/cg-runner" ROUNDTRIP_BIN="$BUILD_DIR/cfree-roundtrip" LINK_EXE_RUNNER="$BUILD_DIR/link-exe-runner" JIT_RUNNER="$BUILD_DIR/jit-runner" +DWARF_CHECK="$BUILD_DIR/cg-check-dwarf" NORMALIZE="$ROOT/test/elf/normalize.py" CLANG_TARGET="--target=aarch64-linux-gnu" @@ -46,12 +52,13 @@ ALLOW_SKIP="${CFREE_TEST_ALLOW_SKIP:-0}" # $1 / CFREE_TEST_FILTER — substring match against case name # $2 / CFREE_TEST_PATHS — subset of "DREJ" (default "DREJ") FILTER="${1:-${CFREE_TEST_FILTER:-}}" -PATHS="${2:-${CFREE_TEST_PATHS:-DREJ}}" +PATHS="${2:-${CFREE_TEST_PATHS:-DREJW}}" case "$PATHS" in *D*) RUN_D=1;; *) RUN_D=0;; esac case "$PATHS" in *R*) RUN_R=1;; *) RUN_R=0;; esac case "$PATHS" in *E*) RUN_E=1;; *) RUN_E=0;; esac case "$PATHS" in *J*) RUN_J=1;; *) RUN_J=0;; esac -T_D=0; T_R=0; T_E=0; T_J=0 # accumulated wall-clock seconds per path +case "$PATHS" in *W*) RUN_W=1;; *) RUN_W=0;; esac +T_D=0; T_R=0; T_E=0; T_J=0; T_W=0 # accumulated wall-clock seconds per path now_ms() { python3 -c 'import time;print(int(time.time()*1000))'; } mkdir -p "$BUILD_DIR" "$BUILD_DIR/cg" @@ -147,6 +154,7 @@ if $CC $CFREE_CFLAGS \ "$TEST_DIR/harness/cases_l.c" \ "$TEST_DIR/harness/cases_n.c" \ "$TEST_DIR/harness/cases_o.c" \ + "$TEST_DIR/harness/cases_p.c" \ "$TEST_DIR/harness/cases_q.c" \ "$LIB_AR" -o "$CG_RUNNER" 2>"$BUILD_DIR/cg-runner.err"; then printf ' %s cg-runner\n' "$(color_grn built)" @@ -201,6 +209,18 @@ if [ $is_aarch64 -eq 1 ]; then fi fi +# cg-check-dwarf — for path W. Always rebuild (small file, picks up +# changes alongside the rest of the harness). +have_dwarf_check=0 +if $CC -I"$ROOT/include" "$TEST_DIR/harness/cg_check_dwarf.c" \ + "$LIB_AR" -o "$DWARF_CHECK" 2>"$BUILD_DIR/cg-check-dwarf.err"; then + have_dwarf_check=1 + printf ' %s cg-check-dwarf\n' "$(color_grn built)" +else + printf ' %s cg-check-dwarf (see %s)\n' \ + "$(color_yel warn)" "$BUILD_DIR/cg-check-dwarf.err" >&2 +fi + printf 'Running cases...\n' # ---- per-case loop --------------------------------------------------------- @@ -235,9 +255,10 @@ for name in $CASES; do fi fi - # ---- emit (needed by R/E/J) ------------------------------------------- + # ---- emit (needed by R/E/J/W) ----------------------------------------- obj="$work/$name.o" - if [ $RUN_R -eq 1 ] || [ $RUN_E -eq 1 ] || [ $RUN_J -eq 1 ]; then + if [ $RUN_R -eq 1 ] || [ $RUN_E -eq 1 ] || [ $RUN_J -eq 1 ] \ + || [ $RUN_W -eq 1 ]; then if ! "$CG_RUNNER" --emit "$name" "$obj" 2>"$work/emit.err"; then note_fail "$name/emit (cg-runner --emit failed; see $work/emit.err)" continue @@ -311,12 +332,38 @@ for name in $CASES; do note_skip "$name/J" "no jit-runner (not aarch64 host)" fi fi + + # ---- Path W: DWARF check ---------------------------------------------- + # Cases that don't register directives produce empty stdout from + # --dwarf-checks; we silently skip those (no SKIP entry, since W is + # opt-in per case rather than per host). + if [ $RUN_W -eq 1 ]; then + "$CG_RUNNER" --dwarf-checks "$name" >"$work/w.directives" \ + 2>"$work/w.dc.err" + if [ -s "$work/w.directives" ]; then + if [ $have_dwarf_check -eq 1 ]; then + t0=$(now_ms) + "$DWARF_CHECK" "$obj" <"$work/w.directives" \ + >"$work/w.out" 2>"$work/w.err" + w_rc=$? + dt=$(( $(now_ms) - t0 )); T_W=$(( T_W + dt )) + if [ "$w_rc" -eq 0 ]; then + note_pass "$name/W (${dt}ms)" + else + note_fail "$name/W (see $work/w.out, $work/w.err; ${dt}ms)" + fi + else + note_skip "$name/W" "no cg-check-dwarf" + fi + fi + fi done # ---- summary --------------------------------------------------------------- printf '\nResults: %s pass, %s fail, %s skip\n' "$PASS" "$FAIL" "$SKIP" -printf 'Time: D=%dms R=%dms E=%dms J=%dms\n' "$T_D" "$T_R" "$T_E" "$T_J" +printf 'Time: D=%dms R=%dms E=%dms J=%dms W=%dms\n' \ + "$T_D" "$T_R" "$T_E" "$T_J" "$T_W" if [ ${#FAIL_NAMES[@]} -gt 0 ]; then printf 'Failed:\n'