Repository navigation
[PHPUnit60] Count assertions outside the test method body in AddDoesNotPerformAssertionToNonAssertingTestRector - #822
Conversation
…onMessage() as assert in AddDoesNotPerformAssertionToNonAssertingTestRector (rectorphp/rector#9965)
… AddDoesNotPerformAssertionToNonAssertingTestRector (rectorphp/rector#9965)
…in Drupal sets (#453) It adds #[DoesNotPerformAssertions] to Drupal tests that do assert: in PHPUnit lifecycle methods such as setUp(), tearDown() or assertPostConditions() of a base class, through parent::testX() calls, via expectUserDeprecationMessage(), or in helpers it can't resolve. With failOnRisky those tests fail. rectorphp/rector-phpunit#822 fixes most of these (rectorphp/rector#9965), but not unresolvable helpers, so the skip stays. https://www.drupal.org/i/3638148
| return null; | ||
| } | ||
|
|
||
| return $this->astResolver->resolveClassMethod($className, $methodName); |
There was a problem hiding this comment.
This is very costly apparoch, that should be used only as last resort. It pases whole file from scratch.
I added bench here to surface it. This PR make this rules run ~16 slower, see #824
Can you split this PR into multiple ones, first without AST resolver? Ideally with a bench script similar to 824
That way we can merge the fast paths now.
There was a problem hiding this comment.
There was a problem hiding this comment.
Was about to do it, wplit into three. Researcher came back with the issue being the parent cache key mostly. Since parent might not be the same.
Do you still want me to have claude split mine up 😉
|
Split up, thanks for #825 and the bench:
Most of the original 16x came from the cache key and from re-parsing the file being processed; both are gone. I'll close this one. |
Fixes rectorphp/rector#9965.
AddDoesNotPerformAssertionToNonAssertingTestRectoronly looks at assertions reachable from the test method body, but PHPUnit counts assertions from more places. It added#[DoesNotPerformAssertions]to three kinds of tests that do assert, which then fail withfailOnRisky: "This test is not expected to perform assertions but performed N assertions".1.
expectUserDeprecationMessage()/expectUserDeprecationMessageMatches()PHPUnit counts every expected deprecation as an assertion (
TestCase::verifyDeprecationExpectations()incrementsnumberOfAssertionsPerformed), but neither method was inAssertCallAnalyzer::ASSERT_METHOD_NAME_PREFIXES. Added theexpectUserDeprecationMessageprefix, which covers both. Of the otherexpect*()methods in PHPUnit'sTestCase, only the exception expectations add to the assertion count, and those were already covered.2.
parent::testFoo()where the parent assertsresolveClassMethodFromCall()resolves a static call through the type of$call->class. Forparent,NameTypeResolver::resolveParent()returns a union of all ancestors (direct parent,TestCase,Assert...), not aTypeWithClassName, so the parent method was never inspected.parentis now resolved to the direct parent class through the scope's class reflection.self/staticalready resolve to a single object type and are unchanged.This also needed a change to the
containsAssertCall()cache key: it wasmd5(prettyPrint($classMethod)), so two methods with the same body in different classes (e.g.parent::testSomething();with an asserting and a non-asserting parent) shared one cached result. The key now includes the class name when the scope has one.3. Assertions in lifecycle methods
Assertions in
setUp(),tearDown(),assertPreConditions(),assertPostConditions()and in methods marked#[Before],#[After],#[PreCondition],#[PostCondition](or the matching annotations) count toward every test of the class. The newLifecycleMethodAssertAnalyzerwalks the class hierarchy up toTestCaseand runscontainsAssertCall()on those methods; the rule skips the class's tests when one of them asserts. For the named hooks only the most derived override counts (PHPUnit calls that one; parents are reached throughparent::, which case 2 now follows). The result is cached per class.Tests
Fixtures that fail without the change:
skip_expect_user_deprecation_messageskip_parent_test_call_with_assertskip_asserting_set_up,skip_asserting_tear_down_in_parent,skip_post_conditions_parent_call,skip_asserting_own_post_conditions,skip_asserting_before_attribute,skip_asserting_after_annotationRegression guards that still get the attribute:
parent_test_call_without_assert(also covers the cache collision)non_asserting_set_upFull suite passes (704 tests), plus ECS, class-leak and Rector.
Context
Found running the composer-based PHPUnit set on Drupal core (https://www.drupal.org/i/3638148), where 68 tests became risky. Running the rule over core's test directories:
Not covered here: hooks declared in traits, and helpers that call
addToAssertionCount()(Drupal'sdrupalGet()does), which is why one of the remaining three still turns risky on core.