Skip to content
Merged
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
13 changes: 13 additions & 0 deletions code_review_graph/changes.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,17 @@

logger = logging.getLogger(__name__)


# Lifecycle and construction methods are exercised implicitly by every test
# that touches their class, so listing them as test gaps is noise that
# inflates the gap count and the Untested summary (#850). Names cover
# PHPUnit/xUnit and Python unittest conventions.
_TEST_GAP_EXEMPT_NAMES = frozenset({
"setUp", "tearDown", "setUpBeforeClass", "tearDownAfterClass",
"setup_method", "teardown_method", "setUpClass", "tearDownClass",
"__construct", "__init__", "__destruct",
})

_GIT_TIMEOUT = int(os.environ.get("CRG_GIT_TIMEOUT", "30")) # seconds, configurable

_SAFE_GIT_REF = re.compile(r"^[A-Za-z0-9_.~^/@{}\-]+$")
Expand Down Expand Up @@ -484,6 +495,8 @@ def analyze_changes(
for node in changed_funcs:
if node.is_test:
continue
if node.name in _TEST_GAP_EXEMPT_NAMES:
continue
# TESTED_BY edges are stored as source=production, target=test by the
# parser, so a changed production function finds its tests by source.
# See: #515
Expand Down
25 changes: 25 additions & 0 deletions tests/test_changes.py
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,31 @@ def test_analyze_changes_with_flows(self):
)
assert len(result["affected_flows"]) >= 1

def test_analyze_changes_lifecycle_methods_not_test_gaps(self):
"""Lifecycle and construction methods are exempt from gaps (#850)."""
self._add_func("setUp", path="thing_test_helper.py",
line_start=1, line_end=5)
self._add_func("tearDown", path="thing_test_helper.py",
line_start=6, line_end=10)
self._add_func("__construct", path="thing.php",
line_start=1, line_end=5)
self._add_func("realMethod", path="thing.php",
line_start=6, line_end=20)

result = analyze_changes(
self.store,
changed_files=["thing_test_helper.py", "thing.php"],
changed_ranges={
"thing_test_helper.py": [(1, 10)],
"thing.php": [(1, 20)],
},
)
gap_names = {g["name"] for g in result["test_gaps"]}
assert "setUp" not in gap_names
assert "tearDown" not in gap_names
assert "__construct" not in gap_names
assert "realMethod" in gap_names

def test_analyze_changes_flows_with_relative_cli_paths(self):
"""Relative changed_files still hit flows stored under absolute paths.

Expand Down