Merge branch 'MDL-32137-master-1' of git://git.luns.net.uk/moodle
[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
SH
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.
29require_once 'PHPUnit/Autoload.php';
5bd40408
PS
30
31
32/**
33 * Collection of utility methods.
34 *
7aea08e1
SH
35 * @package core
36 * @category phpunit
5bd40408
PS
37 * @copyright 2012 Petr Skoda {@link http://skodak.org}
38 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
39 */
40class phpunit_util {
41 /**
42 * @var array original content of all database tables
43 */
44 protected static $tabledata = null;
45
7aea08e1
SH
46 /**
47 * @var array An array of globals cloned from CFG
48 */
5bd40408
PS
49 protected static $globals = array();
50
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;
58
59 if (!isset(self::$tabledata)) {
60 $data = file_get_contents("$CFG->dataroot/phpunit/tabledata.ser");
61 self::$tabledata = unserialize($data);
62 }
63
64 if (!is_array(self::$tabledata)) {
65 phpunit_bootstrap_error('Can not read dataroot/phpunit/tabledata.ser or invalid format!');
66 }
67
68 return self::$tabledata;
69 }
70
71 /**
72 * Initialise CFG using data from fresh new install.
73 * @static
74 */
75 public static function initialise_cfg() {
76 global $CFG, $DB;
77
78 if (!file_exists("$CFG->dataroot/phpunit/tabledata.ser")) {
79 // most probably PHPUnit CLI installer
80 return;
81 }
82
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 }
89
90 $data = self::get_tabledata();
91
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 }
102
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;
112
113 $data = self::get_tabledata();
114
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();
130
131 purge_all_caches();
132
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 }
140
141 /**
142 * Called during bootstrap only!
143 * @static
5bd40408
PS
144 */
145 public static function init_globals() {
146 global $CFG;
147
148 self::$globals['CFG'] = clone($CFG);
149 }
150
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 }
168
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;
178
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 }
184
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 }
194
195 return true;
196 }
197
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;
206
207 if (!self::is_test_site()) {
208 return false;
209 }
210
211 $tables = $DB->get_tables(true);
212
213 if (!$tables) {
214 return false;
215 }
216
217 if (!get_config('core', 'phpunittest')) {
218 return false;
219 }
220
221 if (!file_exists("$CFG->dataroot/phpunit/tabledata.ser")) {
222 return false;
223 }
224
225 if (!file_exists("$CFG->dataroot/phpunit/versionshash.txt")) {
226 return false;
227 }
228
229 $hash = phpunit_util::get_version_hash();
230 $oldhash = file_get_contents("$CFG->dataroot/phpunit/versionshash.txt");
231
232 if ($hash !== $oldhash) {
233 return false;
234 }
235
236 return true;
237 }
238
239 /**
240 * Drop all test site data.
241 *
242 * Note: To be used from CLI scripts only.
243 *
244 * @static
7aea08e1 245 * @return void may terminate execution with exit code
5bd40408
PS
246 */
247 public static function drop_site() {
248 global $DB, $CFG;
249
250 if (!self::is_test_site()) {
251 cli_error('Can not drop non-test sites!!', 131);
252 }
253
254 // drop dataroot
255 remove_dir($CFG->dataroot, true);
256 phpunit_bootstrap_initdataroot($CFG->dataroot);
257
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();
265
266 // now drop them
267 foreach ($tables as $tablename) {
268 $table = new xmldb_table($tablename);
269 $DB->get_manager()->drop_table($table);
270 }
271 }
272
273 /**
274 * Perform a fresh test site installation
275 *
276 * Note: To be used from CLI scripts only.
277 *
278 * @static
7aea08e1 279 * @return void may terminate execution with exit code
5bd40408
PS
280 */
281 public static function install_site() {
282 global $DB, $CFG;
283
284 if (!self::is_test_site()) {
285 cli_error('Can not install non-test sites!!', 131);
286 }
287
288 if ($DB->get_tables()) {
289 cli_error('Database tables already installed, drop the site first.', 133);
290 }
291
292 $options = array();
293 $options['adminpass'] = 'admin'; // removed later
294 $options['shortname'] = 'phpunit';
295 $options['fullname'] = 'PHPUnit test site';
296
297 install_cli_database($options, false);
298
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'));
301
302 // add test db flag
303 set_config('phpunittest', 'phpunittest');
304
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);
314
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 }
320
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;
328
329 $versions = array();
330
331 // main version first
332 $version = null;
333 include($CFG->dirroot.'/version.php');
334 $versions['core'] = $version;
335
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 }
345
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 }
360
361 $hash = sha1(serialize($versions));
362
363 return $hash;
364 }
365
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;
373
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");
381
382 $suites = '';
383
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;
396
397 $suite = str_replace('@component@', $component, $template);
398 $suite = str_replace('@dir@', $dir, $suite);
399
400 $suites .= $suite;
401 }
402 }
403
7aea08e1 404 $data = preg_replace('|<!--@plugin_suites_start@-->.*<!--@plugin_suites_end@-->|s', $suites, $data, 1);
5bd40408
PS
405
406 @unlink("$CFG->dirroot/phpunit.xml");
407 file_put_contents("$CFG->dirroot/phpunit.xml", $data);
408 }
409}
410
411
412/**
413 * Simplified emulation test case for legacy SimpleTest.
414 *
415 * Note: this is supposed to work for very simple tests only.
416 *
7aea08e1
SH
417 * @deprecated since 2.3
418 * @package core
419 * @category phpunit
5bd40408
PS
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 */
424class UnitTestCase extends PHPUnit_Framework_TestCase {
425
426 /**
7aea08e1 427 * @deprecated since 2.3
5bd40408
PS
428 * @param bool $expected
429 * @param string $message
5bd40408
PS
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 }
438
439 /**
7aea08e1 440 * @deprecated since 2.3
5bd40408
PS
441 * @param bool $expected
442 * @param string $message
5bd40408
PS
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 }
451
452 /**
7aea08e1 453 * @deprecated since 2.3
5bd40408
PS
454 * @static
455 * @param mixed $actual
456 * @param string $messages
5bd40408
PS
457 */
458 public static function assertTrue($actual, $messages = '') {
459 parent::assertTrue((bool)$actual, $messages);
460 }
461
462 /**
7aea08e1 463 * @deprecated since 2.3
5bd40408
PS
464 * @static
465 * @param mixed $actual
466 * @param string $messages
5bd40408
PS
467 */
468 public static function assertFalse($actual, $messages = '') {
469 parent::assertFalse((bool)$actual, $messages);
470 }
471
472 /**
7aea08e1 473 * @deprecated since 2.3
5bd40408
PS
474 * @static
475 * @param mixed $expected
476 * @param mixed $actual
477 * @param string $message
5bd40408
PS
478 */
479 public static function assertEqual($expected, $actual, $message = '') {
480 parent::assertEquals($expected, $actual, $message);
481 }
482
483 /**
7aea08e1 484 * @deprecated since 2.3
5bd40408
PS
485 * @static
486 * @param mixed $expected
487 * @param mixed $actual
488 * @param string $message
5bd40408
PS
489 */
490 public static function assertNotEqual($expected, $actual, $message = '') {
491 parent::assertNotEquals($expected, $actual, $message);
492 }
493
494 /**
7aea08e1 495 * @deprecated since 2.3
5bd40408
PS
496 * @static
497 * @param mixed $expected
498 * @param mixed $actual
499 * @param string $message
5bd40408
PS
500 */
501 public static function assertIdentical($expected, $actual, $message = '') {
502 parent::assertSame($expected, $actual, $message);
503 }
504
505 /**
7aea08e1 506 * @deprecated since 2.3
5bd40408
PS
507 * @static
508 * @param mixed $expected
509 * @param mixed $actual
510 * @param string $message
5bd40408
PS
511 */
512 public static function assertNotIdentical($expected, $actual, $message = '') {
513 parent::assertNotSame($expected, $actual, $message);
514 }
515
516 /**
7aea08e1 517 * @deprecated since 2.3
5bd40408
PS
518 * @static
519 * @param mixed $actual
520 * @param mixed $expected
521 * @param string $message
5bd40408
PS
522 */
523 public static function assertIsA($actual, $expected, $message = '') {
524 parent::assertInstanceOf($expected, $actual, $message);
525 }
526}
527
528
529/**
7aea08e1
SH
530 * The simplest PHPUnit test case customised for Moodle
531 *
532 * This test case does not modify database or any globals.
5bd40408 533 *
7aea08e1
SH
534 * @package core
535 * @category phpunit
5bd40408
PS
536 * @copyright 2012 Petr Skoda {@link http://skodak.org}
537 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
538 */
539class basic_testcase extends PHPUnit_Framework_TestCase {
540
541 /**
542 * Constructs a test case with the given name.
543 *
7aea08e1
SH
544 * @param string $name
545 * @param array $data
546 * @param string $dataName
5bd40408
PS
547 */
548 public function __construct($name = NULL, array $data = array(), $dataName = '') {
549 parent::__construct($name, $data, $dataName);
550
551 $this->setBackupGlobals(false);
552 $this->setBackupStaticAttributes(false);
553 $this->setRunTestInSeparateProcess(false);
554 $this->setInIsolation(false);
555 }
556
557 /**
558 * Runs the bare test sequence.
559 * @return void
560 */
561 public function runBare() {
562 global $CFG, $USER, $DB;
563
564 $dbwrites = $DB->perf_get_writes();
565
566 parent::runBare();
567
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);
578
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 }
586
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 }
592
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 }
598
599 //TODO: somehow find out if there are changes in dataroot
600 }
7aea08e1 601}