Merge branch 'MDL-32657-master-1' of git://git.luns.net.uk/moodle
[moodle.git] / lib / phpunit / lib.php
blob7004abf4d6e176b63af039e113bdf655189f2588
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.
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/>.
17 /**
18 * Various PHPUnit classes and functions
20 * @package core
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
26 require_once 'PHPUnit/Autoload.php';
27 require_once 'PHPUnit/Extensions/Database/Autoload.php';
30 /**
31 * Collection of utility methods.
33 * @package core
34 * @category phpunit
35 * @copyright 2012 Petr Skoda {@link http://skodak.org}
36 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
38 class phpunit_util {
39 /** @var string current version hash from php files */
40 protected static $versionhash = null;
42 /** @var array original content of all database tables*/
43 protected static $tabledata = null;
45 /** @var array original structure of all database tables */
46 protected static $tablestructure = null;
48 /** @var array An array of original globals, restored after each test */
49 protected static $globals = array();
51 /** @var int last value of db writes counter, used for db resetting */
52 public static $lastdbwrites = null;
54 /** @var phpunit_data_generator */
55 protected static $generator = null;
57 /** @var resource used for prevention of parallel test execution */
58 protected static $lockhandle = null;
60 /**
61 * Prevent parallel test execution - this can not work in Moodle because we modify database and dataroot.
63 * Note: do not call manually!
65 * @internal
66 * @static
67 * @return void
69 public static function acquire_test_lock() {
70 global $CFG;
71 if (!file_exists("$CFG->phpunit_dataroot/phpunit")) {
72 // dataroot not initialised yet
73 return;
75 if (!file_exists("$CFG->phpunit_dataroot/phpunit/lock")) {
76 file_put_contents("$CFG->phpunit_dataroot/phpunit/lock", 'This file prevents concurrent execution of Moodle PHPUnit tests');
77 phpunit_boostrap_fix_file_permissions("$CFG->phpunit_dataroot/phpunit/lock");
79 if (self::$lockhandle = fopen("$CFG->phpunit_dataroot/phpunit/lock", 'r')) {
80 $wouldblock = null;
81 $locked = flock(self::$lockhandle, (LOCK_EX | LOCK_NB), $wouldblock);
82 if (!$locked) {
83 if ($wouldblock) {
84 echo "Waiting for other test execution to complete...\n";
86 $locked = flock(self::$lockhandle, LOCK_EX);
88 if (!$locked) {
89 fclose(self::$lockhandle);
90 self::$lockhandle = null;
93 register_shutdown_function(array('phpunit_util', 'release_test_lock'));
96 /**
97 * Note: do not call manually!
98 * @internal
99 * @static
100 * @return void
102 public static function release_test_lock() {
103 if (self::$lockhandle) {
104 flock(self::$lockhandle, LOCK_UN);
105 fclose(self::$lockhandle);
106 self::$lockhandle = null;
111 * Load global $CFG;
112 * @internal
113 * @static
114 * @return void
116 public static function initialise_cfg() {
117 global $DB;
118 $dbhash = false;
119 try {
120 $dbhash = $DB->get_field('config', 'value', array('name'=>'phpunittest'));
121 } catch (Exception $e) {
122 // not installed yet
123 initialise_cfg();
124 return;
126 if ($dbhash !== phpunit_util::get_version_hash()) {
127 // do not set CFG - the only way forward is to drop and reinstall
128 return;
130 // standard CFG init
131 initialise_cfg();
135 * Get data generator
136 * @static
137 * @return phpunit_data_generator
139 public static function get_data_generator() {
140 if (is_null(self::$generator)) {
141 require_once(__DIR__.'/generatorlib.php');
142 self::$generator = new phpunit_data_generator();
144 return self::$generator;
148 * Returns contents of all tables right after installation.
149 * @static
150 * @return array $table=>$records
152 protected static function get_tabledata() {
153 global $CFG;
155 if (!file_exists("$CFG->dataroot/phpunit/tabledata.ser")) {
156 // not initialised yet
157 return array();
160 if (!isset(self::$tabledata)) {
161 $data = file_get_contents("$CFG->dataroot/phpunit/tabledata.ser");
162 self::$tabledata = unserialize($data);
165 if (!is_array(self::$tabledata)) {
166 phpunit_bootstrap_error(1, 'Can not read dataroot/phpunit/tabledata.ser or invalid format, reinitialize test database.');
169 return self::$tabledata;
173 * Returns structure of all tables right after installation.
174 * @static
175 * @return array $table=>$records
177 public static function get_tablestructure() {
178 global $CFG;
180 if (!file_exists("$CFG->dataroot/phpunit/tablestructure.ser")) {
181 // not initialised yet
182 return array();
185 if (!isset(self::$tablestructure)) {
186 $data = file_get_contents("$CFG->dataroot/phpunit/tablestructure.ser");
187 self::$tablestructure = unserialize($data);
190 if (!is_array(self::$tablestructure)) {
191 phpunit_bootstrap_error(1, 'Can not read dataroot/phpunit/tablestructure.ser or invalid format, reinitialize test database.');
194 return self::$tablestructure;
198 * Returns list of tables that are unmodified and empty.
200 * @static
201 * @return array of table names, empty if unknown
203 protected static function guess_unmodified_empty_tables() {
204 global $DB;
206 $dbfamily = $DB->get_dbfamily();
208 if ($dbfamily === 'mysql') {
209 $empties = array();
210 $prefix = $DB->get_prefix();
211 $rs = $DB->get_recordset_sql("SHOW TABLE STATUS LIKE ?", array($prefix.'%'));
212 foreach ($rs as $info) {
213 $table = strtolower($info->name);
214 if (strpos($table, $prefix) !== 0) {
215 // incorrect table match caused by _
216 continue;
218 if (!is_null($info->auto_increment)) {
219 $table = preg_replace('/^'.preg_quote($prefix, '/').'/', '', $table);
220 if ($info->auto_increment == 1) {
221 $empties[$table] = $table;
225 $rs->close();
226 return $empties;
228 } else if ($dbfamily === 'mssql') {
229 $empties = array();
230 $prefix = $DB->get_prefix();
231 $sql = "SELECT t.name
232 FROM sys.identity_columns i
233 JOIN sys.tables t ON t.object_id = i.object_id
234 WHERE t.name LIKE ?
235 AND i.name = 'id'
236 AND i.last_value IS NULL";
237 $rs = $DB->get_recordset_sql($sql, array($prefix.'%'));
238 foreach ($rs as $info) {
239 $table = strtolower($info->name);
240 if (strpos($table, $prefix) !== 0) {
241 // incorrect table match caused by _
242 continue;
244 $table = preg_replace('/^'.preg_quote($prefix, '/').'/', '', $table);
245 $empties[$table] = $table;
247 $rs->close();
248 return $empties;
250 } else {
251 return array();
256 * Reset all database sequences to initial values.
258 * @static
259 * @param array $empties tables that are known to be unmodified and empty
260 * @return void
262 public static function reset_all_database_sequences(array $empties = null) {
263 global $DB;
265 if (!$data = self::get_tabledata()) {
266 // not initialised yet
267 return;
269 if (!$structure = self::get_tablestructure()) {
270 // not initialised yet
271 return;
274 $dbfamily = $DB->get_dbfamily();
275 if ($dbfamily === 'postgres') {
276 $queries = array();
277 $prefix = $DB->get_prefix();
278 foreach ($data as $table=>$records) {
279 if (isset($structure[$table]['id']) and $structure[$table]['id']->auto_increment) {
280 if (empty($records)) {
281 $nextid = 1;
282 } else {
283 $lastrecord = end($records);
284 $nextid = $lastrecord->id + 1;
286 $queries[] = "ALTER SEQUENCE {$prefix}{$table}_id_seq RESTART WITH $nextid";
289 if ($queries) {
290 $DB->change_database_structure(implode(';', $queries));
293 } else if ($dbfamily === 'mysql') {
294 $sequences = array();
295 $prefix = $DB->get_prefix();
296 $rs = $DB->get_recordset_sql("SHOW TABLE STATUS LIKE ?", array($prefix.'%'));
297 foreach ($rs as $info) {
298 $table = strtolower($info->name);
299 if (strpos($table, $prefix) !== 0) {
300 // incorrect table match caused by _
301 continue;
303 if (!is_null($info->auto_increment)) {
304 $table = preg_replace('/^'.preg_quote($prefix, '/').'/', '', $table);
305 $sequences[$table] = $info->auto_increment;
308 $rs->close();
309 foreach ($data as $table=>$records) {
310 if (isset($structure[$table]['id']) and $structure[$table]['id']->auto_increment) {
311 if (isset($sequences[$table])) {
312 if (empty($records)) {
313 $lastid = 0;
314 } else {
315 $lastrecord = end($records);
316 $lastid = $lastrecord->id;
318 if ($sequences[$table] != $lastid +1) {
319 $DB->get_manager()->reset_sequence($table);
322 } else {
323 $DB->get_manager()->reset_sequence($table);
328 } else {
329 // note: does mssql and oracle support any kind of faster reset?
330 if (is_null($empties)) {
331 $empties = self::guess_unmodified_empty_tables();
333 foreach ($data as $table=>$records) {
334 if (isset($empties[$table])) {
335 continue;
337 if (isset($structure[$table]['id']) and $structure[$table]['id']->auto_increment) {
338 $DB->get_manager()->reset_sequence($table);
345 * Reset all database tables to default values.
346 * @static
347 * @return bool true if reset done, false if skipped
349 public static function reset_database() {
350 global $DB;
352 $tables = $DB->get_tables(false);
353 if (!$tables or empty($tables['config'])) {
354 // not installed yet
355 return false;
358 if (!is_null(self::$lastdbwrites) and self::$lastdbwrites == $DB->perf_get_writes()) {
359 return false;
361 if (!$data = self::get_tabledata()) {
362 // not initialised yet
363 return false;
365 if (!$structure = self::get_tablestructure()) {
366 // not initialised yet
367 return false;
370 $empties = self::guess_unmodified_empty_tables();
372 foreach ($data as $table=>$records) {
373 if (empty($records)) {
374 if (isset($empties[$table])) {
375 // table was not modified and is empty
376 } else {
377 $DB->delete_records($table, array());
379 continue;
382 if (isset($structure[$table]['id']) and $structure[$table]['id']->auto_increment) {
383 $currentrecords = $DB->get_records($table, array(), 'id ASC');
384 $changed = false;
385 foreach ($records as $id=>$record) {
386 if (!isset($currentrecords[$id])) {
387 $changed = true;
388 break;
390 if ((array)$record != (array)$currentrecords[$id]) {
391 $changed = true;
392 break;
394 unset($currentrecords[$id]);
396 if (!$changed) {
397 if ($currentrecords) {
398 $lastrecord = end($records);
399 $DB->delete_records_select($table, "id > ?", array($lastrecord->id));
400 continue;
401 } else {
402 continue;
407 $DB->delete_records($table, array());
408 foreach ($records as $record) {
409 $DB->import_record($table, $record, false, true);
413 // reset all next record ids - aka sequences
414 self::reset_all_database_sequences($empties);
416 // remove extra tables
417 foreach ($tables as $table) {
418 if (!isset($data[$table])) {
419 $DB->get_manager()->drop_table(new xmldb_table($table));
423 self::$lastdbwrites = $DB->perf_get_writes();
425 return true;
429 * Purge dataroot directory
430 * @static
431 * @return void
433 public static function reset_dataroot() {
434 global $CFG;
436 $handle = opendir($CFG->dataroot);
437 $skip = array('.', '..', 'phpunittestdir.txt', 'phpunit', '.htaccess');
438 while (false !== ($item = readdir($handle))) {
439 if (in_array($item, $skip)) {
440 continue;
442 if (is_dir("$CFG->dataroot/$item")) {
443 remove_dir("$CFG->dataroot/$item", false);
444 } else {
445 unlink("$CFG->dataroot/$item");
448 closedir($handle);
449 make_temp_directory('');
450 make_cache_directory('');
451 make_cache_directory('htmlpurifier');
455 * Reset contents of all database tables to initial values, reset caches, etc.
457 * Note: this is relatively slow (cca 2 seconds for pg and 7 for mysql) - please use with care!
459 * @static
460 * @param bool $logchanges log changes in global state and database in error log
461 * @return void
463 public static function reset_all_data($logchanges = false) {
464 global $DB, $CFG, $USER, $SITE, $COURSE, $PAGE, $OUTPUT, $SESSION;
466 // reset global $DB in case somebody mocked it
467 $DB = self::get_global_backup('DB');
469 if ($DB->is_transaction_started()) {
470 // we can not reset inside transaction
471 $DB->force_transaction_rollback();
474 $resetdb = self::reset_database();
475 $warnings = array();
477 if ($logchanges) {
478 if ($resetdb) {
479 $warnings[] = 'Warning: unexpected database modification, resetting DB state';
482 $oldcfg = self::get_global_backup('CFG');
483 $oldsite = self::get_global_backup('SITE');
484 foreach($CFG as $k=>$v) {
485 if (!property_exists($oldcfg, $k)) {
486 $warnings[] = 'Warning: unexpected new $CFG->'.$k.' value';
487 } else if ($oldcfg->$k !== $CFG->$k) {
488 $warnings[] = 'Warning: unexpected change of $CFG->'.$k.' value';
490 unset($oldcfg->$k);
493 if ($oldcfg) {
494 foreach($oldcfg as $k=>$v) {
495 $warnings[] = 'Warning: unexpected removal of $CFG->'.$k;
499 if ($USER->id != 0) {
500 $warnings[] = 'Warning: unexpected change of $USER';
503 if ($COURSE->id != $oldsite->id) {
504 $warnings[] = 'Warning: unexpected change of $COURSE';
508 // restore original globals
509 $_SERVER = self::get_global_backup('_SERVER');
510 $CFG = self::get_global_backup('CFG');
511 $SITE = self::get_global_backup('SITE');
512 $COURSE = $SITE;
514 // reinitialise following globals
515 $OUTPUT = new bootstrap_renderer();
516 $PAGE = new moodle_page();
517 $FULLME = null;
518 $ME = null;
519 $SCRIPT = null;
520 $SESSION = new stdClass();
521 $_SESSION['SESSION'] =& $SESSION;
523 // set fresh new not-logged-in user
524 $user = new stdClass();
525 $user->id = 0;
526 $user->mnethostid = $CFG->mnet_localhost_id;
527 session_set_user($user);
529 // reset all static caches
530 accesslib_clear_all_caches(true);
531 get_string_manager()->reset_caches();
532 events_get_handlers('reset');
533 textlib::reset_caches();
534 //TODO: add more resets here and probably refactor them to new core function
536 // purge dataroot directory
537 self::reset_dataroot();
539 // restore original config once more in case resetting of caches changed CFG
540 $CFG = self::get_global_backup('CFG');
542 // inform data generator
543 self::get_data_generator()->reset();
545 // fix PHP settings
546 error_reporting($CFG->debug);
548 // verify db writes just in case something goes wrong in reset
549 if (self::$lastdbwrites != $DB->perf_get_writes()) {
550 error_log('Unexpected DB writes in phpunit_util::reset_all_data()');
551 self::$lastdbwrites = $DB->perf_get_writes();
554 if ($warnings) {
555 $warnings = implode("\n", $warnings);
556 trigger_error($warnings, E_USER_WARNING);
561 * Called during bootstrap only!
562 * @internal
563 * @static
564 * @return void
566 public static function bootstrap_init() {
567 global $CFG, $SITE, $DB;
569 // backup the globals
570 self::$globals['_SERVER'] = $_SERVER;
571 self::$globals['CFG'] = clone($CFG);
572 self::$globals['SITE'] = clone($SITE);
573 self::$globals['DB'] = $DB;
575 // refresh data in all tables, clear caches, etc.
576 phpunit_util::reset_all_data();
580 * Returns original state of global variable.
581 * @static
582 * @param string $name
583 * @return mixed
585 public static function get_global_backup($name) {
586 if ($name === 'DB') {
587 // no cloning of database object,
588 // we just need the original reference, not original state
589 return self::$globals['DB'];
591 if (isset(self::$globals[$name])) {
592 if (is_object(self::$globals[$name])) {
593 $return = clone(self::$globals[$name]);
594 return $return;
595 } else {
596 return self::$globals[$name];
599 return null;
603 * Does this site (db and dataroot) appear to be used for production?
604 * We try very hard to prevent accidental damage done to production servers!!
606 * @static
607 * @return bool
609 public static function is_test_site() {
610 global $DB, $CFG;
612 if (!file_exists("$CFG->dataroot/phpunittestdir.txt")) {
613 // this is already tested in bootstrap script,
614 // but anyway presence of this file means the dataroot is for testing
615 return false;
618 $tables = $DB->get_tables(false);
619 if ($tables) {
620 if (!$DB->get_manager()->table_exists('config')) {
621 return false;
623 if (!get_config('core', 'phpunittest')) {
624 return false;
628 return true;
632 * Is this site initialised to run unit tests?
634 * @static
635 * @return int array errorcode=>message, 0 means ok
637 public static function testing_ready_problem() {
638 global $CFG, $DB;
640 $tables = $DB->get_tables(false);
642 if (!self::is_test_site()) {
643 // dataroot was verified in bootstrap, so it must be DB
644 return array(PHPUNIT_EXITCODE_CONFIGERROR, 'Can not use database for testing, try different prefix');
647 if (empty($tables)) {
648 return array(PHPUNIT_EXITCODE_INSTALL, '');
651 if (!file_exists("$CFG->dataroot/phpunit/tabledata.ser") or !file_exists("$CFG->dataroot/phpunit/tablestructure.ser")) {
652 return array(PHPUNIT_EXITCODE_REINSTALL, '');
655 if (!file_exists("$CFG->dataroot/phpunit/versionshash.txt")) {
656 return array(PHPUNIT_EXITCODE_REINSTALL, '');
659 $hash = phpunit_util::get_version_hash();
660 $oldhash = file_get_contents("$CFG->dataroot/phpunit/versionshash.txt");
662 if ($hash !== $oldhash) {
663 return array(PHPUNIT_EXITCODE_REINSTALL, '');
666 $dbhash = get_config('core', 'phpunittest');
667 if ($hash !== $dbhash) {
668 return array(PHPUNIT_EXITCODE_REINSTALL, '');
671 return array(0, '');
675 * Drop all test site data.
677 * Note: To be used from CLI scripts only.
679 * @static
680 * @return void may terminate execution with exit code
682 public static function drop_site() {
683 global $DB, $CFG;
685 if (!self::is_test_site()) {
686 phpunit_bootstrap_error(PHPUNIT_EXITCODE_CONFIGERROR, 'Can not drop non-test site!!');
689 // purge dataroot
690 self::reset_dataroot();
691 phpunit_bootstrap_initdataroot($CFG->dataroot);
692 $keep = array('.', '..', 'lock', 'webrunner.xml');
693 $files = scandir("$CFG->dataroot/phpunit");
694 foreach ($files as $file) {
695 if (in_array($file, $keep)) {
696 continue;
698 $path = "$CFG->dataroot/phpunit/$file";
699 if (is_dir($path)) {
700 remove_dir($path, false);
701 } else {
702 unlink($path);
706 // drop all tables
707 $tables = $DB->get_tables(false);
708 if (isset($tables['config'])) {
709 // config always last to prevent problems with interrupted drops!
710 unset($tables['config']);
711 $tables['config'] = 'config';
713 foreach ($tables as $tablename) {
714 $table = new xmldb_table($tablename);
715 $DB->get_manager()->drop_table($table);
720 * Perform a fresh test site installation
722 * Note: To be used from CLI scripts only.
724 * @static
725 * @return void may terminate execution with exit code
727 public static function install_site() {
728 global $DB, $CFG;
730 if (!self::is_test_site()) {
731 phpunit_bootstrap_error(PHPUNIT_EXITCODE_CONFIGERROR, 'Can not install on non-test site!!');
734 if ($DB->get_tables()) {
735 list($errorcode, $message) = phpunit_util::testing_ready_problem();
736 if ($errorcode) {
737 phpunit_bootstrap_error(PHPUNIT_EXITCODE_REINSTALL, 'Database tables already present, Moodle PHPUnit test environment can not be initialised');
738 } else {
739 phpunit_bootstrap_error(0, 'Moodle PHPUnit test environment is already initialised');
743 $options = array();
744 $options['adminpass'] = 'admin';
745 $options['shortname'] = 'phpunit';
746 $options['fullname'] = 'PHPUnit test site';
748 install_cli_database($options, false);
750 // install timezone info
751 $timezones = get_records_csv($CFG->libdir.'/timezone.txt', 'timezone');
752 update_timezone_records($timezones);
754 // add test db flag
755 $hash = phpunit_util::get_version_hash();
756 set_config('phpunittest', $hash);
758 // store data for all tables
759 $data = array();
760 $structure = array();
761 $tables = $DB->get_tables();
762 foreach ($tables as $table) {
763 $columns = $DB->get_columns($table);
764 $structure[$table] = $columns;
765 if (isset($columns['id']) and $columns['id']->auto_increment) {
766 $data[$table] = $DB->get_records($table, array(), 'id ASC');
767 } else {
768 // there should not be many of these
769 $data[$table] = $DB->get_records($table, array());
772 $data = serialize($data);
773 file_put_contents("$CFG->dataroot/phpunit/tabledata.ser", $data);
774 phpunit_boostrap_fix_file_permissions("$CFG->dataroot/phpunit/tabledata.ser");
776 $structure = serialize($structure);
777 file_put_contents("$CFG->dataroot/phpunit/tablestructure.ser", $structure);
778 phpunit_boostrap_fix_file_permissions("$CFG->dataroot/phpunit/tablestructure.ser");
780 // hash all plugin versions - helps with very fast detection of db structure changes
781 file_put_contents("$CFG->dataroot/phpunit/versionshash.txt", $hash);
782 phpunit_boostrap_fix_file_permissions("$CFG->dataroot/phpunit/versionshash.txt", $hash);
786 * Calculate unique version hash for all plugins and core.
787 * @static
788 * @return string sha1 hash
790 public static function get_version_hash() {
791 global $CFG;
793 if (self::$versionhash) {
794 return self::$versionhash;
797 $versions = array();
799 // main version first
800 $version = null;
801 include($CFG->dirroot.'/version.php');
802 $versions['core'] = $version;
804 // modules
805 $mods = get_plugin_list('mod');
806 ksort($mods);
807 foreach ($mods as $mod => $fullmod) {
808 $module = new stdClass();
809 $module->version = null;
810 include($fullmod.'/version.php');
811 $versions[$mod] = $module->version;
814 // now the rest of plugins
815 $plugintypes = get_plugin_types();
816 unset($plugintypes['mod']);
817 ksort($plugintypes);
818 foreach ($plugintypes as $type=>$unused) {
819 $plugs = get_plugin_list($type);
820 ksort($plugs);
821 foreach ($plugs as $plug=>$fullplug) {
822 $plugin = new stdClass();
823 $plugin->version = null;
824 @include($fullplug.'/version.php');
825 $versions[$plug] = $plugin->version;
829 self::$versionhash = sha1(serialize($versions));
831 return self::$versionhash;
835 * Builds dirroot/phpunit.xml and dataroot/phpunit/webrunner.xml files using defaults from /phpunit.xml.dist
836 * @static
837 * @return bool true means main config file created, false means only dataroot file created
839 public static function build_config_file() {
840 global $CFG;
842 $template = '
843 <testsuite name="@component@">
844 <directory suffix="_test.php">@dir@</directory>
845 </testsuite>';
846 $data = file_get_contents("$CFG->dirroot/phpunit.xml.dist");
848 $suites = '';
850 $plugintypes = get_plugin_types();
851 ksort($plugintypes);
852 foreach ($plugintypes as $type=>$unused) {
853 $plugs = get_plugin_list($type);
854 ksort($plugs);
855 foreach ($plugs as $plug=>$fullplug) {
856 if (!file_exists("$fullplug/tests/")) {
857 continue;
859 $dir = substr($fullplug, strlen($CFG->dirroot)+1);
860 $dir .= '/tests';
861 $component = $type.'_'.$plug;
863 $suite = str_replace('@component@', $component, $template);
864 $suite = str_replace('@dir@', $dir, $suite);
866 $suites .= $suite;
870 $data = preg_replace('|<!--@plugin_suites_start@-->.*<!--@plugin_suites_end@-->|s', $suites, $data, 1);
872 $result = false;
873 if (is_writable($CFG->dirroot)) {
874 if ($result = file_put_contents("$CFG->dirroot/phpunit.xml", $data)) {
875 phpunit_boostrap_fix_file_permissions("$CFG->dirroot/phpunit.xml");
879 // 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
880 $data = str_replace('lib/phpunit/', $CFG->dirroot.DIRECTORY_SEPARATOR.'lib'.DIRECTORY_SEPARATOR.'phpunit'.DIRECTORY_SEPARATOR, $data);
881 $data = preg_replace('|<directory suffix="_test.php">([^<]+)</directory>|',
882 '<directory suffix="_test.php">'.$CFG->dirroot.(DIRECTORY_SEPARATOR === '\\' ? '\\\\' : DIRECTORY_SEPARATOR).'$1</directory>',
883 $data);
884 file_put_contents("$CFG->dataroot/phpunit/webrunner.xml", $data);
885 phpunit_boostrap_fix_file_permissions("$CFG->dataroot/phpunit/webrunner.xml");
887 return (bool)$result;
891 * Builds phpunit.xml files for all components using defaults from /phpunit.xml.dist
893 * @static
894 * @return void, stops if can not write files
896 public static function build_component_config_files() {
897 global $CFG;
899 $template = '
900 <testsuites>
901 <testsuite name="@component@">
902 <directory suffix="_test.php">.</directory>
903 </testsuite>
904 </testsuites>';
906 // Use the upstream file as source for the distributed configurations
907 $ftemplate = file_get_contents("$CFG->dirroot/phpunit.xml.dist");
908 $ftemplate = preg_replace('|<!--All core suites.*</testsuites>|s', '<!--@component_suite@-->', $ftemplate);
910 // Get all the components
911 $components = self::get_all_plugins_with_tests() + self::get_all_subsystems_with_tests();
913 // Get all the directories having tests
914 $directories = self::get_all_directories_with_tests();
916 // Find any directory not covered by proper components
917 $remaining = array_diff($directories, $components);
919 // Add them to the list of components
920 $components += $remaining;
922 // Create the corresponding phpunit.xml file for each component
923 foreach ($components as $cname => $cpath) {
924 // Calculate the component suite
925 $ctemplate = $template;
926 $ctemplate = str_replace('@component@', $cname, $ctemplate);
928 // Apply it to the file template
929 $fcontents = str_replace('<!--@component_suite@-->', $ctemplate, $ftemplate);
931 // fix link to schema
932 $level = substr_count(str_replace('\\', '/', $cpath), '/') - substr_count(str_replace('\\', '/', $CFG->dirroot), '/');
933 $fcontents = str_replace('lib/phpunit/phpunit.xsd', str_repeat('../', $level).'lib/phpunit/phpunit.xsd', $fcontents);
934 $fcontents = str_replace('lib/phpunit/bootstrap.php', str_repeat('../', $level).'lib/phpunit/bootstrap.php', $fcontents);
936 // Write the file
937 $result = false;
938 if (is_writable($cpath)) {
939 if ($result = (bool)file_put_contents("$cpath/phpunit.xml", $fcontents)) {
940 phpunit_boostrap_fix_file_permissions("$cpath/phpunit.xml");
943 // Problems writing file, throw error
944 if (!$result) {
945 phpunit_bootstrap_error(PHPUNIT_EXITCODE_CONFIGWARNING, "Can not create $cpath/phpunit.xml configuration file, verify dir permissions");
951 * Returns all the plugins having PHPUnit tests
953 * @return array all the plugins having PHPUnit tests
956 private static function get_all_plugins_with_tests() {
957 $pluginswithtests = array();
959 $plugintypes = get_plugin_types();
960 ksort($plugintypes);
961 foreach ($plugintypes as $type => $unused) {
962 $plugs = get_plugin_list($type);
963 ksort($plugs);
964 foreach ($plugs as $plug => $fullplug) {
965 // Look for tests recursively
966 if (self::directory_has_tests($fullplug)) {
967 $pluginswithtests[$type . '_' . $plug] = $fullplug;
971 return $pluginswithtests;
975 * Returns all the subsystems having PHPUnit tests
977 * Note we are hacking here the list of subsystems
978 * to cover some well-known subsystems that are not properly
979 * returned by the {@link get_core_subsystems()} function.
981 * @return array all the subsystems having PHPUnit tests
983 private static function get_all_subsystems_with_tests() {
984 global $CFG;
986 $subsystemswithtests = array();
988 $subsystems = get_core_subsystems();
990 // Hack the list a bit to cover some well-known ones
991 $subsystems['backup'] = 'backup';
992 $subsystems['db-dml'] = 'lib/dml';
993 $subsystems['db-ddl'] = 'lib/ddl';
995 ksort($subsystems);
996 foreach ($subsystems as $subsys => $relsubsys) {
997 if ($relsubsys === null) {
998 continue;
1000 $fullsubsys = $CFG->dirroot . '/' . $relsubsys;
1001 if (!is_dir($fullsubsys)) {
1002 continue;
1004 // Look for tests recursively
1005 if (self::directory_has_tests($fullsubsys)) {
1006 $subsystemswithtests['core_' . $subsys] = $fullsubsys;
1009 return $subsystemswithtests;
1013 * Returns all the directories having tests
1015 * @return array all directories having tests
1017 private static function get_all_directories_with_tests() {
1018 global $CFG;
1020 $dirs = array();
1021 $dirite = new RecursiveDirectoryIterator($CFG->dirroot);
1022 $iteite = new RecursiveIteratorIterator($dirite);
1023 $regite = new RegexIterator($iteite, '|/tests/.*_test\.php$|');
1024 foreach ($regite as $path => $element) {
1025 $key = dirname(dirname($path));
1026 $value = trim(str_replace('/', '_', str_replace($CFG->dirroot, '', $key)), '_');
1027 $dirs[$key] = $value;
1029 ksort($dirs);
1030 return array_flip($dirs);
1034 * Returns if a given directory has tests (recursively)
1036 * @param $dir string full path to the directory to look for phpunit tests
1037 * @return bool if a given directory has tests (true) or no (false)
1039 private static function directory_has_tests($dir) {
1040 if (!is_dir($dir)) {
1041 return false;
1044 $dirite = new RecursiveDirectoryIterator($dir);
1045 $iteite = new RecursiveIteratorIterator($dirite);
1046 $regite = new RegexIterator($iteite, '|/tests/.*_test\.php$|');
1047 $regite->rewind();
1048 if ($regite->valid()) {
1049 return true;
1051 return false;
1057 * Simplified emulation test case for legacy SimpleTest.
1059 * Note: this is supposed to work for very simple tests only.
1061 * @deprecated since 2.3
1062 * @package core
1063 * @category phpunit
1064 * @author Petr Skoda
1065 * @copyright 2012 Petr Skoda {@link http://skodak.org}
1066 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1068 abstract class UnitTestCase extends PHPUnit_Framework_TestCase {
1071 * @deprecated since 2.3
1072 * @param bool $expected
1073 * @param string $message
1074 * @return void
1076 public function expectException($expected, $message = '') {
1077 // alternatively use phpdocs: @expectedException ExceptionClassName
1078 if (!$expected) {
1079 return;
1081 $this->setExpectedException('moodle_exception', $message);
1085 * @deprecated since 2.3
1086 * @param bool $expected
1087 * @param string $message
1088 * @return void
1090 public function expectError($expected = false, $message = '') {
1091 // alternatively use phpdocs: @expectedException PHPUnit_Framework_Error
1092 if (!$expected) {
1093 return;
1095 $this->setExpectedException('PHPUnit_Framework_Error', $message);
1099 * @deprecated since 2.3
1100 * @static
1101 * @param mixed $actual
1102 * @param string $messages
1103 * @return void
1105 public static function assertTrue($actual, $messages = '') {
1106 parent::assertTrue((bool)$actual, $messages);
1110 * @deprecated since 2.3
1111 * @static
1112 * @param mixed $actual
1113 * @param string $messages
1114 * @return void
1116 public static function assertFalse($actual, $messages = '') {
1117 parent::assertFalse((bool)$actual, $messages);
1121 * @deprecated since 2.3
1122 * @static
1123 * @param mixed $expected
1124 * @param mixed $actual
1125 * @param string $message
1126 * @return void
1128 public static function assertEqual($expected, $actual, $message = '') {
1129 parent::assertEquals($expected, $actual, $message);
1133 * @deprecated since 2.3
1134 * @static
1135 * @param mixed $expected
1136 * @param mixed $actual
1137 * @param float|int $margin
1138 * @param string $message
1139 * @return void
1141 public static function assertWithinMargin($expected, $actual, $margin, $message = '') {
1142 parent::assertEquals($expected, $actual, '', $margin, $message);
1146 * @deprecated since 2.3
1147 * @static
1148 * @param mixed $expected
1149 * @param mixed $actual
1150 * @param string $message
1151 * @return void
1153 public static function assertNotEqual($expected, $actual, $message = '') {
1154 parent::assertNotEquals($expected, $actual, $message);
1158 * @deprecated since 2.3
1159 * @static
1160 * @param mixed $expected
1161 * @param mixed $actual
1162 * @param string $message
1163 * @return void
1165 public static function assertIdentical($expected, $actual, $message = '') {
1166 parent::assertSame($expected, $actual, $message);
1170 * @deprecated since 2.3
1171 * @static
1172 * @param mixed $expected
1173 * @param mixed $actual
1174 * @param string $message
1175 * @return void
1177 public static function assertNotIdentical($expected, $actual, $message = '') {
1178 parent::assertNotSame($expected, $actual, $message);
1182 * @deprecated since 2.3
1183 * @static
1184 * @param mixed $actual
1185 * @param mixed $expected
1186 * @param string $message
1187 * @return void
1189 public static function assertIsA($actual, $expected, $message = '') {
1190 if ($expected === 'array') {
1191 parent::assertEquals('array', gettype($actual), $message);
1192 } else {
1193 parent::assertInstanceOf($expected, $actual, $message);
1198 * @deprecated since 2.3
1199 * @static
1200 * @param mixed $pattern
1201 * @param mixed $string
1202 * @param string $message
1203 * @return void
1205 public static function assertPattern($pattern, $string, $message = '') {
1206 parent::assertRegExp($pattern, $string, $message);
1210 * @deprecated since 2.3
1211 * @static
1212 * @param mixed $pattern
1213 * @param mixed $string
1214 * @param string $message
1215 * @return void
1217 public static function assertNotPattern($pattern, $string, $message = '') {
1218 parent::assertNotRegExp($pattern, $string, $message);
1224 * The simplest PHPUnit test case customised for Moodle
1226 * It is intended for isolated tests that do not modify database or any globals.
1228 * @package core
1229 * @category phpunit
1230 * @copyright 2012 Petr Skoda {@link http://skodak.org}
1231 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1233 abstract class basic_testcase extends PHPUnit_Framework_TestCase {
1236 * Constructs a test case with the given name.
1238 * Note: use setUp() or setUpBeforeClass() in your test cases.
1240 * @param string $name
1241 * @param array $data
1242 * @param string $dataName
1244 final public function __construct($name = null, array $data = array(), $dataName = '') {
1245 parent::__construct($name, $data, $dataName);
1247 $this->setBackupGlobals(false);
1248 $this->setBackupStaticAttributes(false);
1249 $this->setRunTestInSeparateProcess(false);
1253 * Runs the bare test sequence and log any changes in global state or database.
1254 * @return void
1256 final public function runBare() {
1257 global $DB;
1259 try {
1260 parent::runBare();
1261 } catch (Exception $e) {
1262 // cleanup after failed expectation
1263 phpunit_util::reset_all_data();
1264 throw $e;
1267 if ($DB->is_transaction_started()) {
1268 phpunit_util::reset_all_data();
1269 throw new coding_exception('basic_testcase '.$this->getName().' is not supposed to use database transactions!');
1272 phpunit_util::reset_all_data(true);
1278 * Advanced PHPUnit test case customised for Moodle.
1280 * @package core
1281 * @category phpunit
1282 * @copyright 2012 Petr Skoda {@link http://skodak.org}
1283 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1285 abstract class advanced_testcase extends PHPUnit_Framework_TestCase {
1286 /** @var bool automatically reset everything? null means log changes */
1287 private $resetAfterTest;
1289 /** @var moodle_transaction */
1290 private $testdbtransaction;
1293 * Constructs a test case with the given name.
1295 * Note: use setUp() or setUpBeforeClass() in your test cases.
1297 * @param string $name
1298 * @param array $data
1299 * @param string $dataName
1301 final public function __construct($name = null, array $data = array(), $dataName = '') {
1302 parent::__construct($name, $data, $dataName);
1304 $this->setBackupGlobals(false);
1305 $this->setBackupStaticAttributes(false);
1306 $this->setRunTestInSeparateProcess(false);
1310 * Runs the bare test sequence.
1311 * @return void
1313 final public function runBare() {
1314 global $DB;
1316 if (phpunit_util::$lastdbwrites != $DB->perf_get_writes()) {
1317 // this happens when previous test does not reset, we can not use transactions
1318 $this->testdbtransaction = null;
1320 } else if ($DB->get_dbfamily() === 'postgres' or $DB->get_dbfamily() === 'mssql') {
1321 // database must allow rollback of DDL, so no mysql here
1322 $this->testdbtransaction = $DB->start_delegated_transaction();
1325 try {
1326 parent::runBare();
1327 // set DB reference in case somebody mocked it in test
1328 $DB = phpunit_util::get_global_backup('DB');
1329 } catch (Exception $e) {
1330 // cleanup after failed expectation
1331 phpunit_util::reset_all_data();
1332 throw $e;
1335 if (!$this->testdbtransaction or $this->testdbtransaction->is_disposed()) {
1336 $this->testdbtransaction = null;
1339 if ($this->resetAfterTest === true) {
1340 if ($this->testdbtransaction) {
1341 $DB->force_transaction_rollback();
1342 phpunit_util::reset_all_database_sequences();
1343 phpunit_util::$lastdbwrites = $DB->perf_get_writes(); // no db reset necessary
1345 phpunit_util::reset_all_data();
1347 } else if ($this->resetAfterTest === false) {
1348 if ($this->testdbtransaction) {
1349 $this->testdbtransaction->allow_commit();
1351 // keep all data untouched for other tests
1353 } else {
1354 // reset but log what changed
1355 if ($this->testdbtransaction) {
1356 try {
1357 $this->testdbtransaction->allow_commit();
1358 } catch (dml_transaction_exception $e) {
1359 phpunit_util::reset_all_data();
1360 throw new coding_exception('Invalid transaction state detected in test '.$this->getName());
1363 phpunit_util::reset_all_data(true);
1366 // make sure test did not forget to close transaction
1367 if ($DB->is_transaction_started()) {
1368 phpunit_util::reset_all_data();
1369 if ($this->getStatus() == PHPUnit_Runner_BaseTestRunner::STATUS_PASSED
1370 or $this->getStatus() == PHPUnit_Runner_BaseTestRunner::STATUS_SKIPPED
1371 or $this->getStatus() == PHPUnit_Runner_BaseTestRunner::STATUS_INCOMPLETE) {
1372 throw new coding_exception('Test '.$this->getName().' did not close database transaction');
1378 * Creates a new FlatXmlDataSet with the given $xmlFile. (absolute path.)
1380 * @param string $xmlFile
1381 * @return PHPUnit_Extensions_Database_DataSet_FlatXmlDataSet
1383 protected function createFlatXMLDataSet($xmlFile) {
1384 return new PHPUnit_Extensions_Database_DataSet_FlatXmlDataSet($xmlFile);
1388 * Creates a new XMLDataSet with the given $xmlFile. (absolute path.)
1390 * @param string $xmlFile
1391 * @return PHPUnit_Extensions_Database_DataSet_XmlDataSet
1393 protected function createXMLDataSet($xmlFile) {
1394 return new PHPUnit_Extensions_Database_DataSet_XmlDataSet($xmlFile);
1398 * Creates a new CsvDataSet from the given array of csv files. (absolute paths.)
1400 * @param array $files array tablename=>cvsfile
1401 * @param string $delimiter
1402 * @param string $enclosure
1403 * @param string $escape
1404 * @return PHPUnit_Extensions_Database_DataSet_CsvDataSet
1406 protected function createCsvDataSet($files, $delimiter = ',', $enclosure = '"', $escape = '"') {
1407 $dataSet = new PHPUnit_Extensions_Database_DataSet_CsvDataSet($delimiter, $enclosure, $escape);
1408 foreach($files as $table=>$file) {
1409 $dataSet->addTable($table, $file);
1411 return $dataSet;
1415 * Creates new ArrayDataSet from given array
1417 * @param array $data array of tables, first row in each table is columns
1418 * @return phpunit_ArrayDataSet
1420 protected function createArrayDataSet(array $data) {
1421 return new phpunit_ArrayDataSet($data);
1425 * Load date into moodle database tables from standard PHPUnit data set.
1427 * Note: it is usually better to use data generators
1429 * @param PHPUnit_Extensions_Database_DataSet_IDataSet $dataset
1430 * @return void
1432 protected function loadDataSet(PHPUnit_Extensions_Database_DataSet_IDataSet $dataset) {
1433 global $DB;
1435 $structure = phpunit_util::get_tablestructure();
1437 foreach($dataset->getTableNames() as $tablename) {
1438 $table = $dataset->getTable($tablename);
1439 $metadata = $dataset->getTableMetaData($tablename);
1440 $columns = $metadata->getColumns();
1442 $doimport = false;
1443 if (isset($structure[$tablename]['id']) and $structure[$tablename]['id']->auto_increment) {
1444 $doimport = in_array('id', $columns);
1447 for($r=0; $r<$table->getRowCount(); $r++) {
1448 $record = $table->getRow($r);
1449 if ($doimport) {
1450 $DB->import_record($tablename, $record);
1451 } else {
1452 $DB->insert_record($tablename, $record);
1455 if ($doimport) {
1456 $DB->get_manager()->reset_sequence(new xmldb_table($tablename));
1462 * Call this method from test if you want to make sure that
1463 * the resetting of database is done the slow way without transaction
1464 * rollback.
1466 * This is useful especially when testing stuff that is not compatible with transactions.
1468 * @return void
1470 public function preventResetByRollback() {
1471 if ($this->testdbtransaction and !$this->testdbtransaction->is_disposed()) {
1472 $this->testdbtransaction->allow_commit();
1473 $this->testdbtransaction = null;
1478 * Reset everything after current test.
1479 * @param bool $reset true means reset state back, false means keep all data for the next test,
1480 * null means reset state and show warnings if anything changed
1481 * @return void
1483 public function resetAfterTest($reset = true) {
1484 $this->resetAfterTest = $reset;
1488 * Cleanup after all tests are executed.
1490 * Note: do not forget to call this if overridden...
1492 * @static
1493 * @return void
1495 public static function tearDownAfterClass() {
1496 phpunit_util::reset_all_data();
1500 * Reset all database tables, restore global state and clear caches and optionally purge dataroot dir.
1501 * @static
1502 * @return void
1504 public static function resetAllData() {
1505 phpunit_util::reset_all_data();
1509 * Set current $USER, reset access cache.
1510 * @static
1511 * @param null|int|stdClass $user user record, null means non-logged-in, integer means userid
1512 * @return void
1514 public static function setUser($user = null) {
1515 global $CFG, $DB;
1517 if (is_object($user)) {
1518 $user = clone($user);
1519 } else if (!$user) {
1520 $user = new stdClass();
1521 $user->id = 0;
1522 $user->mnethostid = $CFG->mnet_localhost_id;
1523 } else {
1524 $user = $DB->get_record('user', array('id'=>$user));
1526 unset($user->description);
1527 unset($user->access);
1529 session_set_user($user);
1533 * Get data generator
1534 * @static
1535 * @return phpunit_data_generator
1537 public static function getDataGenerator() {
1538 return phpunit_util::get_data_generator();
1542 * Recursively visit all the files in the source tree. Calls the callback
1543 * function with the pathname of each file found.
1545 * @param string $path the folder to start searching from.
1546 * @param string $callback the method of this class to call with the name of each file found.
1547 * @param string $fileregexp a regexp used to filter the search (optional).
1548 * @param bool $exclude If true, pathnames that match the regexp will be ignored. If false,
1549 * only files that match the regexp will be included. (default false).
1550 * @param array $ignorefolders will not go into any of these folders (optional).
1551 * @return void
1553 public function recurseFolders($path, $callback, $fileregexp = '/.*/', $exclude = false, $ignorefolders = array()) {
1554 $files = scandir($path);
1556 foreach ($files as $file) {
1557 $filepath = $path .'/'. $file;
1558 if (strpos($file, '.') === 0) {
1559 /// Don't check hidden files.
1560 continue;
1561 } else if (is_dir($filepath)) {
1562 if (!in_array($filepath, $ignorefolders)) {
1563 $this->recurseFolders($filepath, $callback, $fileregexp, $exclude, $ignorefolders);
1565 } else if ($exclude xor preg_match($fileregexp, $filepath)) {
1566 $this->$callback($filepath);
1574 * based on array iterator code from PHPUnit documentation by Sebastian Bergmann
1575 * and added new constructor parameter for different array types.
1577 class phpunit_ArrayDataSet extends PHPUnit_Extensions_Database_DataSet_AbstractDataSet {
1579 * @var array
1581 protected $tables = array();
1584 * @param array $data
1586 public function __construct(array $data) {
1587 foreach ($data AS $tableName => $rows) {
1588 $firstrow = reset($rows);
1590 if (array_key_exists(0, $firstrow)) {
1591 // columns in first row
1592 $columnsInFirstRow = true;
1593 $columns = $firstrow;
1594 $key = key($rows);
1595 unset($rows[$key]);
1596 } else {
1597 // column name is in each row as key
1598 $columnsInFirstRow = false;
1599 $columns = array_keys($firstrow);
1602 $metaData = new PHPUnit_Extensions_Database_DataSet_DefaultTableMetaData($tableName, $columns);
1603 $table = new PHPUnit_Extensions_Database_DataSet_DefaultTable($metaData);
1605 foreach ($rows AS $row) {
1606 if ($columnsInFirstRow) {
1607 $row = array_combine($columns, $row);
1609 $table->addRow($row);
1611 $this->tables[$tableName] = $table;
1615 protected function createIterator($reverse = FALSE) {
1616 return new PHPUnit_Extensions_Database_DataSet_DefaultTableIterator($this->tables, $reverse);
1619 public function getTable($tableName) {
1620 if (!isset($this->tables[$tableName])) {
1621 throw new InvalidArgumentException("$tableName is not a table in the current database.");
1624 return $this->tables[$tableName];
1630 * Special test case for testing of DML drivers and DDL layer.
1632 * Note: Use only 'test_table*' names when creating new tables.
1634 * For DML/DDL developers: you can add following settings to config.php if you want to test different driver than the main one,
1635 * the reason is to allow testing of incomplete drivers that do not allow full PHPUnit environment
1636 * initialisation (the database can be empty).
1637 * $CFG->phpunit_extra_drivers = array(
1638 * 1=>array('dbtype'=>'mysqli', 'dbhost'=>'localhost', 'dbname'=>'moodle', 'dbuser'=>'root', 'dbpass'=>'', 'prefix'=>'phpu2_'),
1639 * 2=>array('dbtype'=>'pgsql', 'dbhost'=>'localhost', 'dbname'=>'moodle', 'dbuser'=>'postgres', 'dbpass'=>'', 'prefix'=>'phpu2_'),
1640 * 3=>array('dbtype'=>'sqlsrv', 'dbhost'=>'127.0.0.1', 'dbname'=>'moodle', 'dbuser'=>'sa', 'dbpass'=>'', 'prefix'=>'phpu2_'),
1641 * 4=>array('dbtype'=>'oci', 'dbhost'=>'127.0.0.1', 'dbname'=>'XE', 'dbuser'=>'sa', 'dbpass'=>'', 'prefix'=>'t_'),
1642 * );
1643 * define('PHPUNIT_TEST_DRIVER')=1; //number is index in the previous array
1645 * @package core
1646 * @category phpunit
1647 * @copyright 2012 Petr Skoda {@link http://skodak.org}
1648 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1650 abstract class database_driver_testcase extends PHPUnit_Framework_TestCase {
1651 /** @var moodle_database connection to extra database */
1652 private static $extradb = null;
1654 /** @var moodle_database used in these tests*/
1655 protected $tdb;
1658 * Constructs a test case with the given name.
1660 * @param string $name
1661 * @param array $data
1662 * @param string $dataName
1664 final public function __construct($name = null, array $data = array(), $dataName = '') {
1665 parent::__construct($name, $data, $dataName);
1667 $this->setBackupGlobals(false);
1668 $this->setBackupStaticAttributes(false);
1669 $this->setRunTestInSeparateProcess(false);
1672 public static function setUpBeforeClass() {
1673 global $CFG;
1674 parent::setUpBeforeClass();
1676 if (!defined('PHPUNIT_TEST_DRIVER')) {
1677 // use normal $DB
1678 return;
1681 if (!isset($CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER])) {
1682 throw new exception('Can not find driver configuration options with index: '.PHPUNIT_TEST_DRIVER);
1685 $dblibrary = empty($CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER]['dblibrary']) ? 'native' : $CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER]['dblibrary'];
1686 $dbtype = $CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER]['dbtype'];
1687 $dbhost = $CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER]['dbhost'];
1688 $dbname = $CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER]['dbname'];
1689 $dbuser = $CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER]['dbuser'];
1690 $dbpass = $CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER]['dbpass'];
1691 $prefix = $CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER]['prefix'];
1692 $dboptions = empty($CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER]['dboptions']) ? array() : $CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER]['dboptions'];
1694 $classname = "{$dbtype}_{$dblibrary}_moodle_database";
1695 require_once("$CFG->libdir/dml/$classname.php");
1696 $d = new $classname();
1697 if (!$d->driver_installed()) {
1698 throw new exception('Database driver for '.$classname.' is not installed');
1701 $d->connect($dbhost, $dbuser, $dbpass, $dbname, $prefix, $dboptions);
1703 self::$extradb = $d;
1706 protected function setUp() {
1707 global $DB;
1708 parent::setUp();
1710 if (self::$extradb) {
1711 $this->tdb = self::$extradb;
1712 } else {
1713 $this->tdb = $DB;
1717 protected function tearDown() {
1718 // delete all test tables
1719 $dbman = $this->tdb->get_manager();
1720 $tables = $this->tdb->get_tables(false);
1721 foreach($tables as $tablename) {
1722 if (strpos($tablename, 'test_table') === 0) {
1723 $table = new xmldb_table($tablename);
1724 $dbman->drop_table($table);
1727 parent::tearDown();
1730 public static function tearDownAfterClass() {
1731 if (self::$extradb) {
1732 self::$extradb->dispose();
1733 self::$extradb = null;
1735 phpunit_util::reset_all_data();
1736 parent::tearDownAfterClass();