Followup for #2946419-9: Entity: Convert system functional tests to phpunit
Problem/Motivation
In TestDiscovery:getTestClasses()
we iterate through all the files that could potentially contain tests. But if the file does not contain a class, or contains a class without annotation, then we pass empty $doc_comment to getTestInfo():
$parser = new StaticReflectionParser($classname, $finder, TRUE);
...
$info = static::getTestInfo($classname, $parser->getDocComment());
...
public static function getTestInfo($classname, $doc_comment = NULL) {
if (!$doc_comment) {
$reflection = new \ReflectionClass($classname);
$doc_comment = $reflection->getDocComment();
}
These lead to execution of triggers inside of file, even if the file is never launched during the testing.
As result, we can not add deprecated trigger to traits, interfaces, *TestBase (without class annotation) etc.
Proposed resolution
getTestInfo()
should not perform reflection if $doc_comment !== NULL
. See #47.
Remaining tasks
User interface changes
API changes
Data model changes
Comment | File | Size | Author |
---|---|---|---|
#51 | interdiff-50-51.txt | 1.73 KB | Anonymous (not verified) |
#51 | 2949363-51.patch | 1.76 KB | Anonymous (not verified) |
#51 | 2949363-51-test-only.patch | 1.14 KB | Anonymous (not verified) |
#50 | interdiff.txt | 1.37 KB | Mile23 |
#50 | 2949363_50.patch | 2 KB | Mile23 |
Comments
Comment #1
Anonymous (not verified) CreditAttribution: Anonymous commentedvaplas created an issue. See original summary.
Comment #2
Anonymous (not verified) CreditAttribution: Anonymous commentedComment #4
Anonymous (not verified) CreditAttribution: Anonymous commentedComment #6
Anonymous (not verified) CreditAttribution: Anonymous commentedComment #7
timmillwoodThis worked for me locally, let's see if testbot agrees.
Comment #8
timmillwoodSorry, the interdiff is correct, but the patch is messed up, let me try again.
Comment #9
timmillwoodComment #12
zymbian CreditAttribution: zymbian as a volunteer commentedDoes the patch fail due to coding standard error ?
Comment #13
timmillwood@zymbian no
Comment #14
Anonymous (not verified) CreditAttribution: Anonymous commented@timmillwood, thanks for help here!
#9 - interesting setting, I did not know. But even if it softens the deprecated processing, should not they arise at all if the trait is not used?
Can we filter the tests list painlessly?
Comment #15
Anonymous (not verified) CreditAttribution: Anonymous commentedComment #16
LendudeI realise this is a work in progress but some observations
This looks like a strange place to put this.
No we won't remove it, we still need to be able to deprecate traits in D9!
Lets come up with more descriptive name TestTraitDeprecationTrait or something?
And we need some way to indicate that this is testing something, so it won't get removed because it's not used anywhere.
Comment #18
Anonymous (not verified) CreditAttribution: Anonymous commentedSo, the problem occurs when all tests are requested (for example, via get
'admin/config/development/testing'
page). Let's just turn off error handling while getting this list.#16: thanks for good points!
Comment #21
Anonymous (not verified) CreditAttribution: Anonymous commentedOkay, back to Trait, because Test is also run, like all other tests.
Comment #23
Mile23So the problem is that when we add @trigger_error() to traits (and classes) we're actually deprecating the file and not the class. So whenever it's included or required, it will trigger the error.
For tests this is a non-issue because we don't deprecate tests, we just change them. For traits (and base classes) it's a problem because if the directory scan includes, requires, or reflects on the file, it will be loaded and the deprecation error is triggered.
If we disable the error handling during TestDiscovery, then there's two things:
1) The error will be 'handled' when the file loads, and we won't see the deprecation error during test time. This means we might as well not @trigger_error() in the first place. (Strictly speaking: We *might* see the error during test time, depending on which test runner, and whether we fork another process to run it.)
2) We've added complexity to TestDiscovery as a workaround that then will lead to false positives in testing, because we want things to explode (a little) when we use a deprecated class.
So basically -1 on #21.
This problem would go away as a side effect of #2296635: Evaluate performance impact of limiting test discovery to *Test.php filename suffix so we should work on that instead.
Comment #24
Anonymous (not verified) CreditAttribution: Anonymous commentedComment #25
Anonymous (not verified) CreditAttribution: Anonymous commentedDue to BC problem we need to go through several minor releases before #2296635: Evaluate performance impact of limiting test discovery to *Test.php filename suffix.
Perhaps we should look for some workaround? How about a more specialized version of the #21?
Comment #27
Anonymous (not verified) CreditAttribution: Anonymous commentedInstead of the test we can use #2970017: Fix "Drupal\system\Tests\Menu\AssertBreadcrumbTrait is deprecated in Drupal 8.4.0 and will be removed before Drupal 9.0.0. Instead, use \Drupal\Tests\system\Functional\Menu\AssertBreadcrumbTrait". Or move this filter there, and close the issue again)
Comment #28
alexpottLet's adapt our test discovery to not scan TestBase.php files. Then we can deprecate TestBase classes. We can then proceed with #2296635: Evaluate performance impact of limiting test discovery to *Test.php filename suffix
Comment #29
alexpottAnd we can do the same for *Trait.php
Comment #30
Mile23Assuming you mean excluding
*testbase.php
files (we still want to scan for simpletest tests).So basically instead of only allowing *Test.php we should exclude *TestBase.php and *Trait.php
Turning this into a meta, so we can track the later work.
Here's a specific issue: #2973127: Exclude *TestBase.php, *Trait.php and *Interface.php files from test discovery reflection
Comment #31
alexpott@Mile23 yep - #2973127: Exclude *TestBase.php, *Trait.php and *Interface.php files from test discovery reflection looks good.
Comment #32
Anonymous (not verified) CreditAttribution: Anonymous commentedClasses have not this problem, so we not need add special rules for *TestBase.
This is what happens:
But
StaticReflectionParser::parse()
designed for parsing classes. So, it is not found doc annotaion for non-class file:So, when we request
$parser->getDocComment()
we get default empty value.But for empty
$doc_comment
we do additional reflection:As result - deprecated fails.
Interdiff between attached patches:
Comment #35
Anonymous (not verified) CreditAttribution: Anonymous commentedMille pardon!
Comment #39
Anonymous (not verified) CreditAttribution: Anonymous commentedMy bad in run-tests.sh for class again.
Comment #40
alexpottAh nice detective work @vaplas. So the real question is why do we bother with the real reflection? If we can't do static reflection let's assume it's not a test? Does that work...
Comment #41
Anonymous (not verified) CreditAttribution: Anonymous commentedUnlikely we can remove
because
getTestInfo()
can be used in other places without pre-parsing. But what if we just pass not empty comment:Attached patch should pass with
KernelTestDiscoveryTest
, and fail withTestDeprecationClassTestBadName
.Comment #43
alexpottSo #41 looks good. The fail is in a test which is found and running it triggers the deprecation - no?
Comment #44
Anonymous (not verified) CreditAttribution: Anonymous commentedYes + still my bad like #39:
IS updated per #40/ #41.
Comment #45
Mile23The combined Voltron powers of not performing reflection if it's not a test plus filtering out based on *TestBase.php and *Trait.php will yield one full solution. Should we close #2973127: Exclude *TestBase.php, *Trait.php and *Interface.php files from test discovery reflection and do it here? I guess I misunderstood all the stuff about phasing in this change.
This really should either return an empty data structure or it should throw
MissingGroupException
. I vote forMissingGroupException
to be consistent with current behavior, and so we avoid false positives.Comment #46
Anonymous (not verified) CreditAttribution: Anonymous commented@Mile23, can you explain in a little more detail what we need to do?
#44 already throw @MissingGroupException, because
'incorrect class annotation'
value does not contain'@group'
. Or did I not get the #45 idea?Comment #47
Mile23OK.... Helps to read the thread a little better. :-)
The problem is that if we're calling from
getTestClasses()
and there's no existing annotation,getTestInfo()
will perform reflection. We don't want that.The solution is to have
getTestInfo()
check forNULL
, because it says this:So we change
!$doc_comment
to$doc_comment === NULL
.Then
getTestInfo()
will not perform reflection because it already has a docblock that happens to be an empty string. It will throwMissingGroupException
for the empty docblock, including those it finds within*TestBase
and so forth.Back in
getTestClasses()
, we then have to figure out whether that's from a real actual test that is only missing a@group
annotation, or whether it's from an abstract type, such as*TestBase
.getTestClasses()
does that by looking for class names that end in*Test
.#2973127: Exclude *TestBase.php, *Trait.php and *Interface.php files from test discovery reflection will help with that, too, by giving us fewer files to begin with. In fact, that single change to
getTestInfo()
will solve the problem forrun-tests.sh
and we don't actually need to filter out*TestBase
etc. However, we still need to changescanDirectory()
for the phpunit test suite classes: #2973127-2: Exclude *TestBase.php, *Trait.php and *Interface.php files from test discovery reflectionComment #48
Anonymous (not verified) CreditAttribution: Anonymous commentedMile23, great thanks!
I did not have enough brains to find it! Done.
Comment #49
Mile23Thanks for working on it. :-)
In an ideal world, we'd have a good way to test whether reflection ended up happening. But we don't, other than feeding in an empty docblock and then comparing whether the class loaded or not.
Comment #50
Mile23Added a test. I was wrong about how to test it... We can just compare which type of exception it throws, if we have both an empty annotation and a nonexistent class.
Comment #51
Anonymous (not verified) CreditAttribution: Anonymous commentedNice idea! 💎
Nit: If the first assert fails, the test will end. Therefore, we can shorten the code.
RTBC++
Comment #52
Mile23And since it's my idea but not actually my code I can still maybe get away with RTBC. :-)
I'm doing this prior to the tests finishing, but this only adds a a test and we had success in #48.
Comment #55
alexpottNo longer very meta :)
Comment #56
alexpottCommitted and pushed f22c3b0952 to 8.6.x and 7257e86610 to 8.5.x. Thanks!
Backported to 8.5.x because this is the test system and making everything work the same is less headachey.
Comment #59
alexpottTo test this patch I used the --list option on the the run-test.sh command. Before and after applying the patch had no differences even with quite a few popular contrib modules hanging about.
Comment #60
Anonymous (not verified) CreditAttribution: Anonymous commented#55: 😆
Perfect news! Thanks! 🚀🎉