feat(testing): line coverage via --coverage + bin/x test --coverage (#45)
Instrument each emitted statement with a per-source-line hit counter, gated behind a new --coverage flag (default off) so ordinary builds — and the compiler's own self-compile — stay byte-identical and the C-free bootstrap fixpoint is untouched. A static line table plus a parallel hit-counter array are dumped at exit through an atexit hook to $LUDIC_COVERAGE (default ludic.cov) as a `FILE <name>` header and `<line> <hits>` rows. bin/x test --coverage compiles the test specs with instrumentation, runs them into per-file dumps, and aggregates a clean per-file line-coverage report that names the unreached lines. Adds examples/library/coverage.ludic (a spec whose tests deliberately miss one branch) and docs. Closes the last open acceptance item of the testing framework (#12). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
c7c8e2779c
commit
498593311f
11 changed files with 15317 additions and 14622 deletions
|
|
@ -140,6 +140,8 @@ function emit_program() -> void {
|
|||
g_uses_loopback = false
|
||||
g_uses_expect = false
|
||||
g_uses_panic = false
|
||||
g_cov_lines = new []int
|
||||
g_cov_active = true
|
||||
loc_name = new []pointer; loc_reg = new []pointer; loc_ty = new []pointer; loc_mut = new []int
|
||||
brk_lbl = new []pointer; cnt_lbl = new []pointer
|
||||
self_stk = new []pointer
|
||||
|
|
@ -148,7 +150,14 @@ function emit_program() -> void {
|
|||
emit_extern_decls()
|
||||
if has_ecs() { emit_ecs_storage() }
|
||||
var i = 0
|
||||
while i < len(prog) { if prog[i].kind == N_FN { emit_fn(prog[i]) }; i = i + 1 }
|
||||
while i < len(prog) {
|
||||
if prog[i].kind == N_FN {
|
||||
g_cov_active = (i < g_prog_user_end) # don't instrument spliced runtime functions
|
||||
emit_fn(prog[i])
|
||||
g_cov_active = true
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
if len(g_events) > 0 { emit_event_fns() } # EV0: @ev_<E> event-dispatch functions
|
||||
if has_ecs() and (len(g_events) > 0 or g_uses_query or g_uses_reflect) { emit_world_table() } # EV2/EV8: the mod reflection ABI (also powers Query.* / Reflect.*)
|
||||
if has_ecs() { emit_ecs_allocator(); emit_snapshot() }
|
||||
|
|
@ -189,6 +198,75 @@ function emit_program() -> void {
|
|||
emith("declare i32 @fprintf(ptr, ptr, ...)\n")
|
||||
emith("@.fmt_panic = private unnamed_addr constant [6 x i8] c\"%s%s\\0A\\00\"\n")
|
||||
}
|
||||
emit_cov_runtime() # issue #45: --coverage tables + exit dump
|
||||
}
|
||||
|
||||
# issue #45: the line-coverage runtime. Emits the static line table, a parallel
|
||||
# hit-counter array, and @cov_dump — a function registered with atexit (via an
|
||||
# LLVM global constructor) that writes `<line> <hits>` rows to the file named by
|
||||
# $LUDIC_COVERAGE (default "ludic.cov"). All gated behind --coverage, so a normal
|
||||
# build emits none of this and stays byte-identical.
|
||||
function emit_cov_runtime() -> void {
|
||||
if not g_coverage { return }
|
||||
let n = len(g_cov_lines)
|
||||
if n == 0 { return }
|
||||
let sn = itoa(n)
|
||||
|
||||
# the line table + zeroed hit counters (module globals)
|
||||
emith("@L_cov_lines = internal global ["); emith(sn); emith(" x i32] [")
|
||||
var i = 0
|
||||
while i < n {
|
||||
if i > 0 { emith(", ") }
|
||||
emith("i32 "); emith(itoa(g_cov_lines[i]))
|
||||
i = i + 1
|
||||
}
|
||||
emith("]\n")
|
||||
emith("@L_cov_hits = internal global ["); emith(sn); emith(" x i32] zeroinitializer\n")
|
||||
emith(`@L_cov_n = internal global i32 {sn}\n`)
|
||||
# the source name, the env-var name, the default path, fopen mode, and row format
|
||||
let fnc = emit_str_const(g_src_name)
|
||||
let envc = emit_str_const("LUDIC_COVERAGE")
|
||||
let defc = emit_str_const("ludic.cov")
|
||||
let modec = emit_str_const("w")
|
||||
emith("@.cov_filefmt = private unnamed_addr constant [9 x i8] c\"FILE %s\\0A\\00\"\n")
|
||||
emith("@.cov_rowfmt = private unnamed_addr constant [7 x i8] c\"%d %d\\0A\\00\"\n")
|
||||
if not g_uses_panic { emith("declare i32 @fprintf(ptr, ptr, ...)\n") }
|
||||
emith("declare i32 @atexit(ptr)\n")
|
||||
|
||||
# @cov_dump: open $LUDIC_COVERAGE (or "ludic.cov"), write a FILE header then one
|
||||
# `<line> <hits>` row per instrumented line, and close.
|
||||
emit("define void @cov_dump() {\nentry:\n")
|
||||
emit(` %env = call ptr @getenv(ptr {envc})\n`)
|
||||
emit(" %noenv = icmp eq ptr %env, null\n")
|
||||
emit(` %path = select i1 %noenv, ptr {defc}, ptr %env\n`)
|
||||
emit(` %f = call ptr @fopen(ptr %path, ptr {modec})\n`)
|
||||
emit(" %bad = icmp eq ptr %f, null\n")
|
||||
emit(" br i1 %bad, label %done, label %write\n")
|
||||
emit("write:\n")
|
||||
emit(` call i32 (ptr, ptr, ...) @fprintf(ptr %f, ptr @.cov_filefmt, ptr {fnc})\n`)
|
||||
emit(" br label %loop\n")
|
||||
emit("loop:\n")
|
||||
emit(" %i = phi i32 [ 0, %write ], [ %i1, %body ]\n")
|
||||
emit(` %go = icmp slt i32 %i, {sn}\n`)
|
||||
emit(" br i1 %go, label %body, label %close\n")
|
||||
emit("body:\n")
|
||||
emit(" %lp = getelementptr inbounds ["); emit(sn); emit(" x i32], ptr @L_cov_lines, i32 0, i32 %i\n")
|
||||
emit(" %lv = load i32, ptr %lp\n")
|
||||
emit(" %hp = getelementptr inbounds ["); emit(sn); emit(" x i32], ptr @L_cov_hits, i32 0, i32 %i\n")
|
||||
emit(" %hv = load i32, ptr %hp\n")
|
||||
emit(" call i32 (ptr, ptr, ...) @fprintf(ptr %f, ptr @.cov_rowfmt, i32 %lv, i32 %hv)\n")
|
||||
emit(" %i1 = add i32 %i, 1\n")
|
||||
emit(" br label %loop\n")
|
||||
emit("close:\n")
|
||||
emit(" %rc = call i32 @fclose(ptr %f)\n")
|
||||
emit(" br label %done\n")
|
||||
emit("done:\n ret void\n}\n\n")
|
||||
|
||||
# register @cov_dump with atexit before main runs (an LLVM global constructor).
|
||||
emit("define void @cov_init() {\nentry:\n")
|
||||
emit(" %r = call i32 @atexit(ptr @cov_dump)\n")
|
||||
emit(" ret void\n}\n\n")
|
||||
emith("@llvm.global_ctors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr @cov_init, ptr null }]\n")
|
||||
}
|
||||
|
||||
# Flush the emitted IR. With a null path it goes to stdout (the pipe the shell
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue