lib MDL-19236 Added boilerplates and copyrights
[moodle.git] / lib / simpletestlib.php
CommitLineData
56768525 1<?php // $Id$
3ef8c936 2/**
3 * Utility functions to make unit testing easier.
b9c639d6 4 *
3ef8c936 5 * These functions, particularly the the database ones, are quick and
b9c639d6 6 * dirty methods for getting things done in test cases. None of these
3ef8c936 7 * methods should be used outside test code.
8 *
9 * @copyright &copy; 2006 The Open University
10 * @author T.J.Hunt@open.ac.uk
11 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
12 * @version $Id$
13 * @package SimpleTestEx
14 */
15
16require_once(dirname(__FILE__) . '/../config.php');
17require_once($CFG->libdir . '/simpletestlib/simpletest.php');
18require_once($CFG->libdir . '/simpletestlib/unit_tester.php');
19require_once($CFG->libdir . '/simpletestlib/expectation.php');
4309bd17 20require_once($CFG->libdir . '/simpletestlib/reporter.php');
a205dcdc 21require_once($CFG->libdir . '/simpletestlib/web_tester.php');
76144765 22require_once($CFG->libdir . '/simpletestlib/mock_objects.php');
3ef8c936 23
24/**
25 * Recursively visit all the files in the source tree. Calls the callback
b9c639d6 26 * function with the pathname of each file found.
27 *
28 * @param $path the folder to start searching from.
3ef8c936 29 * @param $callback the function to call with the name of each file found.
30 * @param $fileregexp a regexp used to filter the search (optional).
b9c639d6 31 * @param $exclude If true, pathnames that match the regexp will be ingored. If false,
3ef8c936 32 * only files that match the regexp will be included. (default false).
33 * @param array $ignorefolders will not go into any of these folders (optional).
b9c639d6 34 */
3ef8c936 35function recurseFolders($path, $callback, $fileregexp = '/.*/', $exclude = false, $ignorefolders = array()) {
36 $files = scandir($path);
37
38 foreach ($files as $file) {
39 $filepath = $path .'/'. $file;
2e710be8 40 if (strpos($file, '.') === 0) {
41 /// Don't check hidden files.
3ef8c936 42 continue;
43 } else if (is_dir($filepath)) {
44 if (!in_array($filepath, $ignorefolders)) {
45 recurseFolders($filepath, $callback, $fileregexp, $exclude, $ignorefolders);
46 }
47 } else if ($exclude xor preg_match($fileregexp, $filepath)) {
48 call_user_func($callback, $filepath);
49 }
50 }
51}
52
53/**
54 * An expectation for comparing strings ignoring whitespace.
55 */
56class IgnoreWhitespaceExpectation extends SimpleExpectation {
57 var $expect;
58
59 function IgnoreWhitespaceExpectation($content, $message = '%s') {
60 $this->SimpleExpectation($message);
61 $this->expect=$this->normalise($content);
62 }
63
64 function test($ip) {
65 return $this->normalise($ip)==$this->expect;
66 }
67
68 function normalise($text) {
69 return preg_replace('/\s+/m',' ',trim($text));
70 }
71
72 function testMessage($ip) {
73 return "Input string [$ip] doesn't match the required value.";
74 }
75}
76
77/**
78 * An Expectation that two arrays contain the same list of values.
79 */
80class ArraysHaveSameValuesExpectation extends SimpleExpectation {
81 var $expect;
82
83 function ArraysHaveSameValuesExpectation($expected, $message = '%s') {
84 $this->SimpleExpectation($message);
85 if (!is_array($expected)) {
86 trigger_error('Attempt to create an ArraysHaveSameValuesExpectation ' .
87 'with an expected value that is not an array.');
88 }
89 $this->expect = $this->normalise($expected);
90 }
91
92 function test($actual) {
93 return $this->normalise($actual) == $this->expect;
94 }
95
96 function normalise($array) {
97 sort($array);
98 return $array;
99 }
100
101 function testMessage($actual) {
102 return 'Array [' . implode(', ', $actual) .
103 '] does not contain the expected list of values [' . implode(', ', $this->expect) . '].';
104 }
105}
106
107/**
108 * An Expectation that compares to objects, and ensures that for every field in the
109 * expected object, there is a key of the same name in the actual object, with
110 * the same value. (The actual object may have other fields to, but we ignore them.)
111 */
112class CheckSpecifiedFieldsExpectation extends SimpleExpectation {
113 var $expect;
114
115 function CheckSpecifiedFieldsExpectation($expected, $message = '%s') {
116 $this->SimpleExpectation($message);
117 if (!is_object($expected)) {
118 trigger_error('Attempt to create a CheckSpecifiedFieldsExpectation ' .
119 'with an expected value that is not an object.');
120 }
121 $this->expect = $expected;
122 }
123
124 function test($actual) {
125 foreach ($this->expect as $key => $value) {
126 if (isset($value) && isset($actual->$key) && $actual->$key == $value) {
127 // OK
128 } else if (is_null($value) && is_null($actual->$key)) {
129 // OK
130 } else {
131 return false;
132 }
133 }
134 return true;
135 }
136
137 function testMessage($actual) {
138 $mismatches = array();
139 foreach ($this->expect as $key => $value) {
140 if (isset($value) && isset($actual->$key) && $actual->$key == $value) {
141 // OK
142 } else if (is_null($value) && is_null($actual->$key)) {
143 // OK
144 } else {
3efb762e 145 $mismatches[] = $key . ' (expected [' . $value . '] got [' . $actual->$key . '].';
3ef8c936 146 }
147 }
148 return 'Actual object does not have all the same fields with the same values as the expected object (' .
149 implode(', ', $mismatches) . ').';
150 }
151}
152
f68cb08b 153/**
154 * This class lets you write unit tests that access a separate set of test
155 * tables with a different prefix. Only those tables you explicitly ask to
156 * be created will be.
240be1d7 157 *
158 * This class has failities for flipping $USER->id.
159 *
160 * The tear-down method for this class should automatically revert any changes
161 * you make during test set-up using the metods defined here. That is, it will
162 * drop tables for you automatically and revert to the real $DB and $USER->id.
f68cb08b 163 */
164class UnitTestCaseUsingDatabase extends UnitTestCase {
165 private $realdb;
166 protected $testdb;
82701e24 167 private $realuserid = null;
f68cb08b 168 private $tables = array();
169
f68cb08b 170 public function __construct($label = false) {
171 global $DB, $CFG;
172
82701e24 173 // Complain if we get this far and $CFG->unittestprefix is not set.
f68cb08b 174 if (empty($CFG->unittestprefix)) {
175 throw new coding_exception('You cannot use UnitTestCaseUsingDatabase unless you set $CFG->unittestprefix.');
176 }
82701e24 177
178 // Only do this after the above text.
f68cb08b 179 parent::UnitTestCase($label);
180
82701e24 181 // Create the test DB instance.
f68cb08b 182 $this->realdb = $DB;
183 $this->testdb = moodle_database::get_driver_instance($CFG->dbtype, $CFG->dblibrary);
184 $this->testdb->connect($CFG->dbhost, $CFG->dbuser, $CFG->dbpass, $CFG->dbname, $CFG->unittestprefix);
185 }
186
187 /**
188 * Switch to using the test database for all queries until further notice.
f68cb08b 189 */
190 protected function switch_to_test_db() {
191 global $DB;
192 if ($DB === $this->testdb) {
193 debugging('switch_to_test_db called when the test DB was already selected. This suggest you are doing something wrong and dangerous. Please review your code immediately.', DEBUG_DEVELOPER);
194 }
195 $DB = $this->testdb;
196 }
197
198 /**
199 * Revert to using the test database for all future queries.
200 */
201 protected function revert_to_real_db() {
202 global $DB;
203 if ($DB !== $this->testdb) {
7fa95ef1 204 debugging('revert_to_real_db called when the test DB was not already selected. This suggest you are doing something wrong and dangerous. Please review your code immediately.', DEBUG_DEVELOPER);
f68cb08b 205 }
206 $DB = $this->realdb;
207 }
208
82701e24 209 /**
210 * Switch $USER->id to a test value.
82701e24 211 *
212 * It might be worth making this method do more robuse $USER switching in future,
213 * however, this is sufficient for my needs at present.
214 */
215 protected function switch_global_user_id($userid) {
216 global $USER;
217 if (!is_null($this->realuserid)) {
218 debugging('switch_global_user_id called when $USER->id was already switched to a different value. This suggest you are doing something wrong and dangerous. Please review your code immediately.', DEBUG_DEVELOPER);
219 } else {
220 $this->realuserid = $USER->id;
221 }
222 $USER->id = $userid;
223 }
224
225 /**
226 * Revert $USER->id to the real value.
227 */
228 protected function revert_global_user_id() {
229 global $USER;
230 if (is_null($this->realuserid)) {
231 debugging('revert_global_user_id called without switch_global_user_id having been called first. This suggest you are doing something wrong and dangerous. Please review your code immediately.', DEBUG_DEVELOPER);
232 } else {
233 $USER->id = $this->realuserid;
234 $this->realuserid = null;
235 }
236 }
237
f68cb08b 238 /**
239 * Check that the user has not forgotten to clean anything up, and if they
240 * have, display a rude message and clean it up for them.
241 */
240be1d7 242 private function automatic_clean_up() {
f68cb08b 243 global $DB;
82701e24 244 $cleanmore = false;
f68cb08b 245
240be1d7 246 // Drop any test tables that were created.
f68cb08b 247 foreach ($this->tables as $tablename => $notused) {
248 $this->drop_test_table($tablename);
249 }
250
240be1d7 251 // Switch back to the real DB if necessary.
f68cb08b 252 if ($DB !== $this->realdb) {
f68cb08b 253 $this->revert_to_real_db();
82701e24 254 $cleanmore = true;
255 }
256
240be1d7 257 // revert_global_user_id if necessary.
82701e24 258 if (!is_null($this->realuserid)) {
82701e24 259 $this->revert_global_user_id();
260 $cleanmore = true;
261 }
262
263 if ($cleanmore) {
264 accesslib_clear_all_caches_for_unit_testing();
f68cb08b 265 }
266 }
267
268 public function tearDown() {
240be1d7 269 $this->automatic_clean_up();
f68cb08b 270 parent::tearDown();
271 }
272
273 public function __destruct() {
240be1d7 274 // Should not be necessary thanks to tearDown, but no harm in belt and braces.
275 $this->automatic_clean_up();
f68cb08b 276 }
277
278 /**
279 * Create a test table just like a real one, getting getting the definition from
280 * the specified install.xml file.
281 * @param string $tablename the name of the test table.
282 * @param string $installxmlfile the install.xml file in which this table is defined.
283 * $CFG->dirroot . '/' will be prepended, and '/db/install.xml' appended,
284 * so you need only specify, for example, 'mod/quiz'.
285 */
286 protected function create_test_table($tablename, $installxmlfile) {
287 global $CFG;
288 if (isset($this->tables[$tablename])) {
c39a4607 289 debugging('You are attempting to create test table ' . $tablename . ' again. It already exists. Please review your code immediately.', DEBUG_DEVELOPER);
f68cb08b 290 return;
291 }
292 $dbman = $this->testdb->get_manager();
293 $dbman->install_one_table_from_xmldb_file($CFG->dirroot . '/' . $installxmlfile . '/db/install.xml', $tablename);
294 $this->tables[$tablename] = 1;
295 }
296
297 /**
298 * Convenience method for calling create_test_table repeatedly.
299 * @param array $tablenames an array of table names.
300 * @param string $installxmlfile the install.xml file in which this table is defined.
301 * $CFG->dirroot . '/' will be prepended, and '/db/install.xml' appended,
302 * so you need only specify, for example, 'mod/quiz'.
303 */
304 protected function create_test_tables($tablenames, $installxmlfile) {
305 foreach ($tablenames as $tablename) {
306 $this->create_test_table($tablename, $installxmlfile);
307 }
308 }
309
310 /**
311 * Drop a test table.
312 * @param $tablename the name of the test table.
313 */
314 protected function drop_test_table($tablename) {
315 if (!isset($this->tables[$tablename])) {
316 debugging('You are attempting to drop test table ' . $tablename . ' but it does not exist. Please review your code immediately.', DEBUG_DEVELOPER);
317 return;
318 }
319 $dbman = $this->testdb->get_manager();
320 $table = new xmldb_table($tablename);
321 $dbman->drop_table($table);
322 unset($this->tables[$tablename]);
323 }
324
325 /**
326 * Convenience method for calling drop_test_table repeatedly.
327 * @param array $tablenames an array of table names.
328 */
329 protected function drop_test_tables($tablenames) {
330 foreach ($tablenames as $tablename) {
331 $this->drop_test_table($tablename);
332 }
333 }
334
335 /**
336 * Load a table with some rows of data. A typical call would look like:
337 *
338 * $config = $this->load_test_data('config_plugins',
339 * array('plugin', 'name', 'value'), array(
340 * array('frog', 'numlegs', 2),
341 * array('frog', 'sound', 'croak'),
342 * array('frog', 'action', 'jump'),
343 * ));
344 *
345 * @param string $table the table name.
346 * @param array $cols the columns to fill.
347 * @param array $data the data to load.
348 * @return array $objects corresponding to $data.
349 */
350 protected function load_test_data($table, array $cols, array $data) {
351 $results = array();
352 foreach ($data as $rowid => $row) {
353 $obj = new stdClass;
354 foreach ($cols as $key => $colname) {
355 $obj->$colname = $row[$key];
356 }
357 $obj->id = $this->testdb->insert_record($table, $obj);
358 $results[$rowid] = $obj;
359 }
360 return $results;
361 }
362
363 /**
364 * Clean up data loaded with load_test_data. The call corresponding to the
365 * example load above would be:
366 *
367 * $this->delete_test_data('config_plugins', $config);
368 *
369 * @param string $table the table name.
370 * @param array $rows the rows to delete. Actually, only $rows[$key]->id is used.
371 */
372 protected function delete_test_data($table, array $rows) {
373 $ids = array();
374 foreach ($rows as $row) {
375 $ids[] = $row->id;
376 }
377 $this->testdb->delete_records_list($table, 'id', $ids);
378 }
379}
380
58fa5d6f 381class FakeDBUnitTestCase extends UnitTestCase {
356e0010 382 public $tables = array();
90997d6d 383 public $pkfile;
384 public $cfg;
274e2947 385 public $DB;
356e0010 386
90997d6d 387 /**
388 * In the constructor, record the max(id) of each test table into a csv file.
389 * If this file already exists, it means that a previous run of unit tests
390 * did not complete, and has left data undeleted in the DB. This data is then
391 * deleted and the file is retained. Otherwise it is created.
392 * @throws moodle_exception if CSV file cannot be created
393 */
b9c639d6 394 public function __construct($label = false) {
84ebf08d 395 global $DB, $CFG;
396
397 if (empty($CFG->unittestprefix)) {
398 return;
399 }
400
b9c639d6 401 parent::UnitTestCase($label);
90997d6d 402 // MDL-16483 Get PKs and save data to text file
84ebf08d 403
90997d6d 404 $this->pkfile = $CFG->dataroot.'/testtablespks.csv';
405 $this->cfg = $CFG;
274e2947 406
407 UnitTestDB::instantiate();
90997d6d 408
409 $tables = $DB->get_tables();
410
411 // The file exists, so use it to truncate tables (tests aborted before test data could be removed)
412 if (file_exists($this->pkfile)) {
413 $this->truncate_test_tables($this->get_table_data($this->pkfile));
414
415 } else { // Create the file
416 $tabledata = '';
417
418 foreach ($tables as $table) {
58fa5d6f 419 if ($table != 'sessions') {
90997d6d 420 if (!$max_id = $DB->get_field_sql("SELECT MAX(id) FROM {$CFG->unittestprefix}{$table}")) {
421 $max_id = 0;
422 }
423 $tabledata .= "$table, $max_id\n";
424 }
425 }
426 if (!file_put_contents($this->pkfile, $tabledata)) {
5d1381c2 427 $a = new stdClass();
428 $a->filename = $this->pkfile;
429 throw new moodle_exception('testtablescsvfileunwritable', 'simpletest', '', $a);
90997d6d 430 }
431 }
432 }
433
434 /**
435 * Given an array of tables and their max id, truncates all test table records whose id is higher than the ones in the $tabledata array.
436 * @param array $tabledata
437 */
438 private function truncate_test_tables($tabledata) {
439 global $CFG, $DB;
440
84ebf08d 441 if (empty($CFG->unittestprefix)) {
442 return;
443 }
444
90997d6d 445 $tables = $DB->get_tables();
446
447 foreach ($tables as $table) {
3f57bd45 448 if ($table != 'sessions' && isset($tabledata[$table])) {
5d1381c2 449 // $DB->delete_records_select($table, "id > ?", array($tabledata[$table]));
90997d6d 450 }
451 }
356e0010 452 }
453
90997d6d 454 /**
455 * Given a filename, opens it and parses the csv contained therein. It expects two fields per line:
456 * 1. Table name
457 * 2. Max id
458 * @param string $filename
459 * @throws moodle_exception if file doesn't exist
460 */
461 public function get_table_data($filename) {
84ebf08d 462 global $CFG;
463
464 if (empty($CFG->unittestprefix)) {
465 return;
466 }
467
90997d6d 468 if (file_exists($this->pkfile)) {
469 $handle = fopen($this->pkfile, 'r');
470 $tabledata = array();
471
472 while (($data = fgetcsv($handle, 1000, ",")) !== false) {
473 $tabledata[$data[0]] = $data[1];
474 }
475 return $tabledata;
476 } else {
5d1381c2 477 $a = new stdClass();
478 $a->filename = $this->pkfile;
479 debug_print_backtrace();
480 throw new moodle_exception('testtablescsvfilemissing', 'simpletest', '', $a);
90997d6d 481 return false;
482 }
483 }
484
485 /**
486 * Method called before each test method. Replaces the real $DB with the one configured for unit tests (different prefix, $CFG->unittestprefix).
487 * Also detects if this config setting is properly set, and if the user table exists.
488 * TODO Improve detection of incorrectly built DB test tables (e.g. detect version discrepancy and offer to upgrade/rebuild)
489 */
356e0010 490 public function setUp() {
84ebf08d 491 global $DB, $CFG;
492
493 if (empty($CFG->unittestprefix)) {
494 return;
495 }
496
356e0010 497 parent::setUp();
274e2947 498 $this->DB =& $DB;
915d745f 499 ob_start();
5d1381c2 500 }
501
502 /**
503 * Method called after each test method. Doesn't do anything extraordinary except restore the global $DB to the real one.
504 */
505 public function tearDown() {
84ebf08d 506 global $DB, $CFG;
507
508 if (empty($CFG->unittestprefix)) {
509 return;
510 }
511
274e2947 512 if (empty($DB)) {
513 $DB = $this->DB;
514 }
5d1381c2 515 $DB->cleanup();
516 parent::tearDown();
747f4a4c 517
518 // Output buffering
915d745f 519 if (ob_get_length() > 0) {
520 ob_end_flush();
521 }
5d1381c2 522 }
523
524 /**
525 * 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
526 * It should also detect if data is missing from the original tables.
527 */
528 public function __destruct() {
529 global $CFG, $DB;
530
84ebf08d 531 if (empty($CFG->unittestprefix)) {
532 return;
533 }
534
5d1381c2 535 $CFG = $this->cfg;
536 $this->tearDown();
537 UnitTestDB::restore();
538 fulldelete($this->pkfile);
539 }
821e4ecf 540
541 /**
542 * Load a table with some rows of data. A typical call would look like:
543 *
544 * $config = $this->load_test_data('config_plugins',
545 * array('plugin', 'name', 'value'), array(
546 * array('frog', 'numlegs', 2),
547 * array('frog', 'sound', 'croak'),
548 * array('frog', 'action', 'jump'),
549 * ));
550 *
551 * @param string $table the table name.
552 * @param array $cols the columns to fill.
553 * @param array $data the data to load.
554 * @return array $objects corresponding to $data.
555 */
556 public function load_test_data($table, array $cols, array $data) {
84ebf08d 557 global $CFG, $DB;
558
559 if (empty($CFG->unittestprefix)) {
560 return;
561 }
562
821e4ecf 563 $results = array();
564 foreach ($data as $rowid => $row) {
565 $obj = new stdClass;
566 foreach ($cols as $key => $colname) {
567 $obj->$colname = $row[$key];
568 }
569 $obj->id = $DB->insert_record($table, $obj);
570 $results[$rowid] = $obj;
571 }
572 return $results;
573 }
574
575 /**
576 * Clean up data loaded with load_test_data. The call corresponding to the
577 * example load above would be:
578 *
579 * $this->delete_test_data('config_plugins', $config);
580 *
581 * @param string $table the table name.
582 * @param array $rows the rows to delete. Actually, only $rows[$key]->id is used.
583 */
584 public function delete_test_data($table, array $rows) {
84ebf08d 585 global $CFG, $DB;
586
587 if (empty($CFG->unittestprefix)) {
588 return;
589 }
590
821e4ecf 591 $ids = array();
592 foreach ($rows as $row) {
593 $ids[] = $row->id;
594 }
595 $DB->delete_records_list($table, 'id', $ids);
596 }
5d1381c2 597}
598
599/**
600 * This is a Database Engine proxy class: It replaces the global object $DB with itself through a call to the
601 * static instantiate() method, and restores the original global $DB through restore().
602 * Internally, it routes all calls to $DB to a real instance of the database engine (aggregated as a member variable),
603 * except those that are defined in this proxy class. This makes it possible to add extra code to the database engine
604 * without subclassing it.
605 */
606class UnitTestDB {
607 public static $DB;
608 private static $real_db;
356e0010 609
5d1381c2 610 public $table_data = array();
611
5d1381c2 612 /**
613 * Call this statically to connect to the DB using the unittest prefix, instantiate
614 * the unit test db, store it as a member variable, instantiate $this and use it as the new global $DB.
615 */
616 public static function instantiate() {
617 global $CFG, $DB;
618 UnitTestDB::$real_db = clone($DB);
90997d6d 619 if (empty($CFG->unittestprefix)) {
b9c639d6 620 print_error("prefixnotset", 'simpletest');
621 }
622
274e2947 623 if (empty(UnitTestDB::$DB)) {
624 UnitTestDB::$DB = moodle_database::get_driver_instance($CFG->dbtype, $CFG->dblibrary);
beaa43db 625 UnitTestDB::$DB->connect($CFG->dbhost, $CFG->dbuser, $CFG->dbpass, $CFG->dbname, $CFG->unittestprefix);
274e2947 626 }
627
5d1381c2 628 $manager = UnitTestDB::$DB->get_manager();
356e0010 629
630 if (!$manager->table_exists('user')) {
b9c639d6 631 print_error('tablesnotsetup', 'simpletest');
632 }
5d1381c2 633
634 $DB = new UnitTestDB();
635 }
636
637 public function __call($method, $args) {
638 // Set args to null if they don't exist (up to 10 args should do)
639 if (!method_exists($this, $method)) {
640 return call_user_func_array(array(UnitTestDB::$DB, $method), $args);
641 } else {
642 call_user_func_array(array($this, $method), $args);
643 }
644 }
645
646 public function __get($variable) {
647 return UnitTestDB::$DB->$variable;
648 }
649
650 public function __set($variable, $value) {
651 UnitTestDB::$DB->$variable = $value;
652 }
653
654 public function __isset($variable) {
655 return isset(UnitTestDB::$DB->$variable);
656 }
657
658 public function __unset($variable) {
659 unset(UnitTestDB::$DB->$variable);
b9c639d6 660 }
661
90997d6d 662 /**
5d1381c2 663 * Overriding insert_record to keep track of the ids inserted during unit tests, so that they can be deleted afterwards
90997d6d 664 */
5d1381c2 665 public function insert_record($table, $dataobject, $returnid=true, $bulk=false) {
356e0010 666 global $DB;
5d1381c2 667 $id = UnitTestDB::$DB->insert_record($table, $dataobject, $returnid, $bulk);
668 $this->table_data[$table][] = $id;
669 return $id;
670 }
356e0010 671
5d1381c2 672 /**
673 * Overriding update_record: If we are updating a record that was NOT inserted by unit tests,
674 * throw an exception and cancel update.
675 * @throws moodle_exception If trying to update a record not inserted by unit tests.
676 */
677 public function update_record($table, $dataobject, $bulk=false) {
678 global $DB;
747f4a4c 679 if ((empty($this->table_data[$table]) || !in_array($dataobject->id, $this->table_data[$table])) && !($table == 'course_categories' && $dataobject->id == 1)) {
274e2947 680 // return UnitTestDB::$DB->update_record($table, $dataobject, $bulk);
681 $a = new stdClass();
682 $a->id = $dataobject->id;
683 $a->table = $table;
684 debug_print_backtrace();
685 throw new moodle_exception('updatingnoninsertedrecord', 'simpletest', '', $a);
5d1381c2 686 } else {
687 return UnitTestDB::$DB->update_record($table, $dataobject, $bulk);
688 }
b9c639d6 689 }
690
691 /**
5d1381c2 692 * Overriding delete_record: If we are deleting a record that was NOT inserted by unit tests,
693 * throw an exception and cancel delete.
694 * @throws moodle_exception If trying to delete a record not inserted by unit tests.
b9c639d6 695 */
274e2947 696 public function delete_records($table, array $conditions=array()) {
5d1381c2 697 global $DB;
747f4a4c 698 $tables_to_ignore = array('context_temp');
699
5d1381c2 700 $a = new stdClass();
701 $a->table = $table;
702
703 // Get ids matching conditions
704 if (!$ids_to_delete = $DB->get_field($table, 'id', $conditions)) {
705 return UnitTestDB::$DB->delete_records($table, $conditions);
706 }
707
708 $proceed_with_delete = true;
709
710 if (!is_array($ids_to_delete)) {
711 $ids_to_delete = array($ids_to_delete);
712 }
713
714 foreach ($ids_to_delete as $id) {
747f4a4c 715 if (!in_array($table, $tables_to_ignore) && (empty($this->table_data[$table]) || !in_array($id, $this->table_data[$table]))) {
5d1381c2 716 $proceed_with_delete = false;
717 $a->id = $id;
718 break;
719 }
720 }
721
722 if ($proceed_with_delete) {
723 return UnitTestDB::$DB->delete_records($table, $conditions);
724 } else {
725 debug_print_backtrace();
726 throw new moodle_exception('deletingnoninsertedrecord', 'simpletest', '', $a);
727 }
b9c639d6 728 }
b9c639d6 729
5d1381c2 730 /**
731 * Overriding delete_records_select: If we are deleting a record that was NOT inserted by unit tests,
732 * throw an exception and cancel delete.
733 * @throws moodle_exception If trying to delete a record not inserted by unit tests.
734 */
735 public function delete_records_select($table, $select, array $params=null) {
736 global $DB;
737 $a = new stdClass();
738 $a->table = $table;
739
740 // Get ids matching conditions
741 if (!$ids_to_delete = $DB->get_field_select($table, 'id', $select, $params)) {
742 return UnitTestDB::$DB->delete_records_select($table, $select, $params);
743 }
744
745 $proceed_with_delete = true;
746
747 foreach ($ids_to_delete as $id) {
748 if (!in_array($id, $this->table_data[$table])) {
749 $proceed_with_delete = false;
750 $a->id = $id;
751 break;
752 }
753 }
754
755 if ($proceed_with_delete) {
756 return UnitTestDB::$DB->delete_records_select($table, $select, $params);
757 } else {
758 debug_print_backtrace();
759 throw new moodle_exception('deletingnoninsertedrecord', 'simpletest', '', $a);
760 }
761 }
762
763 /**
764 * Removes from the test DB all the records that were inserted during unit tests,
765 */
766 public function cleanup() {
767 global $DB;
768 foreach ($this->table_data as $table => $ids) {
769 foreach ($ids as $id) {
770 $DB->delete_records($table, array('id' => $id));
771 }
772 }
773 }
774
775 /**
776 * Restores the global $DB object.
777 */
778 public static function restore() {
779 global $DB;
780 $DB = UnitTestDB::$real_db;
781 }
782
783 public function get_field($table, $return, array $conditions) {
784 if (!is_array($conditions)) {
785 debug_print_backtrace();
786 }
787 return UnitTestDB::$DB->get_field($table, $return, $conditions);
788 }
789}
bb48a537 790?>