MDL-32323 all our base test case classes should be abstract
[moodle.git] / lib / phpunit / lib.php
CommitLineData
5bd40408
PS
1<?php
2// This file is part of Moodle - http://moodle.org/
3//
4// Moodle is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// Moodle is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
16
17/**
18 * Various PHPUnit classes and functions
19 *
7aea08e1 20 * @package core
5bd40408
PS
21 * @category phpunit
22 * @copyright 2012 Petr Skoda {@link http://skodak.org}
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24 */
25
7aea08e1 26require_once 'PHPUnit/Autoload.php';
5bd40408
PS
27
28
29/**
30 * Collection of utility methods.
31 *
7aea08e1
SH
32 * @package core
33 * @category phpunit
5bd40408
PS
34 * @copyright 2012 Petr Skoda {@link http://skodak.org}
35 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
36 */
37class phpunit_util {
38 /**
39 * @var array original content of all database tables
40 */
41 protected static $tabledata = null;
42
0b9251e3
PS
43 /**
44 * @var array original structure of all database tables
45 */
46 protected static $tablestructure = null;
47
7aea08e1 48 /**
920f4efe 49 * @var array An array of original globals, restored after each test
7aea08e1 50 */
5bd40408
PS
51 protected static $globals = array();
52
a3d5830a
PS
53 /**
54 * @var int last value of db writes counter, used for db resetting
55 */
50be93d1 56 public static $lastdbwrites = null;
a3d5830a
PS
57
58 /**
59 * @var phpunit_data_generator
60 */
61 protected static $generator = null;
62
920f4efe
PS
63 /**
64 * @var resource used for prevention of parallel test execution
65 */
4be2ad36
PS
66 protected static $lockhandle = null;
67
68 /**
920f4efe 69 * Prevent parallel test execution - this can not work in Moodle because we modify database and dataroot.
4be2ad36
PS
70 *
71 * Note: do not call manually!
72 *
920f4efe 73 * @internal
4be2ad36
PS
74 * @static
75 * @return void
76 */
77 public static function acquire_test_lock() {
78 global $CFG;
6c583c75
PS
79 if (!file_exists("$CFG->phpunit_dataroot/phpunit")) {
80 // dataroot not initialised yet
81 return;
82 }
4be2ad36
PS
83 if (!file_exists("$CFG->phpunit_dataroot/phpunit/lock")) {
84 file_put_contents("$CFG->phpunit_dataroot/phpunit/lock", 'This file prevents concurrent execution of Moodle PHPUnit tests');
85 phpunit_boostrap_fix_file_permissions("$CFG->phpunit_dataroot/phpunit/lock");
86 }
87 if (self::$lockhandle = fopen("$CFG->phpunit_dataroot/phpunit/lock", 'r')) {
88 $wouldblock = null;
89 $locked = flock(self::$lockhandle, (LOCK_EX | LOCK_NB), $wouldblock);
e3701541
PS
90 if (!$locked) {
91 if ($wouldblock) {
92 echo "Waiting for other test execution to complete...\n";
93 }
4be2ad36
PS
94 $locked = flock(self::$lockhandle, LOCK_EX);
95 }
96 if (!$locked) {
97 fclose(self::$lockhandle);
98 self::$lockhandle = null;
99 }
100 }
101 register_shutdown_function(array('phpunit_util', 'release_test_lock'));
102 }
103
104 /**
105 * Note: do not call manually!
920f4efe 106 * @internal
4be2ad36
PS
107 * @static
108 * @return void
109 */
110 public static function release_test_lock() {
111 if (self::$lockhandle) {
112 flock(self::$lockhandle, LOCK_UN);
113 fclose(self::$lockhandle);
114 self::$lockhandle = null;
115 }
116 }
117
a3d5830a
PS
118 /**
119 * Get data generator
120 * @static
121 * @return phpunit_data_generator
122 */
123 public static function get_data_generator() {
124 if (is_null(self::$generator)) {
125 require_once(__DIR__.'/generatorlib.php');
126 self::$generator = new phpunit_data_generator();
127 }
128 return self::$generator;
129 }
130
5bd40408
PS
131 /**
132 * Returns contents of all tables right after installation.
133 * @static
134 * @return array $table=>$records
135 */
136 protected static function get_tabledata() {
137 global $CFG;
138
a3d5830a
PS
139 if (!file_exists("$CFG->dataroot/phpunit/tabledata.ser")) {
140 // not initialised yet
141 return array();
142 }
143
5bd40408
PS
144 if (!isset(self::$tabledata)) {
145 $data = file_get_contents("$CFG->dataroot/phpunit/tabledata.ser");
146 self::$tabledata = unserialize($data);
147 }
148
149 if (!is_array(self::$tabledata)) {
7b0ff213 150 phpunit_bootstrap_error(1, 'Can not read dataroot/phpunit/tabledata.ser or invalid format, reinitialize test database.');
5bd40408
PS
151 }
152
153 return self::$tabledata;
154 }
155
0b9251e3
PS
156 /**
157 * Returns structure of all tables right after installation.
158 * @static
159 * @return array $table=>$records
160 */
161 protected static function get_tablestructure() {
162 global $CFG;
163
164 if (!file_exists("$CFG->dataroot/phpunit/tablestructure.ser")) {
165 // not initialised yet
166 return array();
167 }
168
169 if (!isset(self::$tablestructure)) {
170 $data = file_get_contents("$CFG->dataroot/phpunit/tablestructure.ser");
171 self::$tablestructure = unserialize($data);
172 }
173
174 if (!is_array(self::$tablestructure)) {
175 phpunit_bootstrap_error(1, 'Can not read dataroot/phpunit/tablestructure.ser or invalid format, reinitialize test database.');
176 }
177
178 return self::$tablestructure;
179 }
180
e10736b3
PS
181 /**
182 * Returns list of tables that are unmodified and empty.
183 *
184 * @static
185 * @return array of table names, empty if unknown
186 */
187 protected static function guess_unmodified_empty_tables() {
188 global $DB;
189
190 $dbfamily = $DB->get_dbfamily();
191
192 if ($dbfamily === 'mysql') {
193 $empties = array();
194 $prefix = $DB->get_prefix();
195 $rs = $DB->get_recordset_sql("SHOW TABLE STATUS LIKE ?", array($prefix.'%'));
196 foreach ($rs as $info) {
197 $table = strtolower($info->name);
198 if (strpos($table, $prefix) !== 0) {
199 // incorrect table match caused by _
200 continue;
201 }
202 if (!is_null($info->auto_increment)) {
203 $table = preg_replace('/^'.preg_quote($prefix, '/').'/', '', $table);
204 if ($info->auto_increment == 1) {
205 $empties[$table] = $table;
206 }
207 }
208 }
209 $rs->close();
210 return $empties;
211
212 } else {
213 return array();
214 }
215 }
216
8b5413cc 217 /**
ab483c0a
PS
218 * Reset all database sequences to initial values.
219 *
8b5413cc 220 * @static
e10736b3 221 * @param array $empties tables that are known to be unmodified and empty
8b5413cc
PS
222 * @return void
223 */
e10736b3 224 public static function reset_all_database_sequences(array $empties = null) {
8b5413cc
PS
225 global $DB;
226
8b5413cc
PS
227 if (!$data = self::get_tabledata()) {
228 // not initialised yet
ab483c0a 229 return;
8b5413cc
PS
230 }
231 if (!$structure = self::get_tablestructure()) {
232 // not initialised yet
ab483c0a 233 return;
8b5413cc
PS
234 }
235
236 $dbfamily = $DB->get_dbfamily();
237 if ($dbfamily === 'postgres') {
238 $queries = array();
239 $prefix = $DB->get_prefix();
240 foreach ($data as $table=>$records) {
ab483c0a 241 if (isset($structure[$table]['id']) and $structure[$table]['id']->auto_increment) {
8b5413cc
PS
242 if (empty($records)) {
243 $nextid = 1;
244 } else {
245 $lastrecord = end($records);
246 $nextid = $lastrecord->id + 1;
247 }
248 $queries[] = "ALTER SEQUENCE {$prefix}{$table}_id_seq RESTART WITH $nextid";
249 }
250 }
251 if ($queries) {
252 $DB->change_database_structure(implode(';', $queries));
253 }
8b5413cc 254
ab483c0a
PS
255 } else if ($dbfamily === 'mysql') {
256 $sequences = array();
8b5413cc
PS
257 $prefix = $DB->get_prefix();
258 $rs = $DB->get_recordset_sql("SHOW TABLE STATUS LIKE ?", array($prefix.'%'));
259 foreach ($rs as $info) {
260 $table = strtolower($info->name);
261 if (strpos($table, $prefix) !== 0) {
262 // incorrect table match caused by _
263 continue;
264 }
265 if (!is_null($info->auto_increment)) {
e10736b3 266 $table = preg_replace('/^'.preg_quote($prefix, '/').'/', '', $table);
8b5413cc
PS
267 $sequences[$table] = $info->auto_increment;
268 }
269 }
270 $rs->close();
ab483c0a
PS
271 foreach ($data as $table=>$records) {
272 if (isset($structure[$table]['id']) and $structure[$table]['id']->auto_increment) {
273 if (isset($sequences[$table])) {
274 if (empty($records)) {
275 $lastid = 0;
276 } else {
277 $lastrecord = end($records);
278 $lastid = $lastrecord->id;
279 }
280 if ($sequences[$table] != $lastid +1) {
281 $DB->get_manager()->reset_sequence($table);
282 }
8b5413cc 283
8b5413cc 284 } else {
8b5413cc
PS
285 $DB->get_manager()->reset_sequence($table);
286 }
ab483c0a
PS
287 }
288 }
8b5413cc 289
ab483c0a
PS
290 } else {
291 // note: does mssql and oracle support any kind of faster reset?
e10736b3
PS
292 if (is_null($empties)) {
293 $empties = self::guess_unmodified_empty_tables();
294 }
ab483c0a 295 foreach ($data as $table=>$records) {
e10736b3
PS
296 if (isset($empties[$table])) {
297 continue;
298 }
ab483c0a 299 if (isset($structure[$table]['id']) and $structure[$table]['id']->auto_increment) {
8b5413cc
PS
300 $DB->get_manager()->reset_sequence($table);
301 }
302 }
303 }
304 }
305
5bd40408 306 /**
a3d5830a 307 * Reset all database tables to default values.
5bd40408 308 * @static
a3d5830a 309 * @return bool true if reset done, false if skipped
5bd40408 310 */
7b0ff213 311 public static function reset_database() {
a3d5830a
PS
312 global $DB;
313
a3d5830a
PS
314 $tables = $DB->get_tables(false);
315 if (!$tables or empty($tables['config'])) {
316 // not installed yet
fe35ccaf 317 return false;
5bd40408
PS
318 }
319
fe35ccaf
PS
320 if (!is_null(self::$lastdbwrites) and self::$lastdbwrites == $DB->perf_get_writes()) {
321 return false;
322 }
323 if (!$data = self::get_tabledata()) {
324 // not initialised yet
325 return false;
326 }
0b9251e3
PS
327 if (!$structure = self::get_tablestructure()) {
328 // not initialised yet
329 return false;
330 }
728eadac 331
e10736b3
PS
332 $empties = self::guess_unmodified_empty_tables();
333
fe35ccaf
PS
334 foreach ($data as $table=>$records) {
335 if (empty($records)) {
e10736b3
PS
336 if (isset($empties[$table])) {
337 // table was not modified and is empty
338 } else {
339 $DB->delete_records($table, array());
340 }
fe35ccaf
PS
341 continue;
342 }
343
ab483c0a 344 if (isset($structure[$table]['id']) and $structure[$table]['id']->auto_increment) {
714f3998
PS
345 $currentrecords = $DB->get_records($table, array(), 'id ASC');
346 $changed = false;
347 foreach ($records as $id=>$record) {
348 if (!isset($currentrecords[$id])) {
349 $changed = true;
350 break;
351 }
352 if ((array)$record != (array)$currentrecords[$id]) {
353 $changed = true;
354 break;
728eadac 355 }
714f3998
PS
356 unset($currentrecords[$id]);
357 }
358 if (!$changed) {
359 if ($currentrecords) {
360 $lastrecord = end($records);
361 $DB->delete_records_select($table, "id > ?", array($lastrecord->id));
362 continue;
363 } else {
364 continue;
a3d5830a 365 }
a3d5830a 366 }
fe35ccaf 367 }
728eadac 368
fe35ccaf 369 $DB->delete_records($table, array());
fe35ccaf
PS
370 foreach ($records as $record) {
371 $DB->import_record($table, $record, false, true);
372 }
373 }
714f3998 374
8b5413cc 375 // reset all next record ids - aka sequences
e10736b3 376 self::reset_all_database_sequences($empties);
728eadac 377
fe35ccaf 378 // remove extra tables
714f3998
PS
379 foreach ($tables as $table) {
380 if (!isset($data[$table])) {
381 $DB->get_manager()->drop_table(new xmldb_table($table));
5bd40408 382 }
5bd40408 383 }
a3d5830a
PS
384
385 self::$lastdbwrites = $DB->perf_get_writes();
386
fe35ccaf 387 return true;
5bd40408
PS
388 }
389
728eadac 390 /**
920f4efe 391 * Purge dataroot directory
728eadac
PS
392 * @static
393 * @return void
394 */
395 public static function reset_dataroot() {
396 global $CFG;
397
398 $handle = opendir($CFG->dataroot);
399 $skip = array('.', '..', 'phpunittestdir.txt', 'phpunit', '.htaccess');
400 while (false !== ($item = readdir($handle))) {
401 if (in_array($item, $skip)) {
402 continue;
403 }
404 if (is_dir("$CFG->dataroot/$item")) {
405 remove_dir("$CFG->dataroot/$item", false);
406 } else {
407 unlink("$CFG->dataroot/$item");
408 }
409 }
410 closedir($handle);
411 make_temp_directory('');
412 make_cache_directory('');
413 make_cache_directory('htmlpurifier');
414 }
415
5bd40408
PS
416 /**
417 * Reset contents of all database tables to initial values, reset caches, etc.
418 *
419 * Note: this is relatively slow (cca 2 seconds for pg and 7 for mysql) - please use with care!
420 *
920f4efe 421 * @static
a3d5830a 422 * @param bool $logchanges log changes in global state and database in error log
a3d5830a 423 * @return void
5bd40408 424 */
7b0ff213 425 public static function reset_all_data($logchanges = false) {
458b3386 426 global $DB, $CFG, $USER, $SITE, $COURSE, $PAGE, $OUTPUT, $SESSION;
5bd40408 427
95dcf965
PS
428 // reset global $DB in case somebody mocked it
429 $DB = self::get_global_backup('DB');
430
ce9f3beb
PS
431 if ($DB->is_transaction_started()) {
432 // we can not reset inside transaction
433 $DB->force_transaction_rollback();
434 }
435
fe35ccaf 436 $resetdb = self::reset_database();
7b0ff213 437 $warnings = array();
5bd40408 438
a3d5830a 439 if ($logchanges) {
fe35ccaf 440 if ($resetdb) {
6e2cff2d 441 $warnings[] = 'Warning: unexpected database modification, resetting DB state';
a3d5830a
PS
442 }
443
444 $oldcfg = self::get_global_backup('CFG');
458b3386 445 $oldsite = self::get_global_backup('SITE');
a3d5830a
PS
446 foreach($CFG as $k=>$v) {
447 if (!property_exists($oldcfg, $k)) {
6e2cff2d 448 $warnings[] = 'Warning: unexpected new $CFG->'.$k.' value';
a3d5830a 449 } else if ($oldcfg->$k !== $CFG->$k) {
6e2cff2d 450 $warnings[] = 'Warning: unexpected change of $CFG->'.$k.' value';
a3d5830a
PS
451 }
452 unset($oldcfg->$k);
453
454 }
455 if ($oldcfg) {
456 foreach($oldcfg as $k=>$v) {
6e2cff2d 457 $warnings[] = 'Warning: unexpected removal of $CFG->'.$k;
5bd40408 458 }
5bd40408 459 }
a3d5830a
PS
460
461 if ($USER->id != 0) {
6e2cff2d 462 $warnings[] = 'Warning: unexpected change of $USER';
5bd40408 463 }
458b3386
PS
464
465 if ($COURSE->id != $oldsite->id) {
6e2cff2d 466 $warnings[] = 'Warning: unexpected change of $COURSE';
458b3386 467 }
5bd40408 468 }
5bd40408 469
920f4efe 470 // restore original globals
6e2cff2d 471 $_SERVER = self::get_global_backup('_SERVER');
a3d5830a 472 $CFG = self::get_global_backup('CFG');
458b3386
PS
473 $SITE = self::get_global_backup('SITE');
474 $COURSE = $SITE;
475
920f4efe 476 // reinitialise following globals
458b3386
PS
477 $OUTPUT = new bootstrap_renderer();
478 $PAGE = new moodle_page();
479 $FULLME = null;
480 $ME = null;
481 $SCRIPT = null;
482 $SESSION = new stdClass();
483 $_SESSION['SESSION'] =& $SESSION;
5bd40408 484
920f4efe 485 // set fresh new not-logged-in user
5bd40408
PS
486 $user = new stdClass();
487 $user->id = 0;
5bd40408
PS
488 $user->mnethostid = $CFG->mnet_localhost_id;
489 session_set_user($user);
a3d5830a
PS
490
491 // reset all static caches
492 accesslib_clear_all_caches(true);
493 get_string_manager()->reset_caches();
812013b1 494 events_get_handlers('reset');
a3d5830a
PS
495 //TODO: add more resets here and probably refactor them to new core function
496
920f4efe 497 // purge dataroot directory
728eadac 498 self::reset_dataroot();
a3d5830a 499
920f4efe 500 // restore original config once more in case resetting of caches changed CFG
a3d5830a
PS
501 $CFG = self::get_global_backup('CFG');
502
a3d5830a
PS
503 // inform data generator
504 self::get_data_generator()->reset();
505
506 // fix PHP settings
507 error_reporting($CFG->debug);
7b0ff213 508
8b5413cc
PS
509 // verify db writes just in case something goes wrong in reset
510 if (self::$lastdbwrites != $DB->perf_get_writes()) {
920f4efe 511 error_log('Unexpected DB writes in phpunit_util::reset_all_data()');
8b5413cc
PS
512 self::$lastdbwrites = $DB->perf_get_writes();
513 }
514
7b0ff213
PS
515 if ($warnings) {
516 $warnings = implode("\n", $warnings);
517 trigger_error($warnings, E_USER_WARNING);
518 }
5bd40408
PS
519 }
520
521 /**
522 * Called during bootstrap only!
920f4efe 523 * @internal
5bd40408 524 * @static
920f4efe 525 * @return void
5bd40408 526 */
a3d5830a 527 public static function bootstrap_init() {
95dcf965 528 global $CFG, $SITE, $DB;
5bd40408 529
a3d5830a 530 // backup the globals
6e2cff2d 531 self::$globals['_SERVER'] = $_SERVER;
5bd40408 532 self::$globals['CFG'] = clone($CFG);
458b3386 533 self::$globals['SITE'] = clone($SITE);
95dcf965 534 self::$globals['DB'] = $DB;
a3d5830a
PS
535
536 // refresh data in all tables, clear caches, etc.
537 phpunit_util::reset_all_data();
5bd40408
PS
538 }
539
540 /**
541 * Returns original state of global variable.
542 * @static
543 * @param string $name
544 * @return mixed
545 */
546 public static function get_global_backup($name) {
95dcf965
PS
547 if ($name === 'DB') {
548 // no cloning of database object,
549 // we just need the original reference, not original state
550 return self::$globals['DB'];
551 }
5bd40408
PS
552 if (isset(self::$globals[$name])) {
553 if (is_object(self::$globals[$name])) {
554 $return = clone(self::$globals[$name]);
555 return $return;
556 } else {
557 return self::$globals[$name];
558 }
559 }
560 return null;
561 }
562
563 /**
564 * Does this site (db and dataroot) appear to be used for production?
565 * We try very hard to prevent accidental damage done to production servers!!
566 *
567 * @static
568 * @return bool
569 */
570 public static function is_test_site() {
571 global $DB, $CFG;
572
573 if (!file_exists("$CFG->dataroot/phpunittestdir.txt")) {
574 // this is already tested in bootstrap script,
a3d5830a 575 // but anyway presence of this file means the dataroot is for testing
5bd40408
PS
576 return false;
577 }
578
579 $tables = $DB->get_tables(false);
580 if ($tables) {
581 if (!$DB->get_manager()->table_exists('config')) {
582 return false;
583 }
584 if (!get_config('core', 'phpunittest')) {
585 return false;
586 }
587 }
588
589 return true;
590 }
591
592 /**
593 * Is this site initialised to run unit tests?
594 *
595 * @static
7b0ff213 596 * @return int array errorcode=>message, 0 means ok
5bd40408 597 */
a3d5830a 598 public static function testing_ready_problem() {
7b0ff213 599 global $CFG, $DB;
5bd40408 600
7b0ff213 601 $tables = $DB->get_tables(false);
5bd40408 602
7b0ff213
PS
603 if (!self::is_test_site()) {
604 // dataroot was verified in bootstrap, so it must be DB
605 return array(131, 'Can not use test database, try changing prefix');
5bd40408
PS
606 }
607
de3d1590
PS
608 if (empty($tables)) {
609 return array(132, '');
610 }
611
0b9251e3 612 if (!file_exists("$CFG->dataroot/phpunit/tabledata.ser") or !file_exists("$CFG->dataroot/phpunit/tablestructure.ser")) {
de3d1590 613 return array(133, '');
5bd40408
PS
614 }
615
616 if (!file_exists("$CFG->dataroot/phpunit/versionshash.txt")) {
de3d1590 617 return array(133, '');
5bd40408
PS
618 }
619
620 $hash = phpunit_util::get_version_hash();
621 $oldhash = file_get_contents("$CFG->dataroot/phpunit/versionshash.txt");
622
623 if ($hash !== $oldhash) {
7b0ff213 624 return array(133, '');
5bd40408
PS
625 }
626
7b0ff213 627 return array(0, '');
5bd40408
PS
628 }
629
630 /**
631 * Drop all test site data.
632 *
633 * Note: To be used from CLI scripts only.
634 *
635 * @static
7aea08e1 636 * @return void may terminate execution with exit code
5bd40408
PS
637 */
638 public static function drop_site() {
639 global $DB, $CFG;
640
641 if (!self::is_test_site()) {
219d1a4e 642 phpunit_bootstrap_error(131, 'Can not drop non-test site!!');
5bd40408
PS
643 }
644
4be2ad36 645 // purge dataroot
728eadac 646 self::reset_dataroot();
5bd40408 647 phpunit_bootstrap_initdataroot($CFG->dataroot);
4be2ad36
PS
648 $keep = array('.', '..', 'lock', 'webrunner.xml');
649 $files = scandir("$CFG->dataroot/phpunit");
650 foreach ($files as $file) {
651 if (in_array($file, $keep)) {
652 continue;
653 }
654 $path = "$CFG->dataroot/phpunit/$file";
655 if (is_dir($path)) {
656 remove_dir($path, false);
657 } else {
658 unlink($path);
659 }
660 }
5bd40408
PS
661
662 // drop all tables
5bd40408 663 $tables = $DB->get_tables(false);
728eadac
PS
664 if (isset($tables['config'])) {
665 // config always last to prevent problems with interrupted drops!
666 unset($tables['config']);
667 $tables['config'] = 'config';
5bd40408 668 }
5bd40408
PS
669 foreach ($tables as $tablename) {
670 $table = new xmldb_table($tablename);
671 $DB->get_manager()->drop_table($table);
672 }
673 }
674
675 /**
676 * Perform a fresh test site installation
677 *
678 * Note: To be used from CLI scripts only.
679 *
680 * @static
7aea08e1 681 * @return void may terminate execution with exit code
5bd40408
PS
682 */
683 public static function install_site() {
684 global $DB, $CFG;
685
686 if (!self::is_test_site()) {
219d1a4e 687 phpunit_bootstrap_error(131, 'Can not install on non-test site!!');
5bd40408
PS
688 }
689
690 if ($DB->get_tables()) {
219d1a4e
PS
691 list($errorcode, $message) = phpunit_util::testing_ready_problem();
692 if ($errorcode) {
b6b8a193 693 phpunit_bootstrap_error(133, 'Database tables already present, Moodle PHPUnit test environment can not be initialised');
219d1a4e 694 } else {
b6b8a193 695 phpunit_bootstrap_error(0, 'Moodle PHPUnit test environment is already initialised');
219d1a4e 696 }
5bd40408
PS
697 }
698
699 $options = array();
6e2cff2d 700 $options['adminpass'] = 'admin';
5bd40408
PS
701 $options['shortname'] = 'phpunit';
702 $options['fullname'] = 'PHPUnit test site';
703
704 install_cli_database($options, false);
705
a3d5830a
PS
706 // install timezone info
707 $timezones = get_records_csv($CFG->libdir.'/timezone.txt', 'timezone');
708 update_timezone_records($timezones);
5bd40408
PS
709
710 // add test db flag
711 set_config('phpunittest', 'phpunittest');
712
713 // store data for all tables
714 $data = array();
0b9251e3 715 $structure = array();
5bd40408
PS
716 $tables = $DB->get_tables();
717 foreach ($tables as $table) {
728eadac 718 $columns = $DB->get_columns($table);
0b9251e3 719 $structure[$table] = $columns;
ab483c0a 720 if (isset($columns['id']) and $columns['id']->auto_increment) {
728eadac
PS
721 $data[$table] = $DB->get_records($table, array(), 'id ASC');
722 } else {
723 // there should not be many of these
724 $data[$table] = $DB->get_records($table, array());
725 }
5bd40408
PS
726 }
727 $data = serialize($data);
5bd40408 728 file_put_contents("$CFG->dataroot/phpunit/tabledata.ser", $data);
6e2cff2d 729 phpunit_boostrap_fix_file_permissions("$CFG->dataroot/phpunit/tabledata.ser");
5bd40408 730
0b9251e3
PS
731 $structure = serialize($structure);
732 file_put_contents("$CFG->dataroot/phpunit/tablestructure.ser", $structure);
733 phpunit_boostrap_fix_file_permissions("$CFG->dataroot/phpunit/tablestructure.ser");
734
5bd40408
PS
735 // hash all plugin versions - helps with very fast detection of db structure changes
736 $hash = phpunit_util::get_version_hash();
5bd40408 737 file_put_contents("$CFG->dataroot/phpunit/versionshash.txt", $hash);
6e2cff2d 738 phpunit_boostrap_fix_file_permissions("$CFG->dataroot/phpunit/versionshash.txt", $hash);
5bd40408
PS
739 }
740
741 /**
920f4efe 742 * Calculate unique version hash for all plugins and core.
5bd40408
PS
743 * @static
744 * @return string sha1 hash
745 */
746 public static function get_version_hash() {
747 global $CFG;
748
749 $versions = array();
750
751 // main version first
752 $version = null;
753 include($CFG->dirroot.'/version.php');
754 $versions['core'] = $version;
755
756 // modules
757 $mods = get_plugin_list('mod');
758 ksort($mods);
759 foreach ($mods as $mod => $fullmod) {
760 $module = new stdClass();
761 $module->version = null;
762 include($fullmod.'/version.php');
763 $versions[$mod] = $module->version;
764 }
765
766 // now the rest of plugins
767 $plugintypes = get_plugin_types();
768 unset($plugintypes['mod']);
769 ksort($plugintypes);
770 foreach ($plugintypes as $type=>$unused) {
771 $plugs = get_plugin_list($type);
772 ksort($plugs);
773 foreach ($plugs as $plug=>$fullplug) {
774 $plugin = new stdClass();
775 $plugin->version = null;
776 @include($fullplug.'/version.php');
777 $versions[$plug] = $plugin->version;
778 }
779 }
780
781 $hash = sha1(serialize($versions));
782
783 return $hash;
784 }
785
786 /**
920f4efe 787 * Builds dirroot/phpunit.xml and dataroot/phpunit/webrunner.xml files using defaults from /phpunit.xml.dist
5bd40408 788 * @static
7b0ff213 789 * @return bool true means main config file created, false means only dataroot file created
5bd40408
PS
790 */
791 public static function build_config_file() {
792 global $CFG;
793
794 $template = '
5bd40408
PS
795 <testsuite name="@component@">
796 <directory suffix="_test.php">@dir@</directory>
a3d5830a 797 </testsuite>';
5bd40408
PS
798 $data = file_get_contents("$CFG->dirroot/phpunit.xml.dist");
799
800 $suites = '';
801
802 $plugintypes = get_plugin_types();
803 ksort($plugintypes);
804 foreach ($plugintypes as $type=>$unused) {
805 $plugs = get_plugin_list($type);
806 ksort($plugs);
807 foreach ($plugs as $plug=>$fullplug) {
808 if (!file_exists("$fullplug/tests/")) {
809 continue;
810 }
811 $dir = preg_replace("|$CFG->dirroot/|", '', $fullplug, 1);
812 $dir .= '/tests';
813 $component = $type.'_'.$plug;
814
815 $suite = str_replace('@component@', $component, $template);
816 $suite = str_replace('@dir@', $dir, $suite);
817
818 $suites .= $suite;
819 }
820 }
821
7aea08e1 822 $data = preg_replace('|<!--@plugin_suites_start@-->.*<!--@plugin_suites_end@-->|s', $suites, $data, 1);
5bd40408 823
7b0ff213
PS
824 $result = false;
825 if (is_writable($CFG->dirroot)) {
826 if ($result = file_put_contents("$CFG->dirroot/phpunit.xml", $data)) {
6e2cff2d 827 phpunit_boostrap_fix_file_permissions("$CFG->dirroot/phpunit.xml");
7b0ff213
PS
828 }
829 }
6e2cff2d 830 // relink - it seems that xml:base does not work in phpunit xml files, remove this nasty hack if you find a way to set xml base for relative refs
7b0ff213
PS
831 $data = str_replace('lib/phpunit/', "$CFG->dirroot/lib/phpunit/", $data);
832 $data = preg_replace('|<directory suffix="_test.php">([^<]+)</directory>|', '<directory suffix="_test.php">'.$CFG->dirroot.'/$1</directory>', $data);
833 file_put_contents("$CFG->dataroot/phpunit/webrunner.xml", $data);
6e2cff2d 834 phpunit_boostrap_fix_file_permissions("$CFG->dataroot/phpunit/webrunner.xml");
7b0ff213
PS
835
836 return (bool)$result;
5bd40408
PS
837 }
838}
839
840
841/**
842 * Simplified emulation test case for legacy SimpleTest.
843 *
844 * Note: this is supposed to work for very simple tests only.
845 *
7aea08e1
SH
846 * @deprecated since 2.3
847 * @package core
848 * @category phpunit
5bd40408
PS
849 * @author Petr Skoda
850 * @copyright 2012 Petr Skoda {@link http://skodak.org}
851 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
852 */
d2999716 853abstract class UnitTestCase extends PHPUnit_Framework_TestCase {
5bd40408
PS
854
855 /**
7aea08e1 856 * @deprecated since 2.3
5bd40408
PS
857 * @param bool $expected
858 * @param string $message
a3d5830a 859 * @return void
5bd40408
PS
860 */
861 public function expectException($expected, $message = '') {
920f4efe 862 // alternatively use phpdocs: @expectedException ExceptionClassName
5bd40408
PS
863 if (!$expected) {
864 return;
865 }
866 $this->setExpectedException('moodle_exception', $message);
867 }
868
869 /**
7aea08e1 870 * @deprecated since 2.3
5bd40408
PS
871 * @param bool $expected
872 * @param string $message
a3d5830a 873 * @return void
5bd40408
PS
874 */
875 public static function expectError($expected = false, $message = '') {
876 // not available in PHPUnit
877 if (!$expected) {
878 return;
879 }
880 self::skipIf(true);
881 }
882
883 /**
7aea08e1 884 * @deprecated since 2.3
5bd40408
PS
885 * @static
886 * @param mixed $actual
887 * @param string $messages
a3d5830a 888 * @return void
5bd40408
PS
889 */
890 public static function assertTrue($actual, $messages = '') {
891 parent::assertTrue((bool)$actual, $messages);
892 }
893
894 /**
7aea08e1 895 * @deprecated since 2.3
5bd40408
PS
896 * @static
897 * @param mixed $actual
898 * @param string $messages
a3d5830a 899 * @return void
5bd40408
PS
900 */
901 public static function assertFalse($actual, $messages = '') {
902 parent::assertFalse((bool)$actual, $messages);
903 }
904
905 /**
7aea08e1 906 * @deprecated since 2.3
5bd40408
PS
907 * @static
908 * @param mixed $expected
909 * @param mixed $actual
910 * @param string $message
a3d5830a 911 * @return void
5bd40408
PS
912 */
913 public static function assertEqual($expected, $actual, $message = '') {
914 parent::assertEquals($expected, $actual, $message);
915 }
916
917 /**
7aea08e1 918 * @deprecated since 2.3
5bd40408
PS
919 * @static
920 * @param mixed $expected
921 * @param mixed $actual
922 * @param string $message
a3d5830a 923 * @return void
5bd40408
PS
924 */
925 public static function assertNotEqual($expected, $actual, $message = '') {
926 parent::assertNotEquals($expected, $actual, $message);
927 }
928
929 /**
7aea08e1 930 * @deprecated since 2.3
5bd40408
PS
931 * @static
932 * @param mixed $expected
933 * @param mixed $actual
934 * @param string $message
a3d5830a 935 * @return void
5bd40408
PS
936 */
937 public static function assertIdentical($expected, $actual, $message = '') {
938 parent::assertSame($expected, $actual, $message);
939 }
940
941 /**
7aea08e1 942 * @deprecated since 2.3
5bd40408
PS
943 * @static
944 * @param mixed $expected
945 * @param mixed $actual
946 * @param string $message
a3d5830a 947 * @return void
5bd40408
PS
948 */
949 public static function assertNotIdentical($expected, $actual, $message = '') {
950 parent::assertNotSame($expected, $actual, $message);
951 }
952
953 /**
7aea08e1 954 * @deprecated since 2.3
5bd40408
PS
955 * @static
956 * @param mixed $actual
957 * @param mixed $expected
958 * @param string $message
a3d5830a 959 * @return void
5bd40408
PS
960 */
961 public static function assertIsA($actual, $expected, $message = '') {
6e2cff2d
PS
962 if ($expected === 'array') {
963 parent::assertEquals(gettype($actual), 'array', $message);
964 } else {
965 parent::assertInstanceOf($expected, $actual, $message);
966 }
5bd40408
PS
967 }
968}
969
970
971/**
7aea08e1
SH
972 * The simplest PHPUnit test case customised for Moodle
973 *
a3d5830a 974 * It is intended for isolated tests that do not modify database or any globals.
5bd40408 975 *
7aea08e1
SH
976 * @package core
977 * @category phpunit
5bd40408
PS
978 * @copyright 2012 Petr Skoda {@link http://skodak.org}
979 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
980 */
d2999716 981abstract class basic_testcase extends PHPUnit_Framework_TestCase {
5bd40408
PS
982
983 /**
984 * Constructs a test case with the given name.
985 *
920f4efe 986 * Note: use setUp() or setUpBeforeClass() in your test cases.
b0e980d7 987 *
7aea08e1
SH
988 * @param string $name
989 * @param array $data
990 * @param string $dataName
5bd40408 991 */
b0e980d7 992 final public function __construct($name = null, array $data = array(), $dataName = '') {
5bd40408
PS
993 parent::__construct($name, $data, $dataName);
994
995 $this->setBackupGlobals(false);
996 $this->setBackupStaticAttributes(false);
997 $this->setRunTestInSeparateProcess(false);
5bd40408
PS
998 }
999
1000 /**
a3d5830a 1001 * Runs the bare test sequence and log any changes in global state or database.
5bd40408
PS
1002 * @return void
1003 */
39e2e9c4 1004 final public function runBare() {
7620602c
PS
1005 global $DB;
1006
713d2091
PS
1007 try {
1008 parent::runBare();
1009 } catch (Exception $e) {
1010 // cleanup after failed expectation
1011 phpunit_util::reset_all_data();
1012 throw $e;
1013 }
7620602c
PS
1014
1015 if ($DB->is_transaction_started()) {
7620602c
PS
1016 phpunit_util::reset_all_data();
1017 throw new coding_exception('basic_testcase '.$this->getName().' is not supposed to use database transactions!');
1018 }
1019
7b0ff213 1020 phpunit_util::reset_all_data(true);
a3d5830a
PS
1021 }
1022}
5bd40408 1023
a3d5830a
PS
1024
1025/**
1026 * Advanced PHPUnit test case customised for Moodle.
1027 *
1028 * @package core
1029 * @category phpunit
1030 * @copyright 2012 Petr Skoda {@link http://skodak.org}
1031 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1032 */
d2999716 1033abstract class advanced_testcase extends PHPUnit_Framework_TestCase {
458b3386 1034 /** @var bool automatically reset everything? null means log changes */
a0c5affe 1035 private $resetAfterTest;
a3d5830a 1036
50be93d1 1037 /** @var moodle_transaction */
a0c5affe 1038 private $testdbtransaction;
50be93d1 1039
a3d5830a
PS
1040 /**
1041 * Constructs a test case with the given name.
1042 *
920f4efe 1043 * Note: use setUp() or setUpBeforeClass() in your test cases.
b0e980d7 1044 *
a3d5830a
PS
1045 * @param string $name
1046 * @param array $data
1047 * @param string $dataName
1048 */
b0e980d7 1049 final public function __construct($name = null, array $data = array(), $dataName = '') {
a3d5830a
PS
1050 parent::__construct($name, $data, $dataName);
1051
1052 $this->setBackupGlobals(false);
1053 $this->setBackupStaticAttributes(false);
1054 $this->setRunTestInSeparateProcess(false);
1055 }
1056
1057 /**
1058 * Runs the bare test sequence.
1059 * @return void
1060 */
39e2e9c4 1061 final public function runBare() {
50be93d1
PS
1062 global $DB;
1063
8b5413cc
PS
1064 if (phpunit_util::$lastdbwrites != $DB->perf_get_writes()) {
1065 // this happens when previous test does not reset, we can not use transactions
1066 $this->testdbtransaction = null;
1067
1068 } else if ($DB->get_dbfamily() === 'postgres') {
50be93d1
PS
1069 // database must allow rollback of DDL, so no mysql here
1070 $this->testdbtransaction = $DB->start_delegated_transaction();
1071 }
1072
713d2091
PS
1073 try {
1074 parent::runBare();
95dcf965
PS
1075 // set DB reference in case somebody mocked it in test
1076 $DB = phpunit_util::get_global_backup('DB');
713d2091
PS
1077 } catch (Exception $e) {
1078 // cleanup after failed expectation
713d2091
PS
1079 phpunit_util::reset_all_data();
1080 throw $e;
1081 }
5bd40408 1082
50be93d1
PS
1083 if (!$this->testdbtransaction or $this->testdbtransaction->is_disposed()) {
1084 $this->testdbtransaction = null;
1085 }
50be93d1 1086
a3d5830a 1087 if ($this->resetAfterTest === true) {
8b5413cc 1088 if ($this->testdbtransaction) {
50be93d1 1089 $DB->force_transaction_rollback();
8b5413cc 1090 phpunit_util::reset_all_database_sequences();
50be93d1
PS
1091 phpunit_util::$lastdbwrites = $DB->perf_get_writes(); // no db reset necessary
1092 }
714f3998 1093 phpunit_util::reset_all_data();
8b5413cc 1094
a3d5830a 1095 } else if ($this->resetAfterTest === false) {
8b5413cc
PS
1096 if ($this->testdbtransaction) {
1097 $this->testdbtransaction->allow_commit();
50be93d1 1098 }
a3d5830a 1099 // keep all data untouched for other tests
8b5413cc 1100
a3d5830a
PS
1101 } else {
1102 // reset but log what changed
8b5413cc 1103 if ($this->testdbtransaction) {
7620602c
PS
1104 try {
1105 $this->testdbtransaction->allow_commit();
1106 } catch (dml_transaction_exception $e) {
7620602c
PS
1107 phpunit_util::reset_all_data();
1108 throw new coding_exception('Invalid transaction state detected in test '.$this->getName());
1109 }
50be93d1 1110 }
7b0ff213 1111 phpunit_util::reset_all_data(true);
a3d5830a 1112 }
7620602c
PS
1113
1114 // make sure test did not forget to close transaction
1115 if ($DB->is_transaction_started()) {
7620602c
PS
1116 phpunit_util::reset_all_data();
1117 if ($this->getStatus() == PHPUnit_Runner_BaseTestRunner::STATUS_PASSED
1118 or $this->getStatus() == PHPUnit_Runner_BaseTestRunner::STATUS_SKIPPED
1119 or $this->getStatus() == PHPUnit_Runner_BaseTestRunner::STATUS_INCOMPLETE) {
1120 throw new coding_exception('Test '.$this->getName().' did not close database transaction');
1121 }
1122 }
a3d5830a
PS
1123 }
1124
8b5413cc
PS
1125 /**
1126 * Call this method from test if you want to make sure that
1127 * the resetting of database is done the slow way without transaction
1128 * rollback.
920f4efe
PS
1129 *
1130 * This is useful especially when testing stuff that is not compatible with transactions.
1131 *
8b5413cc
PS
1132 * @return void
1133 */
1134 public function preventResetByRollback() {
1135 if ($this->testdbtransaction and !$this->testdbtransaction->is_disposed()) {
1136 $this->testdbtransaction->allow_commit();
1137 $this->testdbtransaction = null;
1138 }
1139 }
1140
a3d5830a
PS
1141 /**
1142 * Reset everything after current test.
1143 * @param bool $reset true means reset state back, false means keep all data for the next test,
1144 * null means reset state and show warnings if anything changed
1145 * @return void
1146 */
1147 public function resetAfterTest($reset = true) {
1148 $this->resetAfterTest = $reset;
1149 }
1150
1151 /**
1152 * Cleanup after all tests are executed.
1153 *
1154 * Note: do not forget to call this if overridden...
1155 *
1156 * @static
1157 * @return void
1158 */
1159 public static function tearDownAfterClass() {
1160 phpunit_util::reset_all_data();
1161 }
1162
1163 /**
1164 * Reset all database tables, restore global state and clear caches and optionally purge dataroot dir.
1165 * @static
1166 * @return void
1167 */
1168 public static function resetAllData() {
1169 phpunit_util::reset_all_data();
1170 }
1171
458b3386
PS
1172 /**
1173 * Set current $USER, reset access cache.
1174 * @static
e72ea4a5 1175 * @param null|int|stdClass $user user record, null means non-logged-in, integer means userid
458b3386
PS
1176 * @return void
1177 */
e72ea4a5
PS
1178 public static function setUser($user = null) {
1179 global $CFG, $DB;
458b3386 1180
e72ea4a5
PS
1181 if (is_object($user)) {
1182 $user = clone($user);
1183 } else if (!$user) {
458b3386
PS
1184 $user = new stdClass();
1185 $user->id = 0;
1186 $user->mnethostid = $CFG->mnet_localhost_id;
1187 } else {
e72ea4a5 1188 $user = $DB->get_record('user', array('id'=>$user));
458b3386
PS
1189 }
1190 unset($user->description);
1191 unset($user->access);
1192
1193 session_set_user($user);
1194 }
1195
a3d5830a
PS
1196 /**
1197 * Get data generator
1198 * @static
1199 * @return phpunit_data_generator
1200 */
1201 public static function getDataGenerator() {
1202 return phpunit_util::get_data_generator();
1203 }
1204
1205 /**
1206 * Recursively visit all the files in the source tree. Calls the callback
1207 * function with the pathname of each file found.
1208 *
6e2cff2d
PS
1209 * @param string $path the folder to start searching from.
1210 * @param string $callback the method of this class to call with the name of each file found.
1211 * @param string $fileregexp a regexp used to filter the search (optional).
1212 * @param bool $exclude If true, pathnames that match the regexp will be ignored. If false,
a3d5830a
PS
1213 * only files that match the regexp will be included. (default false).
1214 * @param array $ignorefolders will not go into any of these folders (optional).
1215 * @return void
1216 */
1217 public function recurseFolders($path, $callback, $fileregexp = '/.*/', $exclude = false, $ignorefolders = array()) {
1218 $files = scandir($path);
1219
1220 foreach ($files as $file) {
1221 $filepath = $path .'/'. $file;
1222 if (strpos($file, '.') === 0) {
1223 /// Don't check hidden files.
1224 continue;
1225 } else if (is_dir($filepath)) {
1226 if (!in_array($filepath, $ignorefolders)) {
1227 $this->recurseFolders($filepath, $callback, $fileregexp, $exclude, $ignorefolders);
1228 }
1229 } else if ($exclude xor preg_match($fileregexp, $filepath)) {
1230 $this->$callback($filepath);
5bd40408 1231 }
a3d5830a
PS
1232 }
1233 }
1234}
5bd40408 1235
a3d5830a
PS
1236
1237/**
458b3386
PS
1238 * Special test case for testing of DML drivers and DDL layer.
1239 *
c06563c8
PS
1240 * Note: Use only 'test_table*' names when creating new tables.
1241 *
1242 * For DML/DDL developers: you can add following settings to config.php if you want to test different driver than the main one,
1243 * the reason is to allow testing of incomplete drivers that do not allow full PHPUnit environment
1244 * initialisation (the database can be empty).
1245 * $CFG->phpunit_extra_drivers = array(
1246 * 1=>array('dbtype'=>'mysqli', 'dbhost'=>'localhost', 'dbname'=>'moodle', 'dbuser'=>'root', 'dbpass'=>'', 'prefix'=>'phpu2_'),
1247 * 2=>array('dbtype'=>'pgsql', 'dbhost'=>'localhost', 'dbname'=>'moodle', 'dbuser'=>'postgres', 'dbpass'=>'', 'prefix'=>'phpu2_'),
1248 * 3=>array('dbtype'=>'sqlsrv', 'dbhost'=>'127.0.0.1', 'dbname'=>'moodle', 'dbuser'=>'sa', 'dbpass'=>'', 'prefix'=>'phpu2_'),
1249 * 4=>array('dbtype'=>'oci', 'dbhost'=>'127.0.0.1', 'dbname'=>'XE', 'dbuser'=>'sa', 'dbpass'=>'', 'prefix'=>'t_'),
1250 * );
1251 * define('PHPUNIT_TEST_DRIVER')=1; //number is index in the previous array
a3d5830a
PS
1252 *
1253 * @package core
1254 * @category phpunit
1255 * @copyright 2012 Petr Skoda {@link http://skodak.org}
1256 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1257 */
d2999716 1258abstract class database_driver_testcase extends PHPUnit_Framework_TestCase {
6e2cff2d 1259 /** @var moodle_database connection to extra database */
a0c5affe 1260 private static $extradb = null;
a3d5830a 1261
6e2cff2d 1262 /** @var moodle_database used in these tests*/
a3d5830a
PS
1263 protected $tdb;
1264
1265 /**
1266 * Constructs a test case with the given name.
1267 *
1268 * @param string $name
1269 * @param array $data
1270 * @param string $dataName
1271 */
b0e980d7 1272 final public function __construct($name = null, array $data = array(), $dataName = '') {
a3d5830a
PS
1273 parent::__construct($name, $data, $dataName);
1274
1275 $this->setBackupGlobals(false);
1276 $this->setBackupStaticAttributes(false);
1277 $this->setRunTestInSeparateProcess(false);
1278 }
1279
1280 public static function setUpBeforeClass() {
1281 global $CFG;
6e2cff2d 1282 parent::setUpBeforeClass();
a3d5830a
PS
1283
1284 if (!defined('PHPUNIT_TEST_DRIVER')) {
1285 // use normal $DB
1286 return;
5bd40408 1287 }
a3d5830a
PS
1288
1289 if (!isset($CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER])) {
1290 throw new exception('Can not find driver configuration options with index: '.PHPUNIT_TEST_DRIVER);
1291 }
1292
1293 $dblibrary = empty($CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER]['dblibrary']) ? 'native' : $CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER]['dblibrary'];
1294 $dbtype = $CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER]['dbtype'];
1295 $dbhost = $CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER]['dbhost'];
1296 $dbname = $CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER]['dbname'];
1297 $dbuser = $CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER]['dbuser'];
1298 $dbpass = $CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER]['dbpass'];
1299 $prefix = $CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER]['prefix'];
1300 $dboptions = empty($CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER]['dboptions']) ? array() : $CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER]['dboptions'];
1301
1302 $classname = "{$dbtype}_{$dblibrary}_moodle_database";
1303 require_once("$CFG->libdir/dml/$classname.php");
1304 $d = new $classname();
1305 if (!$d->driver_installed()) {
1306 throw new exception('Database driver for '.$classname.' is not installed');
1307 }
1308
1309 $d->connect($dbhost, $dbuser, $dbpass, $dbname, $prefix, $dboptions);
1310
1311 self::$extradb = $d;
1312 }
1313
1cbf2a20 1314 protected function setUp() {
a3d5830a 1315 global $DB;
6e2cff2d 1316 parent::setUp();
5bd40408 1317
a3d5830a
PS
1318 if (self::$extradb) {
1319 $this->tdb = self::$extradb;
1320 } else {
1321 $this->tdb = $DB;
5bd40408 1322 }
a3d5830a 1323 }
5bd40408 1324
1cbf2a20 1325 protected function tearDown() {
a3d5830a
PS
1326 // delete all test tables
1327 $dbman = $this->tdb->get_manager();
1328 $tables = $this->tdb->get_tables(false);
1329 foreach($tables as $tablename) {
1330 if (strpos($tablename, 'test_table') === 0) {
1331 $table = new xmldb_table($tablename);
1332 $dbman->drop_table($table);
1333 }
5bd40408 1334 }
6e2cff2d 1335 parent::tearDown();
a3d5830a 1336 }
5bd40408 1337
a3d5830a
PS
1338 public static function tearDownAfterClass() {
1339 if (self::$extradb) {
1340 self::$extradb->dispose();
1341 self::$extradb = null;
1342 }
1343 phpunit_util::reset_all_data();
6e2cff2d 1344 parent::tearDownAfterClass();
5bd40408 1345 }
a3d5830a 1346}