Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions desloppify/engine/detectors/coverage/mapping.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,24 @@ def _map_test_to_source(
return None


def _map_test_to_sources(
test_path: str,
production_set: set[str],
lang_name: str,
) -> set[str]:
"""Match a test file to every production file it covers by language scope.

Optional plural hook for languages whose test scope is wider than one
name-paired file (Go tests cover their whole package). Returns an empty
set when the language module does not define it.
"""
mod = _load_lang_test_coverage_module(lang_name)
mapper = getattr(mod, "map_test_to_sources", None)
if callable(mapper):
return mapper(test_path, production_set)
return set()


def naming_based_mapping(
test_files: set[str],
production_files: set[str],
Expand All @@ -171,6 +189,8 @@ def naming_based_mapping(
prod_by_basename.setdefault(bn, []).append(p)

for tf in test_files:
tested |= _map_test_to_sources(tf, production_files, lang_name)

matched = _map_test_to_source(tf, production_files, lang_name)
if matched:
tested.add(matched)
Expand Down Expand Up @@ -224,6 +244,7 @@ def get_test_files_for_prod(
parsed_imports_by_test,
parse_test_imports_fn=_parse_test_imports,
map_test_to_source_fn=_map_test_to_source,
map_test_to_sources_fn=_map_test_to_sources,
project_root=str(get_project_root()),
)

Expand Down
6 changes: 6 additions & 0 deletions desloppify/engine/detectors/coverage/mapping_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ def get_test_files_for_prod(
*,
parse_test_imports_fn: Callable[[str, set[str], dict[str, str], str], set[str]],
map_test_to_source_fn: Callable[[str, set[str], str], str | None],
map_test_to_sources_fn: Callable[[str, set[str], str], set[str]] | None = None,
project_root: str,
) -> list[str]:
"""Find which test files exercise a given production file."""
Expand Down Expand Up @@ -217,6 +218,11 @@ def get_test_files_for_prod(
continue
if map_test_to_source_fn(test_path, {prod_file}, lang_name) == prod_file:
result.append(test_path)
continue
if map_test_to_sources_fn is not None and prod_file in map_test_to_sources_fn(
test_path, {prod_file}, lang_name
):
result.append(test_path)
return result


Expand Down
20 changes: 20 additions & 0 deletions desloppify/languages/go/test_coverage.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,26 @@ def map_test_to_source(test_path: str, production_set: set[str]) -> str | None:
return None


def map_test_to_sources(test_path: str, production_set: set[str]) -> set[str]:
"""Map a Go test file to every production file in its package.

Go compiles all files of a directory as one package and `go test`
reports coverage package-wide: a test file can exercise any identifier
in its package regardless of which file defines it. Name-pairing alone
(foo_test.go -> foo.go) therefore under-reports Go coverage and flags
files as untested_module even when the package's test suite covers
them (e.g. ingest_test.go covering plan.go, verify.go, run.go).
"""
if not test_path.endswith("_test.go"):
return set()
test_dir = os.path.dirname(test_path)
return {
p
for p in production_set
if os.path.dirname(p) == test_dir and p.endswith(".go")
}


def strip_test_markers(basename: str) -> str | None:
"""Strip Go test naming marker to derive source basename."""
if basename.endswith("_test.go"):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -910,3 +910,29 @@ def test_deno_serve_file_classified_as_runtime_entrypoint(self, tmp_path):
entries, _ = detect_test_coverage(graph, zone_map, "typescript")
assert len(entries) == 1
assert entries[0]["detail"]["kind"] == "runtime_entrypoint_no_direct_tests"


def test_naming_based_mapping_go_credits_whole_package():
"""Go tests are package-scoped: one _test.go file covers every sibling
production file, not just its name-paired counterpart."""
production = {
"internal/ingest/plan.go",
"internal/ingest/verify.go",
"internal/ingest/fetch.go",
"internal/other/other.go",
}
tests = {"internal/ingest/ingest_test.go", "internal/ingest/fetch_test.go"}
tested = naming_based_mapping(tests, production, "go")
assert tested == {
"internal/ingest/plan.go",
"internal/ingest/verify.go",
"internal/ingest/fetch.go",
}


def test_naming_based_mapping_plural_hook_absent_is_noop():
"""Languages without map_test_to_sources keep name-paired behavior only."""
production = {"src/widget.ts", "src/other.ts"}
tests = {"src/widget.test.ts"}
tested = naming_based_mapping(tests, production, "typescript")
assert "src/other.ts" not in tested
22 changes: 22 additions & 0 deletions desloppify/tests/lang/go/test_go_test_coverage.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,25 @@ def test_resolve_import_spec_matches_module_prefixed_path_by_suffix():
def test_resolve_import_spec_skips_special_imports():
production = {"pkg/service/handler.go"}
assert go_cov.resolve_import_spec("unsafe", "pkg/service/handler_test.go", production) is None


def test_map_test_to_sources_covers_whole_package():
# Go compiles a directory as one package and `go test` reports coverage
# package-wide, so one test file covers every sibling production file.
production = {
"internal/ingest/plan.go",
"internal/ingest/verify.go",
"internal/ingest/run.go",
"internal/other/other.go",
}
covered = go_cov.map_test_to_sources("internal/ingest/ingest_test.go", production)
assert covered == {
"internal/ingest/plan.go",
"internal/ingest/verify.go",
"internal/ingest/run.go",
}


def test_map_test_to_sources_ignores_non_test_files():
production = {"internal/ingest/plan.go"}
assert go_cov.map_test_to_sources("internal/ingest/notes.md", production) == set()