Problem

  • Simpletest's TestBase class uses a range of fragile/unreliable class properties in order to track and validate the current state of the test environment preparations.

  • All child classes have to manually check those properties. They can easily forget to check these requirements or even intentionally skip them.

    → A malformed test setup causes a test to run against the parent site.

Details

  • Simpletest has a built-in detection to not execute a test if something went wrong during setup. This exists so that if the setup of the child site went wrong the test doesn't tamper with the actual parent site.

  • The way this currently works, though, is that at the end of prepareEnvironment(), $this->setupEnvironment is set to TRUE. If anything goes wrong before that, the function early returns with a FALSE.

    This means however, that child classes that override setUp() (i.e. 80%) have to check the return value of parent::setUp() (or alternatively $this->setupEnvironment) before doing any custom setup.

  • Additionally, the pattern of early returning is not applied consistently. For example, $this->prepareConfigDirectories() may return FALSE, but that is not caught in the calling code.

Proposed resolution

  1. Move the call to prepareEnvironment() out of setUp() and call it from the test run dispatcher method TestBase::run() instead.

  2. Move the calls to prepareDatabasePrefix() and changeDatabasePrefix() into prepareEnvironment() in order to guarantee their invocation.

  3. Change all error conditions into exceptions and properly catch them in the test run dispatcher method TestBase::run().

  4. Move the entire logic of TestBase::tearDown() into a new restoreEnvironment(), so as to guarantee its invocation in a controlled form from TestBase::run() in the exact same way as prepareEnvironment().

  5. Lastly, make all of these methods private, since tests must not be able to override or call them.

  6. And of course, remove all of the obsolete TestBase::$setup* class properties.

Comments

tstoeckler’s picture

StatusFileSize
new2.78 KB
new38.75 KB

So the attached patch is what I had to do to get a proper test result message, i.e. the screenshot, when executing a test even though my config directories were not setup correctly. Note that with approach 1. the hunk in the views test would have to be repeated in just about every test case we have!

tstoeckler’s picture

Status: Active » Needs review
Related issues: +#1757536: Move settings.php to /settings directory, fold sites.php into settings.php
StatusFileSize
new9.5 KB

Hit this again while debugging #1757536: Move settings.php to /settings directory, fold sites.php into settings.php.

Let's see how far this gets us.

DISCLAIMER: This patch messes with Simpletest's setup detection. It may delete the parent site in case I messed up!!!

tstoeckler’s picture

Title: Simpletest's setup()-detection is insufficient » Use exceptions when something goes wrong in test setup
Status: Needs work » Needs review
StatusFileSize
new9.18 KB

Oops.

tstoeckler’s picture

Status: Needs work » Needs review
StatusFileSize
new195.1 KB

Sorry for the noise. This should apply, at least.

tstoeckler’s picture

Status: Needs work » Needs review
StatusFileSize
new2.67 KB
new9.92 KB

This one should definitely fix the notices and hopefully the fails as well.

I also removed the accidentally added *.orig files from the patch, which I accidentally included in #7. Not including that in the interdiff.

tstoeckler’s picture

Status: Needs work » Needs review
StatusFileSize
new2.44 KB
new12.36 KB

Should fix the remaining fails. *crossing fingers*

tstoeckler’s picture

Issue tags: +Needs tests

InstallerTest failing above is actually a good thing as it means we have some test coverage in this area. It also means we need to expand test coverage here, though. I initially didn't think it was possible but I will build on InstallerTest, which is not exactly nice (because extremely verbose).

Tagging accordingly. I would welcome some reviews before working on tests, however.

berdir’s picture

sun’s picture

