aboutsummaryrefslogtreecommitdiff
path: root/scripts/coverage-tests.sh
blob: cf0382598f042e56c464ceabeb4bc2f93cdf815e (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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
#!/bin/sh
# scripts/coverage-tests.sh
# Generate line-coverage reports for trbldoc's shell sources from the test
# suite. Answers "what is the test suite not exercising".
#
# Mechanism: the suite runs the tool via `bash "$TRBLDOC"`, so exporting
# BASH_ENV=scripts/lib/trace-init.sh makes each target process emit an xtrace
# of every executed line. We union the executed lines across the whole run and
# compare against the executable lines in each target source. No modification
# of trbldoc.sh or the tests is required.
#
# POSIX sh; works under mingw bash, WSL bash and Alpine/busybox ash. The traced
# run itself uses bash (the suite already invokes bash), which is one of the
# supported target shells.
#
# Usage:
#   scripts/coverage-tests.sh [-s all|unit|integration] [-o OUTDIR]
#                             [-t "file.sh ..."] [-m MIN_PERCENT]
#
#   -s SUITE       suite to run (default: integration)
#   -o OUTDIR      output directory (default: reports/coverage-<timestamp>)
#   -t LIST        space-separated target source files (default: trbldoc.sh)
#   -m MIN_PERCENT fail (exit 2) if overall coverage is below this (default: 0)

set -u

SELF="$0"
. "$(dirname -- "$0")/lib/common.sh"
tb_resolve_paths "$SELF"

SUITE=integration
OUTDIR=""
TARGETS="trbldoc.sh"
MIN_PERCENT=0

while getopts ":s:o:t:m:h" opt; do
  case "$opt" in
    s) SUITE="$OPTARG" ;;
    o) OUTDIR="$OPTARG" ;;
    t) TARGETS="$OPTARG" ;;
    m) MIN_PERCENT="$OPTARG" ;;
    h) sed -n '2,30p' "$SELF"; exit 0 ;;
    :) tb_die "option -$OPTARG requires an argument" ;;
    \?) tb_die "unknown option -$OPTARG" ;;
  esac
done

tb_require awk
tb_require sort

