From 5d1381c2c36e855ac094590c38fbc90f0c4dfb1a Mon Sep 17 00:00:00 2001 From: nicolasconnault Date: Fri, 19 Sep 2008 14:28:22 +0000 Subject: [PATCH] MDL-16486 Implemented the proxy DB class. Sort of working, but still some issues to sort out, like fix_course_sort_order, which updates a record NOT inserted by the unit test. --- lang/en_utf8/simpletest.php | 4 + lib/simpletestlib.php | 225 ++++++++++++++++-- .../test_chat_portfolio_callers.php | 4 - mod/resource/lib.php | 3 +- .../test_resource_portfolio_callers.php | 2 +- 5 files changed, 210 insertions(+), 28 deletions(-) diff --git a/lang/en_utf8/simpletest.php b/lang/en_utf8/simpletest.php index 27a15097d1..5eab9a4cde 100644 --- a/lang/en_utf8/simpletest.php +++ b/lang/en_utf8/simpletest.php @@ -4,6 +4,8 @@ $string['all'] = 'ALL'; $string['addconfigprefix'] = 'Add prefix to config file'; +$string['deletingnoninsertedrecord'] = 'Trying to delete a record that was not inserted by these unit tests (id $a->id in table $a->table).'; +$string['deletingnoninsertedrecords'] = 'Trying to delete records that were not inserted by these unit tests (from table $a->table).'; $string['exception'] = 'Exception'; $string['fail'] = 'Fail'; $string['ignorefile'] = 'Ignore tests in the file'; @@ -29,7 +31,9 @@ $string['showsearch'] = 'Show the search for test files.'; $string['stacktrace'] = 'Stack trace:'; $string['summary'] = '{$a->run}/{$a->total} test cases complete: {$a->passes} passes, {$a->fails} fails and {$a->exceptions} exceptions.'; $string['tablesnotsetup'] = 'Unit test tables are not yet built. Do you want to build them now?.'; +$string['testtablescsvfileunwritable'] = 'The test tables CSV file is not writable ($a->filename)'; $string['thorough'] = 'Run a thorough test (may be slow).'; +$string['updatingnoninsertedrecord'] = 'Trying to update a record that was not inserted by these unit tests (id $a->id in table $a->table).'; $string['uncaughtexception'] = 'Uncaught exception [{$a->getMessage()}] in [{$a->getFile()}:{$a->getLine()}] TESTS ABORTED.'; $string['unittests'] = 'Unit tests'; $string['version'] = 'Using SimpleTest version $a.'; diff --git a/lib/simpletestlib.php b/lib/simpletestlib.php index b83bdd38f3..1db52cff49 100644 --- a/lib/simpletestlib.php +++ b/lib/simpletestlib.php @@ -150,7 +150,6 @@ class CheckSpecifiedFieldsExpectation extends SimpleExpectation { } class MoodleUnitTestCase extends UnitTestCase { - public $real_db; public $tables = array(); public $pkfile; public $cfg; @@ -189,7 +188,9 @@ class MoodleUnitTestCase extends UnitTestCase { } } if (!file_put_contents($this->pkfile, $tabledata)) { - throw new moodle_exception('testtablescsvfileunwritable', 'error'); + $a = new stdClass(); + $a->filename = $this->pkfile; + throw new moodle_exception('testtablescsvfileunwritable', 'simpletest', '', $a); } } } @@ -205,7 +206,7 @@ class MoodleUnitTestCase extends UnitTestCase { foreach ($tables as $table) { if ($table != 'sessions2' && isset($tabledata[$table])) { - $DB->delete_records_select($table, "id > ?", array($tabledata[$table])); + // $DB->delete_records_select($table, "id > ?", array($tabledata[$table])); } } } @@ -227,7 +228,10 @@ class MoodleUnitTestCase extends UnitTestCase { } return $tabledata; } else { - throw new moodle_exception('testtablescsvfilemissing', 'error'); + $a = new stdClass(); + $a->filename = $this->pkfile; + debug_print_backtrace(); + throw new moodle_exception('testtablescsvfilemissing', 'simpletest', '', $a); return false; } } @@ -238,45 +242,222 @@ class MoodleUnitTestCase extends UnitTestCase { * TODO Improve detection of incorrectly built DB test tables (e.g. detect version discrepancy and offer to upgrade/rebuild) */ public function setUp() { - global $CFG, $DB; parent::setUp(); + UnitTestDB::instantiate(); + } + + /** + * Method called after each test method. Doesn't do anything extraordinary except restore the global $DB to the real one. + */ + public function tearDown() { + global $DB; + $DB->cleanup(); + parent::tearDown(); + } + + /** + * This will execute once all the tests have been run. It should delete the text file holding info about database contents prior to the tests + * It should also detect if data is missing from the original tables. + */ + public function __destruct() { + global $CFG, $DB; + + $CFG = $this->cfg; + $this->tearDown(); + UnitTestDB::restore(); + fulldelete($this->pkfile); + } +} + +/** + * This is a Database Engine proxy class: It replaces the global object $DB with itself through a call to the + * static instantiate() method, and restores the original global $DB through restore(). + * Internally, it routes all calls to $DB to a real instance of the database engine (aggregated as a member variable), + * except those that are defined in this proxy class. This makes it possible to add extra code to the database engine + * without subclassing it. + */ +class UnitTestDB { + public static $DB; + private static $real_db; - $this->real_db = $DB; + public $table_data = array(); + + public function __construct() { + + } + + /** + * Call this statically to connect to the DB using the unittest prefix, instantiate + * the unit test db, store it as a member variable, instantiate $this and use it as the new global $DB. + */ + public static function instantiate() { + global $CFG, $DB; + UnitTestDB::$real_db = clone($DB); if (empty($CFG->unittestprefix)) { print_error("prefixnotset", 'simpletest'); } - $DB = moodle_database::get_driver_instance($CFG->dbtype, $CFG->dblibrary); - $DB->connect($CFG->dbhost, $CFG->dbuser, $CFG->dbpass, $CFG->dbname, $CFG->dbpersist, $CFG->unittestprefix); - $manager = $DB->get_manager(); + UnitTestDB::$DB = moodle_database::get_driver_instance($CFG->dbtype, $CFG->dblibrary); + UnitTestDB::$DB->connect($CFG->dbhost, $CFG->dbuser, $CFG->dbpass, $CFG->dbname, $CFG->dbpersist, $CFG->unittestprefix); + $manager = UnitTestDB::$DB->get_manager(); if (!$manager->table_exists('user')) { print_error('tablesnotsetup', 'simpletest'); } + + $DB = new UnitTestDB(); + } + + public function __call($method, $args) { + // Set args to null if they don't exist (up to 10 args should do) + if (!method_exists($this, $method)) { + return call_user_func_array(array(UnitTestDB::$DB, $method), $args); + } else { + call_user_func_array(array($this, $method), $args); + } + } + + public function __get($variable) { + return UnitTestDB::$DB->$variable; + } + + public function __set($variable, $value) { + UnitTestDB::$DB->$variable = $value; + } + + public function __isset($variable) { + return isset(UnitTestDB::$DB->$variable); + } + + public function __unset($variable) { + unset(UnitTestDB::$DB->$variable); } /** - * Method called after each test method. Doesn't do anything extraordinary except restore the global $DB to the real one. + * Overriding insert_record to keep track of the ids inserted during unit tests, so that they can be deleted afterwards */ - public function tearDown() { + public function insert_record($table, $dataobject, $returnid=true, $bulk=false) { global $DB; - parent::tearDown(); + $id = UnitTestDB::$DB->insert_record($table, $dataobject, $returnid, $bulk); + $this->table_data[$table][] = $id; + return $id; + } - $DB = $this->real_db; + /** + * Overriding update_record: If we are updating a record that was NOT inserted by unit tests, + * throw an exception and cancel update. + * @throws moodle_exception If trying to update a record not inserted by unit tests. + */ + public function update_record($table, $dataobject, $bulk=false) { + global $DB; + if (empty($this->table_data[$table]) || !in_array($dataobject->id, $this->table_data[$table])) { + return UnitTestDB::$DB->update_record($table, $dataobject, $bulk); + // $a = new stdClass(); + // $a->id = $dataobject->id; + // $a->table = $table; + // debug_print_backtrace(); + // throw new moodle_exception('updatingnoninsertedrecord', 'simpletest', '', $a); + } else { + return UnitTestDB::$DB->update_record($table, $dataobject, $bulk); + } } /** - * This will execute once all the tests have been run. It should delete the text file holding info about database contents prior to the tests - * It should also detect if data is missing from the original tables. + * Overriding delete_record: If we are deleting a record that was NOT inserted by unit tests, + * throw an exception and cancel delete. + * @throws moodle_exception If trying to delete a record not inserted by unit tests. */ - public function __destruct() { - global $CFG; - $CFG = $this->cfg; - $this->truncate_test_tables($this->get_table_data($this->pkfile)); - fulldelete($this->pkfile); - $this->tearDown(); + public function delete_records($table, array $conditions=null) { + global $DB; + $a = new stdClass(); + $a->table = $table; + + // Get ids matching conditions + if (!$ids_to_delete = $DB->get_field($table, 'id', $conditions)) { + return UnitTestDB::$DB->delete_records($table, $conditions); + } + + $proceed_with_delete = true; + + if (!is_array($ids_to_delete)) { + $ids_to_delete = array($ids_to_delete); + } + + foreach ($ids_to_delete as $id) { + if (!in_array($id, $this->table_data[$table])) { + $proceed_with_delete = false; + $a->id = $id; + break; + } + } + + if ($proceed_with_delete) { + return UnitTestDB::$DB->delete_records($table, $conditions); + } else { + debug_print_backtrace(); + throw new moodle_exception('deletingnoninsertedrecord', 'simpletest', '', $a); + } } -} + /** + * Overriding delete_records_select: If we are deleting a record that was NOT inserted by unit tests, + * throw an exception and cancel delete. + * @throws moodle_exception If trying to delete a record not inserted by unit tests. + */ + public function delete_records_select($table, $select, array $params=null) { + global $DB; + $a = new stdClass(); + $a->table = $table; + + // Get ids matching conditions + if (!$ids_to_delete = $DB->get_field_select($table, 'id', $select, $params)) { + return UnitTestDB::$DB->delete_records_select($table, $select, $params); + } + + $proceed_with_delete = true; + + foreach ($ids_to_delete as $id) { + if (!in_array($id, $this->table_data[$table])) { + $proceed_with_delete = false; + $a->id = $id; + break; + } + } + + if ($proceed_with_delete) { + return UnitTestDB::$DB->delete_records_select($table, $select, $params); + } else { + debug_print_backtrace(); + throw new moodle_exception('deletingnoninsertedrecord', 'simpletest', '', $a); + } + } + + /** + * Removes from the test DB all the records that were inserted during unit tests, + */ + public function cleanup() { + global $DB; + foreach ($this->table_data as $table => $ids) { + foreach ($ids as $id) { + $DB->delete_records($table, array('id' => $id)); + } + } + } + + /** + * Restores the global $DB object. + */ + public static function restore() { + global $DB; + $DB = UnitTestDB::$real_db; + } + + public function get_field($table, $return, array $conditions) { + if (!is_array($conditions)) { + debug_print_backtrace(); + } + return UnitTestDB::$DB->get_field($table, $return, $conditions); + } +} ?> diff --git a/mod/chat/simpletest/test_chat_portfolio_callers.php b/mod/chat/simpletest/test_chat_portfolio_callers.php index 48333c4a47..14e9565267 100644 --- a/mod/chat/simpletest/test_chat_portfolio_callers.php +++ b/mod/chat/simpletest/test_chat_portfolio_callers.php @@ -32,10 +32,6 @@ class testChatPortfolioCallers extends portfoliolib_test { $this->caller = parent::setup_caller('chat_portfolio_caller', array('id' => $cm->id), $userid); } - public function tearDown() { - parent::tearDown(); - } - public function test_caller_sha1() { $sha1 = $this->caller->get_sha1(); $this->caller->prepare_package(); diff --git a/mod/resource/lib.php b/mod/resource/lib.php index 6eb9dee09d..7e43f659d8 100644 --- a/mod/resource/lib.php +++ b/mod/resource/lib.php @@ -103,7 +103,7 @@ class resource_base { $morenavlinks = array($this->strresources => 'index.php?id='.$this->course->id, $this->resource->name => ''); - $PAGE->print_header($this->course->shortname.': %fullname%', $morenavlinks, "", "", + $PAGE->print_header($this->course->shortname.': %fullname%', $morenavlinks, "", "", update_module_button($this->cm->id, $this->course->id, $this->strresource)); echo ''; @@ -728,6 +728,7 @@ class resource_portfolio_caller extends portfolio_module_caller_base { require_once($this->resourcefile); $this->resource= new $resourceclass($this->cm->id); if (!is_callable(array($this->resource, 'portfolio_prepare_package')) || !is_callable(array($this->resource, 'portfolio_get_sha1'))) { + debug_print_backtrace(); throw new portfolio_exception('portfolionotimplemented', 'resource', null, $this->cm->type); } $this->supportedformats = array(self::type_to_format($this->cm->type)); diff --git a/mod/resource/simpletest/test_resource_portfolio_callers.php b/mod/resource/simpletest/test_resource_portfolio_callers.php index 992ba35740..1ad23ca767 100644 --- a/mod/resource/simpletest/test_resource_portfolio_callers.php +++ b/mod/resource/simpletest/test_resource_portfolio_callers.php @@ -19,7 +19,7 @@ class testResourcePortfolioCallers extends portfoliolib_test { $resource_type = new stdClass(); $resource_type->type = GENERATOR_SEQUENCE; - $resource_type->options = array('file', 'text', 'html'); + $resource_type->options = array('text', 'html'); $settings = array('quiet' => 1, 'pre_cleanup' => 1, 'modules_list' => array($this->module_type), -- 2.39.5