Skip to content

Commit 576622b

Browse files
author
unit-test-impl-leader
committed
test(unit): add run_coverage.sh one-shot runner
Wraps 'make coverage' with friendlier output + extra modes: --quick : reuse existing .gcda, skip rebuild (~2s vs ~5s) --file <c> : per-line gcov detail for one source file --serve : start http.server on :8080 to view HTML report --clean : remove all coverage artifacts
1 parent 0f0472e commit 576622b

1 file changed

Lines changed: 208 additions & 0 deletions

File tree

tests/unit/run_coverage.sh

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
#!/usr/bin/env bash
2+
#
3+
# run_coverage.sh — F-Stack lib/ unit-test coverage one-shot runner.
4+
#
5+
# Usage:
6+
# ./run_coverage.sh # full coverage: build + test + report + summary
7+
# ./run_coverage.sh --quick # skip rebuild if .gcda already exist
8+
# ./run_coverage.sh --file <c> # show per-line gcov detail for one file
9+
# # (e.g. --file ff_log.c)
10+
# ./run_coverage.sh --serve # also start an HTTP server on :8080 to view
11+
# # the HTML report (Ctrl-C to stop)
12+
# ./run_coverage.sh --clean # remove all coverage artifacts and exit
13+
# ./run_coverage.sh -h | --help # show this help
14+
#
15+
# Exit codes:
16+
# 0 success, all G8 thresholds met
17+
# 1 coverage build/test failure or G8 threshold violation
18+
# 2 bad CLI arguments
19+
#
20+
21+
set -u
22+
23+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
24+
cd "$SCRIPT_DIR"
25+
26+
# --- ANSI helpers ---------------------------------------------------------
27+
if [ -t 1 ] && [ "${TERM:-}" != "dumb" ]; then
28+
C_BOLD=$'\033[1m'
29+
C_GREEN=$'\033[32m'
30+
C_YELLOW=$'\033[33m'
31+
C_RED=$'\033[31m'
32+
C_CYAN=$'\033[36m'
33+
C_RESET=$'\033[0m'
34+
else
35+
C_BOLD=""; C_GREEN=""; C_YELLOW=""; C_RED=""; C_CYAN=""; C_RESET=""
36+
fi
37+
38+
banner() { printf "\n${C_BOLD}${C_CYAN}===== %s =====${C_RESET}\n" "$1"; }
39+
ok() { printf "${C_GREEN}[ OK ]${C_RESET} %s\n" "$1"; }
40+
warn() { printf "${C_YELLOW}[WARN]${C_RESET} %s\n" "$1"; }
41+
err() { printf "${C_RED}[FAIL]${C_RESET} %s\n" "$1" >&2; }
42+
43+
# --- preflight ------------------------------------------------------------
44+
check_tools() {
45+
local missing=0
46+
for t in gcc make pkg-config lcov genhtml awk; do
47+
if ! command -v "$t" >/dev/null 2>&1; then
48+
err "required tool missing: $t"
49+
missing=1
50+
fi
51+
done
52+
if ! pkg-config --exists cmocka; then
53+
err "cmocka pkg-config not found (try: dnf install -y libcmocka-devel)"
54+
missing=1
55+
fi
56+
if ! pkg-config --exists libdpdk; then
57+
warn "libdpdk pkg-config not found — test_ff_dpdk_{if,kni} may fail"
58+
fi
59+
[ "$missing" -eq 0 ]
60+
}
61+
62+
# --- per-file gcov detail -------------------------------------------------
63+
show_file_detail() {
64+
local f="$1"
65+
if [ -z "$f" ]; then
66+
err "--file requires a filename (e.g. --file ff_log.c)"
67+
exit 2
68+
fi
69+
70+
local lib_obj_dir="$SCRIPT_DIR/lib_objs"
71+
if [ ! -f "$lib_obj_dir/${f%.c}.gcno" ]; then
72+
warn "$lib_obj_dir/${f%.c}.gcno not found; running 'make coverage' first..."
73+
run_coverage_full
74+
fi
75+
banner "Per-line gcov detail for $f"
76+
cd "$lib_obj_dir"
77+
gcov "$f" 2>/dev/null | head -20
78+
if [ -f "${f}.gcov" ]; then
79+
echo ""
80+
printf "${C_BOLD}Showing first 60 lines of ${f}.gcov:${C_RESET}\n"
81+
printf " ${C_GREEN}<n>${C_RESET} : executed n times\n"
82+
printf " ${C_RED}#####${C_RESET} : NOT covered\n"
83+
printf " ${C_YELLOW}-${C_RESET} : not executable (comment / decl)\n\n"
84+
head -60 "${f}.gcov"
85+
else
86+
warn "no .gcov output produced (maybe ${f%.c}.gcda missing)"
87+
fi
88+
}
89+
90+
# --- full coverage flow ---------------------------------------------------
91+
run_coverage_full() {
92+
banner "1/4 Run make coverage (build + test + lcov + threshold check)"
93+
if make coverage; then
94+
ok "make coverage exit=0"
95+
else
96+
local rv=$?
97+
err "make coverage exit=$rv"
98+
return $rv
99+
fi
100+
101+
banner "2/4 Project-wide summary (all files including P2/P3 partials)"
102+
lcov --summary coverage.info --rc branch_coverage=1 2>&1 \
103+
| grep -E "lines|functions|branches" \
104+
| sed 's/^/ /'
105+
106+
banner "3/4 Per-file thresholds (spec 06 §6.1, P0/P1 only)"
107+
bash ./coverage_threshold.sh coverage.info 2>&1 \
108+
| grep -E "PASS|FAIL|threshold|G8" \
109+
| sed 's/^/ /'
110+
111+
banner "4/4 Reports"
112+
if [ -f "$SCRIPT_DIR/coverage_report/index.html" ]; then
113+
ok "HTML : $SCRIPT_DIR/coverage_report/index.html"
114+
else
115+
warn "HTML report not produced"
116+
fi
117+
[ -f "$SCRIPT_DIR/coverage.info" ] && ok "lcov : $SCRIPT_DIR/coverage.info"
118+
return 0
119+
}
120+
121+
# --- quick: reuse existing .gcda --------------------------------------------
122+
run_coverage_quick() {
123+
if ! find lib_objs -name '*.gcda' -print -quit 2>/dev/null | grep -q '.'; then
124+
warn "no .gcda found; falling back to full coverage flow"
125+
run_coverage_full
126+
return
127+
fi
128+
banner "Quick mode: reusing existing .gcda (no rebuild)"
129+
rm_via_wrapper coverage.info
130+
rm_via_wrapper coverage_report
131+
lcov --capture --directory lib_objs --directory . \
132+
--output-file coverage.info --rc branch_coverage=1 \
133+
--ignore-errors mismatch,negative,inconsistent,empty,unused,source 2>&1 \
134+
| tail -3
135+
lcov --remove coverage.info \
136+
'/usr/*' '*/cmocka.h' '*/tests/unit/test_*.c' '*/tests/unit/common/*' \
137+
--output-file coverage.info.clean --rc branch_coverage=1 \
138+
--ignore-errors unused,inconsistent,empty 2>&1 | tail -3
139+
mv coverage.info.clean coverage.info
140+
genhtml coverage.info --output-directory coverage_report \
141+
--branch-coverage \
142+
--title "F-Stack lib/ unit-test coverage" \
143+
--ignore-errors source,inconsistent,corrupt 2>&1 | tail -3
144+
bash ./coverage_threshold.sh coverage.info
145+
}
146+
147+
# --- helper: remove via workspace wrapper (NFR-U-7) ------------------------
148+
rm_via_wrapper() {
149+
local tgt="$1"
150+
if [ -e "$tgt" ]; then
151+
/data/workspace/rm_tmp_file.sh "$(realpath "$tgt")" >/dev/null 2>&1
152+
fi
153+
}
154+
155+
run_clean() {
156+
banner "Cleaning coverage artifacts"
157+
make coverage_clean
158+
ok "done"
159+
}
160+
161+
# --- argument parsing ------------------------------------------------------
162+
ACTION="full"
163+
FILE_ARG=""
164+
SERVE=0
165+
166+
while [ $# -gt 0 ]; do
167+
case "$1" in
168+
--quick) ACTION="quick" ;;
169+
--file) ACTION="file"; FILE_ARG="${2:-}"; shift ;;
170+
--serve) SERVE=1 ;;
171+
--clean) ACTION="clean" ;;
172+
-h|--help)
173+
sed -n '2,20p' "$0" | sed 's/^# \?//'
174+
exit 0
175+
;;
176+
*)
177+
err "unknown argument: $1"
178+
echo "Try: $0 --help" >&2
179+
exit 2
180+
;;
181+
esac
182+
shift
183+
done
184+
185+
# --- dispatch --------------------------------------------------------------
186+
check_tools || exit 1
187+
188+
case "$ACTION" in
189+
full) run_coverage_full || exit 1 ;;
190+
quick) run_coverage_quick || exit 1 ;;
191+
file) show_file_detail "$FILE_ARG" ;;
192+
clean) run_clean; exit 0 ;;
193+
esac
194+
195+
# --- optional HTTP server for HTML viewing --------------------------------
196+
if [ "$SERVE" -eq 1 ] && [ -d "$SCRIPT_DIR/coverage_report" ]; then
197+
PORT="${COVERAGE_PORT:-8080}"
198+
banner "Serving HTML report on http://0.0.0.0:${PORT} (Ctrl-C to stop)"
199+
cd "$SCRIPT_DIR/coverage_report"
200+
if command -v python3 >/dev/null 2>&1; then
201+
python3 -m http.server "$PORT"
202+
elif command -v python2 >/dev/null 2>&1; then
203+
python2 -m SimpleHTTPServer "$PORT"
204+
else
205+
err "no python found; cannot start HTTP server"
206+
exit 1
207+
fi
208+
fi

0 commit comments

Comments
 (0)