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 | * Advanced test case. | |
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 | ||
26 | ||
27 | /** | |
28 | * Advanced PHPUnit test case customised for Moodle. | |
29 | * | |
30 | * @package core | |
31 | * @category phpunit | |
32 | * @copyright 2012 Petr Skoda {@link http://skodak.org} | |
33 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
34 | */ | |
b9432e1a | 35 | abstract class advanced_testcase extends base_testcase { |
7e7cfe7a PS |
36 | /** @var bool automatically reset everything? null means log changes */ |
37 | private $resetAfterTest; | |
38 | ||
39 | /** @var moodle_transaction */ | |
40 | private $testdbtransaction; | |
41 | ||
2a67e105 PS |
42 | /** @var int timestamp used for current time asserts */ |
43 | private $currenttimestart; | |
44 | ||
7e7cfe7a PS |
45 | /** |
46 | * Constructs a test case with the given name. | |
47 | * | |
48 | * Note: use setUp() or setUpBeforeClass() in your test cases. | |
49 | * | |
50 | * @param string $name | |
51 | * @param array $data | |
52 | * @param string $dataName | |
53 | */ | |
54 | final public function __construct($name = null, array $data = array(), $dataName = '') { | |
55 | parent::__construct($name, $data, $dataName); | |
56 | ||
57 | $this->setBackupGlobals(false); | |
58 | $this->setBackupStaticAttributes(false); | |
fc1785b0 | 59 | $this->setPreserveGlobalState(false); |
7e7cfe7a PS |
60 | } |
61 | ||
62 | /** | |
63 | * Runs the bare test sequence. | |
64 | * @return void | |
65 | */ | |
26218b78 | 66 | final public function runBare(): void { |
7e7cfe7a PS |
67 | global $DB; |
68 | ||
69 | if (phpunit_util::$lastdbwrites != $DB->perf_get_writes()) { | |
70 | // this happens when previous test does not reset, we can not use transactions | |
71 | $this->testdbtransaction = null; | |
72 | ||
73 | } else if ($DB->get_dbfamily() === 'postgres' or $DB->get_dbfamily() === 'mssql') { | |
74 | // database must allow rollback of DDL, so no mysql here | |
75 | $this->testdbtransaction = $DB->start_delegated_transaction(); | |
76 | } | |
77 | ||
78 | try { | |
2a67e105 | 79 | $this->setCurrentTimeStart(); |
7e7cfe7a PS |
80 | parent::runBare(); |
81 | // set DB reference in case somebody mocked it in test | |
82 | $DB = phpunit_util::get_global_backup('DB'); | |
ef5b5e05 PS |
83 | |
84 | // Deal with any debugging messages. | |
ef151737 | 85 | $debugerror = phpunit_util::display_debugging_messages(true); |
5ecf8e8f | 86 | $this->resetDebugging(); |
ef151737 RS |
87 | if (!empty($debugerror)) { |
88 | trigger_error('Unexpected debugging() call detected.'."\n".$debugerror, E_USER_NOTICE); | |
c386f35b | 89 | } |
ef5b5e05 | 90 | |
d74b7e42 TL |
91 | } catch (Exception $ex) { |
92 | $e = $ex; | |
93 | } catch (Throwable $ex) { | |
1766e6a1 MG |
94 | // Engine errors in PHP7 throw exceptions of type Throwable (this "catch" will be ignored in PHP5). |
95 | $e = $ex; | |
d74b7e42 TL |
96 | } |
97 | ||
98 | if (isset($e)) { | |
7e7cfe7a | 99 | // cleanup after failed expectation |
5ecf8e8f | 100 | self::resetAllData(); |
7e7cfe7a PS |
101 | throw $e; |
102 | } | |
103 | ||
104 | if (!$this->testdbtransaction or $this->testdbtransaction->is_disposed()) { | |
105 | $this->testdbtransaction = null; | |
106 | } | |
107 | ||
108 | if ($this->resetAfterTest === true) { | |
109 | if ($this->testdbtransaction) { | |
110 | $DB->force_transaction_rollback(); | |
111 | phpunit_util::reset_all_database_sequences(); | |
112 | phpunit_util::$lastdbwrites = $DB->perf_get_writes(); // no db reset necessary | |
113 | } | |
5ecf8e8f | 114 | self::resetAllData(null); |
7e7cfe7a PS |
115 | |
116 | } else if ($this->resetAfterTest === false) { | |
117 | if ($this->testdbtransaction) { | |
118 | $this->testdbtransaction->allow_commit(); | |
119 | } | |
120 | // keep all data untouched for other tests | |
121 | ||
122 | } else { | |
123 | // reset but log what changed | |
124 | if ($this->testdbtransaction) { | |
125 | try { | |
126 | $this->testdbtransaction->allow_commit(); | |
127 | } catch (dml_transaction_exception $e) { | |
5ecf8e8f | 128 | self::resetAllData(); |
7e7cfe7a PS |
129 | throw new coding_exception('Invalid transaction state detected in test '.$this->getName()); |
130 | } | |
131 | } | |
5ecf8e8f | 132 | self::resetAllData(true); |
7e7cfe7a PS |
133 | } |
134 | ||
0616f045 AN |
135 | // Reset context cache. |
136 | context_helper::reset_caches(); | |
137 | ||
7e7cfe7a PS |
138 | // make sure test did not forget to close transaction |
139 | if ($DB->is_transaction_started()) { | |
5ecf8e8f | 140 | self::resetAllData(); |
801a372d EL |
141 | if ($this->getStatus() == PHPUnit\Runner\BaseTestRunner::STATUS_PASSED |
142 | or $this->getStatus() == PHPUnit\Runner\BaseTestRunner::STATUS_SKIPPED | |
143 | or $this->getStatus() == PHPUnit\Runner\BaseTestRunner::STATUS_INCOMPLETE) { | |
7e7cfe7a PS |
144 | throw new coding_exception('Test '.$this->getName().' did not close database transaction'); |
145 | } | |
146 | } | |
147 | } | |
148 | ||
149 | /** | |
150 | * Creates a new FlatXmlDataSet with the given $xmlFile. (absolute path.) | |
151 | * | |
152 | * @param string $xmlFile | |
801a372d | 153 | * @return PHPUnit\DbUnit\DataSet\FlatXmlDataSet |
7e7cfe7a PS |
154 | */ |
155 | protected function createFlatXMLDataSet($xmlFile) { | |
8183def6 | 156 | // TODO: MDL-67673 - removed |
801a372d | 157 | return new PHPUnit\DbUnit\DataSet\FlatXmlDataSet($xmlFile); |
7e7cfe7a PS |
158 | } |
159 | ||
160 | /** | |
161 | * Creates a new XMLDataSet with the given $xmlFile. (absolute path.) | |
162 | * | |
163 | * @param string $xmlFile | |
801a372d | 164 | * @return PHPUnit\DbUnit\DataSet\XmlDataSet |
7e7cfe7a PS |
165 | */ |
166 | protected function createXMLDataSet($xmlFile) { | |
8183def6 EL |
167 | // TODO: MDL-67673 - deprecate this (debugging...) |
168 | return $this->dataset_from_files([$xmlFile]); | |
7e7cfe7a PS |
169 | } |
170 | ||
171 | /** | |
172 | * Creates a new CsvDataSet from the given array of csv files. (absolute paths.) | |
173 | * | |
174 | * @param array $files array tablename=>cvsfile | |
8183def6 EL |
175 | * @param string $delimiter unused |
176 | * @param string $enclosure unused | |
177 | * @param string $escape unused | |
178 | * @return phpunit_dataset | |
7e7cfe7a PS |
179 | */ |
180 | protected function createCsvDataSet($files, $delimiter = ',', $enclosure = '"', $escape = '"') { | |
8183def6 EL |
181 | // TODO: MDL-67673 - deprecate this (debugging...) |
182 | return $this->dataset_from_files($files); | |
7e7cfe7a PS |
183 | } |
184 | ||
185 | /** | |
186 | * Creates new ArrayDataSet from given array | |
187 | * | |
188 | * @param array $data array of tables, first row in each table is columns | |
189 | * @return phpunit_ArrayDataSet | |
190 | */ | |
191 | protected function createArrayDataSet(array $data) { | |
8183def6 EL |
192 | // TODO: MDL-67673 - deprecate this (debugging...) |
193 | return $this->dataset_from_array($data); | |
7e7cfe7a PS |
194 | } |
195 | ||
196 | /** | |
197 | * Load date into moodle database tables from standard PHPUnit data set. | |
198 | * | |
199 | * Note: it is usually better to use data generators | |
200 | * | |
8183def6 | 201 | * @param phpunit_dataset $dataset |
7e7cfe7a PS |
202 | * @return void |
203 | */ | |
8183def6 EL |
204 | protected function loadDataSet(phpunit_dataset $dataset) { |
205 | // TODO: MDL-67673 - deprecate this (debugging...) | |
206 | $dataset->to_database(); | |
207 | } | |
7e7cfe7a | 208 | |
8183def6 EL |
209 | /** |
210 | * Creates a new dataset from CVS/XML files. | |
211 | * | |
212 | * This method accepts an array of full paths to CSV or XML files to be loaded | |
213 | * into the dataset. For CSV files, the name of the table which the file belongs | |
214 | * to needs to be specified. Example: | |
215 | * | |
216 | * $fullpaths = [ | |
217 | * '/path/to/users.xml', | |
218 | * 'course' => '/path/to/courses.csv', | |
219 | * ]; | |
220 | * | |
221 | * @param array $files full paths to CSV or XML files to load. | |
222 | * @return phpunit_dataset | |
223 | */ | |
224 | protected function dataset_from_files(array $files) { | |
225 | // We ignore $delimiter, $enclosure and $escape, use the default ones in your fixtures. | |
226 | $dataset = new phpunit_dataset(); | |
227 | $dataset->from_files($files); | |
228 | return $dataset; | |
229 | } | |
7e7cfe7a | 230 | |
8183def6 EL |
231 | /** |
232 | * Creates a new dataset from string (CSV or XML). | |
233 | * | |
234 | * @param string $content contents (CSV or XML) to load. | |
235 | * @param string $type format of the content to be loaded (csv or xml). | |
236 | * @param string $table name of the table which the file belongs to (only for CSV files). | |
237 | */ | |
238 | protected function dataset_from_string(string $content, string $type, ?string $table = null) { | |
239 | $dataset = new phpunit_dataset(); | |
240 | $dataset->from_string($content, $type, $table); | |
241 | return $dataset; | |
242 | } | |
7e7cfe7a | 243 | |
8183def6 EL |
244 | /** |
245 | * Creates a new dataset from PHP array. | |
246 | * | |
247 | * @param array $data array of tables, see {@see phpunit_dataset::from_array()} for supported formats. | |
248 | * @return phpunit_dataset | |
249 | */ | |
250 | protected function dataset_from_array(array $data) { | |
251 | $dataset = new phpunit_dataset(); | |
252 | $dataset->from_array($data); | |
253 | return $dataset; | |
7e7cfe7a PS |
254 | } |
255 | ||
256 | /** | |
257 | * Call this method from test if you want to make sure that | |
258 | * the resetting of database is done the slow way without transaction | |
259 | * rollback. | |
260 | * | |
261 | * This is useful especially when testing stuff that is not compatible with transactions. | |
262 | * | |
263 | * @return void | |
264 | */ | |
265 | public function preventResetByRollback() { | |
266 | if ($this->testdbtransaction and !$this->testdbtransaction->is_disposed()) { | |
267 | $this->testdbtransaction->allow_commit(); | |
268 | $this->testdbtransaction = null; | |
269 | } | |
270 | } | |
271 | ||
272 | /** | |
273 | * Reset everything after current test. | |
274 | * @param bool $reset true means reset state back, false means keep all data for the next test, | |
275 | * null means reset state and show warnings if anything changed | |
276 | * @return void | |
277 | */ | |
278 | public function resetAfterTest($reset = true) { | |
279 | $this->resetAfterTest = $reset; | |
280 | } | |
281 | ||
ef5b5e05 PS |
282 | /** |
283 | * Return debugging messages from the current test. | |
94c9db54 | 284 | * @return array with instances having 'message', 'level' and 'stacktrace' property. |
ef5b5e05 PS |
285 | */ |
286 | public function getDebuggingMessages() { | |
287 | return phpunit_util::get_debugging_messages(); | |
288 | } | |
289 | ||
290 | /** | |
96f81ea3 PS |
291 | * Clear all previous debugging messages in current test |
292 | * and revert to default DEVELOPER_DEBUG level. | |
ef5b5e05 PS |
293 | */ |
294 | public function resetDebugging() { | |
94c9db54 | 295 | phpunit_util::reset_debugging(); |
ef5b5e05 PS |
296 | } |
297 | ||
298 | /** | |
299 | * Assert that exactly debugging was just called once. | |
300 | * | |
301 | * Discards the debugging message if successful. | |
302 | * | |
303 | * @param null|string $debugmessage null means any | |
304 | * @param null|string $debuglevel null means any | |
305 | * @param string $message | |
306 | */ | |
307 | public function assertDebuggingCalled($debugmessage = null, $debuglevel = null, $message = '') { | |
5ecf8e8f | 308 | $debugging = $this->getDebuggingMessages(); |
ef151737 RS |
309 | $debugdisplaymessage = "\n".phpunit_util::display_debugging_messages(true); |
310 | $this->resetDebugging(); | |
311 | ||
ef5b5e05 PS |
312 | $count = count($debugging); |
313 | ||
314 | if ($count == 0) { | |
315 | if ($message === '') { | |
316 | $message = 'Expectation failed, debugging() not triggered.'; | |
317 | } | |
318 | $this->fail($message); | |
319 | } | |
320 | if ($count > 1) { | |
321 | if ($message === '') { | |
ef151737 | 322 | $message = 'Expectation failed, debugging() triggered '.$count.' times.'.$debugdisplaymessage; |
ef5b5e05 PS |
323 | } |
324 | $this->fail($message); | |
325 | } | |
326 | $this->assertEquals(1, $count); | |
327 | ||
ef151737 | 328 | $message .= $debugdisplaymessage; |
ef5b5e05 PS |
329 | $debug = reset($debugging); |
330 | if ($debugmessage !== null) { | |
331 | $this->assertSame($debugmessage, $debug->message, $message); | |
332 | } | |
333 | if ($debuglevel !== null) { | |
334 | $this->assertSame($debuglevel, $debug->level, $message); | |
335 | } | |
ef5b5e05 PS |
336 | } |
337 | ||
20ff2fba DM |
338 | /** |
339 | * Asserts how many times debugging has been called. | |
340 | * | |
341 | * @param int $expectedcount The expected number of times | |
342 | * @param array $debugmessages Expected debugging messages, one for each expected message. | |
343 | * @param array $debuglevels Expected debugging levels, one for each expected message. | |
344 | * @param string $message | |
345 | * @return void | |
346 | */ | |
347 | public function assertDebuggingCalledCount($expectedcount, $debugmessages = array(), $debuglevels = array(), $message = '') { | |
348 | if (!is_int($expectedcount)) { | |
349 | throw new coding_exception('assertDebuggingCalledCount $expectedcount argument should be an integer.'); | |
350 | } | |
351 | ||
352 | $debugging = $this->getDebuggingMessages(); | |
ef151737 RS |
353 | $message .= "\n".phpunit_util::display_debugging_messages(true); |
354 | $this->resetDebugging(); | |
355 | ||
20ff2fba DM |
356 | $this->assertEquals($expectedcount, count($debugging), $message); |
357 | ||
358 | if ($debugmessages) { | |
359 | if (!is_array($debugmessages) || count($debugmessages) != $expectedcount) { | |
360 | throw new coding_exception('assertDebuggingCalledCount $debugmessages should contain ' . $expectedcount . ' messages'); | |
361 | } | |
362 | foreach ($debugmessages as $key => $debugmessage) { | |
363 | $this->assertSame($debugmessage, $debugging[$key]->message, $message); | |
364 | } | |
365 | } | |
366 | ||
367 | if ($debuglevels) { | |
368 | if (!is_array($debuglevels) || count($debuglevels) != $expectedcount) { | |
369 | throw new coding_exception('assertDebuggingCalledCount $debuglevels should contain ' . $expectedcount . ' messages'); | |
370 | } | |
371 | foreach ($debuglevels as $key => $debuglevel) { | |
372 | $this->assertSame($debuglevel, $debugging[$key]->level, $message); | |
373 | } | |
374 | } | |
20ff2fba DM |
375 | } |
376 | ||
94c9db54 PS |
377 | /** |
378 | * Call when no debugging() messages expected. | |
379 | * @param string $message | |
380 | */ | |
ef5b5e05 | 381 | public function assertDebuggingNotCalled($message = '') { |
5ecf8e8f | 382 | $debugging = $this->getDebuggingMessages(); |
ef5b5e05 PS |
383 | $count = count($debugging); |
384 | ||
385 | if ($message === '') { | |
386 | $message = 'Expectation failed, debugging() was triggered.'; | |
387 | } | |
ef151737 RS |
388 | $message .= "\n".phpunit_util::display_debugging_messages(true); |
389 | $this->resetDebugging(); | |
ef5b5e05 PS |
390 | $this->assertEquals(0, $count, $message); |
391 | } | |
392 | ||
d6277b0c FM |
393 | /** |
394 | * Assert that an event legacy data is equal to the expected value. | |
395 | * | |
396 | * @param mixed $expected expected data. | |
397 | * @param \core\event\base $event the event object. | |
398 | * @param string $message | |
399 | * @return void | |
400 | */ | |
401 | public function assertEventLegacyData($expected, \core\event\base $event, $message = '') { | |
402 | $legacydata = phpunit_event_mock::testable_get_legacy_eventdata($event); | |
403 | if ($message === '') { | |
404 | $message = 'Event legacy data does not match expected value.'; | |
405 | } | |
406 | $this->assertEquals($expected, $legacydata, $message); | |
407 | } | |
408 | ||
409 | /** | |
410 | * Assert that an event legacy log data is equal to the expected value. | |
411 | * | |
412 | * @param mixed $expected expected data. | |
413 | * @param \core\event\base $event the event object. | |
414 | * @param string $message | |
415 | * @return void | |
416 | */ | |
417 | public function assertEventLegacyLogData($expected, \core\event\base $event, $message = '') { | |
418 | $legacydata = phpunit_event_mock::testable_get_legacy_logdata($event); | |
419 | if ($message === '') { | |
420 | $message = 'Event legacy log data does not match expected value.'; | |
421 | } | |
422 | $this->assertEquals($expected, $legacydata, $message); | |
423 | } | |
424 | ||
623a32e5 RT |
425 | /** |
426 | * Assert that an event is not using event->contxet. | |
427 | * While restoring context might not be valid and it should not be used by event url | |
428 | * or description methods. | |
429 | * | |
430 | * @param \core\event\base $event the event object. | |
431 | * @param string $message | |
432 | * @return void | |
433 | */ | |
434 | public function assertEventContextNotUsed(\core\event\base $event, $message = '') { | |
435 | // Save current event->context and set it to false. | |
436 | $eventcontext = phpunit_event_mock::testable_get_event_context($event); | |
437 | phpunit_event_mock::testable_set_event_context($event, false); | |
438 | if ($message === '') { | |
439 | $message = 'Event should not use context property of event in any method.'; | |
440 | } | |
441 | ||
442 | // Test event methods should not use event->context. | |
443 | $event->get_url(); | |
444 | $event->get_description(); | |
445 | $event->get_legacy_eventname(); | |
446 | phpunit_event_mock::testable_get_legacy_eventdata($event); | |
447 | phpunit_event_mock::testable_get_legacy_logdata($event); | |
448 | ||
449 | // Restore event->context. | |
450 | phpunit_event_mock::testable_set_event_context($event, $eventcontext); | |
451 | } | |
452 | ||
2a67e105 PS |
453 | /** |
454 | * Stores current time as the base for assertTimeCurrent(). | |
455 | * | |
456 | * Note: this is called automatically before calling individual test methods. | |
457 | * @return int current time | |
458 | */ | |
459 | public function setCurrentTimeStart() { | |
460 | $this->currenttimestart = time(); | |
461 | return $this->currenttimestart; | |
462 | } | |
463 | ||
464 | /** | |
465 | * Assert that: start < $time < time() | |
466 | * @param int $time | |
467 | * @param string $message | |
468 | * @return void | |
469 | */ | |
470 | public function assertTimeCurrent($time, $message = '') { | |
471 | $msg = ($message === '') ? 'Time is lower that allowed start value' : $message; | |
472 | $this->assertGreaterThanOrEqual($this->currenttimestart, $time, $msg); | |
473 | $msg = ($message === '') ? 'Time is in the future' : $message; | |
474 | $this->assertLessThanOrEqual(time(), $time, $msg); | |
475 | } | |
d6277b0c | 476 | |
4c9e03f0 PS |
477 | /** |
478 | * Starts message redirection. | |
479 | * | |
480 | * You can verify if messages were sent or not by inspecting the messages | |
481 | * array in the returned messaging sink instance. The redirection | |
482 | * can be stopped by calling $sink->close(); | |
483 | * | |
484 | * @return phpunit_message_sink | |
485 | */ | |
486 | public function redirectMessages() { | |
487 | return phpunit_util::start_message_redirection(); | |
488 | } | |
489 | ||
1aba6b2b AN |
490 | /** |
491 | * Starts email redirection. | |
492 | * | |
493 | * You can verify if email were sent or not by inspecting the email | |
494 | * array in the returned phpmailer sink instance. The redirection | |
495 | * can be stopped by calling $sink->close(); | |
496 | * | |
497 | * @return phpunit_message_sink | |
498 | */ | |
499 | public function redirectEmails() { | |
500 | return phpunit_util::start_phpmailer_redirection(); | |
501 | } | |
502 | ||
62401e8f PS |
503 | /** |
504 | * Starts event redirection. | |
505 | * | |
506 | * You can verify if events were triggered or not by inspecting the events | |
507 | * array in the returned event sink instance. The redirection | |
508 | * can be stopped by calling $sink->close(); | |
509 | * | |
510 | * @return phpunit_event_sink | |
511 | */ | |
512 | public function redirectEvents() { | |
513 | return phpunit_util::start_event_redirection(); | |
514 | } | |
515 | ||
7e7cfe7a PS |
516 | /** |
517 | * Reset all database tables, restore global state and clear caches and optionally purge dataroot dir. | |
5ecf8e8f T |
518 | * |
519 | * @param bool $detectchanges | |
520 | * true - changes in global state and database are reported as errors | |
521 | * false - no errors reported | |
522 | * null - only critical problems are reported as errors | |
7e7cfe7a PS |
523 | * @return void |
524 | */ | |
5ecf8e8f T |
525 | public static function resetAllData($detectchanges = false) { |
526 | phpunit_util::reset_all_data($detectchanges); | |
7e7cfe7a PS |
527 | } |
528 | ||
529 | /** | |
530 | * Set current $USER, reset access cache. | |
531 | * @static | |
d59ef9c5 | 532 | * @param null|int|stdClass $user user record, null or 0 means non-logged-in, positive integer means userid |
7e7cfe7a PS |
533 | * @return void |
534 | */ | |
535 | public static function setUser($user = null) { | |
536 | global $CFG, $DB; | |
537 | ||
538 | if (is_object($user)) { | |
539 | $user = clone($user); | |
540 | } else if (!$user) { | |
541 | $user = new stdClass(); | |
542 | $user->id = 0; | |
543 | $user->mnethostid = $CFG->mnet_localhost_id; | |
544 | } else { | |
545 | $user = $DB->get_record('user', array('id'=>$user)); | |
546 | } | |
547 | unset($user->description); | |
548 | unset($user->access); | |
d59ef9c5 | 549 | unset($user->preference); |
7e7cfe7a | 550 | |
faea2413 RT |
551 | // Enusre session is empty, as it may contain caches and user specific info. |
552 | \core\session\manager::init_empty_session(); | |
553 | ||
d79d5ac2 | 554 | \core\session\manager::set_user($user); |
7e7cfe7a PS |
555 | } |
556 | ||
d59ef9c5 PS |
557 | /** |
558 | * Set current $USER to admin account, reset access cache. | |
559 | * @static | |
560 | * @return void | |
561 | */ | |
562 | public static function setAdminUser() { | |
563 | self::setUser(2); | |
564 | } | |
565 | ||
566 | /** | |
567 | * Set current $USER to guest account, reset access cache. | |
568 | * @static | |
569 | * @return void | |
570 | */ | |
571 | public static function setGuestUser() { | |
572 | self::setUser(1); | |
573 | } | |
574 | ||
d6e7a63d PS |
575 | /** |
576 | * Change server and default php timezones. | |
577 | * | |
578 | * @param string $servertimezone timezone to set in $CFG->timezone (not validated) | |
579 | * @param string $defaultphptimezone timezone to fake default php timezone (must be valid) | |
580 | */ | |
581 | public static function setTimezone($servertimezone = 'Australia/Perth', $defaultphptimezone = 'Australia/Perth') { | |
582 | global $CFG; | |
583 | $CFG->timezone = $servertimezone; | |
584 | core_date::phpunit_override_default_php_timezone($defaultphptimezone); | |
585 | core_date::set_default_server_timezone(); | |
586 | } | |
587 | ||
7e7cfe7a PS |
588 | /** |
589 | * Get data generator | |
590 | * @static | |
5c3c2c81 | 591 | * @return testing_data_generator |
7e7cfe7a PS |
592 | */ |
593 | public static function getDataGenerator() { | |
594 | return phpunit_util::get_data_generator(); | |
595 | } | |
596 | ||
a9d2f1b4 PS |
597 | /** |
598 | * Returns UTL of the external test file. | |
599 | * | |
600 | * The result depends on the value of following constants: | |
601 | * - TEST_EXTERNAL_FILES_HTTP_URL | |
602 | * - TEST_EXTERNAL_FILES_HTTPS_URL | |
603 | * | |
604 | * They should point to standard external test files repository, | |
605 | * it defaults to 'http://download.moodle.org/unittest'. | |
606 | * | |
607 | * False value means skip tests that require external files. | |
608 | * | |
609 | * @param string $path | |
610 | * @param bool $https true if https required | |
611 | * @return string url | |
612 | */ | |
613 | public function getExternalTestFileUrl($path, $https = false) { | |
614 | $path = ltrim($path, '/'); | |
615 | if ($path) { | |
616 | $path = '/'.$path; | |
617 | } | |
618 | if ($https) { | |
619 | if (defined('TEST_EXTERNAL_FILES_HTTPS_URL')) { | |
620 | if (!TEST_EXTERNAL_FILES_HTTPS_URL) { | |
621 | $this->markTestSkipped('Tests using external https test files are disabled'); | |
622 | } | |
623 | return TEST_EXTERNAL_FILES_HTTPS_URL.$path; | |
624 | } | |
625 | return 'https://download.moodle.org/unittest'.$path; | |
626 | } | |
627 | ||
628 | if (defined('TEST_EXTERNAL_FILES_HTTP_URL')) { | |
629 | if (!TEST_EXTERNAL_FILES_HTTP_URL) { | |
630 | $this->markTestSkipped('Tests using external http test files are disabled'); | |
631 | } | |
632 | return TEST_EXTERNAL_FILES_HTTP_URL.$path; | |
633 | } | |
634 | return 'http://download.moodle.org/unittest'.$path; | |
635 | } | |
636 | ||
7e7cfe7a PS |
637 | /** |
638 | * Recursively visit all the files in the source tree. Calls the callback | |
639 | * function with the pathname of each file found. | |
640 | * | |
641 | * @param string $path the folder to start searching from. | |
642 | * @param string $callback the method of this class to call with the name of each file found. | |
643 | * @param string $fileregexp a regexp used to filter the search (optional). | |
644 | * @param bool $exclude If true, pathnames that match the regexp will be ignored. If false, | |
645 | * only files that match the regexp will be included. (default false). | |
646 | * @param array $ignorefolders will not go into any of these folders (optional). | |
647 | * @return void | |
648 | */ | |
649 | public function recurseFolders($path, $callback, $fileregexp = '/.*/', $exclude = false, $ignorefolders = array()) { | |
650 | $files = scandir($path); | |
651 | ||
652 | foreach ($files as $file) { | |
653 | $filepath = $path .'/'. $file; | |
654 | if (strpos($file, '.') === 0) { | |
655 | /// Don't check hidden files. | |
656 | continue; | |
657 | } else if (is_dir($filepath)) { | |
658 | if (!in_array($filepath, $ignorefolders)) { | |
659 | $this->recurseFolders($filepath, $callback, $fileregexp, $exclude, $ignorefolders); | |
660 | } | |
661 | } else if ($exclude xor preg_match($fileregexp, $filepath)) { | |
662 | $this->$callback($filepath); | |
663 | } | |
664 | } | |
665 | } | |
74ee9d29 RS |
666 | |
667 | /** | |
668 | * Wait for a second to roll over, ensures future calls to time() return a different result. | |
669 | * | |
670 | * This is implemented instead of sleep() as we do not need to wait a full second. In some cases | |
671 | * due to calls we may wait more than sleep() would have, on average it will be less. | |
672 | */ | |
673 | public function waitForSecond() { | |
674 | $starttime = time(); | |
675 | while (time() == $starttime) { | |
676 | usleep(50000); | |
677 | } | |
678 | } | |
7bd269e8 AN |
679 | |
680 | /** | |
681 | * Run adhoc tasks, optionally matching the specified classname. | |
682 | * | |
683 | * @param string $matchclass The name of the class to match on. | |
684 | * @param int $matchuserid The userid to match. | |
685 | */ | |
686 | protected function runAdhocTasks($matchclass = '', $matchuserid = null) { | |
687 | global $CFG, $DB; | |
688 | require_once($CFG->libdir.'/cronlib.php'); | |
689 | ||
690 | $params = []; | |
691 | if (!empty($matchclass)) { | |
692 | if (strpos($matchclass, '\\') !== 0) { | |
693 | $matchclass = '\\' . $matchclass; | |
694 | } | |
695 | $params['classname'] = $matchclass; | |
696 | } | |
697 | ||
698 | if (!empty($matchuserid)) { | |
699 | $params['userid'] = $matchuserid; | |
700 | } | |
701 | ||
702 | $lock = $this->createMock(\core\lock\lock::class); | |
703 | $cronlock = $this->createMock(\core\lock\lock::class); | |
704 | ||
705 | $tasks = $DB->get_recordset('task_adhoc', $params); | |
706 | foreach ($tasks as $record) { | |
707 | // Note: This is for cron only. | |
708 | // We do not lock the tasks. | |
709 | $task = \core\task\manager::adhoc_task_from_record($record); | |
710 | ||
711 | $user = null; | |
712 | if ($userid = $task->get_userid()) { | |
713 | // This task has a userid specified. | |
714 | $user = \core_user::get_user($userid); | |
715 | ||
716 | // User found. Check that they are suitable. | |
717 | \core_user::require_active_user($user, true, true); | |
718 | } | |
719 | ||
720 | $task->set_lock($lock); | |
721 | if (!$task->is_blocking()) { | |
722 | $cronlock->release(); | |
723 | } else { | |
724 | $task->set_cron_lock($cronlock); | |
725 | } | |
726 | ||
727 | cron_prepare_core_renderer(); | |
728 | $this->setUser($user); | |
729 | ||
730 | $task->execute(); | |
731 | \core\task\manager::adhoc_task_complete($task); | |
732 | ||
733 | unset($task); | |
734 | } | |
735 | $tasks->close(); | |
736 | } | |
7e7cfe7a | 737 | } |