@@ -227,78 +227,57 @@ tasks:
227227
228228 test:unit:coverage :
229229 desc : Run all unit tests with coverage and generate summaries + HTML reports
230+ cmds :
231+ - cmd : |
232+ echo "Removing existing reports in {{.COVERAGE_DIR}}/unit"
233+ rm -rf {{.COVERAGE_DIR}}/unit/*
234+ if: "[ -d {{.COVERAGE_DIR}}/unit ]"
235+
236+ - cmd : |
237+ echo "Creating new coverage directory: {{.COVERAGE_DIR}}/unit"
238+ mkdir -p {{.COVERAGE_DIR}}/unit
239+ if: "[ ! -d {{.COVERAGE_DIR}}/unit ]"
240+
241+ - for : { var: GO_MOD_DIR_UNIT_TEST }
242+ # NOTE: CI does not need go test stdout, local uses separate command to show the report
243+ cmd : |
244+ path={{.ITEM}}
245+ coverage_dir_path="${path%/*}"
246+ mkdir -p {{.COVERAGE_DIR}}/unit/${coverage_dir_path}
247+
248+ echo "Running coverage tests in {{.ITEM}}"
249+ go test -C {{.ITEM}} -covermode=atomic -coverprofile="{{.COVERAGE_DIR}}/unit/{{.ITEM}}.out" ./... > /dev/null
250+
251+ test:unit:coverage:local :
252+ desc : Run all unit tests with coverage and generate summaries + HTML reports locally
230253 vars :
231- COVERAGE_DIR : ' {{ .COVERAGE_DIR | default (print .ROOT_DIR "/.coverage/unit") }}'
254+ COVERAGE_IGNORED_FILES : ' \.pb\.go|mock_|tests|testdata' # NOTE: Need to keep sync with codecov.yml manually
255+ COVERAGE_MODULES : " {{ .GO_MOD_DIR_UNIT_TEST }}" # NOTE: Multiple module definition require newline separation
256+ SHOW_TOTAL_ONLY : ' {{ .SHOW_TOTAL_ONLY | default "false" }}'
232257 cmds :
233- - echo "Removing existing coverage directory and reports"
234- - rm -rf {{.COVERAGE_DIR}}/*
235- - echo "Creating new coverage directory"
236- - mkdir -p {{.COVERAGE_DIR}}
237- - |
238- set -euo pipefail
239- # Build newline-separated list of full module paths (no basename) to support nested modules
240- modules="
241- {{range $dir := .GO_MOD_DIR_UNIT_TEST | splitList "\n"}}{{if $dir}}{{$dir}}
242- {{end}}{{end}}"
243- echo "$modules" | while IFS= read -r m; do
244- [ -z "$m" ] && continue
245- if [ -d "$m" ]; then
246- echo "[coverage] Testing module: $m"
247- mkdir -p "{{.COVERAGE_DIR}}/$(dirname "$m")"
248- report_json="{{.COVERAGE_DIR}}/test-report-$(echo "$m" | tr '/' '-').json"
249- (
250- cd "$m"
251- # Run tests with coverprofile (text format)
252- if go test -covermode=atomic -coverprofile="{{.COVERAGE_DIR}}/$m.out.tmp" ./... -json 2>&1 | tee "$report_json" >/dev/null; then
253- echo "[coverage] Completed: $m"
254- else
255- status=$?
256- echo "[coverage][warn] Tests failed in $m (exit $status); continuing"
257- fi
258- )
259- # Filter out generated files (matching codecov.yml ignores)
260- if [ -f "{{.COVERAGE_DIR}}/$m.out.tmp" ]; then
261- grep -v "\.pb\.go" "{{.COVERAGE_DIR}}/$m.out.tmp" | grep -v "mock_.*\.go" > "{{.COVERAGE_DIR}}/$m.out" || echo "mode: atomic" > "{{.COVERAGE_DIR}}/$m.out"
262- rm "{{.COVERAGE_DIR}}/$m.out.tmp"
263- else
264- echo "[coverage] No coverage generated for $m"
265- echo "mode: atomic" > "{{.COVERAGE_DIR}}/$m.out"
266- fi
267- fi
268- done
258+ - task : test:unit:coverage
269259
270- - |
271- set -euo pipefail
272- echo "[coverage] Generating per-module summaries"
273- : > {{.COVERAGE_DIR}}/summary.txt
274- modules="
275- {{range $dir := .GO_MOD_DIR_UNIT_TEST | splitList "\n"}}{{if $dir}}{{$dir}}
276- {{end}}{{end}}"
277- echo "$modules" | while IFS= read -r m; do
278- [ -z "$m" ] && continue
279- if [ -f "{{.COVERAGE_DIR}}/$m.out" ]; then
280- if (cd "$m" && go tool cover -func={{.COVERAGE_DIR}}/$m.out > {{.COVERAGE_DIR}}/$m.func.txt 2>/dev/null); then
281- tail -n1 {{.COVERAGE_DIR}}/$m.func.txt | sed "s|^total:|[$m] total:|" >> {{.COVERAGE_DIR}}/summary.txt || true
282- else
283- echo "[$m] total: (error generating summary)" >> {{.COVERAGE_DIR}}/summary.txt
284- fi
285- fi
286- done
287- echo "[coverage] Summary:"; cat {{.COVERAGE_DIR}}/summary.txt
260+ - cmd : rm /tmp/total_report_results.txt || true
261+ if : ' {{ eq .SHOW_TOTAL_ONLY "true" }}'
288262
289- - |
290- set -euo pipefail
291- echo "[coverage] Generating HTML reports"
292- modules="
293- {{range $dir := .GO_MOD_DIR_UNIT_TEST | splitList "\n"}}{{if $dir}}{{$dir}}
294- {{end}}{{end}}"
295- echo "$modules" | while IFS= read -r m; do
296- [ -z "$m" ] && continue
297- if [ -f "{{.COVERAGE_DIR}}/$m.out" ] && [ -d "$m" ]; then
298- (cd "$m" && go tool cover -html={{.COVERAGE_DIR}}/$m.out -o {{.COVERAGE_DIR}}/$m.html || true)
299- fi
300- done
301- echo "[coverage] Generated HTML files:"; out=$(find {{.COVERAGE_DIR}} -name '*.html' 2>/dev/null | sed 's|{{.COVERAGE_DIR}}/| - |'); echo "${out:- (none)}"
263+ - for : { var: GO_MOD_DIR_UNIT_TEST }
264+ cmd : |
265+ echo "Filtering out ignored files from coverage for {{.ITEM}}"
266+ grep -vE "{{.COVERAGE_IGNORED_FILES}}" {{.COVERAGE_DIR}}/unit/{{.ITEM}}.out > {{.COVERAGE_DIR}}/unit/{{.ITEM}}.out.tmp
267+
268+ mv {{.COVERAGE_DIR}}/unit/{{.ITEM}}.out.tmp {{.COVERAGE_DIR}}/unit/{{.ITEM}}.out
269+
270+ echo "Running go tool cover -func={{.COVERAGE_DIR}}/unit/{{.ITEM}}.out"
271+ {{if eq .SHOW_TOTAL_ONLY "true"}}
272+ go tool -C {{.ITEM}} cover -func={{.COVERAGE_DIR}}/unit/{{.ITEM}}.out | grep total | sed 's@total@{{.ITEM}} - total@g' >> /tmp/total_report_results.txt
273+ {{else}}
274+ go tool -C {{.ITEM}} cover -func={{.COVERAGE_DIR}}/unit/{{.ITEM}}.out
275+ {{end}}
276+
277+ - cmd : |
278+ cat /tmp/total_report_results.txt | column -t | sort
279+ rm /tmp/total_report_results.txt
280+ if: '{{ eq .SHOW_TOTAL_ONLY "true" }}'
302281
303282 bench :
304283 desc : Run bench tests on codebase
0 commit comments