MDL-67673 phpunit: Move tests to use new phpunit_dataset
[moodle.git] / lib / phpunit / classes / advanced_testcase.php
CommitLineData
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 35abstract 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}