From 8b5413ccf2785fffc65c172c75b7b2b1f66c6f8d Mon Sep 17 00:00:00 2001 From: Petr Skoda Date: Sat, 7 Apr 2012 12:09:41 +0200 Subject: [PATCH] MDL-32323 rework db reset once more, now with tests --- lib/phpunit/lib.php | 178 ++++++++++++++++++++++++------------- lib/tests/phpunit_test.php | 63 +++++++++++++ 2 files changed, 179 insertions(+), 62 deletions(-) diff --git a/lib/phpunit/lib.php b/lib/phpunit/lib.php index 8fa8096492c..521958ce639 100644 --- a/lib/phpunit/lib.php +++ b/lib/phpunit/lib.php @@ -167,6 +167,86 @@ class phpunit_util { return self::$tablestructure; } + /** + * Reset all database sequences + * @static + * @return void + */ + public static function reset_all_database_sequences() { + global $DB; + + // reset all sequences as fast as possible, it is usually faster to find out current value than to update all, + // please note we really must verify all tables because sometimes records are added and later removed, + // but in the next run we really want to start again from MAX(id)+1 + + if (!$data = self::get_tabledata()) { + // not initialised yet + return false; + } + if (!$structure = self::get_tablestructure()) { + // not initialised yet + return false; + } + + $dbfamily = $DB->get_dbfamily(); + if ($dbfamily === 'postgres') { + $queries = array(); + $prefix = $DB->get_prefix(); + foreach ($data as $table=>$records) { + if (isset($structure[$table]['id']) and $structure[$table]['id']->primary_key) { + if (empty($records)) { + $nextid = 1; + } else { + $lastrecord = end($records); + $nextid = $lastrecord->id + 1; + } + $queries[] = "ALTER SEQUENCE {$prefix}{$table}_id_seq RESTART WITH $nextid"; + } + } + if ($queries) { + $DB->change_database_structure(implode(';', $queries)); + } + return; + } + + $sequences = array(); + if ($dbfamily === 'mysql') { + $prefix = $DB->get_prefix(); + $rs = $DB->get_recordset_sql("SHOW TABLE STATUS LIKE ?", array($prefix.'%')); + foreach ($rs as $info) { + $table = strtolower($info->name); + if (strpos($table, $prefix) !== 0) { + // incorrect table match caused by _ + continue; + } + if (!is_null($info->auto_increment)) { + $table = preg_replace('/^'.preg_quote($prefix).'/', '', $table); + $sequences[$table] = $info->auto_increment; + } + } + $rs->close(); + } + + foreach ($data as $table=>$records) { + if (isset($structure[$table]['id']) and $structure[$table]['id']->primary_key) { + if (isset($sequences[$table])) { + if (empty($records)) { + $lastid = 0; + } else { + $lastrecord = end($records); + $lastid = $lastrecord->id; + } + if ($sequences[$table] != $lastid +1) { + $DB->get_manager()->reset_sequence($table); + } + + } else { + $DB->get_manager()->reset_sequence($table); + } + } + } + } + /** * Reset all database tables to default values. * @static @@ -230,53 +310,8 @@ class phpunit_util { } } - // reset all sequences as fast as possible, it is usually faster to find out current value than to update all, - // please note we really must verify all tables because sometimes records are added and later removed, - // but in the next run we really want to start again from MAX(id)+1 - $dbfamily = $DB->get_dbfamily(); - $sequences = array(); - if ($dbfamily === 'mysql') { - $prefix = $DB->get_prefix(); - $rs = $DB->get_recordset_sql("SHOW TABLE STATUS LIKE ?", array($prefix.'%')); - foreach ($rs as $info) { - $table = strtolower($info->name); - if (strpos($table, $prefix) !== 0) { - // incorrect table match caused by _ - continue; - } - if (!is_null($info->auto_increment)) { - $table = preg_replace('/^'.preg_quote($prefix).'/', '', $table); - $sequences[$table] = $info->auto_increment; - } - } - $rs->close(); - } else if ($dbfamily === 'postgres') { - foreach ($data as $table=>$records) { - if (isset($structure[$table]['id']) and $structure[$table]['id']->primary_key) { - $sequences[$table] = $DB->get_field_sql("SELECT last_value FROM {{$table}}_id_seq;"); - - } - } - } - - foreach ($data as $table=>$records) { - if (isset($structure[$table]['id']) and $structure[$table]['id']->primary_key) { - if (isset($sequences[$table])) { - if (empty($records)) { - $lastid = 0; - } else { - $lastrecord = end($records); - $lastid = $lastrecord->id; - } - if ($sequences[$table] != $lastid +1) { - $DB->get_manager()->reset_sequence($table); - } - - } else { - $DB->get_manager()->reset_sequence($table); - } - } - } + // reset all next record ids - aka sequences + self::reset_all_database_sequences(); // remove extra tables foreach ($tables as $table) { @@ -394,18 +429,18 @@ class phpunit_util { // restore original config once more in case resetting of caches changes CFG $CFG = self::get_global_backup('CFG'); - // verify db writes just in case something goes wrong in reset - if (self::$lastdbwrites != $DB->perf_get_writes()) { - error_log('Unexpected DB writes in reset_all_data.'); - self::$lastdbwrites = $DB->perf_get_writes(); - } - // inform data generator self::get_data_generator()->reset(); // fix PHP settings error_reporting($CFG->debug); + // verify db writes just in case something goes wrong in reset + if (self::$lastdbwrites != $DB->perf_get_writes()) { + error_log('Unexpected DB writes in reset_all_data.'); + self::$lastdbwrites = $DB->perf_get_writes(); + } + if ($warnings) { $warnings = implode("\n", $warnings); trigger_error($warnings, E_USER_WARNING); @@ -903,10 +938,10 @@ class basic_testcase extends PHPUnit_Framework_TestCase { */ class advanced_testcase extends PHPUnit_Framework_TestCase { /** @var bool automatically reset everything? null means log changes */ - protected $resetAfterTest = null; + protected $resetAfterTest; /** @var moodle_transaction */ - protected $testdbtransaction = null; + protected $testdbtransaction; /** * Constructs a test case with the given name. @@ -932,7 +967,11 @@ class advanced_testcase extends PHPUnit_Framework_TestCase { public function runBare() { global $DB; - if ($DB->get_dbfamily() === 'postgres') { + if (phpunit_util::$lastdbwrites != $DB->perf_get_writes()) { + // this happens when previous test does not reset, we can not use transactions + $this->testdbtransaction = null; + + } else if ($DB->get_dbfamily() === 'postgres') { // database must allow rollback of DDL, so no mysql here $this->testdbtransaction = $DB->start_delegated_transaction(); } @@ -942,28 +981,43 @@ class advanced_testcase extends PHPUnit_Framework_TestCase { if (!$this->testdbtransaction or $this->testdbtransaction->is_disposed()) { $this->testdbtransaction = null; } - $trans = $this->testdbtransaction; if ($this->resetAfterTest === true) { - if ($trans) { + if ($this->testdbtransaction) { $DB->force_transaction_rollback(); + phpunit_util::reset_all_database_sequences(); phpunit_util::$lastdbwrites = $DB->perf_get_writes(); // no db reset necessary } phpunit_util::reset_all_data(); + } else if ($this->resetAfterTest === false) { - if ($trans) { - $trans->allow_commit(); + if ($this->testdbtransaction) { + $this->testdbtransaction->allow_commit(); } // keep all data untouched for other tests + } else { // reset but log what changed - if ($trans) { - $trans->allow_commit(); + if ($this->testdbtransaction) { + $this->testdbtransaction->allow_commit(); } phpunit_util::reset_all_data(true); } } + /** + * Call this method from test if you want to make sure that + * the resetting of database is done the slow way without transaction + * rollback. + * @return void + */ + public function preventResetByRollback() { + if ($this->testdbtransaction and !$this->testdbtransaction->is_disposed()) { + $this->testdbtransaction->allow_commit(); + $this->testdbtransaction = null; + } + } + /** * Reset everything after current test. * @param bool $reset true means reset state back, false means keep all data for the next test, diff --git a/lib/tests/phpunit_test.php b/lib/tests/phpunit_test.php index 95e8393a16b..e651bbccc11 100644 --- a/lib/tests/phpunit_test.php +++ b/lib/tests/phpunit_test.php @@ -192,6 +192,69 @@ class core_phpunit_advanced_testcase extends advanced_testcase { $this->assertSame($_SESSION['USER'], $USER); } + public function test_database_reset_repeated() { + global $DB; + + $this->resetAfterTest(true); + + $this->preventResetByRollback(); + + $this->assertEquals(1, $DB->count_records('course')); // only frontpage in new site + + // this is weird table - id is NOT a sequence here + $this->assertEquals(0, $DB->count_records('context_temp')); + $DB->import_record('context_temp', array('id'=>5, 'path'=>'/1/2', 'depth'=>2)); + $record = $DB->get_record('context_temp', array()); + $this->assertEquals(5, $record->id); + + $this->assertEquals(0, $DB->count_records('course_display')); + $originaldisplayid = $DB->insert_record('course_display', array('userid'=>2, 'course'=>1, 'display'=>1)); + $this->assertEquals(1, $originaldisplayid); + + $course = $this->getDataGenerator()->create_course(); + $this->assertEquals(2, $course->id); + + $this->assertEquals(2, $DB->count_records('user')); + $DB->delete_records('user', array('id'=>1)); + $this->assertEquals(1, $DB->count_records('user')); + + //========= + + $this->resetAllData(); + + $this->assertEquals(1, $DB->count_records('course')); // only frontpage in new site + $this->assertEquals(0, $DB->count_records('context_temp')); // only frontpage in new site + $course = $this->getDataGenerator()->create_course(); + $this->assertEquals(2, $course->id); + + $displayid = $DB->insert_record('course_display', array('userid'=>2, 'course'=>1, 'display'=>1)); + $this->assertEquals($originaldisplayid, $displayid); + + $this->assertEquals(2, $DB->count_records('user')); + $DB->delete_records('user', array('id'=>2)); + $user = $this->getDataGenerator()->create_user(); + $this->assertEquals(3, $user->id); + + // ========= + + $this->resetAllData(); + + $course = $this->getDataGenerator()->create_course(); + $this->assertEquals(2, $course->id); + + $this->assertEquals(2, $DB->count_records('user')); + $DB->delete_records('user', array('id'=>2)); + + //========== + + $this->resetAllData(); + + $course = $this->getDataGenerator()->create_course(); + $this->assertEquals(2, $course->id); + + $this->assertEquals(2, $DB->count_records('user')); + } + public function test_getDataGenerator() { $generator = $this->getDataGenerator(); $this->assertInstanceOf('phpunit_data_generator', $generator);