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
21 changes: 21 additions & 0 deletions changelog.d/228-django-unittest-hooks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
### Fixed — Django and unittest hooks no longer read as dead code

Before: `--quality-delta` reported a Python hook as dead when no call in the tree named it. unittest and Django call
`setUp`, `tearDown`, `setUpClass` and `setUpTestData` themselves. The runner calls each `test*` method. A closure inside a
method that called `self.hook()` also lost the call. Each such method appeared as dead code.

Now: the tool treats these names as called. It covers `setUpModule`, `tearDownModule`, `load_tests`, `runTest`, `asyncSetUp` and `asyncTearDown` too. The pytest xunit hooks `setup_module`, `teardown_module`,
`setup_function`, `teardown_function`, `setup_class`, `teardown_class`, `setup_method` and `teardown_method` count as called too. A `test*`
method (any name that starts with `test`) and an xunit hook count as called only in a test module: a path `isTestPath` accepts, or a Django `tests.py`. A closure
that calls `self.` or `cls.` keeps the overrides of the method it calls live. A closure that binds `self` or `cls` as its own
parameter does not.

The HEAD snapshot scheme moves from 22 to 23. Old cached snapshots are not used.

Not covered, still reported: pytest's conftest hooks such as `pytest_configure`. (`@pytest.fixture` functions are not reported as dead code here.)

The hook names live in one table per framework (`kUnittestHooks`, `kXunitHooks`, `kUnittestModuleHooks` in `quality.h`), shared with `--dead-code` and `--safe-delete`, which now also skip `setUpTestData` and `runTest` in a TestCase subclass.

Limit: the name check ignores types and scope. A method named `setUp`, `tearDown` or `load_tests` in application code is also treated as called. In a test module, any def named `test*` counts as called, nested defs and helper functions included. The tool errs toward live here.

A closure that binds `self` or `cls` as its own parameter does not count, even for `def cb(self=self)`.
2 changes: 1 addition & 1 deletion changelog.d/versions.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ Python rebind-withholding records a 224 placeholder on theirs; 226 sits above ev
(212 to 225). A cache written by 0.6.8 is re-parsed once. `kCacheVersion` 36 → 40: a Rust binding typed only through a trait
(`&dyn T`, `impl T`, a bound), a Rust `impl T for X` block and a JS/TS binding written after its declaration are new binding
records, so a cache written before them is rejected and rebuilt (the branches that added them used 37, 38 and 39). The
quality-snapshot scheme stays 22. The session legend dictionary is `dictv=2259c5025ac90271 entries=930`.
quality-snapshot scheme moves 22 → 23 (#228: Django and unittest hooks and closure self-dispatch change the dead set; old snapshots are not used). The session legend dictionary is `dictv=2259c5025ac90271 entries=930`.
10 changes: 9 additions & 1 deletion src/langfacts.h
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,14 @@ struct LangFacts
/// open too (ifacedispatch.h). A Kotlin-only tree is unchanged.
bool samFromJavaLambdas = false;

/// Python: unittest, Django and pytest call setUp, tearDown, setup_method and the other test-framework hooks themselves
/// (quality.h pythonFrameworkInvokedSymbol).
bool frameworkEntryHooks = false;

/// Python: a closure inside a method can call `self.hook()` / `cls.hook()` for its host, so a receiver-bound call dispatches
/// through the host's overrides (quality.h pythonReceiverParameters, pythonDispatchRequests). A separate fact from the hooks.
bool closureReceiverDispatch = false;

/// Kotlin: an `interface` is a class_declaration like a class (queries/kotlin/tags.scm: "both land here as @definition.class"),
/// so the symbol's kind cannot tell the two apart; shared code that needs an interface (ifacedispatch.h's lambda test) reads a
/// class of such a language as a possible one.
Expand Down Expand Up @@ -182,7 +190,7 @@ inline constexpr std::array<std::string_view, 3> kJavaObjectMethods = { "equals"
inline constexpr std::array<LangFacts, kLangCount> kLangFacts = { {
{ .lang = Lang::Cpp, .defectShapeNeedsMacroSet = true, .noNestedNamedFunctions = true, .memberSeparator = "::",
.transitiveIncludes = true },
{ .lang = Lang::Python, .baseImportEvidence = BaseImportEvidence::PythonImport },
{ .lang = Lang::Python, .frameworkEntryHooks = true, .closureReceiverDispatch = true, .baseImportEvidence = BaseImportEvidence::PythonImport },
{ .lang = Lang::TypeScript, .overloadSignatures = true, .structuralTyping = true, .importBoundConstruction = true, .baseImportEvidence = BaseImportEvidence::JsImport,
.writtenBindingUndecided = true },
{ .lang = Lang::Go, .structuralTyping = true },
Expand Down
Loading
Loading