Problem
-
Simpletest's
TestBaseclass 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->setupEnvironmentis set toTRUE. If anything goes wrong before that, the function early returns with aFALSE.This means however, that child classes that override
setUp()(i.e. 80%) have to check the return value ofparent::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 returnFALSE, but that is not caught in the calling code.
Proposed resolution
-
Move the call to
prepareEnvironment()out ofsetUp()and call it from the test run dispatcher methodTestBase::run()instead. -
Move the calls to
prepareDatabasePrefix()andchangeDatabasePrefix()intoprepareEnvironment()in order to guarantee their invocation. -
Change all error conditions into exceptions and properly catch them in the test run dispatcher method
TestBase::run(). -
Move the entire logic of
TestBase::tearDown()into a newrestoreEnvironment(), so as to guarantee its invocation in a controlled form fromTestBase::run()in the exact same way asprepareEnvironment(). -
Lastly, make all of these methods
private, since tests must not be able to override or call them. -
And of course, remove all of the obsolete
TestBase::$setup*class properties.
| Comment | File | Size | Author |
|---|---|---|---|
| #28 | drupal8.test-env-setup.28.patch | 27.67 KB | sun |
| #27 | interdiff.txt | 8.32 KB | sun |
| #27 | drupal8.test-env-setup.27.patch | 28.44 KB | sun |
| #25 | 2170023-25-test-env-setup.patch | 21.22 KB | tstoeckler |
| #25 | 2170023-18-25-interdiff.txt | 2.08 KB | tstoeckler |
Comments
Comment #1
tstoecklerSo 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!
Comment #2
tstoecklerHit 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!!!
Comment #4
tstoecklerOops.
Comment #7
tstoecklerSorry for the noise. This should apply, at least.
Comment #9
tstoecklerThis 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.
Comment #11
tstoecklerShould fix the remaining fails. *crossing fingers*
Comment #12
tstoecklerInstallerTest 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.
Comment #13
berdirCool, this should take care of #2049967: WebTestBase massacre when required modules cannot be enabled too, I think?
Comment #14
sunNote 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.
The boolean $this->setup flag/property is also obsolete when throwing exceptions, isn't it?
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:
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?
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'."
Comment #15
tstoeckler1. 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.
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.
Comment #16
sunPlease 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.
Comment #17
tstoecklerRe #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.
Comment #18
sunJust 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.
Comment #23
sunThe errors reported by the testbot are always different ones. Sorry for the noise.
Comment #25
tstoecklerThis 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...
Comment #27
sunWell 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 + privateTestBase::restoreEnvironment()method.Like
TestBase::prepareEnvironment(),TestBase::restoreEnvironment()is also called only fromTestBase::run()in a controlled fashion.Lastly:
I adjusted
BrokenSetUpTestaccordingly. 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 + privateTestBase::$originalproperty (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.
Comment #28
sunSorry, the changes to the Error class were not supposed to be contained.
Comment #30
sunCreated #2177079: Test classes have access to TestBase::$original* properties for #27
Comment #31
sunYay, we're green! :-)
Comment #32
tstoecklerI 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:
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.
Comment #33
sunMinor correction:
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.
Comment #34
sun28: drupal8.test-env-setup.28.patch queued for re-testing.
Comment #35
catchLooks like good clean-up and I couldn't find any complaints. Committed/pushed to 8.x, thanks!
Comment #36
chx commentedThis broke http://wearepropeople.com/blog/debugging-tests-from-console-using-phpsto... please roll back.
Comment #37
tstoecklerSince 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
Comment #38
chx commentedI 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.
Comment #39
sunAs 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
Comment #40
tstoecklerYes, 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.
Comment #41
berdirFYI, 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.
Comment #42
chx commentedEdit: nevermind. Clearly there's something broken in my setup. Debugging.
Comment #43
chx commentedI filed a bug report http://youtrack.jetbrains.com/issue/WI-21929
Comment #44
chx commentedThe problem is solved by raising Max. simulatenous connections as described in http://youtrack.jetbrains.com/issue/WI-8440
Comment #46
David_Rothstein commentedIt'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.