Skip to content

[PHPUnit60] Fix false positive in AddDoesNotPerformAssertionToNonAssertingTestRector#687

Merged
TomasVotruba merged 1 commit into
mainfrom
fix-nested-assert-depth-false-positive
Jun 29, 2026
Merged

[PHPUnit60] Fix false positive in AddDoesNotPerformAssertionToNonAssertingTestRector#687
TomasVotruba merged 1 commit into
mainfrom
fix-nested-assert-depth-false-positive

Conversation

@TomasVotruba

Copy link
Copy Markdown
Member

What

AssertCallAnalyzer::containsAssertCall() incremented $classMethodNestingLevel on every call but never decremented it. So instead of tracking real recursion depth, the counter accumulated across the whole depth-first walk of a single test method.

Why it caused false positives

When a non-asserting call (e.g. a production static call) was visited before an assert-performing helper, it burned through the level-5 budget. The later helper recursion then hit level > MAX_NESTED_METHOD_CALL_LEVEL and short-circuited to false, so a test that does assert via a helper was wrongly tagged @doesNotPerformAssertions.

Order-dependent: any test where a non-asserting call precedes the assert helper in source order.

Found while investigating why webonyx/graphql-php skips this rule as "False-positive". Running the rule there flagged 7+ tests that assert through helpers ($this->runTestCase(), $this->expectGraphQLValue(), $this->checkHandlesNonNullableLists(), ...). With this fix only genuine markTestSkipped() tests remain.

Fix

Wrap the body in try { ... } finally { --$this->classMethodNestingLevel; } so the depth is restored on unwind — sibling calls in the same DFS keep their full budget.

Test

Added keep fixture keep_deep_calls_before_assert_helper.php.inc: a deep non-asserting chain visited before an assert helper. Fails on the old code (annotation added), passes with the fix.

…rtingTestRector

The nesting-depth counter in AssertCallAnalyzer was incremented on every
containsAssertCall() but never decremented, so it accumulated across the whole
depth-first walk of a single test method instead of tracking real recursion
depth.

When a non-asserting call (e.g. a production static call) was visited before an
assert-performing helper, it exhausted the level-5 budget; the later helper was
then short-circuited to "no assertions" and the test wrongly got
@doesNotPerformAssertions.

Wrap the body in try/finally so the level is restored on unwind. Add a keep
fixture with a deep non-asserting chain preceding an assert helper.
@TomasVotruba

Copy link
Copy Markdown
Member Author

This might be a perf bottleneck, but lets give it a go. We can adjust nested level to lower value if needed.

@TomasVotruba TomasVotruba merged commit 2e1cd29 into main Jun 29, 2026
8 checks passed
@TomasVotruba TomasVotruba deleted the fix-nested-assert-depth-false-positive branch June 29, 2026 08:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant