diff --git a/.gitignore b/.gitignore index cb1a133..eab2ca6 100644 --- a/.gitignore +++ b/.gitignore @@ -21,4 +21,8 @@ id_rsa /todos /coverage.out /coverage.html +/build/unit/ +/build/combined/ +/build/combined.cover.out +/build/combined.cover.html /.codex diff --git a/Makefile b/Makefile index 021ba3a..b67d4ec 100644 --- a/Makefile +++ b/Makefile @@ -33,7 +33,7 @@ GO_LDFLAGS := -X banger/internal/buildinfo.Version=$(VERSION) -X banger/internal .DEFAULT_GOAL := help -.PHONY: help build banger bangerd test fmt tidy clean install uninstall lint lint-go lint-shell coverage coverage-html coverage-total smoke smoke-build smoke-coverage-html smoke-clean smoke-fresh +.PHONY: help build banger bangerd test fmt tidy clean install uninstall lint lint-go lint-shell coverage coverage-html coverage-total coverage-combined coverage-combined-html smoke smoke-build smoke-coverage-html smoke-clean smoke-fresh help: @printf '%s\n' \ @@ -45,6 +45,8 @@ help: ' make coverage Run tests with coverage; print per-package + total' \ ' make coverage-html Open a browsable per-line HTML report (writes coverage.html)' \ ' make coverage-total Print just the total statement coverage (for scripts/CI)' \ + ' make coverage-combined Merge unit-test + smoke covdata; print per-package + total' \ + ' make coverage-combined-html HTML report of the merged unit+smoke coverage' \ ' make lint Run gofmt + go vet + shellcheck (errors)' \ ' make fmt Format Go sources under cmd/ and internal/' \ ' make tidy Run go mod tidy' \ @@ -87,6 +89,36 @@ coverage-html: coverage coverage-total: @$(GO) test -coverpkg=./... -coverprofile=coverage.out ./... >/dev/null 2>&1 && $(GO) tool cover -func=coverage.out | awk '/^total:/ {print $$NF}' +# coverage-combined unions unit-test coverage and smoke coverage into +# one report. Unit tests cover pure-Go logic (error branches, parsing, +# handler wiring); smoke covers the real sudo / firecracker / dm-snap +# paths that unit tests physically can't reach. Separately each tells +# half the story. Merged, this is the single "what's not being +# exercised at all" view. +# +# Requires an up-to-date smoke run (the target depends on smoke-build +# to rebuild instrumented binaries; re-run `make smoke` yourself if +# scenarios changed). Modes must match; smoke uses the default 'set', +# so the unit run below drops the default 'atomic' for alignment. +COMBINED_COVER_DIR := $(BUILD_DIR)/combined +UNIT_COVER_DIR := $(BUILD_DIR)/unit/covdata +coverage-combined: + @test -d "$(SMOKE_COVER_DIR)" && test "$$(ls -A $(SMOKE_COVER_DIR) 2>/dev/null)" || { \ + echo 'no smoke covdata at $(SMOKE_COVER_DIR); run `make smoke` first' >&2; exit 1; \ + } + rm -rf "$(UNIT_COVER_DIR)" "$(COMBINED_COVER_DIR)" + mkdir -p "$(UNIT_COVER_DIR)" "$(COMBINED_COVER_DIR)" + $(GO) test -cover -coverpkg=./... ./... -args -test.gocoverdir="$(abspath $(UNIT_COVER_DIR))" >/dev/null + $(GO) tool covdata merge -i="$(UNIT_COVER_DIR),$(SMOKE_COVER_DIR)" -o="$(COMBINED_COVER_DIR)" + $(GO) tool covdata textfmt -i="$(COMBINED_COVER_DIR)" -o="$(BUILD_DIR)/combined.cover.out" + @echo '' + @echo 'Per-package (merged unit + smoke):' + @$(GO) tool cover -func="$(BUILD_DIR)/combined.cover.out" | awk -F'\t+' '/^total:/ {total=$$NF; next} {pkg=$$1; sub("banger/", "", pkg); sub("/[^/]+:[0-9]+:$$", "", pkg); pkgs[pkg]+=1; covered[pkg]+=$$NF+0} END {for (p in pkgs) printf " %-40s %.1f%% (avg of %d funcs)\n", p, covered[p]/pkgs[p], pkgs[p] | "sort"; print ""; print "Total statement coverage:", total}' + +coverage-combined-html: coverage-combined + $(GO) tool cover -html="$(BUILD_DIR)/combined.cover.out" -o "$(BUILD_DIR)/combined.cover.html" + @echo 'wrote $(BUILD_DIR)/combined.cover.html' + lint: lint-go lint-shell lint-go: