Commit | Line | Data |
---|---|---|
7e7cfe7a 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 | * Utility class. | |
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 | */ | |
25 | ||
0ea35584 | 26 | require_once(__DIR__.'/../../testing/classes/util.php'); |
7e7cfe7a PS |
27 | |
28 | /** | |
29 | * Collection of utility methods. | |
30 | * | |
31 | * @package core | |
32 | * @category phpunit | |
33 | * @copyright 2012 Petr Skoda {@link http://skodak.org} | |
34 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
35 | */ | |
0ea35584 | 36 | class phpunit_util extends testing_util { |
a9236a2c PS |
37 | /** |
38 | * @var int last value of db writes counter, used for db resetting | |
39 | */ | |
40 | public static $lastdbwrites = null; | |
41 | ||
7e7cfe7a PS |
42 | /** @var array An array of original globals, restored after each test */ |
43 | protected static $globals = array(); | |
44 | ||
ef5b5e05 PS |
45 | /** @var array list of debugging messages triggered during the last test execution */ |
46 | protected static $debuggings = array(); | |
47 | ||
4c9e03f0 PS |
48 | /** @var phpunit_message_sink alternative target for moodle messaging */ |
49 | protected static $messagesink = null; | |
50 | ||
1aba6b2b AN |
51 | /** @var phpunit_phpmailer_sink alternative target for phpmailer messaging */ |
52 | protected static $phpmailersink = null; | |
53 | ||
62401e8f PS |
54 | /** @var phpunit_message_sink alternative target for moodle messaging */ |
55 | protected static $eventsink = null; | |
56 | ||
7e7cfe7a | 57 | /** |
0ea35584 | 58 | * @var array Files to skip when resetting dataroot folder |
7e7cfe7a | 59 | */ |
0ea35584 | 60 | protected static $datarootskiponreset = array('.', '..', 'phpunittestdir.txt', 'phpunit', '.htaccess'); |
7e7cfe7a PS |
61 | |
62 | /** | |
0ea35584 | 63 | * @var array Files to skip when dropping dataroot folder |
7e7cfe7a | 64 | */ |
0ea35584 | 65 | protected static $datarootskipondrop = array('.', '..', 'lock', 'webrunner.xml'); |
7e7cfe7a PS |
66 | |
67 | /** | |
68 | * Load global $CFG; | |
69 | * @internal | |
70 | * @static | |
71 | * @return void | |
72 | */ | |
73 | public static function initialise_cfg() { | |
74 | global $DB; | |
75 | $dbhash = false; | |
76 | try { | |
77 | $dbhash = $DB->get_field('config', 'value', array('name'=>'phpunittest')); | |
78 | } catch (Exception $e) { | |
79 | // not installed yet | |
80 | initialise_cfg(); | |
81 | return; | |
82 | } | |
c5701ce7 | 83 | if ($dbhash !== core_component::get_all_versions_hash()) { |
7e7cfe7a PS |
84 | // do not set CFG - the only way forward is to drop and reinstall |
85 | return; | |
86 | } | |
87 | // standard CFG init | |
88 | initialise_cfg(); | |
89 | } | |
90 | ||
7e7cfe7a PS |
91 | /** |
92 | * Reset contents of all database tables to initial values, reset caches, etc. | |
93 | * | |
94 | * Note: this is relatively slow (cca 2 seconds for pg and 7 for mysql) - please use with care! | |
95 | * | |
96 | * @static | |
71fc5003 PS |
97 | * @param bool $detectchanges |
98 | * true - changes in global state and database are reported as errors | |
99 | * false - no errors reported | |
100 | * null - only critical problems are reported as errors | |
7e7cfe7a PS |
101 | * @return void |
102 | */ | |
71fc5003 | 103 | public static function reset_all_data($detectchanges = false) { |
e17dbeeb | 104 | global $DB, $CFG, $USER, $SITE, $COURSE, $PAGE, $OUTPUT, $SESSION; |
7e7cfe7a | 105 | |
4c9e03f0 | 106 | // Stop any message redirection. |
5ecf8e8f | 107 | self::stop_message_redirection(); |
4c9e03f0 | 108 | |
62401e8f | 109 | // Stop any message redirection. |
5ecf8e8f | 110 | self::stop_event_redirection(); |
62401e8f | 111 | |
556e3a9e AN |
112 | // Start a new email redirection. |
113 | // This will clear any existing phpmailer redirection. | |
114 | // We redirect all phpmailer output to this message sink which is | |
115 | // called instead of phpmailer actually sending the message. | |
5ecf8e8f | 116 | self::start_phpmailer_redirection(); |
556e3a9e | 117 | |
84f08cfa DW |
118 | // We used to call gc_collect_cycles here to ensure desctructors were called between tests. |
119 | // This accounted for 25% of the total time running phpunit - so we removed it. | |
120 | ||
ef5b5e05 PS |
121 | // Show any unhandled debugging messages, the runbare() could already reset it. |
122 | self::display_debugging_messages(); | |
123 | self::reset_debugging(); | |
124 | ||
7e7cfe7a PS |
125 | // reset global $DB in case somebody mocked it |
126 | $DB = self::get_global_backup('DB'); | |
127 | ||
128 | if ($DB->is_transaction_started()) { | |
129 | // we can not reset inside transaction | |
130 | $DB->force_transaction_rollback(); | |
131 | } | |
132 | ||
133 | $resetdb = self::reset_database(); | |
96805290 | 134 | $localename = self::get_locale_name(); |
7e7cfe7a PS |
135 | $warnings = array(); |
136 | ||
71fc5003 | 137 | if ($detectchanges === true) { |
7e7cfe7a PS |
138 | if ($resetdb) { |
139 | $warnings[] = 'Warning: unexpected database modification, resetting DB state'; | |
140 | } | |
141 | ||
142 | $oldcfg = self::get_global_backup('CFG'); | |
143 | $oldsite = self::get_global_backup('SITE'); | |
144 | foreach($CFG as $k=>$v) { | |
145 | if (!property_exists($oldcfg, $k)) { | |
146 | $warnings[] = 'Warning: unexpected new $CFG->'.$k.' value'; | |
147 | } else if ($oldcfg->$k !== $CFG->$k) { | |
148 | $warnings[] = 'Warning: unexpected change of $CFG->'.$k.' value'; | |
149 | } | |
150 | unset($oldcfg->$k); | |
151 | ||
152 | } | |
153 | if ($oldcfg) { | |
154 | foreach($oldcfg as $k=>$v) { | |
155 | $warnings[] = 'Warning: unexpected removal of $CFG->'.$k; | |
156 | } | |
157 | } | |
158 | ||
159 | if ($USER->id != 0) { | |
160 | $warnings[] = 'Warning: unexpected change of $USER'; | |
161 | } | |
162 | ||
163 | if ($COURSE->id != $oldsite->id) { | |
164 | $warnings[] = 'Warning: unexpected change of $COURSE'; | |
165 | } | |
82081c1f | 166 | |
96805290 DP |
167 | if (setlocale(LC_TIME, 0) !== $localename) { |
168 | $warnings[] = 'Warning: unexpected change of locale'; | |
d6e7a63d | 169 | } |
70faad65 PS |
170 | } |
171 | ||
172 | if (ini_get('max_execution_time') != 0) { | |
173 | // This is special warning for all resets because we do not want any | |
174 | // libraries to mess with timeouts unintentionally. | |
175 | // Our PHPUnit integration is not supposed to change it either. | |
176 | ||
71fc5003 PS |
177 | if ($detectchanges !== false) { |
178 | $warnings[] = 'Warning: max_execution_time was changed to '.ini_get('max_execution_time'); | |
179 | } | |
70faad65 | 180 | set_time_limit(0); |
7e7cfe7a PS |
181 | } |
182 | ||
183 | // restore original globals | |
184 | $_SERVER = self::get_global_backup('_SERVER'); | |
185 | $CFG = self::get_global_backup('CFG'); | |
186 | $SITE = self::get_global_backup('SITE'); | |
596ea56f JP |
187 | $_GET = array(); |
188 | $_POST = array(); | |
189 | $_FILES = array(); | |
190 | $_REQUEST = array(); | |
7e7cfe7a PS |
191 | $COURSE = $SITE; |
192 | ||
193 | // reinitialise following globals | |
194 | $OUTPUT = new bootstrap_renderer(); | |
195 | $PAGE = new moodle_page(); | |
196 | $FULLME = null; | |
197 | $ME = null; | |
198 | $SCRIPT = null; | |
2e00d01d PS |
199 | |
200 | // Empty sessison and set fresh new not-logged-in user. | |
201 | \core\session\manager::init_empty_session(); | |
7e7cfe7a PS |
202 | |
203 | // reset all static caches | |
d8a1f426 | 204 | \core\event\manager::phpunit_reset(); |
7e7cfe7a | 205 | accesslib_clear_all_caches(true); |
a46e11b5 PS |
206 | get_string_manager()->reset_caches(true); |
207 | reset_text_filters_cache(true); | |
7e7cfe7a | 208 | events_get_handlers('reset'); |
2f1e464a | 209 | core_text::reset_caches(); |
e61a9638 | 210 | get_message_processors(false, true, true); |
73a0f3ba | 211 | filter_manager::reset_caches(); |
91fed57a | 212 | core_filetypes::reset_caches(); |
213 | ||
89a73b08 | 214 | // Reset static unit test options. |
215 | if (class_exists('\availability_date\condition', false)) { | |
216 | \availability_date\condition::set_current_time_for_test(0); | |
217 | } | |
218 | ||
6b8e46ad RT |
219 | // Reset internal users. |
220 | core_user::reset_internal_users(); | |
221 | ||
6fd1cf05 | 222 | //TODO MDL-25290: add more resets here and probably refactor them to new core function |
7e7cfe7a | 223 | |
2beba297 | 224 | // Reset course and module caches. |
13d5c938 | 225 | if (class_exists('format_base')) { |
2beba297 | 226 | // If file containing class is not loaded, there is no cache there anyway. |
13d5c938 PS |
227 | format_base::reset_course_cache(0); |
228 | } | |
b46be6ad | 229 | get_fast_modinfo(0, 0, true); |
13d5c938 | 230 | |
98547432 | 231 | // Reset other singletons. |
e87214bd PS |
232 | if (class_exists('core_plugin_manager')) { |
233 | core_plugin_manager::reset_caches(true); | |
98547432 | 234 | } |
e87214bd PS |
235 | if (class_exists('\core\update\checker')) { |
236 | \core\update\checker::reset_caches(true); | |
98547432 PŠ |
237 | } |
238 | ||
27479b5c | 239 | // Clear static cache within restore. |
240 | if (class_exists('restore_section_structure_step')) { | |
241 | restore_section_structure_step::reset_caches(); | |
242 | } | |
243 | ||
7e7cfe7a PS |
244 | // purge dataroot directory |
245 | self::reset_dataroot(); | |
246 | ||
247 | // restore original config once more in case resetting of caches changed CFG | |
248 | $CFG = self::get_global_backup('CFG'); | |
249 | ||
250 | // inform data generator | |
251 | self::get_data_generator()->reset(); | |
252 | ||
253 | // fix PHP settings | |
254 | error_reporting($CFG->debug); | |
255 | ||
d6e7a63d PS |
256 | // Reset the date/time class. |
257 | core_date::phpunit_reset(); | |
258 | ||
259 | // Make sure the time locale is consistent - that is Australian English. | |
96805290 | 260 | setlocale(LC_TIME, $localename); |
d6e7a63d | 261 | |
7e7cfe7a PS |
262 | // verify db writes just in case something goes wrong in reset |
263 | if (self::$lastdbwrites != $DB->perf_get_writes()) { | |
264 | error_log('Unexpected DB writes in phpunit_util::reset_all_data()'); | |
265 | self::$lastdbwrites = $DB->perf_get_writes(); | |
266 | } | |
267 | ||
268 | if ($warnings) { | |
269 | $warnings = implode("\n", $warnings); | |
270 | trigger_error($warnings, E_USER_WARNING); | |
271 | } | |
272 | } | |
273 | ||
a9236a2c PS |
274 | /** |
275 | * Reset all database tables to default values. | |
276 | * @static | |
277 | * @return bool true if reset done, false if skipped | |
278 | */ | |
279 | public static function reset_database() { | |
280 | global $DB; | |
281 | ||
282 | if (!is_null(self::$lastdbwrites) and self::$lastdbwrites == $DB->perf_get_writes()) { | |
283 | return false; | |
284 | } | |
285 | ||
286 | if (!parent::reset_database()) { | |
287 | return false; | |
288 | } | |
289 | ||
290 | self::$lastdbwrites = $DB->perf_get_writes(); | |
291 | ||
292 | return true; | |
293 | } | |
294 | ||
7e7cfe7a PS |
295 | /** |
296 | * Called during bootstrap only! | |
297 | * @internal | |
298 | * @static | |
299 | * @return void | |
300 | */ | |
301 | public static function bootstrap_init() { | |
302 | global $CFG, $SITE, $DB; | |
303 | ||
304 | // backup the globals | |
305 | self::$globals['_SERVER'] = $_SERVER; | |
306 | self::$globals['CFG'] = clone($CFG); | |
307 | self::$globals['SITE'] = clone($SITE); | |
308 | self::$globals['DB'] = $DB; | |
309 | ||
310 | // refresh data in all tables, clear caches, etc. | |
5ecf8e8f | 311 | self::reset_all_data(); |
7e7cfe7a PS |
312 | } |
313 | ||
15bac12e PS |
314 | /** |
315 | * Print some Moodle related info to console. | |
316 | * @internal | |
317 | * @static | |
318 | * @return void | |
319 | */ | |
320 | public static function bootstrap_moodle_info() { | |
7514c2f2 | 321 | echo self::get_site_info(); |
15bac12e PS |
322 | } |
323 | ||
7e7cfe7a PS |
324 | /** |
325 | * Returns original state of global variable. | |
326 | * @static | |
327 | * @param string $name | |
328 | * @return mixed | |
329 | */ | |
330 | public static function get_global_backup($name) { | |
331 | if ($name === 'DB') { | |
332 | // no cloning of database object, | |
333 | // we just need the original reference, not original state | |
334 | return self::$globals['DB']; | |
335 | } | |
336 | if (isset(self::$globals[$name])) { | |
337 | if (is_object(self::$globals[$name])) { | |
338 | $return = clone(self::$globals[$name]); | |
339 | return $return; | |
340 | } else { | |
341 | return self::$globals[$name]; | |
342 | } | |
343 | } | |
344 | return null; | |
345 | } | |
346 | ||
7e7cfe7a PS |
347 | /** |
348 | * Is this site initialised to run unit tests? | |
349 | * | |
350 | * @static | |
351 | * @return int array errorcode=>message, 0 means ok | |
352 | */ | |
353 | public static function testing_ready_problem() { | |
0ea35584 | 354 | global $DB; |
7e7cfe7a | 355 | |
96805290 DP |
356 | $localename = self::get_locale_name(); |
357 | if (setlocale(LC_TIME, $localename) === false) { | |
358 | return array(PHPUNIT_EXITCODE_CONFIGERROR, "Required locale '$localename' is not installed."); | |
359 | } | |
360 | ||
7e7cfe7a PS |
361 | if (!self::is_test_site()) { |
362 | // dataroot was verified in bootstrap, so it must be DB | |
363 | return array(PHPUNIT_EXITCODE_CONFIGERROR, 'Can not use database for testing, try different prefix'); | |
364 | } | |
365 | ||
0ea35584 | 366 | $tables = $DB->get_tables(false); |
7e7cfe7a PS |
367 | if (empty($tables)) { |
368 | return array(PHPUNIT_EXITCODE_INSTALL, ''); | |
369 | } | |
370 | ||
0ea35584 | 371 | if (!self::is_test_data_updated()) { |
7e7cfe7a PS |
372 | return array(PHPUNIT_EXITCODE_REINSTALL, ''); |
373 | } | |
374 | ||
375 | return array(0, ''); | |
376 | } | |
377 | ||
378 | /** | |
379 | * Drop all test site data. | |
380 | * | |
381 | * Note: To be used from CLI scripts only. | |
382 | * | |
383 | * @static | |
85b72a75 | 384 | * @param bool $displayprogress if true, this method will echo progress information. |
7e7cfe7a PS |
385 | * @return void may terminate execution with exit code |
386 | */ | |
85b72a75 | 387 | public static function drop_site($displayprogress = false) { |
7e7cfe7a PS |
388 | global $DB, $CFG; |
389 | ||
390 | if (!self::is_test_site()) { | |
391 | phpunit_bootstrap_error(PHPUNIT_EXITCODE_CONFIGERROR, 'Can not drop non-test site!!'); | |
392 | } | |
393 | ||
85b72a75 TH |
394 | // Purge dataroot |
395 | if ($displayprogress) { | |
396 | echo "Purging dataroot:\n"; | |
397 | } | |
0ea35584 | 398 | |
7e7cfe7a | 399 | self::reset_dataroot(); |
0ea35584 DM |
400 | testing_initdataroot($CFG->dataroot, 'phpunit'); |
401 | self::drop_dataroot(); | |
7e7cfe7a PS |
402 | |
403 | // drop all tables | |
0ea35584 | 404 | self::drop_database($displayprogress); |
7e7cfe7a PS |
405 | } |
406 | ||
407 | /** | |
408 | * Perform a fresh test site installation | |
409 | * | |
410 | * Note: To be used from CLI scripts only. | |
411 | * | |
412 | * @static | |
413 | * @return void may terminate execution with exit code | |
414 | */ | |
415 | public static function install_site() { | |
416 | global $DB, $CFG; | |
417 | ||
418 | if (!self::is_test_site()) { | |
419 | phpunit_bootstrap_error(PHPUNIT_EXITCODE_CONFIGERROR, 'Can not install on non-test site!!'); | |
420 | } | |
421 | ||
422 | if ($DB->get_tables()) { | |
5ecf8e8f | 423 | list($errorcode, $message) = self::testing_ready_problem(); |
7e7cfe7a PS |
424 | if ($errorcode) { |
425 | phpunit_bootstrap_error(PHPUNIT_EXITCODE_REINSTALL, 'Database tables already present, Moodle PHPUnit test environment can not be initialised'); | |
426 | } else { | |
427 | phpunit_bootstrap_error(0, 'Moodle PHPUnit test environment is already initialised'); | |
428 | } | |
429 | } | |
430 | ||
431 | $options = array(); | |
432 | $options['adminpass'] = 'admin'; | |
433 | $options['shortname'] = 'phpunit'; | |
434 | $options['fullname'] = 'PHPUnit test site'; | |
435 | ||
436 | install_cli_database($options, false); | |
437 | ||
14ecbb2e AN |
438 | // Set the admin email address. |
439 | $DB->set_field('user', 'email', 'admin@example.com', array('username' => 'admin')); | |
440 | ||
770eac98 PŠ |
441 | // Disable all logging for performance and sanity reasons. |
442 | set_config('enabled_stores', '', 'tool_log'); | |
443 | ||
c78f19d1 JM |
444 | // We need to keep the installed dataroot filedir files. |
445 | // So each time we reset the dataroot before running a test, the default files are still installed. | |
446 | self::save_original_data_files(); | |
447 | ||
0ea35584 DM |
448 | // Store version hash in the database and in a file. |
449 | self::store_versions_hash(); | |
7e7cfe7a | 450 | |
0ea35584 DM |
451 | // Store database data and structure. |
452 | self::store_database_state(); | |
7e7cfe7a PS |
453 | } |
454 | ||
455 | /** | |
456 | * Builds dirroot/phpunit.xml and dataroot/phpunit/webrunner.xml files using defaults from /phpunit.xml.dist | |
457 | * @static | |
458 | * @return bool true means main config file created, false means only dataroot file created | |
459 | */ | |
460 | public static function build_config_file() { | |
461 | global $CFG; | |
462 | ||
463 | $template = ' | |
a9edd8a6 | 464 | <testsuite name="@component@_testsuite"> |
7e7cfe7a PS |
465 | <directory suffix="_test.php">@dir@</directory> |
466 | </testsuite>'; | |
467 | $data = file_get_contents("$CFG->dirroot/phpunit.xml.dist"); | |
468 | ||
469 | $suites = ''; | |
470 | ||
46f6f7f2 | 471 | $plugintypes = core_component::get_plugin_types(); |
7e7cfe7a PS |
472 | ksort($plugintypes); |
473 | foreach ($plugintypes as $type=>$unused) { | |
bd3b3bba | 474 | $plugs = core_component::get_plugin_list($type); |
7e7cfe7a PS |
475 | ksort($plugs); |
476 | foreach ($plugs as $plug=>$fullplug) { | |
477 | if (!file_exists("$fullplug/tests/")) { | |
478 | continue; | |
479 | } | |
480 | $dir = substr($fullplug, strlen($CFG->dirroot)+1); | |
481 | $dir .= '/tests'; | |
482 | $component = $type.'_'.$plug; | |
483 | ||
484 | $suite = str_replace('@component@', $component, $template); | |
485 | $suite = str_replace('@dir@', $dir, $suite); | |
486 | ||
487 | $suites .= $suite; | |
488 | } | |
489 | } | |
529495f7 RS |
490 | // Start a sequence between 100000 and 199000 to ensure each call to init produces |
491 | // different ids in the database. This reduces the risk that hard coded values will | |
492 | // end up being placed in phpunit or behat test code. | |
493 | $sequencestart = 100000 + mt_rand(0, 99) * 1000; | |
7e7cfe7a PS |
494 | |
495 | $data = preg_replace('|<!--@plugin_suites_start@-->.*<!--@plugin_suites_end@-->|s', $suites, $data, 1); | |
ddffa9d6 PS |
496 | $data = str_replace( |
497 | '<const name="PHPUNIT_SEQUENCE_START" value=""/>', | |
498 | '<const name="PHPUNIT_SEQUENCE_START" value="' . $sequencestart . '"/>', | |
499 | $data); | |
7e7cfe7a PS |
500 | |
501 | $result = false; | |
502 | if (is_writable($CFG->dirroot)) { | |
503 | if ($result = file_put_contents("$CFG->dirroot/phpunit.xml", $data)) { | |
0ea35584 | 504 | testing_fix_file_permissions("$CFG->dirroot/phpunit.xml"); |
7e7cfe7a PS |
505 | } |
506 | } | |
507 | ||
508 | // 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 | |
509 | $data = str_replace('lib/phpunit/', $CFG->dirroot.DIRECTORY_SEPARATOR.'lib'.DIRECTORY_SEPARATOR.'phpunit'.DIRECTORY_SEPARATOR, $data); | |
510 | $data = preg_replace('|<directory suffix="_test.php">([^<]+)</directory>|', | |
511 | '<directory suffix="_test.php">'.$CFG->dirroot.(DIRECTORY_SEPARATOR === '\\' ? '\\\\' : DIRECTORY_SEPARATOR).'$1</directory>', | |
512 | $data); | |
513 | file_put_contents("$CFG->dataroot/phpunit/webrunner.xml", $data); | |
0ea35584 | 514 | testing_fix_file_permissions("$CFG->dataroot/phpunit/webrunner.xml"); |
7e7cfe7a PS |
515 | |
516 | return (bool)$result; | |
517 | } | |
518 | ||
519 | /** | |
520 | * Builds phpunit.xml files for all components using defaults from /phpunit.xml.dist | |
521 | * | |
522 | * @static | |
523 | * @return void, stops if can not write files | |
524 | */ | |
525 | public static function build_component_config_files() { | |
526 | global $CFG; | |
527 | ||
528 | $template = ' | |
529 | <testsuites> | |
4463f996 | 530 | <testsuite name="@component@_testsuite"> |
7e7cfe7a PS |
531 | <directory suffix="_test.php">.</directory> |
532 | </testsuite> | |
533 | </testsuites>'; | |
534 | ||
9462323b RS |
535 | // Start a sequence between 100000 and 199000 to ensure each call to init produces |
536 | // different ids in the database. This reduces the risk that hard coded values will | |
537 | // end up being placed in phpunit or behat test code. | |
538 | $sequencestart = 100000 + mt_rand(0, 99) * 1000; | |
539 | ||
7e7cfe7a PS |
540 | // Use the upstream file as source for the distributed configurations |
541 | $ftemplate = file_get_contents("$CFG->dirroot/phpunit.xml.dist"); | |
542 | $ftemplate = preg_replace('|<!--All core suites.*</testsuites>|s', '<!--@component_suite@-->', $ftemplate); | |
543 | ||
f8228402 DM |
544 | // Gets all the components with tests |
545 | $components = tests_finder::get_components_with_tests('phpunit'); | |
7e7cfe7a PS |
546 | |
547 | // Create the corresponding phpunit.xml file for each component | |
548 | foreach ($components as $cname => $cpath) { | |
549 | // Calculate the component suite | |
550 | $ctemplate = $template; | |
551 | $ctemplate = str_replace('@component@', $cname, $ctemplate); | |
552 | ||
553 | // Apply it to the file template | |
554 | $fcontents = str_replace('<!--@component_suite@-->', $ctemplate, $ftemplate); | |
ddffa9d6 PS |
555 | $fcontents = str_replace( |
556 | '<const name="PHPUNIT_SEQUENCE_START" value=""/>', | |
557 | '<const name="PHPUNIT_SEQUENCE_START" value="' . $sequencestart . '"/>', | |
558 | $fcontents); | |
7e7cfe7a PS |
559 | |
560 | // fix link to schema | |
561 | $level = substr_count(str_replace('\\', '/', $cpath), '/') - substr_count(str_replace('\\', '/', $CFG->dirroot), '/'); | |
ed7259d1 | 562 | $fcontents = str_replace('lib/phpunit/', str_repeat('../', $level).'lib/phpunit/', $fcontents); |
7e7cfe7a PS |
563 | |
564 | // Write the file | |
565 | $result = false; | |
566 | if (is_writable($cpath)) { | |
567 | if ($result = (bool)file_put_contents("$cpath/phpunit.xml", $fcontents)) { | |
0ea35584 | 568 | testing_fix_file_permissions("$cpath/phpunit.xml"); |
7e7cfe7a PS |
569 | } |
570 | } | |
571 | // Problems writing file, throw error | |
572 | if (!$result) { | |
573 | phpunit_bootstrap_error(PHPUNIT_EXITCODE_CONFIGWARNING, "Can not create $cpath/phpunit.xml configuration file, verify dir permissions"); | |
574 | } | |
575 | } | |
576 | } | |
577 | ||
ef5b5e05 PS |
578 | /** |
579 | * To be called from debugging() only. | |
580 | * @param string $message | |
581 | * @param int $level | |
582 | * @param string $from | |
583 | */ | |
584 | public static function debugging_triggered($message, $level, $from) { | |
585 | // Store only if debugging triggered from actual test, | |
586 | // we need normal debugging outside of tests to find problems in our phpunit integration. | |
587 | $backtrace = debug_backtrace(); | |
588 | ||
589 | foreach ($backtrace as $bt) { | |
590 | $intest = false; | |
591 | if (isset($bt['object']) and is_object($bt['object'])) { | |
592 | if ($bt['object'] instanceof PHPUnit_Framework_TestCase) { | |
593 | if (strpos($bt['function'], 'test') === 0) { | |
594 | $intest = true; | |
595 | break; | |
596 | } | |
597 | } | |
598 | } | |
599 | } | |
600 | if (!$intest) { | |
601 | return false; | |
602 | } | |
603 | ||
604 | $debug = new stdClass(); | |
605 | $debug->message = $message; | |
606 | $debug->level = $level; | |
607 | $debug->from = $from; | |
608 | ||
609 | self::$debuggings[] = $debug; | |
610 | ||
611 | return true; | |
612 | } | |
613 | ||
614 | /** | |
615 | * Resets the list of debugging messages. | |
616 | */ | |
617 | public static function reset_debugging() { | |
618 | self::$debuggings = array(); | |
96f81ea3 | 619 | set_debugging(DEBUG_DEVELOPER); |
ef5b5e05 PS |
620 | } |
621 | ||
622 | /** | |
623 | * Returns all debugging messages triggered during test. | |
624 | * @return array with instances having message, level and stacktrace property. | |
625 | */ | |
626 | public static function get_debugging_messages() { | |
627 | return self::$debuggings; | |
628 | } | |
629 | ||
630 | /** | |
631 | * Prints out any debug messages accumulated during test execution. | |
632 | * @return bool false if no debug messages, true if debug triggered | |
633 | */ | |
634 | public static function display_debugging_messages() { | |
635 | if (empty(self::$debuggings)) { | |
636 | return false; | |
637 | } | |
638 | foreach(self::$debuggings as $debug) { | |
639 | echo 'Debugging: ' . $debug->message . "\n" . trim($debug->from) . "\n"; | |
640 | } | |
641 | ||
642 | return true; | |
643 | } | |
4c9e03f0 PS |
644 | |
645 | /** | |
646 | * Start message redirection. | |
647 | * | |
648 | * Note: Do not call directly from tests, | |
649 | * use $sink = $this->redirectMessages() instead. | |
650 | * | |
651 | * @return phpunit_message_sink | |
652 | */ | |
653 | public static function start_message_redirection() { | |
654 | if (self::$messagesink) { | |
655 | self::stop_message_redirection(); | |
656 | } | |
657 | self::$messagesink = new phpunit_message_sink(); | |
658 | return self::$messagesink; | |
659 | } | |
660 | ||
661 | /** | |
662 | * End message redirection. | |
663 | * | |
664 | * Note: Do not call directly from tests, | |
665 | * use $sink->close() instead. | |
666 | */ | |
667 | public static function stop_message_redirection() { | |
668 | self::$messagesink = null; | |
669 | } | |
670 | ||
671 | /** | |
672 | * Are messages redirected to some sink? | |
673 | * | |
674 | * Note: to be called from messagelib.php only! | |
675 | * | |
676 | * @return bool | |
677 | */ | |
678 | public static function is_redirecting_messages() { | |
679 | return !empty(self::$messagesink); | |
680 | } | |
681 | ||
682 | /** | |
683 | * To be called from messagelib.php only! | |
684 | * | |
685 | * @param stdClass $message record from message_read table | |
686 | * @return bool true means send message, false means message "sent" to sink. | |
687 | */ | |
688 | public static function message_sent($message) { | |
689 | if (self::$messagesink) { | |
690 | self::$messagesink->add_message($message); | |
691 | } | |
692 | } | |
62401e8f | 693 | |
1aba6b2b AN |
694 | /** |
695 | * Start phpmailer redirection. | |
696 | * | |
697 | * Note: Do not call directly from tests, | |
698 | * use $sink = $this->redirectEmails() instead. | |
699 | * | |
700 | * @return phpunit_phpmailer_sink | |
701 | */ | |
702 | public static function start_phpmailer_redirection() { | |
703 | if (self::$phpmailersink) { | |
556e3a9e AN |
704 | // If an existing mailer sink is active, just clear it. |
705 | self::$phpmailersink->clear(); | |
706 | } else { | |
707 | self::$phpmailersink = new phpunit_phpmailer_sink(); | |
1aba6b2b | 708 | } |
1aba6b2b AN |
709 | return self::$phpmailersink; |
710 | } | |
711 | ||
712 | /** | |
713 | * End phpmailer redirection. | |
714 | * | |
715 | * Note: Do not call directly from tests, | |
716 | * use $sink->close() instead. | |
717 | */ | |
718 | public static function stop_phpmailer_redirection() { | |
719 | self::$phpmailersink = null; | |
720 | } | |
721 | ||
722 | /** | |
723 | * Are messages for phpmailer redirected to some sink? | |
724 | * | |
725 | * Note: to be called from moodle_phpmailer.php only! | |
726 | * | |
727 | * @return bool | |
728 | */ | |
729 | public static function is_redirecting_phpmailer() { | |
730 | return !empty(self::$phpmailersink); | |
731 | } | |
732 | ||
733 | /** | |
734 | * To be called from messagelib.php only! | |
735 | * | |
736 | * @param stdClass $message record from message_read table | |
737 | * @return bool true means send message, false means message "sent" to sink. | |
738 | */ | |
739 | public static function phpmailer_sent($message) { | |
740 | if (self::$phpmailersink) { | |
741 | self::$phpmailersink->add_message($message); | |
742 | } | |
743 | } | |
744 | ||
62401e8f PS |
745 | /** |
746 | * Start event redirection. | |
747 | * | |
748 | * @private | |
749 | * Note: Do not call directly from tests, | |
750 | * use $sink = $this->redirectEvents() instead. | |
751 | * | |
752 | * @return phpunit_event_sink | |
753 | */ | |
754 | public static function start_event_redirection() { | |
755 | if (self::$eventsink) { | |
756 | self::stop_event_redirection(); | |
757 | } | |
758 | self::$eventsink = new phpunit_event_sink(); | |
759 | return self::$eventsink; | |
760 | } | |
761 | ||
762 | /** | |
763 | * End event redirection. | |
764 | * | |
765 | * @private | |
766 | * Note: Do not call directly from tests, | |
767 | * use $sink->close() instead. | |
768 | */ | |
769 | public static function stop_event_redirection() { | |
770 | self::$eventsink = null; | |
771 | } | |
772 | ||
773 | /** | |
774 | * Are events redirected to some sink? | |
775 | * | |
776 | * Note: to be called from \core\event\base only! | |
777 | * | |
778 | * @private | |
779 | * @return bool | |
780 | */ | |
781 | public static function is_redirecting_events() { | |
782 | return !empty(self::$eventsink); | |
783 | } | |
784 | ||
785 | /** | |
786 | * To be called from \core\event\base only! | |
787 | * | |
788 | * @private | |
789 | * @param \core\event\base $event record from event_read table | |
790 | * @return bool true means send event, false means event "sent" to sink. | |
791 | */ | |
792 | public static function event_triggered(\core\event\base $event) { | |
793 | if (self::$eventsink) { | |
794 | self::$eventsink->add_event($event); | |
795 | } | |
796 | } | |
96805290 DP |
797 | |
798 | /** | |
799 | * Gets the name of the locale for testing environment (Australian English) | |
800 | * depending on platform environment. | |
801 | * | |
802 | * @return string the locale name. | |
803 | */ | |
804 | protected static function get_locale_name() { | |
805 | global $CFG; | |
806 | if ($CFG->ostype === 'WINDOWS') { | |
807 | return 'English_Australia.1252'; | |
808 | } else { | |
809 | return 'en_AU.UTF-8'; | |
810 | } | |
811 | } | |
7e7cfe7a | 812 | } |