aboutsummaryrefslogtreecommitdiff
path: root/languages/go/tests
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-05-31 13:07:40 -0500
committerCraig Jennings <c@cjennings.net>2026-05-31 13:07:40 -0500
commit47ca509e69b6a1472a735a4b9521a952e7434491 (patch)
tree9101eb376d233c455f653ad857474b3a0dae767f /languages/go/tests
parentaf478a42b18c4d5e0712c4cb43036126d36c56b5 (diff)
downloadrulesets-47ca509e69b6a1472a735a4b9521a952e7434491.tar.gz
rulesets-47ca509e69b6a1472a735a4b9521a952e7434491.zip
feat(go): add coverage-summary as a Go bundle coverage slice
Third language in the coverage-summary fan-out, after Elisp and Python. Same kernel: count every source file on disk that's absent from the coverage profile as 0% and weight the project number by file, so an untested file stays visible instead of being averaged away. The script at languages/go/claude/scripts/coverage-summary.go parses a cover.out profile, maps each import-path-qualified entry back to an on-disk relative path using the module path from go.mod, and reports a file-weighted number plus the missing files. It's standard library only, so it runs anywhere via go run, and it doesn't reimplement the per-function table that go tool cover -func already prints. I proved it against a real go test -coverprofile run, not just a synthetic fixture, since the Go toolchain is installed here. Two findings to flag. Modern go test ./... already lists every module package in the profile at 0% even when untested, so for in-module code the missing-file list is usually empty. The detection earns its keep on build-tagged files and dirs outside ./.... And this is a coverage-only slice of a Go bundle that doesn't otherwise exist yet: there's no go.md rule file, so sync-language-bundle.sh can't fingerprint it (detection keys on a bundle's own .claude/rules). The script installs via make install-lang LANG=go but won't be sync-maintained until the Go bundle gets real rules and a CLAUDE.md. Building that out is the natural companion task. Tests are black-box: a Go test in its own throwaway module runs the script via go run against temp fixtures and checks output, so the shipped script dir stays test-free. They cover missing-file detection, all-tracked, _test.go exclusion, and the missing-report error. make test gained a go test discovery path for languages/*/tests, guarded so environments without Go skip it cleanly.
Diffstat (limited to 'languages/go/tests')
-rw-r--r--languages/go/tests/coverage_summary_test.go142
-rw-r--r--languages/go/tests/go.mod3
2 files changed, 145 insertions, 0 deletions
diff --git a/languages/go/tests/coverage_summary_test.go b/languages/go/tests/coverage_summary_test.go
new file mode 100644
index 0000000..6b2fb44
--- /dev/null
+++ b/languages/go/tests/coverage_summary_test.go
@@ -0,0 +1,142 @@
+// Black-box tests for the Go bundle coverage-summary script.
+//
+// The script ships into a project's .claude/scripts/ and runs via `go run`, so
+// these tests exercise the real CLI rather than importing it: build a throwaway
+// module (go.mod + source files + a cover.out profile), run the script against
+// it, and assert on stdout. Keeping the test in its own module here means the
+// shipped script dir stays test-free.
+//
+// Normal / Boundary / Error coverage at the behavior level: missing-file
+// detection, the file-weighted number, all-tracked, ignoring _test.go, and a
+// missing-report error.
+package gocovtest
+
+import (
+ "os"
+ "os/exec"
+ "path/filepath"
+ "runtime"
+ "strings"
+ "testing"
+)
+
+// scriptPath resolves coverage-summary.go relative to this test file.
+func scriptPath(t *testing.T) string {
+ t.Helper()
+ _, thisFile, _, ok := runtime.Caller(0)
+ if !ok {
+ t.Fatal("cannot locate test file")
+ }
+ p := filepath.Join(filepath.Dir(thisFile), "..", "claude", "scripts", "coverage-summary.go")
+ abs, err := filepath.Abs(p)
+ if err != nil {
+ t.Fatal(err)
+ }
+ return abs
+}
+
+// fixture writes a go.mod, the given source files, and a cover.out under a temp
+// module root. sources maps relpath -> contents; profile is the raw cover.out.
+func fixture(t *testing.T, sources map[string]string, profile string) string {
+ t.Helper()
+ root := t.TempDir()
+ write := func(rel, body string) {
+ full := filepath.Join(root, rel)
+ if err := os.MkdirAll(filepath.Dir(full), 0o755); err != nil {
+ t.Fatal(err)
+ }
+ if err := os.WriteFile(full, []byte(body), 0o644); err != nil {
+ t.Fatal(err)
+ }
+ }
+ write("go.mod", "module example.com/m\n\ngo 1.26\n")
+ for rel, body := range sources {
+ write(rel, body)
+ }
+ write("cover.out", profile)
+ return root
+}
+
+// run executes the script against a fixture root, returning combined output + err.
+func run(t *testing.T, root, sourceDir string) (string, error) {
+ t.Helper()
+ cmd := exec.Command("go", "run", scriptPath(t),
+ filepath.Join(root, "cover.out"), filepath.Join(root, sourceDir), root)
+ out, err := cmd.CombinedOutput()
+ return string(out), err
+}
+
+func TestMissingFileSurfacedAndCountedZero(t *testing.T) {
+ root := fixture(t,
+ map[string]string{
+ "calc/calc.go": "package calc\n\nfunc Add(a, b int) int { return a + b }\n",
+ "calc/untested.go": "package calc\n\nfunc Mul(a, b int) int { return a * b }\n",
+ },
+ // calc.go fully covered (2/2 stmts); untested.go absent from the profile.
+ "mode: set\nexample.com/m/calc/calc.go:3.40,3.56 2 1\n",
+ )
+ out, err := run(t, root, ".")
+ if err != nil {
+ t.Fatalf("script failed: %v\n%s", err, out)
+ }
+ if !strings.Contains(out, "calc/untested.go") {
+ t.Errorf("missing file not surfaced:\n%s", out)
+ }
+ if !strings.Contains(out, "0%") {
+ t.Errorf("missing-as-0%% note absent:\n%s", out)
+ }
+ // calc.go = 100%, untested.go missing = 0% -> 50.0%
+ if !strings.Contains(out, "50.0%") {
+ t.Errorf("expected project number 50.0%%:\n%s", out)
+ }
+}
+
+func TestAllTrackedNoMissing(t *testing.T) {
+ root := fixture(t,
+ map[string]string{"calc/calc.go": "package calc\n\nfunc Add(a, b int) int { return a + b }\n"},
+ "mode: set\nexample.com/m/calc/calc.go:3.40,3.56 2 1\n",
+ )
+ out, err := run(t, root, ".")
+ if err != nil {
+ t.Fatalf("script failed: %v\n%s", err, out)
+ }
+ if !strings.Contains(out, "Not in coverage report: 0 file") {
+ t.Errorf("expected zero missing files:\n%s", out)
+ }
+ if !strings.Contains(out, "100.0%") {
+ t.Errorf("expected 100.0%% project number:\n%s", out)
+ }
+}
+
+func TestTestFilesAreNotCountedAsSource(t *testing.T) {
+ root := fixture(t,
+ map[string]string{
+ "calc/calc.go": "package calc\n\nfunc Add(a, b int) int { return a + b }\n",
+ "calc/calc_test.go": "package calc\n\nimport \"testing\"\n\nfunc TestX(t *testing.T) {}\n",
+ },
+ "mode: set\nexample.com/m/calc/calc.go:3.40,3.56 2 1\n",
+ )
+ out, err := run(t, root, ".")
+ if err != nil {
+ t.Fatalf("script failed: %v\n%s", err, out)
+ }
+ if strings.Contains(out, "calc_test.go") {
+ t.Errorf("_test.go wrongly counted as source:\n%s", out)
+ }
+ if !strings.Contains(out, "100.0%") {
+ t.Errorf("expected 100.0%% (test file excluded):\n%s", out)
+ }
+}
+
+func TestMissingReportErrors(t *testing.T) {
+ root := fixture(t,
+ map[string]string{"calc/calc.go": "package calc\n"},
+ "mode: set\n",
+ )
+ cmd := exec.Command("go", "run", scriptPath(t),
+ filepath.Join(root, "does-not-exist.out"), root, root)
+ out, err := cmd.CombinedOutput()
+ if err == nil {
+ t.Errorf("expected non-zero exit for missing report; output:\n%s", out)
+ }
+}
diff --git a/languages/go/tests/go.mod b/languages/go/tests/go.mod
new file mode 100644
index 0000000..db72cb1
--- /dev/null
+++ b/languages/go/tests/go.mod
@@ -0,0 +1,3 @@
+module gocovtest
+
+go 1.26