[ -n "$OUTDIR" ] || OUTDIR="$REPO_ROOT/reports/coverage-$(tb_timestamp)"
case "$OUTDIR" in
  /*|[A-Za-z]:[\\/]*)
    ;;
  *)
    OUTDIR="$REPO_ROOT/$OUTDIR"
    ;;
esac
mkdir -p "$OUTDIR/html" || tb_die "cannot create output dir: $OUTDIR"
JUNIT_DIR="$OUTDIR/junit"; mkdir -p "$JUNIT_DIR"
TRACE_DIR="$OUTDIR/trace"; rm -rf "$TRACE_DIR"; mkdir -p "$TRACE_DIR"

SUITE_DIRS="$(tb_suite_dirs "$SUITE")"
SUITE_DIRS_REL="$(tb_suite_dirs_rel "$SUITE")"

# Basename filter for the trace hook + absolute source paths for reporting.
TRACE_TARGETS=""
SRC_FILES=""
for tf in $TARGETS; do
  TRACE_TARGETS="$TRACE_TARGETS ${tf##*/}"
  case "$tf" in
    /*) p="$tf" ;;
    *)  p="$REPO_ROOT/$tf" ;;
  esac
  [ -f "$p" ] || tb_die "target source not found: $p"
  SRC_FILES="$SRC_FILES $p"
done

# ── run suite under xtrace ───────────────────────────────────────────────────
tb_info "running bats ($SUITE) under coverage instrumentation..."
if tb_bats_usable "$SUITE"; then
  BASH_ENV="$LIB_DIR/trace-init.sh"
  TRBLDOC_TRACE_DIR="$TRACE_DIR"
  TRBLDOC_TRACE_MODE="cov"
  TRBLDOC_TRACE_TARGETS="$TRACE_TARGETS"
  export BASH_ENV TRBLDOC_TRACE_DIR TRBLDOC_TRACE_MODE TRBLDOC_TRACE_TARGETS
  # shellcheck disable=SC2086
  bats --report-formatter junit --output "$JUNIT_DIR" $SUITE_DIRS \
    > "$OUTDIR/bats-output.txt" 2>&1 || tb_info "bats reported failures (see bats-output.txt)"
  unset BASH_ENV TRBLDOC_TRACE_DIR TRBLDOC_TRACE_MODE TRBLDOC_TRACE_TARGETS
elif command -v wsl >/dev/null 2>&1; then
  tb_info "local bats appears unusable; retrying via WSL bats..."
  WSL_REPO="$(tb_to_wsl_path "$REPO_ROOT")"
  WSL_LIB="$(tb_to_wsl_path "$LIB_DIR")"
  WSL_TRACE="$(tb_to_wsl_path "$TRACE_DIR")"
  WSL_JUNIT="$(tb_to_wsl_path "$JUNIT_DIR")"
  wsl -- bash -lc \
    "cd '$WSL_REPO' && export BASH_ENV='$WSL_LIB/trace-init.sh' TRBLDOC_TRACE_DIR='$WSL_TRACE' TRBLDOC_TRACE_MODE='cov' TRBLDOC_TRACE_TARGETS='$TRACE_TARGETS'; bats --report-formatter junit --output '$WSL_JUNIT' $SUITE_DIRS_REL" \
    > "$OUTDIR/bats-output.txt" 2>&1 || tb_info "bats (WSL) reported failures (see bats-output.txt)"
else
  tb_die "local bats command appears unusable and no WSL fallback is available"
fi

if ! ls "$TRACE_DIR"/trace.*.log >/dev/null 2>&1; then
  if grep -qi "bash\\\\r" "$OUTDIR/bats-output.txt" 2>/dev/null; then
    tb_die "bats appears broken (CRLF shebang: 'bash\\r'). Reinstall bats or run dos2unix on the bats scripts, then retry."
  fi
  tb_die "no trace data captured; ensure bats actually runs tests (see $OUTDIR/bats-output.txt)"
fi

# ── union executed lines ─────────────────────────────────────────────────────
awk -f "$LIB_DIR/trace-extract.awk" -v MODE=cov "$TRACE_DIR"/trace.*.log \
  | sort -u > "$OUTDIR/covered.tsv"

# ── per-file coverage report + summary ───────────────────────────────────────
# shellcheck disable=SC2086
awk -f "$LIB_DIR/coverage-report.awk" \
  -v COVERED="$OUTDIR/covered.tsv" \
  -v HTMLDIR="$OUTDIR/html" \
  $SRC_FILES > "$OUTDIR/summary.rows"

{
  printf 'file,executable,covered,percent\n'
  cat "$OUTDIR/summary.rows"
} > "$OUTDIR/summary.csv"

awk -f "$LIB_DIR/render-coverage-index.awk" \
  -v TITLE="trbldoc coverage ($SUITE)" \
  "$OUTDIR/summary.rows" > "$OUTDIR/index.html"

# ── overall percent + threshold gate ─────────────────────────────────────────
OVERALL="$(awk -F',' '{t+=$2; c+=$3} END{ if(t>0) printf "%.1f", c*100.0/t; else print "0.0" }' "$OUTDIR/summary.rows")"

printf '\n==== coverage summary (%s) ====\n' "$SUITE"
awk -F',' '{ printf "%6.1f%%  %-20s (%d/%d)\n", $4, $1, $3, $2 }' "$OUTDIR/summary.rows"
printf '  overall: %s%%\n' "$OVERALL"
printf '\nReports written to: %s\n' "$OUTDIR"
printf '  coverage index: %s\n' "$OUTDIR/index.html"

# Threshold enforcement (integer or decimal compare via awk).
if awk -v o="$OVERALL" -v m="$MIN_PERCENT" 'BEGIN{ exit !(m>0 && o+0 < m+0) }'; then
  tb_die "coverage ${OVERALL}% is below the required minimum ${MIN_PERCENT}%"
fi