# scripts/lib/coverage-report.awk # Produce line-coverage reports for shell sources from an executed-line set. # # Inputs: # -v COVERED= TSV of "\t" executed lines (sorted -u). # -v HTMLDIR= directory to write per-file .html reports into. # ARGV the source files to report on (absolute paths). # # Output (stdout): one CSV row per source file: # file,total_executable,covered,percent # # "Executable" lines are determined heuristically: non-blank, non-comment # lines that are not pure structural tokens (then/do/done/else/fi/esac/{/}). # Heredoc/multiline string bodies are approximated and may read as missed; # see docs/coverage.md for the documented limitations. function trim(s) { sub(/^[ \t]+/, "", s); sub(/[ \t]+$/, "", s); return s } function is_exec(line, t) { t = trim(line) if (t == "") return 0 if (t ~ /^#/) return 0 if (t ~ /^(then|do|done|else|fi|esac)([ \t].*)?$/) return 0 if (t ~ /^[{}]([ \t;].*)?$/) return 0 if (t ~ /^[()]([ \t].*)?$/) return 0 if (t ~ /^;;/) return 0 return 1 } function esc(s) { gsub(/&/, "\\&", s) gsub(//, "\\>", s) return s } function basename(p) { sub(/.*\//, "", p); sub(/.*\\/, "", p); return p } BEGIN { FS = "\t" # Load the executed-line set. while ((getline ln < COVERED) > 0) { nf = split(ln, a, "\t") if (nf >= 2) covered[a[1] SUBSEP a[2]] = 1 } close(COVERED) } # New source file: open its HTML report and reset counters. FNR == 1 { if (cur != "") finish_file() cur = FILENAME curbase = basename(FILENAME) total = 0; hit = 0 html = HTMLDIR "/" curbase ".html" print "" > html print "coverage: " esc(curbase) "" > html print "" > html print "

coverage: " esc(curbase) "

" > html } { cls = "na" if (is_exec($0)) { total++ if ((curbase SUBSEP FNR) in covered) { hit++; cls = "hit" } else { cls = "miss" } } print "" > html } function finish_file( pct) { print "
" FNR "" esc($0) "
" > html close(html) pct = (total > 0 ? (hit * 100.0 / total) : 0) printf "%s,%d,%d,%.1f\n", curbase, total, hit, pct } END { if (cur != "") finish_file() }