2 // This file is part of Moodle - http://moodle.org/
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.
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/>.
18 * Various PHPUnit classes and functions
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'; // necessary when loaded from cli/util.php script
30 * Collection of utility methods.
32 * @package core_phpunit
33 * @copyright 2012 Petr Skoda {@link http://skodak.org}
34 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
38 * @var array original content of all database tables
40 protected static $tabledata = null;
42 protected static $globals = array();
45 * Returns contents of all tables right after installation.
47 * @return array $table=>$records
49 protected static function get_tabledata() {
52 if (!isset(self::$tabledata)) {
53 $data = file_get_contents("$CFG->dataroot/phpunit/tabledata.ser");
54 self::$tabledata = unserialize($data);
57 if (!is_array(self::$tabledata)) {
58 phpunit_bootstrap_error('Can not read dataroot/phpunit/tabledata.ser or invalid format!');
61 return self::$tabledata;
65 * Initialise CFG using data from fresh new install.
68 public static function initialise_cfg() {
71 if (!file_exists("$CFG->dataroot/phpunit/tabledata.ser")) {
72 // most probably PHPUnit CLI installer
76 if (!$DB->get_manager()->table_exists('config') or !$DB->count_records('config')) {
77 @unlink("$CFG->dataroot/phpunit/tabledata.ser");
78 @unlink("$CFG->dataroot/phpunit/versionshash.txt");
79 self::$tabledata = null;
83 $data = self::get_tabledata();
85 foreach($data['config'] as $record) {
86 $name = $record->name;
87 $value = $record->value;
88 if (property_exists($CFG, $name)) {
89 // config.php settings always take precedence
92 $CFG->{$name} = $value;
97 * Reset contents of all database tables to initial values, reset caches, etc.
99 * Note: this is relatively slow (cca 2 seconds for pg and 7 for mysql) - please use with care!
103 public static function reset_all_data() {
106 $data = self::get_tabledata();
108 $trans = $DB->start_delegated_transaction(); // faster and safer
109 foreach ($data as $table=>$records) {
110 $DB->delete_records($table, array());
112 foreach ($records as $record) {
113 if (is_null($resetseq)) {
114 $resetseq = property_exists($record, 'id');
116 $DB->import_record($table, $record, false, true);
118 if ($resetseq === true) {
119 $DB->get_manager()->reset_sequence($table, true);
122 $trans->allow_commit();
126 $user = new stdClass();
129 $user->mnethostid = $CFG->mnet_localhost_id;
130 session_set_user($user);
131 accesslib_clear_all_caches_for_unit_testing();
135 * Called during bootstrap only!
139 public static function init_globals() {
142 self::$globals['CFG'] = clone($CFG);
146 * Returns original state of global variable.
148 * @param string $name
151 public static function get_global_backup($name) {
152 if (isset(self::$globals[$name])) {
153 if (is_object(self::$globals[$name])) {
154 $return = clone(self::$globals[$name]);
157 return self::$globals[$name];
164 * Does this site (db and dataroot) appear to be used for production?
165 * We try very hard to prevent accidental damage done to production servers!!
170 public static function is_test_site() {
173 if (!file_exists("$CFG->dataroot/phpunittestdir.txt")) {
174 // this is already tested in bootstrap script,
175 // but anway presence of this file means the dataroot is for testing
179 $tables = $DB->get_tables(false);
181 if (!$DB->get_manager()->table_exists('config')) {
184 if (!get_config('core', 'phpunittest')) {
193 * Is this site initialised to run unit tests?
198 public static function is_testing_ready() {
201 if (!self::is_test_site()) {
205 $tables = $DB->get_tables(true);
211 if (!get_config('core', 'phpunittest')) {
215 if (!file_exists("$CFG->dataroot/phpunit/tabledata.ser")) {
219 if (!file_exists("$CFG->dataroot/phpunit/versionshash.txt")) {
223 $hash = phpunit_util::get_version_hash();
224 $oldhash = file_get_contents("$CFG->dataroot/phpunit/versionshash.txt");
226 if ($hash !== $oldhash) {
234 * Drop all test site data.
236 * Note: To be used from CLI scripts only.
239 * @return void, may terminate execution with exit code
241 public static function drop_site() {
244 if (!self::is_test_site()) {
245 cli_error('Can not drop non-test sites!!', 131);
249 remove_dir($CFG->dataroot, true);
250 phpunit_bootstrap_initdataroot($CFG->dataroot);
253 $trans = $DB->start_delegated_transaction();
254 $tables = $DB->get_tables(false);
255 foreach ($tables as $tablename) {
256 $DB->delete_records($tablename, array());
258 $trans->allow_commit();
261 foreach ($tables as $tablename) {
262 $table = new xmldb_table($tablename);
263 $DB->get_manager()->drop_table($table);
268 * Perform a fresh test site installation
270 * Note: To be used from CLI scripts only.
273 * @return void, may terminate execution with exit code
275 public static function install_site() {
278 if (!self::is_test_site()) {
279 cli_error('Can not install non-test sites!!', 131);
282 if ($DB->get_tables()) {
283 cli_error('Database tables already installed, drop the site first.', 133);
287 $options['adminpass'] = 'admin'; // removed later
288 $options['shortname'] = 'phpunit';
289 $options['fullname'] = 'PHPUnit test site';
291 install_cli_database($options, false);
293 // just in case remove admin password so that normal login is not possible
294 $DB->set_field('user', 'password', 'not cached', array('username' => 'admin'));
297 set_config('phpunittest', 'phpunittest');
299 // store data for all tables
301 $tables = $DB->get_tables();
302 foreach ($tables as $table) {
303 $data[$table] = $DB->get_records($table, array());
305 $data = serialize($data);
306 @unlink("$CFG->dataroot/phpunit/tabledata.ser");
307 file_put_contents("$CFG->dataroot/phpunit/tabledata.ser", $data);
309 // hash all plugin versions - helps with very fast detection of db structure changes
310 $hash = phpunit_util::get_version_hash();
311 @unlink("$CFG->dataroot/phpunit/versionshash.txt");
312 file_put_contents("$CFG->dataroot/phpunit/versionshash.txt", $hash);
316 * Culculate unique version hash for all available plugins and core.
318 * @return string sha1 hash
320 public static function get_version_hash() {
325 // main version first
327 include($CFG->dirroot.'/version.php');
328 $versions['core'] = $version;
331 $mods = get_plugin_list('mod');
333 foreach ($mods as $mod => $fullmod) {
334 $module = new stdClass();
335 $module->version = null;
336 include($fullmod.'/version.php');
337 $versions[$mod] = $module->version;
340 // now the rest of plugins
341 $plugintypes = get_plugin_types();
342 unset($plugintypes['mod']);
344 foreach ($plugintypes as $type=>$unused) {
345 $plugs = get_plugin_list($type);
347 foreach ($plugs as $plug=>$fullplug) {
348 $plugin = new stdClass();
349 $plugin->version = null;
350 @include($fullplug.'/version.php');
351 $versions[$plug] = $plugin->version;
355 $hash = sha1(serialize($versions));
361 * Builds /phpunit.xml file using defaults from /phpunit.xml.dist
365 public static function build_config_file() {
370 <testsuite name="@component@">
371 <directory suffix="_test.php">@dir@</directory>
374 $data = file_get_contents("$CFG->dirroot/phpunit.xml.dist");
378 $plugintypes = get_plugin_types();
380 foreach ($plugintypes as $type=>$unused) {
381 $plugs = get_plugin_list($type);
383 foreach ($plugs as $plug=>$fullplug) {
384 if (!file_exists("$fullplug/tests/")) {
387 $dir = preg_replace("|$CFG->dirroot/|", '', $fullplug, 1);
389 $component = $type.'_'.$plug;
391 $suite = str_replace('@component@', $component, $template);
392 $suite = str_replace('@dir@', $dir, $suite);
398 $data = preg_replace('|<!--@plugin_suits_start@-->.*<!--@plugin_suits_end@-->|s', $suites, $data, 1);
400 @unlink("$CFG->dirroot/phpunit.xml");
401 file_put_contents("$CFG->dirroot/phpunit.xml", $data);
407 * Simplified emulation test case for legacy SimpleTest.
409 * Note: this is supposed to work for very simple tests only.
412 * @package core_phpunit
414 * @copyright 2012 Petr Skoda {@link http://skodak.org}
415 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
417 class UnitTestCase extends PHPUnit_Framework_TestCase {
421 * @param bool $expected
422 * @param string $message
425 public function expectException($expected, $message = '') {
426 // use phpdocs: @expectedException ExceptionClassName
430 $this->setExpectedException('moodle_exception', $message);
435 * @param bool $expected
436 * @param string $message
439 public static function expectError($expected = false, $message = '') {
440 // not available in PHPUnit
450 * @param mixed $actual
451 * @param string $messages
454 public static function assertTrue($actual, $messages = '') {
455 parent::assertTrue((bool)$actual, $messages);
461 * @param mixed $actual
462 * @param string $messages
465 public static function assertFalse($actual, $messages = '') {
466 parent::assertFalse((bool)$actual, $messages);
472 * @param mixed $expected
473 * @param mixed $actual
474 * @param string $message
477 public static function assertEqual($expected, $actual, $message = '') {
478 parent::assertEquals($expected, $actual, $message);
484 * @param mixed $expected
485 * @param mixed $actual
486 * @param string $message
489 public static function assertNotEqual($expected, $actual, $message = '') {
490 parent::assertNotEquals($expected, $actual, $message);
496 * @param mixed $expected
497 * @param mixed $actual
498 * @param string $message
501 public static function assertIdentical($expected, $actual, $message = '') {
502 parent::assertSame($expected, $actual, $message);
508 * @param mixed $expected
509 * @param mixed $actual
510 * @param string $message
513 public static function assertNotIdentical($expected, $actual, $message = '') {
514 parent::assertNotSame($expected, $actual, $message);
520 * @param mixed $actual
521 * @param mixed $expected
522 * @param string $message
525 public static function assertIsA($actual, $expected, $message = '') {
526 parent::assertInstanceOf($expected, $actual, $message);
532 * The simplest PHPUnit test case customised for Moodle,
533 * do not modify database or any globals.
535 * @package core_phpunit
536 * @copyright 2012 Petr Skoda {@link http://skodak.org}
537 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
539 class basic_testcase extends PHPUnit_Framework_TestCase {
542 * Constructs a test case with the given name.
544 * @param string $name
546 * @param string $dataName
548 public function __construct($name = NULL, array $data = array(), $dataName = '') {
549 parent::__construct($name, $data, $dataName);
551 $this->setBackupGlobals(false);
552 $this->setBackupStaticAttributes(false);
553 $this->setRunTestInSeparateProcess(false);
554 $this->setInIsolation(false);
558 * Runs the bare test sequence.
561 public function runBare() {
562 global $CFG, $USER, $DB;
564 $dbwrites = $DB->perf_get_writes();
568 $oldcfg = phpunit_util::get_global_backup('CFG');
569 foreach($CFG as $k=>$v) {
570 if (!property_exists($oldcfg, $k)) {
572 error_log('warning: unexpected new $CFG->'.$k.' value in testcase: '.get_class($this).'->'.$this->getName(true));
573 } else if ($oldcfg->$k !== $CFG->$k) {
574 $CFG->$k = $oldcfg->$k;
575 error_log('warning: unexpected change of $CFG->'.$k.' value in testcase: '.get_class($this).'->'.$this->getName(true));
581 foreach($oldcfg as $k=>$v) {
583 error_log('warning: unexpected removal of $CFG->'.$k.' in testcase: '.get_class($this).'->'.$this->getName(true));
587 if ($USER->id != 0) {
588 error_log('warning: unexpected change of $USER in testcase: '.get_class($this).'->'.$this->getName(true));
589 $USER = new stdClass();
593 if ($dbwrites != $DB->perf_get_writes()) {
594 //TODO: find out what was changed exactly
595 error_log('warning: unexpected database modification, resetting DB state in testcase: '.get_class($this).'->'.$this->getName(true));
596 phpunit_util::reset_all_data();
599 //TODO: somehow find out if there are changes in dataroot