MDL-32323 allow execution of tests through util.php and add option to include alterna...
[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
7aea08e1
SH
43 /**
44 * @var array An array of globals cloned from CFG
45 */
5bd40408
PS
46 protected static $globals = array();
47
a3d5830a
PS
48 /**
49 * @var int last value of db writes counter, used for db resetting
50 */
51 protected static $lastdbwrites = null;
52
53 /**
54 * @var phpunit_data_generator
55 */
56 protected static $generator = null;
57
58 /**
59 * Get data generator
60 * @static
61 * @return phpunit_data_generator
62 */
63 public static function get_data_generator() {
64 if (is_null(self::$generator)) {
65 require_once(__DIR__.'/generatorlib.php');
66 self::$generator = new phpunit_data_generator();
67 }
68 return self::$generator;
69 }
70
5bd40408
PS
71 /**
72 * Returns contents of all tables right after installation.
73 * @static
74 * @return array $table=>$records
75 */
76 protected static function get_tabledata() {
77 global $CFG;
78
a3d5830a
PS
79 if (!file_exists("$CFG->dataroot/phpunit/tabledata.ser")) {
80 // not initialised yet
81 return array();
82 }
83
5bd40408
PS
84 if (!isset(self::$tabledata)) {
85 $data = file_get_contents("$CFG->dataroot/phpunit/tabledata.ser");
86 self::$tabledata = unserialize($data);
87 }
88
89 if (!is_array(self::$tabledata)) {
7b0ff213 90 phpunit_bootstrap_error(1, 'Can not read dataroot/phpunit/tabledata.ser or invalid format, reinitialize test database.');
5bd40408
PS
91 }
92
93 return self::$tabledata;
94 }
95
96 /**
a3d5830a 97 * Reset all database tables to default values.
5bd40408 98 * @static
a3d5830a 99 * @return bool true if reset done, false if skipped
5bd40408 100 */
7b0ff213 101 public static function reset_database() {
a3d5830a
PS
102 global $DB;
103
a3d5830a
PS
104 $tables = $DB->get_tables(false);
105 if (!$tables or empty($tables['config'])) {
106 // not installed yet
5bd40408
PS
107 return;
108 }
109
a3d5830a
PS
110 $dbreset = false;
111 if (is_null(self::$lastdbwrites) or self::$lastdbwrites != $DB->perf_get_writes()) {
112 if ($data = self::get_tabledata()) {
113 $trans = $DB->start_delegated_transaction(); // faster and safer
728eadac
PS
114
115 $resetseq = array();
a3d5830a 116 foreach ($data as $table=>$records) {
728eadac
PS
117 if (empty($records)) {
118 if ($DB->count_records($table)) {
119 $DB->delete_records($table, array());
120 $resetseq[$table] = $table;
121 }
122 continue;
123 }
124
125 $firstrecord = reset($records);
126 if (property_exists($firstrecord, 'id')) {
03c2d04d 127 if ($DB->count_records($table) >= count($records)) {
728eadac
PS
128 $currentrecords = $DB->get_records($table, array(), 'id ASC');
129 $changed = false;
130 foreach ($records as $id=>$record) {
131 if (!isset($currentrecords[$id])) {
132 $changed = true;
133 break;
134 }
135 if ((array)$record != (array)$currentrecords[$id]) {
136 $changed = true;
137 break;
138 }
03c2d04d 139 unset($currentrecords[$id]);
728eadac
PS
140 }
141 if (!$changed) {
03c2d04d
PS
142 if ($currentrecords) {
143 $remainingfirst = reset($currentrecords);
144 $lastrecord = end($records);
145 if ($remainingfirst->id > $lastrecord->id) {
146 $DB->delete_records_select($table, "id >= ?", array($remainingfirst->id));
147 $resetseq[$table] = $table;
148 continue;
149 }
150 } else {
151 continue;
152 }
728eadac
PS
153 }
154 }
155 }
156
a3d5830a 157 $DB->delete_records($table, array());
728eadac
PS
158 if (property_exists($firstrecord, 'id')) {
159 $resetseq[$table] = $table;
160 }
a3d5830a 161 foreach ($records as $record) {
a3d5830a
PS
162 $DB->import_record($table, $record, false, true);
163 }
a3d5830a 164 }
728eadac
PS
165 // reset all sequences
166 foreach ($resetseq as $table) {
167 $DB->get_manager()->reset_sequence($table, true);
168 }
169
a3d5830a 170 $trans->allow_commit();
728eadac 171
dbf5a447
PS
172 // remove extra tables
173 foreach ($tables as $tablename) {
174 if (!isset($data[$tablename])) {
175 $DB->get_manager()->drop_table(new xmldb_table($tablename));
176 }
177 }
728eadac 178 $dbreset = true;
5bd40408 179 }
5bd40408 180 }
a3d5830a
PS
181
182 self::$lastdbwrites = $DB->perf_get_writes();
183
184 return $dbreset;
5bd40408
PS
185 }
186
728eadac
PS
187 /**
188 * Purge dataroot
189 * @static
190 * @return void
191 */
192 public static function reset_dataroot() {
193 global $CFG;
194
195 $handle = opendir($CFG->dataroot);
196 $skip = array('.', '..', 'phpunittestdir.txt', 'phpunit', '.htaccess');
197 while (false !== ($item = readdir($handle))) {
198 if (in_array($item, $skip)) {
199 continue;
200 }
201 if (is_dir("$CFG->dataroot/$item")) {
202 remove_dir("$CFG->dataroot/$item", false);
203 } else {
204 unlink("$CFG->dataroot/$item");
205 }
206 }
207 closedir($handle);
208 make_temp_directory('');
209 make_cache_directory('');
210 make_cache_directory('htmlpurifier');
211 }
212
5bd40408
PS
213 /**
214 * Reset contents of all database tables to initial values, reset caches, etc.
215 *
216 * Note: this is relatively slow (cca 2 seconds for pg and 7 for mysql) - please use with care!
217 *
a3d5830a 218 * @param bool $logchanges log changes in global state and database in error log
a3d5830a 219 * @return void
5bd40408
PS
220 * @static
221 */
7b0ff213 222 public static function reset_all_data($logchanges = false) {
458b3386 223 global $DB, $CFG, $USER, $SITE, $COURSE, $PAGE, $OUTPUT, $SESSION;
5bd40408 224
7b0ff213
PS
225 self::reset_database($logchanges);
226 $warnings = array();
5bd40408 227
a3d5830a 228 if ($logchanges) {
7b0ff213 229 if (self::$lastdbwrites != $DB->perf_get_writes()) {
6e2cff2d 230 $warnings[] = 'Warning: unexpected database modification, resetting DB state';
a3d5830a
PS
231 }
232
233 $oldcfg = self::get_global_backup('CFG');
458b3386 234 $oldsite = self::get_global_backup('SITE');
a3d5830a
PS
235 foreach($CFG as $k=>$v) {
236 if (!property_exists($oldcfg, $k)) {
6e2cff2d 237 $warnings[] = 'Warning: unexpected new $CFG->'.$k.' value';
a3d5830a 238 } else if ($oldcfg->$k !== $CFG->$k) {
6e2cff2d 239 $warnings[] = 'Warning: unexpected change of $CFG->'.$k.' value';
a3d5830a
PS
240 }
241 unset($oldcfg->$k);
242
243 }
244 if ($oldcfg) {
245 foreach($oldcfg as $k=>$v) {
6e2cff2d 246 $warnings[] = 'Warning: unexpected removal of $CFG->'.$k;
5bd40408 247 }
5bd40408 248 }
a3d5830a
PS
249
250 if ($USER->id != 0) {
6e2cff2d 251 $warnings[] = 'Warning: unexpected change of $USER';
5bd40408 252 }
458b3386
PS
253
254 if ($COURSE->id != $oldsite->id) {
6e2cff2d 255 $warnings[] = 'Warning: unexpected change of $COURSE';
458b3386 256 }
5bd40408 257 }
5bd40408 258
a3d5830a 259 // restore original config
6e2cff2d 260 $_SERVER = self::get_global_backup('_SERVER');
a3d5830a 261 $CFG = self::get_global_backup('CFG');
458b3386
PS
262 $SITE = self::get_global_backup('SITE');
263 $COURSE = $SITE;
264
265 // recreate globals
266 $OUTPUT = new bootstrap_renderer();
267 $PAGE = new moodle_page();
268 $FULLME = null;
269 $ME = null;
270 $SCRIPT = null;
271 $SESSION = new stdClass();
272 $_SESSION['SESSION'] =& $SESSION;
5bd40408 273
a3d5830a 274 // set fresh new user
5bd40408
PS
275 $user = new stdClass();
276 $user->id = 0;
5bd40408
PS
277 $user->mnethostid = $CFG->mnet_localhost_id;
278 session_set_user($user);
a3d5830a
PS
279
280 // reset all static caches
281 accesslib_clear_all_caches(true);
282 get_string_manager()->reset_caches();
283 //TODO: add more resets here and probably refactor them to new core function
284
285 // purge dataroot
728eadac 286 self::reset_dataroot();
a3d5830a
PS
287
288 // restore original config once more in case resetting of caches changes CFG
289 $CFG = self::get_global_backup('CFG');
290
291 // remember db writes
292 self::$lastdbwrites = $DB->perf_get_writes();
293
294 // inform data generator
295 self::get_data_generator()->reset();
296
297 // fix PHP settings
298 error_reporting($CFG->debug);
7b0ff213
PS
299
300 if ($warnings) {
301 $warnings = implode("\n", $warnings);
302 trigger_error($warnings, E_USER_WARNING);
303 }
5bd40408
PS
304 }
305
306 /**
307 * Called during bootstrap only!
308 * @static
5bd40408 309 */
a3d5830a 310 public static function bootstrap_init() {
458b3386 311 global $CFG, $SITE;
5bd40408 312
a3d5830a 313 // backup the globals
6e2cff2d 314 self::$globals['_SERVER'] = $_SERVER;
5bd40408 315 self::$globals['CFG'] = clone($CFG);
458b3386 316 self::$globals['SITE'] = clone($SITE);
a3d5830a
PS
317
318 // refresh data in all tables, clear caches, etc.
319 phpunit_util::reset_all_data();
5bd40408
PS
320 }
321
322 /**
323 * Returns original state of global variable.
324 * @static
325 * @param string $name
326 * @return mixed
327 */
328 public static function get_global_backup($name) {
329 if (isset(self::$globals[$name])) {
330 if (is_object(self::$globals[$name])) {
331 $return = clone(self::$globals[$name]);
332 return $return;
333 } else {
334 return self::$globals[$name];
335 }
336 }
337 return null;
338 }
339
340 /**
341 * Does this site (db and dataroot) appear to be used for production?
342 * We try very hard to prevent accidental damage done to production servers!!
343 *
344 * @static
345 * @return bool
346 */
347 public static function is_test_site() {
348 global $DB, $CFG;
349
350 if (!file_exists("$CFG->dataroot/phpunittestdir.txt")) {
351 // this is already tested in bootstrap script,
a3d5830a 352 // but anyway presence of this file means the dataroot is for testing
5bd40408
PS
353 return false;
354 }
355
356 $tables = $DB->get_tables(false);
357 if ($tables) {
358 if (!$DB->get_manager()->table_exists('config')) {
359 return false;
360 }
361 if (!get_config('core', 'phpunittest')) {
362 return false;
363 }
364 }
365
366 return true;
367 }
368
369 /**
370 * Is this site initialised to run unit tests?
371 *
372 * @static
7b0ff213 373 * @return int array errorcode=>message, 0 means ok
5bd40408 374 */
a3d5830a 375 public static function testing_ready_problem() {
7b0ff213 376 global $CFG, $DB;
5bd40408 377
7b0ff213 378 $tables = $DB->get_tables(false);
5bd40408 379
7b0ff213
PS
380 if (!self::is_test_site()) {
381 // dataroot was verified in bootstrap, so it must be DB
382 return array(131, 'Can not use test database, try changing prefix');
5bd40408
PS
383 }
384
385 if (!file_exists("$CFG->dataroot/phpunit/tabledata.ser")) {
6e2cff2d 386 if (empty($tables)) {
7b0ff213
PS
387 return array(132, '');
388 } else {
389 return array(133, '');
390 }
5bd40408
PS
391 }
392
393 if (!file_exists("$CFG->dataroot/phpunit/versionshash.txt")) {
6e2cff2d 394 if (empty($tables)) {
7b0ff213
PS
395 return array(132, '');
396 } else {
397 return array(133, '');
398 }
5bd40408
PS
399 }
400
401 $hash = phpunit_util::get_version_hash();
402 $oldhash = file_get_contents("$CFG->dataroot/phpunit/versionshash.txt");
403
404 if ($hash !== $oldhash) {
7b0ff213 405 return array(133, '');
5bd40408
PS
406 }
407
7b0ff213 408 return array(0, '');
5bd40408
PS
409 }
410
411 /**
412 * Drop all test site data.
413 *
414 * Note: To be used from CLI scripts only.
415 *
416 * @static
7aea08e1 417 * @return void may terminate execution with exit code
5bd40408
PS
418 */
419 public static function drop_site() {
420 global $DB, $CFG;
421
422 if (!self::is_test_site()) {
423 cli_error('Can not drop non-test sites!!', 131);
424 }
425
426 // drop dataroot
728eadac 427 self::reset_dataroot();
5bd40408 428 phpunit_bootstrap_initdataroot($CFG->dataroot);
728eadac 429 remove_dir("$CFG->dataroot/phpunit", true);
5bd40408
PS
430
431 // drop all tables
5bd40408 432 $tables = $DB->get_tables(false);
728eadac
PS
433 if (isset($tables['config'])) {
434 // config always last to prevent problems with interrupted drops!
435 unset($tables['config']);
436 $tables['config'] = 'config';
5bd40408 437 }
5bd40408
PS
438 foreach ($tables as $tablename) {
439 $table = new xmldb_table($tablename);
440 $DB->get_manager()->drop_table($table);
441 }
442 }
443
444 /**
445 * Perform a fresh test site installation
446 *
447 * Note: To be used from CLI scripts only.
448 *
449 * @static
7aea08e1 450 * @return void may terminate execution with exit code
5bd40408
PS
451 */
452 public static function install_site() {
453 global $DB, $CFG;
454
455 if (!self::is_test_site()) {
456 cli_error('Can not install non-test sites!!', 131);
457 }
458
459 if ($DB->get_tables()) {
460 cli_error('Database tables already installed, drop the site first.', 133);
461 }
462
463 $options = array();
6e2cff2d 464 $options['adminpass'] = 'admin';
5bd40408
PS
465 $options['shortname'] = 'phpunit';
466 $options['fullname'] = 'PHPUnit test site';
467
468 install_cli_database($options, false);
469
a3d5830a
PS
470 // install timezone info
471 $timezones = get_records_csv($CFG->libdir.'/timezone.txt', 'timezone');
472 update_timezone_records($timezones);
5bd40408
PS
473
474 // add test db flag
475 set_config('phpunittest', 'phpunittest');
476
477 // store data for all tables
478 $data = array();
479 $tables = $DB->get_tables();
480 foreach ($tables as $table) {
728eadac
PS
481 $columns = $DB->get_columns($table);
482 if (isset($columns['id'])) {
483 $data[$table] = $DB->get_records($table, array(), 'id ASC');
484 } else {
485 // there should not be many of these
486 $data[$table] = $DB->get_records($table, array());
487 }
5bd40408
PS
488 }
489 $data = serialize($data);
5bd40408 490 file_put_contents("$CFG->dataroot/phpunit/tabledata.ser", $data);
6e2cff2d 491 phpunit_boostrap_fix_file_permissions("$CFG->dataroot/phpunit/tabledata.ser");
5bd40408
PS
492
493 // hash all plugin versions - helps with very fast detection of db structure changes
494 $hash = phpunit_util::get_version_hash();
5bd40408 495 file_put_contents("$CFG->dataroot/phpunit/versionshash.txt", $hash);
6e2cff2d 496 phpunit_boostrap_fix_file_permissions("$CFG->dataroot/phpunit/versionshash.txt", $hash);
5bd40408
PS
497 }
498
499 /**
a3d5830a 500 * Calculate unique version hash for all available plugins and core.
5bd40408
PS
501 * @static
502 * @return string sha1 hash
503 */
504 public static function get_version_hash() {
505 global $CFG;
506
507 $versions = array();
508
509 // main version first
510 $version = null;
511 include($CFG->dirroot.'/version.php');
512 $versions['core'] = $version;
513
514 // modules
515 $mods = get_plugin_list('mod');
516 ksort($mods);
517 foreach ($mods as $mod => $fullmod) {
518 $module = new stdClass();
519 $module->version = null;
520 include($fullmod.'/version.php');
521 $versions[$mod] = $module->version;
522 }
523
524 // now the rest of plugins
525 $plugintypes = get_plugin_types();
526 unset($plugintypes['mod']);
527 ksort($plugintypes);
528 foreach ($plugintypes as $type=>$unused) {
529 $plugs = get_plugin_list($type);
530 ksort($plugs);
531 foreach ($plugs as $plug=>$fullplug) {
532 $plugin = new stdClass();
533 $plugin->version = null;
534 @include($fullplug.'/version.php');
535 $versions[$plug] = $plugin->version;
536 }
537 }
538
539 $hash = sha1(serialize($versions));
540
541 return $hash;
542 }
543
544 /**
7b0ff213 545 * Builds dirroot/phpunit.xml and dataroot/phpunit/webrunner.xml file using defaults from /phpunit.xml.dist
5bd40408 546 * @static
7b0ff213 547 * @return bool true means main config file created, false means only dataroot file created
5bd40408
PS
548 */
549 public static function build_config_file() {
550 global $CFG;
551
552 $template = '
5bd40408
PS
553 <testsuite name="@component@">
554 <directory suffix="_test.php">@dir@</directory>
a3d5830a 555 </testsuite>';
5bd40408
PS
556 $data = file_get_contents("$CFG->dirroot/phpunit.xml.dist");
557
558 $suites = '';
559
560 $plugintypes = get_plugin_types();
561 ksort($plugintypes);
562 foreach ($plugintypes as $type=>$unused) {
563 $plugs = get_plugin_list($type);
564 ksort($plugs);
565 foreach ($plugs as $plug=>$fullplug) {
566 if (!file_exists("$fullplug/tests/")) {
567 continue;
568 }
569 $dir = preg_replace("|$CFG->dirroot/|", '', $fullplug, 1);
570 $dir .= '/tests';
571 $component = $type.'_'.$plug;
572
573 $suite = str_replace('@component@', $component, $template);
574 $suite = str_replace('@dir@', $dir, $suite);
575
576 $suites .= $suite;
577 }
578 }
579
7aea08e1 580 $data = preg_replace('|<!--@plugin_suites_start@-->.*<!--@plugin_suites_end@-->|s', $suites, $data, 1);
5bd40408 581
7b0ff213
PS
582 $result = false;
583 if (is_writable($CFG->dirroot)) {
584 if ($result = file_put_contents("$CFG->dirroot/phpunit.xml", $data)) {
6e2cff2d 585 phpunit_boostrap_fix_file_permissions("$CFG->dirroot/phpunit.xml");
7b0ff213
PS
586 }
587 }
6e2cff2d 588 // 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
589 $data = str_replace('lib/phpunit/', "$CFG->dirroot/lib/phpunit/", $data);
590 $data = preg_replace('|<directory suffix="_test.php">([^<]+)</directory>|', '<directory suffix="_test.php">'.$CFG->dirroot.'/$1</directory>', $data);
591 file_put_contents("$CFG->dataroot/phpunit/webrunner.xml", $data);
6e2cff2d 592 phpunit_boostrap_fix_file_permissions("$CFG->dataroot/phpunit/webrunner.xml");
7b0ff213
PS
593
594 return (bool)$result;
5bd40408
PS
595 }
596}
597
598
599/**
600 * Simplified emulation test case for legacy SimpleTest.
601 *
602 * Note: this is supposed to work for very simple tests only.
603 *
7aea08e1
SH
604 * @deprecated since 2.3
605 * @package core
606 * @category phpunit
5bd40408
PS
607 * @author Petr Skoda
608 * @copyright 2012 Petr Skoda {@link http://skodak.org}
609 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
610 */
611class UnitTestCase extends PHPUnit_Framework_TestCase {
612
613 /**
7aea08e1 614 * @deprecated since 2.3
5bd40408
PS
615 * @param bool $expected
616 * @param string $message
a3d5830a 617 * @return void
5bd40408
PS
618 */
619 public function expectException($expected, $message = '') {
620 // use phpdocs: @expectedException ExceptionClassName
621 if (!$expected) {
622 return;
623 }
624 $this->setExpectedException('moodle_exception', $message);
625 }
626
627 /**
7aea08e1 628 * @deprecated since 2.3
5bd40408
PS
629 * @param bool $expected
630 * @param string $message
a3d5830a 631 * @return void
5bd40408
PS
632 */
633 public static function expectError($expected = false, $message = '') {
634 // not available in PHPUnit
635 if (!$expected) {
636 return;
637 }
638 self::skipIf(true);
639 }
640
641 /**
7aea08e1 642 * @deprecated since 2.3
5bd40408
PS
643 * @static
644 * @param mixed $actual
645 * @param string $messages
a3d5830a 646 * @return void
5bd40408
PS
647 */
648 public static function assertTrue($actual, $messages = '') {
649 parent::assertTrue((bool)$actual, $messages);
650 }
651
652 /**
7aea08e1 653 * @deprecated since 2.3
5bd40408
PS
654 * @static
655 * @param mixed $actual
656 * @param string $messages
a3d5830a 657 * @return void
5bd40408
PS
658 */
659 public static function assertFalse($actual, $messages = '') {
660 parent::assertFalse((bool)$actual, $messages);
661 }
662
663 /**
7aea08e1 664 * @deprecated since 2.3
5bd40408
PS
665 * @static
666 * @param mixed $expected
667 * @param mixed $actual
668 * @param string $message
a3d5830a 669 * @return void
5bd40408
PS
670 */
671 public static function assertEqual($expected, $actual, $message = '') {
672 parent::assertEquals($expected, $actual, $message);
673 }
674
675 /**
7aea08e1 676 * @deprecated since 2.3
5bd40408
PS
677 * @static
678 * @param mixed $expected
679 * @param mixed $actual
680 * @param string $message
a3d5830a 681 * @return void
5bd40408
PS
682 */
683 public static function assertNotEqual($expected, $actual, $message = '') {
684 parent::assertNotEquals($expected, $actual, $message);
685 }
686
687 /**
7aea08e1 688 * @deprecated since 2.3
5bd40408
PS
689 * @static
690 * @param mixed $expected
691 * @param mixed $actual
692 * @param string $message
a3d5830a 693 * @return void
5bd40408
PS
694 */
695 public static function assertIdentical($expected, $actual, $message = '') {
696 parent::assertSame($expected, $actual, $message);
697 }
698
699 /**
7aea08e1 700 * @deprecated since 2.3
5bd40408
PS
701 * @static
702 * @param mixed $expected
703 * @param mixed $actual
704 * @param string $message
a3d5830a 705 * @return void
5bd40408
PS
706 */
707 public static function assertNotIdentical($expected, $actual, $message = '') {
708 parent::assertNotSame($expected, $actual, $message);
709 }
710
711 /**
7aea08e1 712 * @deprecated since 2.3
5bd40408
PS
713 * @static
714 * @param mixed $actual
715 * @param mixed $expected
716 * @param string $message
a3d5830a 717 * @return void
5bd40408
PS
718 */
719 public static function assertIsA($actual, $expected, $message = '') {
6e2cff2d
PS
720 if ($expected === 'array') {
721 parent::assertEquals(gettype($actual), 'array', $message);
722 } else {
723 parent::assertInstanceOf($expected, $actual, $message);
724 }
5bd40408
PS
725 }
726}
727
728
729/**
7aea08e1
SH
730 * The simplest PHPUnit test case customised for Moodle
731 *
a3d5830a 732 * It is intended for isolated tests that do not modify database or any globals.
5bd40408 733 *
7aea08e1
SH
734 * @package core
735 * @category phpunit
5bd40408
PS
736 * @copyright 2012 Petr Skoda {@link http://skodak.org}
737 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
738 */
739class basic_testcase extends PHPUnit_Framework_TestCase {
740
741 /**
742 * Constructs a test case with the given name.
743 *
b0e980d7
PS
744 * Note: use setUp() or setUpBeforeClass() in custom test cases.
745 *
7aea08e1
SH
746 * @param string $name
747 * @param array $data
748 * @param string $dataName
5bd40408 749 */
b0e980d7 750 final public function __construct($name = null, array $data = array(), $dataName = '') {
5bd40408
PS
751 parent::__construct($name, $data, $dataName);
752
753 $this->setBackupGlobals(false);
754 $this->setBackupStaticAttributes(false);
755 $this->setRunTestInSeparateProcess(false);
5bd40408
PS
756 }
757
758 /**
a3d5830a 759 * Runs the bare test sequence and log any changes in global state or database.
5bd40408
PS
760 * @return void
761 */
762 public function runBare() {
a3d5830a 763 parent::runBare();
7b0ff213 764 phpunit_util::reset_all_data(true);
a3d5830a
PS
765 }
766}
5bd40408 767
a3d5830a
PS
768
769/**
770 * Advanced PHPUnit test case customised for Moodle.
771 *
772 * @package core
773 * @category phpunit
774 * @copyright 2012 Petr Skoda {@link http://skodak.org}
775 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
776 */
777class advanced_testcase extends PHPUnit_Framework_TestCase {
458b3386 778 /** @var bool automatically reset everything? null means log changes */
a3d5830a
PS
779 protected $resetAfterTest;
780
781 /**
782 * Constructs a test case with the given name.
783 *
b0e980d7
PS
784 * Note: use setUp() or setUpBeforeClass() in custom test cases.
785 *
a3d5830a
PS
786 * @param string $name
787 * @param array $data
788 * @param string $dataName
789 */
b0e980d7 790 final public function __construct($name = null, array $data = array(), $dataName = '') {
a3d5830a
PS
791 parent::__construct($name, $data, $dataName);
792
793 $this->setBackupGlobals(false);
794 $this->setBackupStaticAttributes(false);
795 $this->setRunTestInSeparateProcess(false);
796 }
797
798 /**
799 * Runs the bare test sequence.
800 * @return void
801 */
802 public function runBare() {
803 $this->resetAfterTest = null;
5bd40408
PS
804
805 parent::runBare();
806
a3d5830a
PS
807 if ($this->resetAfterTest === true) {
808 self::resetAllData();
809 } else if ($this->resetAfterTest === false) {
810 // keep all data untouched for other tests
811 } else {
812 // reset but log what changed
7b0ff213 813 phpunit_util::reset_all_data(true);
a3d5830a
PS
814 }
815
816 $this->resetAfterTest = null;
817 }
818
819 /**
820 * Reset everything after current test.
821 * @param bool $reset true means reset state back, false means keep all data for the next test,
822 * null means reset state and show warnings if anything changed
823 * @return void
824 */
825 public function resetAfterTest($reset = true) {
826 $this->resetAfterTest = $reset;
827 }
828
829 /**
830 * Cleanup after all tests are executed.
831 *
832 * Note: do not forget to call this if overridden...
833 *
834 * @static
835 * @return void
836 */
837 public static function tearDownAfterClass() {
838 phpunit_util::reset_all_data();
839 }
840
841 /**
842 * Reset all database tables, restore global state and clear caches and optionally purge dataroot dir.
843 * @static
844 * @return void
845 */
846 public static function resetAllData() {
847 phpunit_util::reset_all_data();
848 }
849
458b3386
PS
850 /**
851 * Set current $USER, reset access cache.
852 * @static
e72ea4a5 853 * @param null|int|stdClass $user user record, null means non-logged-in, integer means userid
458b3386
PS
854 * @return void
855 */
e72ea4a5
PS
856 public static function setUser($user = null) {
857 global $CFG, $DB;
458b3386 858
e72ea4a5
PS
859 if (is_object($user)) {
860 $user = clone($user);
861 } else if (!$user) {
458b3386
PS
862 $user = new stdClass();
863 $user->id = 0;
864 $user->mnethostid = $CFG->mnet_localhost_id;
865 } else {
e72ea4a5 866 $user = $DB->get_record('user', array('id'=>$user));
458b3386
PS
867 }
868 unset($user->description);
869 unset($user->access);
870
871 session_set_user($user);
872 }
873
a3d5830a
PS
874 /**
875 * Get data generator
876 * @static
877 * @return phpunit_data_generator
878 */
879 public static function getDataGenerator() {
880 return phpunit_util::get_data_generator();
881 }
882
883 /**
884 * Recursively visit all the files in the source tree. Calls the callback
885 * function with the pathname of each file found.
886 *
6e2cff2d
PS
887 * @param string $path the folder to start searching from.
888 * @param string $callback the method of this class to call with the name of each file found.
889 * @param string $fileregexp a regexp used to filter the search (optional).
890 * @param bool $exclude If true, pathnames that match the regexp will be ignored. If false,
a3d5830a
PS
891 * only files that match the regexp will be included. (default false).
892 * @param array $ignorefolders will not go into any of these folders (optional).
893 * @return void
894 */
895 public function recurseFolders($path, $callback, $fileregexp = '/.*/', $exclude = false, $ignorefolders = array()) {
896 $files = scandir($path);
897
898 foreach ($files as $file) {
899 $filepath = $path .'/'. $file;
900 if (strpos($file, '.') === 0) {
901 /// Don't check hidden files.
902 continue;
903 } else if (is_dir($filepath)) {
904 if (!in_array($filepath, $ignorefolders)) {
905 $this->recurseFolders($filepath, $callback, $fileregexp, $exclude, $ignorefolders);
906 }
907 } else if ($exclude xor preg_match($fileregexp, $filepath)) {
908 $this->$callback($filepath);
5bd40408 909 }
a3d5830a
PS
910 }
911 }
912}
5bd40408 913
a3d5830a
PS
914
915/**
458b3386
PS
916 * Special test case for testing of DML drivers and DDL layer.
917 *
918 * Note: Use only 'test_table*' when creating new tables.
a3d5830a
PS
919 *
920 * @package core
921 * @category phpunit
922 * @copyright 2012 Petr Skoda {@link http://skodak.org}
923 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
924 */
925class database_driver_testcase extends PHPUnit_Framework_TestCase {
6e2cff2d 926 /** @var moodle_database connection to extra database */
a3d5830a 927 protected static $extradb = null;
a3d5830a 928
6e2cff2d 929 /** @var moodle_database used in these tests*/
a3d5830a
PS
930 protected $tdb;
931
932 /**
933 * Constructs a test case with the given name.
934 *
935 * @param string $name
936 * @param array $data
937 * @param string $dataName
938 */
b0e980d7 939 final public function __construct($name = null, array $data = array(), $dataName = '') {
a3d5830a
PS
940 parent::__construct($name, $data, $dataName);
941
942 $this->setBackupGlobals(false);
943 $this->setBackupStaticAttributes(false);
944 $this->setRunTestInSeparateProcess(false);
945 }
946
947 public static function setUpBeforeClass() {
948 global $CFG;
6e2cff2d 949 parent::setUpBeforeClass();
a3d5830a
PS
950
951 if (!defined('PHPUNIT_TEST_DRIVER')) {
952 // use normal $DB
953 return;
5bd40408 954 }
a3d5830a
PS
955
956 if (!isset($CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER])) {
957 throw new exception('Can not find driver configuration options with index: '.PHPUNIT_TEST_DRIVER);
958 }
959
960 $dblibrary = empty($CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER]['dblibrary']) ? 'native' : $CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER]['dblibrary'];
961 $dbtype = $CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER]['dbtype'];
962 $dbhost = $CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER]['dbhost'];
963 $dbname = $CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER]['dbname'];
964 $dbuser = $CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER]['dbuser'];
965 $dbpass = $CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER]['dbpass'];
966 $prefix = $CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER]['prefix'];
967 $dboptions = empty($CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER]['dboptions']) ? array() : $CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER]['dboptions'];
968
969 $classname = "{$dbtype}_{$dblibrary}_moodle_database";
970 require_once("$CFG->libdir/dml/$classname.php");
971 $d = new $classname();
972 if (!$d->driver_installed()) {
973 throw new exception('Database driver for '.$classname.' is not installed');
974 }
975
976 $d->connect($dbhost, $dbuser, $dbpass, $dbname, $prefix, $dboptions);
977
978 self::$extradb = $d;
979 }
980
1cbf2a20 981 protected function setUp() {
a3d5830a 982 global $DB;
6e2cff2d 983 parent::setUp();
5bd40408 984
a3d5830a
PS
985 if (self::$extradb) {
986 $this->tdb = self::$extradb;
987 } else {
988 $this->tdb = $DB;
5bd40408 989 }
a3d5830a 990 }
5bd40408 991
1cbf2a20 992 protected function tearDown() {
a3d5830a
PS
993 // delete all test tables
994 $dbman = $this->tdb->get_manager();
995 $tables = $this->tdb->get_tables(false);
996 foreach($tables as $tablename) {
997 if (strpos($tablename, 'test_table') === 0) {
998 $table = new xmldb_table($tablename);
999 $dbman->drop_table($table);
1000 }
5bd40408 1001 }
6e2cff2d 1002 parent::tearDown();
a3d5830a 1003 }
5bd40408 1004
a3d5830a
PS
1005 public static function tearDownAfterClass() {
1006 if (self::$extradb) {
1007 self::$extradb->dispose();
1008 self::$extradb = null;
1009 }
1010 phpunit_util::reset_all_data();
6e2cff2d 1011 parent::tearDownAfterClass();
5bd40408 1012 }
a3d5830a 1013}