Problem/Motivation

For simpletest we have to parse all available tests. This becomes more and more a problem, as our test suite increases, which is great!

Proposed resolution

Use doctrine's StaticReflectionReader (also used by the Plugin system) and parse the class comment using regular expressions.

We could also use https://github.com/sun/staticreflection which was once written, especially for this purpose. But it appears unmaintained.

Remaining tasks

Review
Commit

User interface changes

None

API changes

All tests have to have an @group declaration - including PHPUnit tests (not currently the case). In HEAD, if a simpletest (a class that extends WebTestBase or KernelTestBase) is instantiable and does not have an @group annotation an exception is produced. This is removed since we can not determine if a class is instantiable using static reflection - we use the @group annotation to determine if something is a test or not (i.e. a trait, abstract base class etc).

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

dawehner’s picture

FileSize
23.63 KB

Just experiments so far, don't trust it. I'll try sun's library next.

alexpott’s picture

It is going to be problematic to make doctrine's annotation parser play nice with PHPUnit type annotations since they expect an open parenthesis.
Compare the annotation in the node entity

 * @ContentEntityType(
 *   id = "node",

with a simpletest annoation

 * @group system
alexpott’s picture

Had a look to see how easy it would be to make Doctrine's annotation parser parse a simpletest / phpunit annotation. It is not simple. The lexer is not built to work this way.

alexpott’s picture

Status: Active » Needs review
FileSize
7.66 KB

Another idea... I've confirmed that the all the test classes are listed when you use the --list option on run-tests.sh. Because we can't determine if something is a PHPUnit test then all the groups are different - which is why I've standardised on using ucfirst().

Before

Overall Summary
Total Incl. Wall Time (microsec): 8,867,624 microsecs
Total Incl. MemUse (bytes): 309,379,776 bytes
Total Incl. PeakMemUse (bytes): 353,417,168 bytes
Number of Function Calls: 3,049,416

After

Overall Summary
Total Incl. Wall Time (microsec): 6,501,695 microsecs
Total Incl. MemUse (bytes): 75,841,216 bytes
Total Incl. PeakMemUse (bytes): 121,684,432 bytes
Number of Function Calls: 2,174,009
alexpott’s picture

FileSize
12.77 KB
9.67 KB

Now lets use the same logic everywhere.

alexpott’s picture

FileSize
16.86 KB
5.74 KB

Now with tests.

I think the regex to get the first summary line can be improved.

dawehner’s picture

Great idea to use the regex!!

+++ b/core/modules/simpletest/src/TestDiscovery.php
@@ -144,24 +152,16 @@ public function getTestClasses($extension = NULL) {
-      // Skip non-test classes.
-      if (!$class->isSubclassOf('Drupal\simpletest\TestBase') && !$class->isSubclassOf('PHPUnit_Framework_TestCase')) {
+      catch (\LogicException $e) {

Note: We could simply use $parser->getReflectionClass() which allows you to check it, if needed. This would allow us to still show the exception, so people will still get help, while writing tests. If you pass along the reflection class, you could even get rid of the "api" change.

alexpott’s picture

re #7 I don't think that'll work unfortunately.

    /**
     * {@inheritDoc}
     */
    public function isSubclassOf($class)
    {
        throw new ReflectionException('Method not implemented');
    }

from StaticReflectionClass.

alexpott’s picture

Priority: Normal » Critical
Issue tags: +Performance
FileSize
3.47 KB
18.93 KB

Improved the summary line parsing and added yet more tests.

A 200mb memory reduction must qualify this as a performance critical - plus the relief to testbots will be huge and it is good for new contributors because the testing page will work without huge amounts of memory for php.

Berdir’s picture

Not sure why the ucfirst() is added here? We've been discussing that in #2301481: Mark test groups as belonging to modules in UI.

alexpott’s picture

Ucfirst() is added because this patch removed the php unit group making duplicate groups even more prominent.

Berdir’s picture

Hm, that is an interesting change, because lot of .travis scripts out there rely on phpunit tests not being in the module (they run phpunit test and then the group tests separately), and they also hardcode the group name. Including my script for http://d8ms.worldempire.ch/, I quite like that I can report phunit/simpletest status separately.

Not saying that we shouldn't do that, just to be aware of the side effects that will have.

alexpott’s picture

Without using something more funky for static reflection (https://github.com/sun/staticreflection whihch looks unmaintained since the original development) we can not know that it is a PHPUnit test - I tried using the use statements but things like BlockContentLocalTasksTest which extends LocalTaskIntegrationTest which extends UnitTestCase make that unrealistic.

If people want to report PHPUnit separately run the PHPunit tests using phpunit. Yep they will get re-run on when using run-tests.sh but I think that is a small price to pay for the gain here.

alexpott’s picture

FileSize
3.13 KB
19.03 KB

@Berdir that said there is a way :) - the namespace all PHPUnit tests start with Drupal/Test/ and simpletest tests cannot have that so... fixed and removed the ucfirst for the other issue.

dawehner’s picture

Status: Needs review » Reviewed & tested by the community

Great work!!

The last submitted patch, 1: 2422019-1.patch, failed testing.

alexpott’s picture

Issue summary: View changes
alexpott’s picture

Issue summary: View changes
Berdir’s picture

Thanks, I'm not sure if it is better to use ucfirst() or not, but I think it is good to discuss that separately and not enforce it as part of a critical issue.

Did not review the patch closely, but +1 to RTBC from me.

amateescu’s picture

Looks good to me as well, found a few nitpicks though :/

  1. +++ b/core/modules/simpletest/src/TestDiscovery.php
    @@ -144,24 +152,16 @@ public function getTestClasses($extension = NULL) {
    +        // skip it.  Most likely it is an abstract class, trait or test fixture.
    

    Extra space here.

  2. +++ b/core/modules/simpletest/src/TestDiscovery.php
    @@ -290,76 +293,53 @@ public static function scanDirectory($namespace_prefix, $path) {
        * @throws \LogicException
        *   If the class does not have a PHPDoc summary line or @coversDefaultClass
        *   annotation.
    

    This should be MissingSummaryLineException now.

  3. +++ b/core/modules/simpletest/src/TestDiscovery.php
    @@ -368,32 +348,26 @@ public static function getTestInfo(\ReflectionClass $class) {
    -   *   The parsed phpDoc summary line.
    +   *   The parsed phpDoc summary line. Empty
    

    Cliffhanger :P

alexpott’s picture

FileSize
1.54 KB
19.28 KB

Thanks @amateescu - fixed all of that.

catch’s picture

Status: Reviewed & tested by the community » Fixed

Committed/pushed to 8.0.x, thanks!

  • catch committed 7ff2724 on 8.0.x
    Issue #2422019 by alexpott, dawehner: Don't use reflection for parsing...
daffie’s picture

Status: Fixed » Needs work
+++ b/core/modules/simpletest/src/Form/SimpletestResultsForm.php
@@ -153,7 +153,7 @@ public function buildForm(array $form, FormStateInterface $form_state, $test_id
diff --git a/core/modules/simpletest/src/TestDiscovery.php b/core/modules/simpletest/src/TestDiscovery.php

+++ b/core/modules/simpletest/src/TestDiscovery.php
@@ -287,79 +290,56 @@ public static function scanDirectory($namespace_prefix, $path) {
+    // Force all PHPUnit tests into the same group.
+    if (strpos($classname, 'Drupal\\Tests\\') === 0) {
+      $info['group'] = 'PHPUnit';

Why do all PHPUnit tests grouped together? For instance a PHPUnit test for blocks should be grouped in block. Not in PHPUnit. If I make a change to the block module and I want to do a quick test to see if all tests for the block-module pass the PHPUnit block tests are not run.

Berdir’s picture

Status: Needs work » Fixed

That's how it worked before, and we agreed to not change the current behavior here.

dawehner’s picture

Why do all PHPUnit tests grouped together? For instance a PHPUnit test for blocks should be grouped in block. Not in PHPUnit. If I make a change to the block module and I want to do a quick test to see if all tests for the block-module pass the PHPUnit block tests are not run.

I'm sorry but this is just the behaviour of the previous HEAD. Afaik the idea was to allow people to run the phpunit tests as fast as possible, but I agree for those cases you should use phpunit directly anyway.

Feel free to open a non-critical followup.

daffie’s picture

Thank you @Berdir and @dawehner for pointing out to me that I should create a new issue for that. :-)

The new issue for the grouping of PHPUnit test (#2427191: Test-runner ignores @group annotations for PHPUnit-based tests in modules.).

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

webchick’s picture

Priority: Critical » Major
Status: Closed (fixed) » Active

Manuel Garcia and I lost 40+ minutes trying to figure out why #2499971: Tests are missing @coversDefaultClass annotations was happening as a result of insufficient documentation related to this issue. Cross-linking #2325985: No documentation about @group @coversDefaultClass @covers.

webchick’s picture

Priority: Major » Critical
Status: Active » Closed (fixed)
webchick’s picture