Find Go coverage by module import path.

Why: `go test -coverprofile` names each file by its module import path.
crapper found it only by path suffix, which works when the module path
happens to end with the project-relative path. A module in a subdirectory
whose name differs from its directory (`svc/go.mod` declaring
`module example.com/api`) scored 0% for every function.

What: `go_import_name` builds the profile's name for a file from the
nearest go.mod's module line and the path inside the module. The bundle
remembers its root and looks a Go file up by that name first, then by the
old suffix match. `_nearest` becomes the shared `nearest_marker` so
coverage and the runners find go.mod the same way. mutator uses
`go_import_name` so the two tools cannot drift.

Proof: three new coverage specs (nested module with a different name,
quoted module path, fallback without a module line) failed first, now
pass; 147 pass, the 2 bb/uml environment failures are unchanged.
crapper on changed code: max CRAP 9.0 (percent_for, unchanged), new
functions <= 7.0. dryer: no candidates in changed files. mutator on the
changed lines: 13/13 killed.
This commit is contained in:
Robert C. Martin
2026-11-10 12:38:04 -06:00
parent 6fd92c4c9b
commit c757ec79a6
4 changed files with 86 additions and 13 deletions
+46
View File
@@ -158,3 +158,49 @@ def test_load_bundle_merges_each_report(tmp_path):
assert bundle.percent_for(function(start_line=3, end_line=4)) == 50.0
assert bundle.go_profile is not None
assert bundle.jacoco is not None
def _go_module(module_dir, module_line, profile_text):
module_dir.mkdir(parents=True, exist_ok=True)
(module_dir / "go.mod").write_text(f"{module_line}\n\ngo 1.21\n", encoding="utf-8")
profile = module_dir / "target" / "coverage" / "go" / "coverage.out"
profile.parent.mkdir(parents=True)
profile.write_text(profile_text, encoding="utf-8")
def test_go_coverage_names_a_nested_file_by_its_module_import_path(tmp_path):
# `go test` keys a profile by import path. The module is `example.com/api`
# but its directory is `svc`, so no suffix of `svc/calc/calc.go` names it.
_go_module(
tmp_path / "svc",
"module example.com/api // the service",
"mode: set\n"
"example.com/api/calc/calc.go:4.2,4.11 1 1\n"
"example.com/api/calc/calc.go:5.3,6.1 1 0\n",
)
bundle = load_bundle(tmp_path)
scored = function(language="go", path="svc/calc/calc.go", start_line=3, end_line=7)
assert bundle.percent_for(scored) == 50.0
def test_go_coverage_reads_a_quoted_module_path(tmp_path):
_go_module(
tmp_path / "cli",
'module "github.com/acme/tool"',
"mode: set\n"
"github.com/acme/tool/main.go:1.1,2.2 1 1\n"
"github.com/acme/tool/cmd/main.go:1.1,2.2 1 0\n",
)
bundle = load_bundle(tmp_path)
assert bundle.percent_for(function(language="go", path="cli/main.go", start_line=1, end_line=2)) == 100.0
assert bundle.percent_for(function(language="go", path="cli/cmd/main.go", start_line=1, end_line=2)) == 0.0
def test_go_coverage_falls_back_to_the_path_without_a_module_name(tmp_path):
profile = tmp_path / "target" / "coverage" / "go" / "coverage.out"
profile.parent.mkdir(parents=True)
profile.write_text("mode: set\nproj/board.go:1.1,2.2 1 1\n", encoding="utf-8")
scored = function(language="go", path="board.go", start_line=1, end_line=2)
assert load_bundle(tmp_path).percent_for(scored) == 100.0
(tmp_path / "go.mod").write_text("go 1.21\n", encoding="utf-8")
assert load_bundle(tmp_path).percent_for(scored) == 100.0