As outlined in #2538462: Contrib Testing Test discovery not working for d6/d7, the old PIFR test runners used to scan module directories to identify tests. The new test runner was built to use the --module argument, which was seen as an improvement over passing multiple dozens of individual test classes along on the run-tests.sh command line.
However, this approach is not working for many contrib projects ... ubercart is one example where passing --module ubercart will result in 'no tests found', due to the chosen module naming scheme not playing nicely with the test discovery assumptions. The suggested approach to get around this is to scan for all tests under the specified directory.
However, instead of building this scanning capability into the new test runner, the maintainers assert that test discovery should be the responsibility of the test framework itself, not the individual runner ... i.e. the scanning for files should be the responsibility of run-tests.sh, not PIFR or DrupalCI.
Proposed Solution
Add a --directory option to run-tests.sh that, when present, will scan for all test files within the specified directory; using the original logic from PIFR to perform the actual test discovery.
Marking major, as this blocks the preferred solution to resolve contrib test discovery within DrupalCI (which in turn blocks the turndown of PIFR).
Comment | File | Size | Author |
---|---|---|---|
#26 | add_directory_option-2551981-26.patch | 3.04 KB | Fabianx |
#21 | 2551981-21-add-directory-option-to-run-tests.patch | 3.04 KB | Mixologic |
#2 | add_directory_option_to_run_tests.patch | 3.33 KB | jthorson |
Comments
Comment #2
jthorson CreditAttribution: jthorson for Drupal Association commentedPatch to add functionality as described.
Comment #3
MixologicHow would this address the issue with ubercart, where we want to run all the tests included with each individual submodule, which are all in separate directories? From what I can tell, --module should be fixed to properly handle that situation.
Comment #4
jthorson CreditAttribution: jthorson for Drupal Association commentedWe would pass --directory modules/ubercart, rather than --module ubercart. The latter would still allow granularity to test a single submodule, while the former would catch all submodules.
EDIT: --directory modules/ubercart would test the ubercart module and all associated sub-modules. --module uc_payment, for example, could be used to test a single submodule. (As could --directory modules/ubercart/uc_payment; but we don't have enough consistency in contrib to be able to assume what directories the submodules will be in.)
The problem is that, today, --module ubercart doesn't test *anything*, while --directory modules/ubercart would. This is the contrib discovery problem that we need to unblock.
Comment #5
XanoI personally find array access to strings very unreadable. I'd go with string functions to get the first character instead.
We're using
stripos()
for the check, which is case-insensitive, but we should probably clarify that in the comment, so '/Tests/' or '/tests/' can be contained...I like these examples. Very helpful!
We're not stripping the trailing slash of the URI, but the separator slash between the root path and the rest (I have no fancy name for that).
I would make this check a little more explicit, to clarify we're looking for a positive integer.
Maybe document we extract the namespace using string manipulation because this was copied verbatim from old code?
Actually, since we load the class later anyway to check if it extends any of the base classes, we can easily use reflection to replace the regular expressions.
Where do we exclude abstract classes?
PSR-0/4 prescribe the (primary) class in any file must have the same name as the file, minus the extension, so we can probably make this simpler, combined with my earlier comment about reflection?
Comment #6
mradcliffeJust a couple of thoughts while reviewing the patch in #2. I do not feel comfortable RTBC because of my lack of experience with PIFR code base.
I'm not familiar with PIFR's argument handling, but this seems strange.
Is the directory argument a string or an array? When is it in array?
What other API functions are available?
That may not be available when porting to Drupal 6 though.
I'm not a fan of this existing PIFR code for getting namespaces, but it seems like the easiest to port between Drupal 6, 7, and 8.
In Drupal 8 it would be possible to check the autoloader. There may be an API call for that if we had access to \Drupal.
In Drupal 7, there's local phpunit and simpletests the latter should be defined in the info file and the former in phpunit.xml(.dist). There's probably no better solution for Drupal 6 or Drupal 7 code.
Comment #7
MixologicAdding a related issue that is listed as an infrastructure blocker to d8.
Comment #8
jthorson CreditAttribution: jthorson for Drupal Association commentedIt's probably worth mentioning that everything after the
foreach ($files as $file) {
line of this patch is a direct copy of existing code already present in run-tests.sh, including the file content parsing for namespaces ... this is cut-and-paste from the--file
argument logic from the same script.Comment #9
chx CreditAttribution: chx commented> I personally find array access to strings very unreadable
It's not array access, it's merely a string offset. There has been many wars wrought over [] vs [} to make this more explicit, it's [] and we will live with it.
> We're using stripos() for the check, which is case-insensitive, but we should probably clarify that in the comment, so '/Tests/' or '/tests/' can be contained...
Noone, ever reads this script.
> Strip the drupal root directory and trailing slash
That's what we strip, the drupal root directory + the trailing slash of that directory.
> I would make this check a little more explicit,
I wouldn't :) it's explicit enough: stripos can return FALSE or a nonnegative integer. We do not need to document PHP especially in a script that noone ever looks into. I am very much for documentation but that's for things people need to call or stumble on when debugging. This is not one.
> Where do we exclude abstract classes?
preg_match_all('@^class
right there: abstract classes do not start with the word class at least not if they adhere to any coding standards.> PSR-0/4 prescribe the (primary) class in any file must have the same name as the file
Uh, if this needs to be backported then classes won't be PSR... same about reflection, when we backport to D7 then you'll hit a fatal when trying to read namespaces from reflection in a PHP version which doesn't have namespaces.
> Is the directory argument a string or an array? When is it in array?
String. $args['directory'] is a string.
Comment #11
catchCommitted/pushed to 8.0.x, thanks!
Comment #12
jthorson CreditAttribution: jthorson for Drupal Association commentedRe-opening for D7 backport.
Comment #13
MixologicHeres the D7 backport patch. The testbots will likely apply this patch to all d7 tests until this makes it into a d7 release.
Comment #15
Mixologicphp5.4 array syntax fixed.
Comment #17
MixologicLets try that again, this time with more fury.
Comment #18
David_Rothstein CreditAttribution: David_Rothstein as a volunteer commentedThis looks like it will find Drupal 8 tests only, but not standard Drupal 7 .test files?
Also, maybe we can find a way to reuse the code in simpletest_test_get_all() here. It's trying to do a similar thing and it would be unfortunate to have to duplicate a lot of it...
Comment #19
MixologicYep, Im in the midst of getting this working - its looking for the wrong class currently, and I miscopied the directory => NULL, to directory => FALSE
simpletest_test_get_all() in d7 only retrieves all tests (it wasn't until d8 that the concept of passing in a $module is possible. It may be that *that* is what needs a backport here).
I'll post another patch soon.
Comment #20
MixologicOkay, I've been testing this patch out and its finding tests properly.
I've added a couple of small things, namely excluding tpl.php and api.php files from the recursive search.
This will find any tests named .test, or any php file under a /tests/ directory, as long as they extend \DrupalTestCase. This may or may not be true for contrib, but so far it has found the right tests (tried views/ctools/examples).
The comments probably need work, but Im going to put this into the simpletestlegacyd7 as a temporary patch so we can exercise it more on the testbots with other code.
Comment #21
Mixologic@David_Rothstein: thanks for the pointer to simpletest_test_get_all() - after looking deeper into it, I realized that it does indeed already have all the logic necessary to find all the tests, and that all we needed to do is mimic what lives under the --file option to compare the files we find vs the tests that exist to get the test list.
Here's a patch that works much better for everything I've been testing against.
Comment #22
mradcliffeSmall nitpick review.
Missing space after control statement and left bracket.
And else if should be elseif on its own line.
Comment #24
Fabianx CreditAttribution: Fabianx as a volunteer and at Tag1 Consulting commentedRTBC! Looks great to me!
Comment #25
Fabianx CreditAttribution: Fabianx as a volunteer and at Tag1 Consulting commentedWe'll have to coordinate this close;y with Drupal CI to avoid failing tests e.g. when git apply --index fails.
Lets test that out by testing another PHP version.
Comment #26
Fabianx CreditAttribution: Fabianx as a volunteer and at Tag1 Consulting commentedComment #28
Fabianx CreditAttribution: Fabianx as a volunteer and at Tag1 Consulting commentedYeah, as I thought. Testbots should use patch --dry-run before the git apply, which is more forgiving if a patch already is applied ...
Comment #29
Mixologicah yeah, drupalci applies this patch to run the d7 tests. I'll investigate the --dry-run - is the expected behavior that "in the event a patch is already applied, continue without applying the patch" ?
Comment #30
Fabianx CreditAttribution: Fabianx as a volunteer and at Tag1 Consulting commented#29> No, what I meant is:
Or alternatively:
Comment #31
Fabianx CreditAttribution: Fabianx as a volunteer and at Tag1 Consulting commentedThis is blocking other work to be tested.
Comment #35
MixologicI added an || true to the "cd /var/www/html && git apply ./2551981-21-add-directory-option-to-run-tests.patch || true", as even the dry run was returning a non-zero error code if it didn't apply. We don't really need to abort processing if that patch does not apply, and it looks like we're actually getting a test run in #26. You'd be free to commit this patch without affecting drupalci.
Comment #36
MixologicG-G-G-Green!
Comment #37
Fabianx CreditAttribution: Fabianx as a volunteer and at Tag1 Consulting commentedGreat! Back to RTBC!
Comment #38
Fabianx CreditAttribution: Fabianx as a volunteer and at Tag1 Consulting commentedI check this line and we use '' elsewhere.
Will fix on commit.
Im theory we do not support /vendor, however even with just phpunit, you could have an additional vendor/ directory in your tests/ directory, so this gets a +1 from me and as its always an absolute path, there will also be a match.
This is non-performance sensitive code, so we prefer:
strpos($args['directory'], '/') === 0
In this case it would always work as we have a string and know it is non-empty though ...
--
Will be fixed on commit.
The stripos() should check !== FALSE as they can in theory return 0.
However again as we deal with an absolute path, that won't be a problem in practice.
--
Will fix on commit anyway, after I checked the code base some more for occurrences of this in Drupal 7.
This code is duplicated below, I think this should be consolidated and this method just setting up test_names and --file option.
--
Will work on that.
Reviewed again in depth and found some things that need work and can be nicer.
Comment #39
Fabianx CreditAttribution: Fabianx as a volunteer and at Tag1 Consulting commentedI decided to only make the minimal change as the 8.x commit also has stripos(), so if we fix that, we need to fix it in both versions.
And again portability of code (same structure as in D8) beats making it perfect.
If someone volunteers, we can open a follow-up D8 + D7 issue though.
Comment #41
Fabianx CreditAttribution: Fabianx as a volunteer and at Tag1 Consulting commentedGiven DrupalCI already uses this patch and given my extensive review above, I feel good to commit this.
Committed and pushed to 7.x! Thank you all very much!
Fixed on commit:
as the empty string evaluates to FALSE as well and NULL is not used anywhere else in the parameters.
Added a new tag for issues that need some love in D7 and D8.
Comment #42
David_Rothstein CreditAttribution: David_Rothstein as a volunteer commentedIt could probably use some cleanup for the code comments too; they seem very Drupal 8-specific since they don't refer to the main type of Drupal 7 test files (*.test).
Also per #18 it really seems to me like it would be possible (and preferable) to reuse https://api.drupal.org/api/drupal/modules!simpletest!simpletest.module/f... here..... i.e. add an optional $directory parameter to that function and then call it. Is there some reason that wouldn't work? (That function is already supposed to be the definitive determination of what tests exist, I think. And it's already called from elsewhere in run-tests.sh.) Perhaps this would make sense as a followup for Drupal 8 also.
Comment #43
MixologicIt's not very obvious from the patch, but this *is* reusing simpletest_test_get_all().
There is a global variable set in lines 49-54 that contains the list of all tests.
The patch loops over that global $all_tests starting in line 495 to validate whether or not the files we have discovered are a member of that list of all tests:
So the other issue with altering the function signature to add an optional directory to simpletest_test_get_all is that the function signature in d8 takes in an *extension*, not a *directory*.
The --directory option is there to allow for running tests that exist in an entire *project*, not limited to individual extensions that come with that project (which is what the --module flag already does).
So, while we might be able to embed this functionality into simpletest_test_get_all, we'd be deviating from the direction that d8 has already gone.
An alternative would be to add another function into simpletest that has this functionality wrappering simpletest_test_get_all, and have run-tests.sh do less logic, but I dont know that we necessarily need to do that, unless we have an equivalent in d8 as well.
Comment #44
Fabianx CreditAttribution: Fabianx as a volunteer and at Tag1 Consulting commentedWe can (both in D7 and D8) roughly do:
because the code is identical.
However if we want to do any cleanup, I suggest we start with D8, then back port it.