Note that I'm moving/consolidating the main test preparation/setup code into a new TestBase::setUp() over in #2171683: Remove all Simpletest overrides and rely on native multi-site functionality instead.

  1. +++ b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php
    @@ -780,31 +787,50 @@ public function run(array $methods = array()) {
    +            $this->setUp();
    +            if (!$this->setup) {
    +              throw new TestSetUpException();
                 }
    

    The boolean $this->setup flag/property is also obsolete when throwing exceptions, isn't it?

  2. +++ b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php
    @@ -780,31 +787,50 @@ public function run(array $methods = array()) {
    +          catch (TestSetUpException $e) {
    ...
    +          catch (TestTearDownException $e) {
    

    I'm not really sure whether it makes any sense to have dedicated exception classes for setUp and tearDown.

    1. Whenever an exception is thrown, the executed test class method is aborted.

    2. If an exception is thrown during setUp(), then the entire test class must be skipped, because none of the methods will succeed.

    But I'm not able to see why we need specialized RuntimeExceptions for that?

    Just catching any kind of exceptions in the different stages is completely sufficient:

    try {
      $this->setUp();
    }
    catch (\Exception $e) {
      $this->exceptionHandler($e);
      // Abort this entire test class.
      continue 2;
    }
    try {
      $this->$method();
    }
    catch (\Exception $e) {
      $this->exceptionHandler($e);
      // Proceed with tearDown().
    }
    try {
      $this->tearDown();
    }
    catch (\Exception $e) {
      $this->exceptionHandler($e);
      // Signify that not all test artifacts could be cleaned up.
    }
    
  3. +++ b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php
    @@ -780,31 +787,50 @@ public function run(array $methods = array()) {
    +            $message = $e->getMessage() ?: t("The test cannot be executed because it has not been set up properly.");
    

    If the thrown exception has no message, can we compose and use a more informative test assertion failure message?

    How about simply casting $exception into a string?

    $message = (string) $e;

    http://php.net/manual/en/exception.tostring.php

    That would give a developer a much better idea of what went wrong.

    In fact, let's always use that instead of manually futzing with $e->getMessage()?


    That said, the old code used TestBase::exceptionHandler(), which contains the proper logic for logging an exception already — Why are you not using that?

  4. +++ b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php
    @@ -953,10 +973,18 @@ protected function prepareEnvironment() {
    +    if (!file_prepare_directory($this->public_files_directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) {
    +      throw new TestSetUpException('Could not prepare the public files directory of the test site.');
    

    Here and elsewhere:

    I'd prefer to have (1) shorter and (2) more informative exception messages:

    "Failed to create public files directory '$this->public_files_directory'."

tstoeckler’s picture

1. I thought so too, at first. (See early patches.) However, we still want the protection that if I override setUp() and forget to call parent::setUp() that the test does not operate on the parent site. I.e.

public function setUp() {
  $this->adminUser = $this->drupalCreateUser(...);
}

public function testFoo() {
  db_drop_table('system');
}

I'm aware that's a very contrived use-case but we have that protection available now so I think we should keep it.

2. Hmm... I thought of like typed exceptions but yeah, catching everything makes sense in this case, I guess. So yeah, will do :-)

3. Sure, makes sense.

4. Sure, will do.

sun’s picture

Please note that I had to refactor the exact same code for the exact same purpose and intentions in #2171683: Remove all Simpletest overrides and rely on native multi-site functionality instead now, in order to account for interactive installer tests.

The interdiff in #37 refactors the entire environment preparation procedures of individual setUp() methods into a single and final (i.e. non-overridable) TestBase::prepareEnvironment() method.

And instead of "hoping" that someone will call it, TestBase::run() force-executes it on its own.

At the same time, that approach kills all of the utterly fragile $setupWhatnot state flags and replaces them with \RuntimeExceptions everywhere.

#2171683: Remove all Simpletest overrides and rely on native multi-site functionality instead is an original spin-off from #1757536: Move settings.php to /settings directory, fold sites.php into settings.php and presents a blocking dependency for that. Given that this issue was seemingly spawned from the same parent issue, I'm not sure whether it makes sense to independently proceed here? — #2171683 gained major interest and lots of love from everyone in the meantime and the hope is that it will land very soon.

All of that being said, I wouldn't actually mind if this change would land upfront. But if possible in any way, it would be great and helpful if we'd copy/paste all of the relevant bits from #2171683 into this patch here.

tstoeckler’s picture

Re #13: I wasn't aware of that issue but I think it makes sense to incorporate that here, yes.

Re #16: I just reviewed the patch there and that looks great. It's certainly an improvement over this but it's not that far off and I think it should be manageable to extract that part into this issue. Will do that later this week, if noone beats me to it.

Those changes also make it much easier to add additional InstallerTest-style tests, which will certainly be required for this to land. Since (at least as I see it) #2049967: WebTestBase massacre when required modules cannot be enabled is also blocked on tests that's another reason to do that here. And since the patch in #2171683: Remove all Simpletest overrides and rely on native multi-site functionality instead is already > 100KB *without* additional tests I think it makes sense to extract those changes regardless, and this issue is a perfect place for that.

sun’s picture

StatusFileSize
new19.43 KB

Just wondering whether it would be possible to extract the test environment setup changes from #2171683: Remove all Simpletest overrides and rely on native multi-site functionality instead as-is...

That said, those changes are strictly required for #2171683. By splitting them into this issue, that issue will be blocked and delayed further...

Time is running out to work on the top-level /settings.php issue (which definitely must happen before beta). So even if this test will happen to pass, I'm not a fan of the split.

sun’s picture

The errors reported by the testbot are always different ones. Sorry for the noise.

Status: Needs review » Needs work

The last submitted patch, 18: drupal8.test-env-setup.18.patch, failed testing.

tstoeckler’s picture

Status: Needs work » Needs review
StatusFileSize
new2.08 KB
new21.22 KB

This includes another hunk from #2171683: Remove all Simpletest overrides and rely on native multi-site functionality instead. This definitely fixes some of the exceptions but not yet sure it's the solution to the testbot's problems...

Status: Needs review » Needs work

The last submitted patch, 25: 2170023-25-test-env-setup.patch, failed testing.

sun’s picture

Assigned: Unassigned » sun
Status: Needs work » Needs review
Issue tags: -Needs tests
StatusFileSize
new28.44 KB
new8.32 KB

Well spotted, @tstoeckler! That was the missing piece. :)

I was very tempted to simply delete the BrokenSetUpTest (because it seemingly no longer applies), but after inspecting the root cause for the test failures (_simpletest_batch_operation() no longer having a ModuleHandler after test completion), I realized that TestBase::tearDown() is still too fragile.

To fix that, TestBase::tearDown() is turned into an empty method, and the original code now lives in a dedicated + private TestBase::restoreEnvironment() method.

Like TestBase::prepareEnvironment(), TestBase::restoreEnvironment() is also called only from TestBase::run() in a controlled fashion.

Lastly:

I adjusted BrokenSetUpTest accordingly. But I actually do not think that this kind of data sharing should be possible.

In fact, it really concerns me that all of the $this->original* properties are accessible to tests. In a separate follow-up issue, we should convert all of them into a single + private TestBase::$original property (array/object) that holds all of the original data, NOT accessible to tests.

For now, I guess I'd be fine with keeping the adjusted test, but ultimately, I think we'll have to delete that test when making the original properties private.

sun’s picture

StatusFileSize
new27.67 KB

Sorry, the changes to the Error class were not supposed to be contained.

The last submitted patch, 27: drupal8.test-env-setup.27.patch, failed testing.

sun’s picture

sun’s picture

Yay, we're green! :-)

tstoeckler’s picture

Status: Needs review » Reviewed & tested by the community

I looked through this patch line by line (again) and couldn't find anything to complain. Hence, marking RTBC. The only thing that I stumbled upon was why we can remove the following:

+++ b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php
@@ -726,33 +718,6 @@ protected function setUp() {
-    // Set the 'simpletest_parent_profile' variable to add the parent profile's
-    // search path to the child site's search paths.
-    // @see drupal_system_listing()
-    $conf['simpletest_parent_profile'] = $this->originalProfile;

I grepped and those are the only occurences of 'simpletest_parent_profile' in core currently, so this is obsolete already.

Note: It does not violate our process that I am marking this RTBC because I did not actually write any of the code in this patch. The fact that #18 is similar to the previous patches simply shows that @sun and I arrived at similar conclusions. In #25 I merely copied a hunk from #2171683: Remove all Simpletest overrides and rely on native multi-site functionality instead into this patch so I didn't write any of that code either. Also this code has been reviewed by several people in that issue.

sun’s picture

Priority: Normal » Major
Issue summary: View changes
Issue tags: +Testing system

Minor correction:

I grepped and those are the only occurences of $conf['simpletest_parent_profile'] in core currently, so this is obsolete already.

The configuration value still exists and is still set by WebTestBase::setUp(), but it lives in the simpletest.settings config object now. Only this adjustment of a top-level global $conf key is obsolete/unused.

FWIW, further discussion on that happens in #2175391: ExtensionDiscovery::getProfileDirectories() explicitly skips parent installation profile in installer, defeating its purpose


Tagging and bumping to major, because at least two other major issues depend on this change.

Updated issue summary to reflect the final proposed solution.

sun’s picture

catch’s picture

Status: Reviewed & tested by the community » Fixed

Looks like good clean-up and I couldn't find any complaints. Committed/pushed to 8.x, thanks!

chx’s picture

Title: Use exceptions when something goes wrong in test setup » [Roll back] Use exceptions when something goes wrong in test setup
Status: Fixed » Active
tstoeckler’s picture

Since a bunch of other issues depend on this can we rather fix that instead of rolling this back?

Also we currently pass the XDEBUG information into the child site so the "--execute-test" thing that is described there should no longer be necessary.

See #2134259: Make the Simpletest XDebug integration work for CLI requests

chx’s picture

Title: [Roll back] Use exceptions when something goes wrong in test setup » Use exceptions when something goes wrong in test setup
Status: Active » Fixed

I have no idea how to fix this but if you can, go ahead, please file a followup. Note that if phpstorm is accepting xdebug connections then --class simply hangs and never finishes. The same happens to drush si except with drush si if you switch off the xdebug connections it continues but the run tests script hangs hopelessly.

All in all, I believe the best would be to make --execute-test work again as that's urgent (not for me -- I reverted this in the IMP sandbox to be able to work) and in a separate issue explore how to make --class and xdebug work.

sun’s picture

As far as I can see, that tutorial rightfully does not work anymore — the executed command line lacks a --test-id.

The lack of a properly prepared $test_id should have aborted test execution in the past already, because test assertion results are recorded and stored for a bogus/non-existing test run ID. The $test_id defaults to 0, so I can only assume that the results of all of those executions were cumulated and mixed into a single test ID.

Thus, that tutorial gives wrong/incomplete advice.

Funnily/coincidentally, I was skeptical whether the validation of updating the test_id with the new database prefix in changeDatabasePrefix() would ever be triggered, but in light of that incomplete/wrong usage of run-tests.sh, I'm glad that the validation successfully kicked in. :)

@tstoeckler is right in that #2134259: Make the Simpletest XDebug integration work for CLI requests and other issues improved the xdebug integration for tests.

However, I agree that the situation can be improved further, so I created:

#2183315: Improve XDebug integration for tests and run-tests.sh

tstoeckler’s picture

Yes, I will look into that. I remember that when I tested #2134259: Make the Simpletest XDebug integration work for CLI requests locally when writing it it worked (and didn't hang) but that might have changed in the meantime. I wasn't aware of --execute-test at that point yet, though.

Edit: Crosspost with @sun.

berdir’s picture

FYI, I did a quick test and while it didn't hang (not sure what you mean with that), it simply never got to my breakpoint in the test class but run the test successfully.

chx’s picture

Edit: nevermind. Clearly there's something broken in my setup. Debugging.

chx’s picture

chx’s picture

The problem is solved by raising Max. simulatenous connections as described in http://youtrack.jetbrains.com/issue/WI-8440

Status: Fixed » Closed (fixed)

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

David_Rothstein’s picture

Version: 8.0.x-dev » 8.0.0

It's possible that some of this (at least the try/catch around the setUp() and tearDown() methods) needs to be backported to Drupal 7. #2763435: Exceptions during the setUp() or tearDown() method of a test are not handled is an issue for that.