mirror of
https://github.com/safishamsi/graphify.git
synced 2026-07-11 18:07:31 +00:00
bee3849810
The cross-file call resolver bailed (#543/#1219 god-node guard) whenever a bare callee name had 2+ definitions without unique import evidence — so a single same-named test mock (or any same-named symbol) dropped the real `calls` edge, erasing the call graph wherever a mock existed (the reporter saw a 76-stub Pester suite wipe everything). Replace the blunt bail with a smarter guard: when a name is ambiguous and import evidence doesn't resolve it, apply tie-breakers — non-test preference (a shared, segment-aware _is_test_path classifier) then path proximity — and emit an INFERRED edge ONLY if exactly one candidate survives, else keep bailing. A real def + a test mock resolves to the real def; two genuine non-test defs still bail (god-node guard intact, no fan-out). Wired into both the extract.py pass and the symbol_resolution.py copy via the shared classifier. Reported by @Schweinehund. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
100 lines
2.7 KiB
Python
100 lines
2.7 KiB
Python
"""Tests for graphify.paths — the shared test-path classifier (#1553)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from graphify.paths import (
|
|
_is_test_path,
|
|
disambiguate_ambiguous_candidates,
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"path",
|
|
[
|
|
# test dir segments
|
|
"tests/foo.py",
|
|
"src/tests/foo.py",
|
|
"test/foo.go",
|
|
"spec/foo.rb",
|
|
"specs/foo.rb",
|
|
"app/__tests__/foo.js",
|
|
"a/b/TESTS/foo.py", # case-insensitive segment
|
|
# test filename conventions
|
|
"src/test_service.py",
|
|
"pkg/service_test.go",
|
|
"src/service.test.ts",
|
|
"src/service.spec.ts",
|
|
"src/service_spec.rb",
|
|
"ps/Module.Tests.ps1",
|
|
"java/FooTest.java",
|
|
"java/FooTests.java",
|
|
"cs/FooTests.cs",
|
|
# windows separators
|
|
"src\\tests\\foo.py",
|
|
"src\\service_test.py",
|
|
],
|
|
)
|
|
def test_is_test_path_positive(path: str) -> None:
|
|
assert _is_test_path(path) is True, path
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"path",
|
|
[
|
|
"",
|
|
"latest.py",
|
|
"contest.py",
|
|
"src/contest.py",
|
|
"src/greatest/x.py",
|
|
"src/service.py",
|
|
"lib/helper.go",
|
|
"src/attestation.py", # "test" only as substring, not a segment
|
|
"src/testimony.py", # filename starts with "test" but no underscore
|
|
"src/contest/x.py", # "contest" is not "test"
|
|
"src/greatest.cs", # ends with "test" but not "Tests.cs"
|
|
"src/protest.java", # not "*Test.java"
|
|
"config/manifest.json",
|
|
],
|
|
)
|
|
def test_is_test_path_negative(path: str) -> None:
|
|
assert _is_test_path(path) is False, path
|
|
|
|
|
|
def test_disambiguate_drops_test_candidate_for_nontest_call_site() -> None:
|
|
winner = disambiguate_ambiguous_candidates(
|
|
["src", "mock"],
|
|
{"src": "src/service.py", "mock": "tests/test_service.py"},
|
|
"src/caller.py",
|
|
)
|
|
assert winner == "src"
|
|
|
|
|
|
def test_disambiguate_bails_on_two_nontest_candidates() -> None:
|
|
winner = disambiguate_ambiguous_candidates(
|
|
["a", "b"],
|
|
{"a": "alpha/a.py", "b": "beta/b.py"},
|
|
"pkg/caller.py",
|
|
)
|
|
assert winner is None
|
|
|
|
|
|
def test_disambiguate_test_call_site_prefers_test_local() -> None:
|
|
winner = disambiguate_ambiguous_candidates(
|
|
["src", "local"],
|
|
{"src": "src/service.py", "local": "tests/test_service.py"},
|
|
"tests/test_service.py",
|
|
)
|
|
assert winner == "local"
|
|
|
|
|
|
def test_disambiguate_path_proximity_same_dir() -> None:
|
|
# Two non-test candidates; the one in the call site's directory wins.
|
|
winner = disambiguate_ambiguous_candidates(
|
|
["near", "far"],
|
|
{"near": "pkg/a/service.py", "far": "pkg/b/service.py"},
|
|
"pkg/a/caller.py",
|
|
)
|
|
assert winner == "near"
|