aboutsummaryrefslogtreecommitdiff
path: root/scripts/lib/coverage-report.awk
blob: 982eb45db039cb2a45b8ce93c6ea1a087cb844b3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
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() }