--quality-delta gates a clean tree on Python: 58 dead-code false positives for Django test hooks dispatched via cls./self.
- Dominant language
- C++
- Stars
- 2.1k
- Forks
- 125
- Avg merge
- 5h 42m
- Merged PRs (30d)
- 136
Description
## 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:
```
...
```
Two problems stacked:
1. **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.
2. **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`:
```python
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
```python
# 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.
Contributor guide
Research direction
Start by reproducing `ripwire . --quality-delta` on the clean-tree reproduction and compare the baseline and working-tree snapshots. Trace the Python resolver for `cls.NAME()` and `self.NAME()` dispatch, then inspect the `--quality-delta` path for identical-tree determinism. Done means a clean tree reports `gating="0"` and subclass-dispatched Django hooks are not reported as dead code.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp, python
- Domain
- cli, devtools, testing-qa
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100