What is wrong
On a Django monolith (~7,100 tracked files, Python), ripwire . --quality-delta on a clean working tree (identical to git HEAD, only untracked directories present) reports 58 gating regressions, all kind="dead-code", all preexisting-worse, exit 2:
<quality-delta baseline="git-HEAD" regressions="58" minor="0" acked="0" stale="0"
preexisting-worse="58" new-symbol="0" gating="58" register-macro-excluded="0"
api-new-surface="0" at="a2c192068+dirty+shallow">
<r kind="dead-code" sym="api_v2/tests/test_boundary_meter_status_logs.py::BoundaryMeterStatusLogTests::create_test_objects" p="api_v2/tests/test_boundary_meter_status_logs.py:82" gating="1" .../>
<r kind="dead-code" sym="api_v2/tests/test_boundary_meter_status_logs.py::BoundaryMeterStatusLogTests::detail_url_kwargs" p="api_v2/tests/test_boundary_meter_status_logs.py:99" gating="1" .../>
...
Two problems stacked:
-
A no-op diff should never gate. Working tree == HEAD, yet the baseline snapshot and the working-tree snapshot disagree on whether these symbols are dead. Whatever differs between the two parses (ordering, resolver tie-breaks, shallow-clone history) makes the exit code unreliable as a pre-commit hook on this repo. --quality-delta=HEAD~1..HEAD on a one-file test commit also reports 28 preexisting-worse dead-code rows in unrelated files.
-
The symbols are not dead. They are Django test-class hooks dispatched dynamically. A base class in api_v2/tests/utils.py does, inside setUpTestData:
cls.create_test_objects()
and ~30 test classes each define their own create_test_objects. Likewise detail_url_kwargs is called as self.detail_url_kwargs(...) from base helpers and overridden per subclass. The name-based resolver sees one call site with ~30 same-language definitions in other files, declines the bind, and every override reads as unreachable. This is the Python equivalent of the C++ self-registering test macro case that register_macros already exempts, but there is no Python-side hook for it.
The --callers=create_test_objects verb reflects the same blind spot (counts_floor="1", callers found only via super().create_test_objects() chains).
Environment
- ripwire 0.6.0 (Release, AppleClang 16.0.0.16000026, built_from=2d2f10e62), macOS arm64
- Repo is a shallow clone (
--doctor reports shallow="1"); private, so I cannot share it, but the shape is any Django project using TestCase subclasses with @classmethod hooks overridden per class.
--doctor: 8 checks, 7 passed (the one failure is an unrelated stale tracked PNG).
Reproduction shape
# tests/base.py
class BaseAPITest(TestCase):
@classmethod
def setUpTestData(cls):
cls.create_test_objects() # dynamic dispatch on cls
@classmethod
def create_test_objects(cls): ...
# tests/test_a.py .. tests/test_z.py (many files)
class ATests(BaseAPITest):
@classmethod
def create_test_objects(cls): ... # reported dead
Run ripwire . --quality-delta on a clean tree. Expected: gating="0". Observed: one gating dead-code row per override.
Workaround that works
ripwire . --quality-delta --quality-ack="Django test hooks dispatched via cls/super" --ack-only=dead-code
After that, gating="0" acked="58". But acking 58 findings to make a clean tree pass is a ratchet built on a false floor.
Suggestions
- Treat a
cls.NAME() / self.NAME() call site whose name has N same-language method definitions across subclasses as reaching all of them for dead-code purposes (receiver is provably an instance/class of the hierarchy). Or, cheaper: extend .ripwire_config with a Python analogue of register_macros naming method names to exempt from dead-code.
- Whatever the resolver decides,
--quality-delta against an identical tree should be deterministic and report zero. That part looks like a bug independent of the dispatch question.
What is wrong
On a Django monolith (~7,100 tracked files, Python),
ripwire . --quality-deltaon a clean working tree (identical togit HEAD, only untracked directories present) reports 58 gating regressions, allkind="dead-code", all preexisting-worse, exit 2:Two problems stacked:
A no-op diff should never gate. Working tree == HEAD, yet the baseline snapshot and the working-tree snapshot disagree on whether these symbols are dead. Whatever differs between the two parses (ordering, resolver tie-breaks, shallow-clone history) makes the exit code unreliable as a pre-commit hook on this repo.
--quality-delta=HEAD~1..HEADon a one-file test commit also reports 28 preexisting-worse dead-code rows in unrelated files.The symbols are not dead. They are Django test-class hooks dispatched dynamically. A base class in
api_v2/tests/utils.pydoes, insidesetUpTestData:and ~30 test classes each define their own
create_test_objects. Likewisedetail_url_kwargsis called asself.detail_url_kwargs(...)from base helpers and overridden per subclass. The name-based resolver sees one call site with ~30 same-language definitions in other files, declines the bind, and every override reads as unreachable. This is the Python equivalent of the C++ self-registering test macro case thatregister_macrosalready exempts, but there is no Python-side hook for it.The
--callers=create_test_objectsverb reflects the same blind spot (counts_floor="1", callers found only viasuper().create_test_objects()chains).Environment
--doctorreportsshallow="1"); private, so I cannot share it, but the shape is any Django project usingTestCasesubclasses with@classmethodhooks overridden per class.--doctor: 8 checks, 7 passed (the one failure is an unrelated stale tracked PNG).Reproduction shape
Run
ripwire . --quality-deltaon a clean tree. Expected:gating="0". Observed: one gating dead-code row per override.Workaround that works
After that,
gating="0" acked="58". But acking 58 findings to make a clean tree pass is a ratchet built on a false floor.Suggestions
cls.NAME()/self.NAME()call site whose name has N same-language method definitions across subclasses as reaching all of them for dead-code purposes (receiver is provably an instance/class of the hierarchy). Or, cheaper: extend.ripwire_configwith a Python analogue ofregister_macrosnaming method names to exempt from dead-code.--quality-deltaagainst an identical tree should be deterministic and report zero. That part looks like a bug independent of the dispatch question.