Tune SimpleTest
boombatower - October 20, 2008 - 00:44
| Project: | Drupal |
| Version: | 7.x-dev |
| Component: | simpletest.module |
| Category: | feature request |
| Priority: | normal |
| Assigned: | boombatower |
| Status: | postponed |
Description
It takes about 20 minutes to run tests on testing.drupal.org.
I thought of an idea for SimpleTest itself that might help make it run faster: Install Drupal the first time and make copy of default database and files setup. Restore from it rather than re-install.
Thoughts?

#1
Working on getting some numbers.
#2
After running first 10 test cases I get about 52% of the time spent installing Drupal. (54 seconds of 103 total)
#3
This is the very basic code I used to benchmark he testing suite. Using .txt extension as there is no need for t.d.o to test this patch.
You will need to create a table called 'benchmark' with fields: type (int), elapse (float).
The results will be displayed after tests finish.
#4
I already investigated this a while back (patch attached). We already can get a significant performance improvement by doing intra-test-class caching (ie. we don't reinstall Drupal between test methods, simply copy back the clean environment).
Caching test environments *between* test-classes (ie. caching a clean installation of a given set of modules) would require locking somehow (because tests can be run concurrently), and I'm not sure how to do this now.
*But:* copying DB tables in a clean, portable manner is not easy. For MySQL, we need to add NO_AUTO_VALUE_ON_ZERO... but this seems to break some part of Drupal (especially where we do
INSERT INTO table VALUES (DEFAULT)).As a conclusion: Crell talked about implementing a clean interface for copying tables in our database layer. We should bother him :)
#5
would using mysql transactions help?
begin transaction
run test
rollback transaction
its a strategy i used in a previous life w/postgres
#6
Possibly and then reseting the files directory, but that is easy and should remain the way it is.
Do we have proper access to transactions in Drupal 7 core?
#7
Transactions can be installed anytime by converting tables to InnoDB. As for InnoDB in core see #301362: Default to InnoDB in MySQL. But shouldn't tests.drupal.org test installing Drupal core from scratch at least once a week?
#8
#4 gives me: Notice: Undefined index: test_id in _simpletest_batch_finished() (line 403 of /home/catch/www/7/modules/simpletest/simpletest.module).
#9
#7: much more often than once a week. It will test it on every patch as they are posted and the whole set of active ones once daily or every other day...
That is why tuning is important.
#10
And to really test the install process it needs to be done on all the supported DB; currently MySql and PostgreSQL. But MySQL has different engines and I would suggest testing on MyISAM and InnoDB as well.
#11
@catch#8: that's one of the issue with that patch, linked to the NO_AUTO_VALUE_ON_ZERO setting.
When assigning a new test_id, simpletest.module does:
<?php$test_id = db_insert('simpletest_test_id')->useDefaults(array('test_id'))->execute();
?>
This translates to:
INSERT INTO simpletest_test_id VALUES(DEFAULT)And that fails when NO_AUTO_VALUE_ON_ZERO is set (DEFAULT translates to 0 so test_id = 0 on the first run, and a duplicate error is generated on the second run).
I still need to study how to solve this issue in a cross-engine manner (inserting NULL would work on MySQL, but probably fail on PostgreSQL).
#12
Another thought, in order to support #10 comments more than one DB server on different ports with different configurations could help in supporting the multiple MySQL engine testing.
#13
If the DB has an auto index, the value shouldn't be specified at all to use the feature. Specifying the value for the index of course will use the value given regardless of the DB engine. I can't find a similar setting as NO_AUTO_VALUE_ON_ZERO for PostgreSQL.
#14
Not at all :)
On MySQL, you are supposed to insert NULL (or omit) a column to have the autoincrement value:
But on PostgreSQL, serials are implemented by (1) sequences and (2) variable default values:
So you are supposed to insert DEFAULT (or omit) to get the automatic value.
#15
Adding support for testing in separate environment is a totally different issue that I am looking into conquering later.
#16
subscribe. very cool stuff.
#17
Here is patch that caches install for entire test session. (base install without any non-default modules)
Then copies db over and installs test specific modules.
Note: I have not run entire suite, but initial results show 30+% speed increase.
This also needs cleanup as I do old style INSERT/UPDATE statements....
#18
There is an error when running aggregator tests...bed time.
#19
It needs to uninstall schema for custom (additional test specific modules) and leave all the default base tables.
#20
I wish there was a way to quadruple-subscribe to an issue.
I want this in core so bad it hurts. ;) Will try and take a look tomorrow when I am more conscious.
#21
Subscribe. It takes more than half an hour to run through all tests on my server, unless the server gets a hiccup halfway in and stops the testing.
#22
Fixed the main concern I had...aggregator tests now run, but get a page title fail on one of them...
#23
Interesting. (Subscribe.)
#24
Coming back to this issue after a bit, I decided to write down the flow of the code so I could more easily visualize it.
First time:
setUp()
* Create base database using default profile.
* Create empty test database (schema only) using default profile.
Normal:
setUp()
* Copy base database in test database.
* Install additional modules.
tearDown()
* Remove additional module tables.
* Empty all default profile tables.
Last time:
tearDown()
* Destroy base database.
* Destory test database.
I noticed an issue....hook_schema_alter()...so i can either see if additional modules have that implemented and only drop the test database then...or always drop the test database to ensure fresh schema.
The main benefit of not dropping will be much larger performance gain and considering hook_schema_alter() is one of the less used hooks...might be worth tyring.
#25
sudo-code. Could move the create of base database to simpletest.module start of batch...but I'm think it will require code duplication...will look into.
<?php
/* drupal_web_test_case.php */
protected function setUp() {
if (!variable_get('simpletest_install_base', FALSE)) {
// Create base database.
}
if (!variable_get('simpletest_install_test', FALSE)) {
// Create test database.
}
// Install additional modules and detect hook_schema_alter() implementations.
// Copy base tables to test tables.
// Flush environment.
}
protected function tearDown() {
// Uninstall additional modules.
if ($this->destroyTestDatabase) {
// Remove test database tables due to hook_schema_alter() implementation.
}
else {
// Empty test database.
}
// Flush environment.
}
/* simpletest.module */
function _simpletest_batch_finished() {
// Destroy test database.
// Destroy base database.
}
?>
#26
In order to allow this to work will multi-process execution as provided by run-tests.sh I will need some sort of data structure to keep track of what test database are open for each base...
variable_get('simpletest_environment_prefixes');
DrupalWebTestCase* construct() - requests test database prefix
* deconstruct() - releases test database prefix
#27
This is not functional...but represents where I got tonight.
The getInfo() function per test case should be static...and is part of what is breaking this.
Feel free to take a look...I haven't even gotten it all to run due to the getInfo() issue.
Otherwise all the code is complete...who knows what it will do.
#28
Blocked by: #376129: Change getInfo() to a static method.
#29
After lots and lots of discussion #376129: Change getInfo() to a static method finally was decided on and made it in!
I'm am working on this.
#30
I have made great progress! I spend a fair amount of time drawing out what I was trying to do and came up with a much better approach with an added benefit. The code currently runs in index.php so I can easily debug it, but I have it to the point where it will consistently re-unse environments and such.
You can see here that it reuses an environment between two test methods.
Removed 0 of 2 tables...
init()...
new BlockTestCase()...
testBox()...
Array
(
)
Array
(
[simpletest_base] => Array
(
[0] => simpletest625643
)
)
testBlock()...
Array
(
[simpletest_base] => Array
(
)
)
Array
(
[simpletest_base] => Array
(
[0] => simpletest625643
)
)
deinit()...
exit()...
I have attached a patch of the essentials to what I am working on. NOTE: some parts need a fair amount of cleanup. (this patch isn't ready for testing)
#31
Actually runs a test! Not how I want it to end up, but it works! (I might split off the second part into separate patch if it ends up being splittable)
Bed time now.
#32
Needs #391340: Job queue API to work as I have planned and re-use prefixed test databases.
#33
Subscribing
#34
@boombatower: be careful, that's already a big patch, and it risks facing the same death as the previous attempt at refactoring Simpletest.
I suggest working on "baby"-steps:
There is really no need for a gigantic patch to do that ;)
#35
#2: created: #449186: Implement DatabaseConnection::duplicate()
#4: This patch does that, it loads the test class itself in an environment which bootstraps in the prefixed database instead of trying to change to it and clearing statics.
#36
Been meaning to work on #3 for a long time #449198: SimpleTest: Clean up test loading and related API.
Your right about splitting it up, much better approach...thx for bringing that up.
Note: the large patch you refer to that died...was very interdependent and required more work to split up then to just get on with life and commit...especially considering how much time I had spent on it...but oh well I let it die.