MDL-31857 phpunit: PHPDoc and typo fixes during integration
[moodle.git] / lib / phpunit / lib.php
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/>.
17 /**
18  * Various PHPUnit classes and functions
19  *
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
24  */
26 // necessary when loaded from cli/util.php script
27 // If this is missing then PHPUnit is not in your PHP include path. This normally
28 // happens if installation didn't complete correctly. Check your environment.
29 require_once 'PHPUnit/Autoload.php';
32 /**
33  * Collection of utility methods.
34  *
35  * @package    core
36  * @category   phpunit
37  * @copyright  2012 Petr Skoda {@link http://skodak.org}
38  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
39  */
40 class phpunit_util {
41     /**
42      * @var array original content of all database tables
43      */
44     protected static $tabledata = null;
46     /**
47      * @var array An array of globals cloned from CFG
48      */
49     protected static $globals = array();
51     /**
52      * Returns contents of all tables right after installation.
53      * @static
54      * @return array $table=>$records
55      */
56     protected static function get_tabledata() {
57         global $CFG;
59         if (!isset(self::$tabledata)) {
60             $data = file_get_contents("$CFG->dataroot/phpunit/tabledata.ser");
61             self::$tabledata = unserialize($data);
62         }
64         if (!is_array(self::$tabledata)) {
65             phpunit_bootstrap_error('Can not read dataroot/phpunit/tabledata.ser or invalid format!');
66         }
68         return self::$tabledata;
69     }
71     /**
72      * Initialise CFG using data from fresh new install.
73      * @static
74      */
75     public static function initialise_cfg() {
76         global $CFG, $DB;
78         if (!file_exists("$CFG->dataroot/phpunit/tabledata.ser")) {
79             // most probably PHPUnit CLI installer
80             return;
81         }
83         if (!$DB->get_manager()->table_exists('config') or !$DB->count_records('config')) {
84             @unlink("$CFG->dataroot/phpunit/tabledata.ser");
85             @unlink("$CFG->dataroot/phpunit/versionshash.txt");
86             self::$tabledata = null;
87             return;
88         }
90         $data = self::get_tabledata();
92         foreach($data['config'] as $record) {
93             $name = $record->name;
94             $value = $record->value;
95             if (property_exists($CFG, $name)) {
96                 // config.php settings always take precedence
97                 continue;
98             }
99             $CFG->{$name} = $value;
100         }
101     }
103     /**
104      * Reset contents of all database tables to initial values, reset caches, etc.
105      *
106      * Note: this is relatively slow (cca 2 seconds for pg and 7 for mysql) - please use with care!
107      *
108      * @static
109      */
110     public static function reset_all_data() {
111         global $DB, $CFG;
113         $data = self::get_tabledata();
115         $trans = $DB->start_delegated_transaction(); // faster and safer
116         foreach ($data as $table=>$records) {
117             $DB->delete_records($table, array());
118             $resetseq = null;
119             foreach ($records as $record) {
120                 if (is_null($resetseq)) {
121                     $resetseq = property_exists($record, 'id');
122                 }
123                 $DB->import_record($table, $record, false, true);
124             }
125             if ($resetseq === true) {
126                 $DB->get_manager()->reset_sequence($table, true);
127             }
128         }
129         $trans->allow_commit();
131         purge_all_caches();
133         $user = new stdClass();
134         $user->id = 0;
135         $user->mnet = 0;
136         $user->mnethostid = $CFG->mnet_localhost_id;
137         session_set_user($user);
138         accesslib_clear_all_caches_for_unit_testing();
139     }
141     /**
142      * Called during bootstrap only!
143      * @static
144      */
145     public static function init_globals() {
146         global $CFG;
148         self::$globals['CFG'] = clone($CFG);
149     }
151     /**
152      * Returns original state of global variable.
153      * @static
154      * @param string $name
155      * @return mixed
156      */
157     public static function get_global_backup($name) {
158         if (isset(self::$globals[$name])) {
159             if (is_object(self::$globals[$name])) {
160                 $return = clone(self::$globals[$name]);
161                 return $return;
162             } else {
163                 return self::$globals[$name];
164             }
165         }
166         return null;
167     }
169     /**
170      * Does this site (db and dataroot) appear to be used for production?
171      * We try very hard to prevent accidental damage done to production servers!!
172      *
173      * @static
174      * @return bool
175      */
176     public static function is_test_site() {
177         global $DB, $CFG;
179         if (!file_exists("$CFG->dataroot/phpunittestdir.txt")) {
180             // this is already tested in bootstrap script,
181             // but anway presence of this file means the dataroot is for testing
182             return false;
183         }
185         $tables = $DB->get_tables(false);
186         if ($tables) {
187             if (!$DB->get_manager()->table_exists('config')) {
188                 return false;
189             }
190             if (!get_config('core', 'phpunittest')) {
191                 return false;
192             }
193         }
195         return true;
196     }
198     /**
199      * Is this site initialised to run unit tests?
200      *
201      * @static
202      * @return bool
203      */
204     public static function is_testing_ready() {
205         global $DB, $CFG;
207         if (!self::is_test_site()) {
208             return false;
209         }
211         $tables = $DB->get_tables(true);
213         if (!$tables) {
214             return false;
215         }
217         if (!get_config('core', 'phpunittest')) {
218              return false;
219         }
221         if (!file_exists("$CFG->dataroot/phpunit/tabledata.ser")) {
222             return false;
223         }
225         if (!file_exists("$CFG->dataroot/phpunit/versionshash.txt")) {
226             return false;
227         }
229         $hash = phpunit_util::get_version_hash();
230         $oldhash = file_get_contents("$CFG->dataroot/phpunit/versionshash.txt");
232         if ($hash !== $oldhash) {
233             return false;
234         }
236         return true;
237     }
239     /**
240      * Drop all test site data.
241      *
242      * Note: To be used from CLI scripts only.
243      *
244      * @static
245      * @return void may terminate execution with exit code
246      */
247     public static function drop_site() {
248         global $DB, $CFG;
250         if (!self::is_test_site()) {
251             cli_error('Can not drop non-test sites!!', 131);
252         }
254         // drop dataroot
255         remove_dir($CFG->dataroot, true);
256         phpunit_bootstrap_initdataroot($CFG->dataroot);
258         // drop all tables
259         $trans = $DB->start_delegated_transaction();
260         $tables = $DB->get_tables(false);
261         foreach ($tables as $tablename) {
262             $DB->delete_records($tablename, array());
263         }
264         $trans->allow_commit();
266         // now drop them
267         foreach ($tables as $tablename) {
268             $table = new xmldb_table($tablename);
269             $DB->get_manager()->drop_table($table);
270         }
271     }
273     /**
274      * Perform a fresh test site installation
275      *
276      * Note: To be used from CLI scripts only.
277      *
278      * @static
279      * @return void may terminate execution with exit code
280      */
281     public static function install_site() {
282         global $DB, $CFG;
284         if (!self::is_test_site()) {
285             cli_error('Can not install non-test sites!!', 131);
286         }
288         if ($DB->get_tables()) {
289             cli_error('Database tables already installed, drop the site first.', 133);
290         }
292         $options = array();
293         $options['adminpass'] = 'admin'; // removed later
294         $options['shortname'] = 'phpunit';
295         $options['fullname'] = 'PHPUnit test site';
297         install_cli_database($options, false);
299         // just in case remove admin password so that normal login is not possible
300         $DB->set_field('user', 'password', 'not cached', array('username' => 'admin'));
302         // add test db flag
303         set_config('phpunittest', 'phpunittest');
305         // store data for all tables
306         $data = array();
307         $tables = $DB->get_tables();
308         foreach ($tables as $table) {
309             $data[$table] = $DB->get_records($table, array());
310         }
311         $data = serialize($data);
312         @unlink("$CFG->dataroot/phpunit/tabledata.ser");
313         file_put_contents("$CFG->dataroot/phpunit/tabledata.ser", $data);
315         // hash all plugin versions - helps with very fast detection of db structure changes
316         $hash = phpunit_util::get_version_hash();
317         @unlink("$CFG->dataroot/phpunit/versionshash.txt");
318         file_put_contents("$CFG->dataroot/phpunit/versionshash.txt", $hash);
319     }
321     /**
322      * Culculate unique version hash for all available plugins and core.
323      * @static
324      * @return string sha1 hash
325      */
326     public static function get_version_hash() {
327         global $CFG;
329         $versions = array();
331         // main version first
332         $version = null;
333         include($CFG->dirroot.'/version.php');
334         $versions['core'] = $version;
336         // modules
337         $mods = get_plugin_list('mod');
338         ksort($mods);
339         foreach ($mods as $mod => $fullmod) {
340             $module = new stdClass();
341             $module->version = null;
342             include($fullmod.'/version.php');
343             $versions[$mod] = $module->version;
344         }
346         // now the rest of plugins
347         $plugintypes = get_plugin_types();
348         unset($plugintypes['mod']);
349         ksort($plugintypes);
350         foreach ($plugintypes as $type=>$unused) {
351             $plugs = get_plugin_list($type);
352             ksort($plugs);
353             foreach ($plugs as $plug=>$fullplug) {
354                 $plugin = new stdClass();
355                 $plugin->version = null;
356                 @include($fullplug.'/version.php');
357                 $versions[$plug] = $plugin->version;
358             }
359         }
361         $hash = sha1(serialize($versions));
363         return $hash;
364     }
366     /**
367      * Builds /phpunit.xml file using defaults from /phpunit.xml.dist
368      * @static
369      * @return void
370      */
371     public static function build_config_file() {
372         global $CFG;
374         $template = '
375     <testsuites>
376         <testsuite name="@component@">
377             <directory suffix="_test.php">@dir@</directory>
378         </testsuite>
379     </testsuites>';
380         $data = file_get_contents("$CFG->dirroot/phpunit.xml.dist");
382         $suites = '';
384         $plugintypes = get_plugin_types();
385         ksort($plugintypes);
386         foreach ($plugintypes as $type=>$unused) {
387             $plugs = get_plugin_list($type);
388             ksort($plugs);
389             foreach ($plugs as $plug=>$fullplug) {
390                 if (!file_exists("$fullplug/tests/")) {
391                     continue;
392                 }
393                 $dir = preg_replace("|$CFG->dirroot/|", '', $fullplug, 1);
394                 $dir .= '/tests';
395                 $component = $type.'_'.$plug;
397                 $suite = str_replace('@component@', $component, $template);
398                 $suite = str_replace('@dir@', $dir, $suite);
400                 $suites .= $suite;
401             }
402         }
404         $data = preg_replace('|<!--@plugin_suites_start@-->.*<!--@plugin_suites_end@-->|s', $suites, $data, 1);
406         @unlink("$CFG->dirroot/phpunit.xml");
407         file_put_contents("$CFG->dirroot/phpunit.xml", $data);
408     }
412 /**
413  * Simplified emulation test case for legacy SimpleTest.
414  *
415  * Note: this is supposed to work for very simple tests only.
416  *
417  * @deprecated since 2.3
418  * @package    core
419  * @category   phpunit
420  * @author     Petr Skoda
421  * @copyright  2012 Petr Skoda {@link http://skodak.org}
422  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
423  */
424 class UnitTestCase extends PHPUnit_Framework_TestCase {
426     /**
427      * @deprecated since 2.3
428      * @param bool $expected
429      * @param string $message
430      */
431     public function expectException($expected, $message = '') {
432         // use phpdocs: @expectedException ExceptionClassName
433         if (!$expected) {
434             return;
435         }
436         $this->setExpectedException('moodle_exception', $message);
437     }
439     /**
440      * @deprecated since 2.3
441      * @param bool $expected
442      * @param string $message
443      */
444     public static function expectError($expected = false, $message = '') {
445         // not available in PHPUnit
446         if (!$expected) {
447             return;
448         }
449         self::skipIf(true);
450     }
452     /**
453      * @deprecated since 2.3
454      * @static
455      * @param mixed $actual
456      * @param string $messages
457      */
458     public static function assertTrue($actual, $messages = '') {
459         parent::assertTrue((bool)$actual, $messages);
460     }
462     /**
463      * @deprecated since 2.3
464      * @static
465      * @param mixed $actual
466      * @param string $messages
467      */
468     public static function assertFalse($actual, $messages = '') {
469         parent::assertFalse((bool)$actual, $messages);
470     }
472     /**
473      * @deprecated since 2.3
474      * @static
475      * @param mixed $expected
476      * @param mixed $actual
477      * @param string $message
478      */
479     public static function assertEqual($expected, $actual, $message = '') {
480         parent::assertEquals($expected, $actual, $message);
481     }
483     /**
484      * @deprecated since 2.3
485      * @static
486      * @param mixed $expected
487      * @param mixed $actual
488      * @param string $message
489      */
490     public static function assertNotEqual($expected, $actual, $message = '') {
491         parent::assertNotEquals($expected, $actual, $message);
492     }
494     /**
495      * @deprecated since 2.3
496      * @static
497      * @param mixed $expected
498      * @param mixed $actual
499      * @param string $message
500      */
501     public static function assertIdentical($expected, $actual, $message = '') {
502         parent::assertSame($expected, $actual, $message);
503     }
505     /**
506      * @deprecated since 2.3
507      * @static
508      * @param mixed $expected
509      * @param mixed $actual
510      * @param string $message
511      */
512     public static function assertNotIdentical($expected, $actual, $message = '') {
513         parent::assertNotSame($expected, $actual, $message);
514     }
516     /**
517      * @deprecated since 2.3
518      * @static
519      * @param mixed $actual
520      * @param mixed $expected
521      * @param string $message
522      */
523     public static function assertIsA($actual, $expected, $message = '') {
524         parent::assertInstanceOf($expected, $actual, $message);
525     }
529 /**
530  * The simplest PHPUnit test case customised for Moodle
531  *
532  * This test case does not modify database or any globals.
533  *
534  * @package    core
535  * @category   phpunit
536  * @copyright  2012 Petr Skoda {@link http://skodak.org}
537  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
538  */
539 class basic_testcase extends PHPUnit_Framework_TestCase {
541     /**
542      * Constructs a test case with the given name.
543      *
544      * @param string $name
545      * @param array  $data
546      * @param string $dataName
547      */
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);
555     }
557     /**
558      * Runs the bare test sequence.
559      * @return void
560      */
561     public function runBare() {
562         global $CFG, $USER, $DB;
564         $dbwrites = $DB->perf_get_writes();
566         parent::runBare();
568         $oldcfg = phpunit_util::get_global_backup('CFG');
569         foreach($CFG as $k=>$v) {
570             if (!property_exists($oldcfg, $k)) {
571                 unset($CFG->$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));
576             }
577             unset($oldcfg->$k);
579         }
580         if ($oldcfg) {
581             foreach($oldcfg as $k=>$v) {
582                 $CFG->$k = $v;
583                 error_log('warning: unexpected removal of $CFG->'.$k.' in testcase: '.get_class($this).'->'.$this->getName(true));
584             }
585         }
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();
590             $USER->id = 0;
591         }
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();
597         }
599         //TODO: somehow find out if there are changes in dataroot
600     }