aboutsummaryrefslogtreecommitdiff
path: root/scripts/lib/coverage-report.awk
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/lib/coverage-report.awk')
-rw-r--r--scripts/lib/coverage-report.awk84
1 files changed, 84 insertions, 0 deletions
diff --git a/scripts/lib/coverage-report.awk b/scripts/lib/coverage-report.awk
new file mode 100644
index 0000000..982eb45
--- /dev/null
+++ b/scripts/lib/coverage-report.awk
@@ -0,0 +1,84 @@
+# scripts/lib/coverage-report.awk
+# Produce line-coverage reports for shell sources from an executed-line set.
+#
+# Inputs:
+# -v COVERED=<file> TSV of "<base>\t<line>" executed lines (sorted -u).
+# -v HTMLDIR=<dir> directory to write per-file <base>.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(/&/, "\\&amp;", s)
+ gsub(/</, "\\&lt;", s)
+ gsub(/>/, "\\&gt;", 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 "<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"utf-8\">" > html
+ print "<title>coverage: " esc(curbase) "</title>" > html
+ print "<style>body{font:13px/1.5 monospace;margin:0;padding:1rem;background:#fff;color:#111}" > html
+ print "h1{font:600 16px sans-serif}" > html
+ print "table{border-collapse:collapse;width:100%}" > html
+ print "td{padding:0 .5rem;white-space:pre;vertical-align:top}" > html
+ print "td.n{color:#999;text-align:right;user-select:none;border-right:1px solid #ddd}" > html
+ print ".hit{background:#e6ffed}.miss{background:#ffeef0}.na{color:#999}" > html
+ print "</style></head><body>" > html
+ print "<h1>coverage: " esc(curbase) "</h1><table>" > html
+}
+
+{
+ cls = "na"
+ if (is_exec($0)) {
+ total++
+ if ((curbase SUBSEP FNR) in covered) { hit++; cls = "hit" } else { cls = "miss" }
+ }
+ print "<tr class=\"" cls "\"><td class=\"n\">" FNR "</td><td>" esc($0) "</td></tr>" > html
+}
+
+function finish_file( pct) {
+ print "</table></body></html>" > 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() }