Merge branch 'master_MDL-70520' of https://github.com/golenkovm/moodle
[moodle.git] / lib / tests / scheduled_task_test.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17 /**
18  * This file contains the unittests for scheduled tasks.
19  *
20  * @package   core
21  * @category  phpunit
22  * @copyright 2013 Damyon Wiese
23  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24  */
26 defined('MOODLE_INTERNAL') || die();
27 require_once(__DIR__ . '/fixtures/task_fixtures.php');
29 /**
30  * Test class for scheduled task.
31  *
32  * @package core
33  * @category task
34  * @copyright 2013 Damyon Wiese
35  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
36  */
37 class core_scheduled_task_testcase extends advanced_testcase {
39     /**
40      * Test the cron scheduling method
41      */
42     public function test_eval_cron_field() {
43         $testclass = new \core\task\scheduled_test_task();
45         $this->assertEquals(20, count($testclass->eval_cron_field('*/3', 0, 59)));
46         $this->assertEquals(31, count($testclass->eval_cron_field('1,*/2', 0, 59)));
47         $this->assertEquals(15, count($testclass->eval_cron_field('1-10,5-15', 0, 59)));
48         $this->assertEquals(13, count($testclass->eval_cron_field('1-10,5-15/2', 0, 59)));
49         $this->assertEquals(3, count($testclass->eval_cron_field('1,2,3,1,2,3', 0, 59)));
50         $this->assertEquals(1, count($testclass->eval_cron_field('-1,10,80', 0, 59)));
51     }
53     public function test_get_next_scheduled_time() {
54         global $CFG;
55         $this->resetAfterTest();
57         $this->setTimezone('Europe/London');
59         // Test job run at 1 am.
60         $testclass = new \core\task\scheduled_test_task();
62         // All fields default to '*'.
63         $testclass->set_hour('1');
64         $testclass->set_minute('0');
65         // Next valid time should be 1am of the next day.
66         $nexttime = $testclass->get_next_scheduled_time();
68         $oneamdate = new DateTime('now', new DateTimeZone('Europe/London'));
69         $oneamdate->setTime(1, 0, 0);
70         // Make it 1 am tomorrow if the time is after 1am.
71         if ($oneamdate->getTimestamp() < time()) {
72             $oneamdate->add(new DateInterval('P1D'));
73         }
74         $oneam = $oneamdate->getTimestamp();
76         $this->assertEquals($oneam, $nexttime, 'Next scheduled time is 1am.');
78         // Disabled flag does not affect next time.
79         $testclass->set_disabled(true);
80         $nexttime = $testclass->get_next_scheduled_time();
81         $this->assertEquals($oneam, $nexttime, 'Next scheduled time is 1am.');
83         // Now test for job run every 10 minutes.
84         $testclass = new \core\task\scheduled_test_task();
86         // All fields default to '*'.
87         $testclass->set_minute('*/10');
88         // Next valid time should be next 10 minute boundary.
89         $nexttime = $testclass->get_next_scheduled_time();
91         $minutes = ((intval(date('i') / 10))+1) * 10;
92         $nexttenminutes = mktime(date('H'), $minutes, 0);
94         $this->assertEquals($nexttenminutes, $nexttime, 'Next scheduled time is in 10 minutes.');
96         // Disabled flag does not affect next time.
97         $testclass->set_disabled(true);
98         $nexttime = $testclass->get_next_scheduled_time();
99         $this->assertEquals($nexttenminutes, $nexttime, 'Next scheduled time is in 10 minutes.');
101         // Test hourly job executed on Sundays only.
102         $testclass = new \core\task\scheduled_test_task();
103         $testclass->set_minute('0');
104         $testclass->set_day_of_week('7');
106         $nexttime = $testclass->get_next_scheduled_time();
108         $this->assertEquals(7, date('N', $nexttime));
109         $this->assertEquals(0, date('i', $nexttime));
111         // Test monthly job
112         $testclass = new \core\task\scheduled_test_task();
113         $testclass->set_minute('32');
114         $testclass->set_hour('0');
115         $testclass->set_day('1');
117         $nexttime = $testclass->get_next_scheduled_time();
119         $this->assertEquals(32, date('i', $nexttime));
120         $this->assertEquals(0, date('G', $nexttime));
121         $this->assertEquals(1, date('j', $nexttime));
122     }
124     public function test_timezones() {
125         global $CFG, $USER;
127         // The timezones used in this test are chosen because they do not use DST - that would break the test.
128         $this->resetAfterTest();
130         $this->setTimezone('Asia/Kabul');
132         $testclass = new \core\task\scheduled_test_task();
134         // Scheduled tasks should always use servertime - so this is 03:30 GMT.
135         $testclass->set_hour('1');
136         $testclass->set_minute('0');
138         // Next valid time should be 1am of the next day.
139         $nexttime = $testclass->get_next_scheduled_time();
141         // GMT+05:45.
142         $USER->timezone = 'Asia/Kathmandu';
143         $userdate = userdate($nexttime);
145         // Should be displayed in user timezone.
146         // I used http://www.timeanddate.com/worldclock/fixedtime.html?msg=Moodle+Test&iso=20160502T01&p1=113
147         // setting my location to Kathmandu to verify this time.
148         $this->assertStringContainsString('2:15 AM', core_text::strtoupper($userdate));
149     }
151     public function test_reset_scheduled_tasks_for_component_customised(): void {
152         $this->resetAfterTest(true);
154         $tasks = \core\task\manager::load_scheduled_tasks_for_component('moodle');
156         // Customise a task.
157         $task = reset($tasks);
158         $task->set_minute('1');
159         $task->set_hour('2');
160         $task->set_month('3');
161         $task->set_day_of_week('4');
162         $task->set_day('5');
163         $task->set_customised('1');
164         \core\task\manager::configure_scheduled_task($task);
166         // Now call reset.
167         \core\task\manager::reset_scheduled_tasks_for_component('moodle');
169         // Fetch the task again.
170         $taskafterreset = \core\task\manager::get_scheduled_task(get_class($task));
172         // The task should still be the same as the customised.
173         $this->assertTaskEquals($task, $taskafterreset);
174     }
176     public function test_reset_scheduled_tasks_for_component_deleted(): void {
177         global $DB;
178         $this->resetAfterTest(true);
180         // Delete a task to simulate the fact that its new.
181         $tasklist = \core\task\manager::load_scheduled_tasks_for_component('moodle');
183         // Note: This test must use a task which does not use any random values.
184         $task = \core\task\manager::get_scheduled_task(core\task\session_cleanup_task::class);
186         $DB->delete_records('task_scheduled', array('classname' => '\\' . trim(get_class($task), '\\')));
187         $this->assertFalse(\core\task\manager::get_scheduled_task(core\task\session_cleanup_task::class));
189         // Now call reset on all the tasks.
190         \core\task\manager::reset_scheduled_tasks_for_component('moodle');
192         // Assert that the second task was added back.
193         $taskafterreset = \core\task\manager::get_scheduled_task(core\task\session_cleanup_task::class);
194         $this->assertNotFalse($taskafterreset);
196         $this->assertTaskEquals($task, $taskafterreset);
197         $this->assertCount(count($tasklist), \core\task\manager::load_scheduled_tasks_for_component('moodle'));
198     }
200     public function test_reset_scheduled_tasks_for_component_changed_in_source(): void {
201         $this->resetAfterTest(true);
203         // Delete a task to simulate the fact that its new.
204         // Note: This test must use a task which does not use any random values.
205         $task = \core\task\manager::get_scheduled_task(core\task\session_cleanup_task::class);
207         // Get a copy of the task before maing changes for later comparison.
208         $taskbeforechange = \core\task\manager::get_scheduled_task(core\task\session_cleanup_task::class);
210         // Edit a task to simulate a change in its definition (as if it was not customised).
211         $task->set_minute('1');
212         $task->set_hour('2');
213         $task->set_month('3');
214         $task->set_day_of_week('4');
215         $task->set_day('5');
216         \core\task\manager::configure_scheduled_task($task);
218         // Fetch the task out for comparison.
219         $taskafterchange = \core\task\manager::get_scheduled_task(core\task\session_cleanup_task::class);
221         // The task should now be different to the original.
222         $this->assertTaskNotEquals($taskbeforechange, $taskafterchange);
224         // Now call reset.
225         \core\task\manager::reset_scheduled_tasks_for_component('moodle');
227         // Fetch the task again.
228         $taskafterreset = \core\task\manager::get_scheduled_task(core\task\session_cleanup_task::class);
230         // The task should now be the same as the original.
231         $this->assertTaskEquals($taskbeforechange, $taskafterreset);
232     }
234     /**
235      * Tests that the reset function deletes old tasks.
236      */
237     public function test_reset_scheduled_tasks_for_component_delete() {
238         global $DB;
239         $this->resetAfterTest(true);
241         $count = $DB->count_records('task_scheduled', array('component' => 'moodle'));
242         $allcount = $DB->count_records('task_scheduled');
244         $task = new \core\task\scheduled_test_task();
245         $task->set_component('moodle');
246         $record = \core\task\manager::record_from_scheduled_task($task);
247         $DB->insert_record('task_scheduled', $record);
248         $this->assertTrue($DB->record_exists('task_scheduled', array('classname' => '\core\task\scheduled_test_task',
249             'component' => 'moodle')));
251         $task = new \core\task\scheduled_test2_task();
252         $task->set_component('moodle');
253         $record = \core\task\manager::record_from_scheduled_task($task);
254         $DB->insert_record('task_scheduled', $record);
255         $this->assertTrue($DB->record_exists('task_scheduled', array('classname' => '\core\task\scheduled_test2_task',
256             'component' => 'moodle')));
258         $aftercount = $DB->count_records('task_scheduled', array('component' => 'moodle'));
259         $afterallcount = $DB->count_records('task_scheduled');
261         $this->assertEquals($count + 2, $aftercount);
262         $this->assertEquals($allcount + 2, $afterallcount);
264         // Now check that the right things were deleted.
265         \core\task\manager::reset_scheduled_tasks_for_component('moodle');
267         $this->assertEquals($count, $DB->count_records('task_scheduled', array('component' => 'moodle')));
268         $this->assertEquals($allcount, $DB->count_records('task_scheduled'));
269         $this->assertFalse($DB->record_exists('task_scheduled', array('classname' => '\core\task\scheduled_test2_task',
270             'component' => 'moodle')));
271         $this->assertFalse($DB->record_exists('task_scheduled', array('classname' => '\core\task\scheduled_test_task',
272             'component' => 'moodle')));
273     }
275     public function test_get_next_scheduled_task() {
276         global $DB;
278         $this->resetAfterTest(true);
279         // Delete all existing scheduled tasks.
280         $DB->delete_records('task_scheduled');
281         // Add a scheduled task.
283         // A task that runs once per hour.
284         $record = new stdClass();
285         $record->blocking = true;
286         $record->minute = '0';
287         $record->hour = '0';
288         $record->dayofweek = '*';
289         $record->day = '*';
290         $record->month = '*';
291         $record->component = 'test_scheduled_task';
292         $record->classname = '\core\task\scheduled_test_task';
294         $DB->insert_record('task_scheduled', $record);
295         // And another one to test failures.
296         $record->classname = '\core\task\scheduled_test2_task';
297         $DB->insert_record('task_scheduled', $record);
298         // And disabled test.
299         $record->classname = '\core\task\scheduled_test3_task';
300         $record->disabled = 1;
301         $DB->insert_record('task_scheduled', $record);
303         $now = time();
305         // Should get handed the first task.
306         $task = \core\task\manager::get_next_scheduled_task($now);
307         $this->assertInstanceOf('\core\task\scheduled_test_task', $task);
308         $task->execute();
310         \core\task\manager::scheduled_task_complete($task);
311         // Should get handed the second task.
312         $task = \core\task\manager::get_next_scheduled_task($now);
313         $this->assertInstanceOf('\core\task\scheduled_test2_task', $task);
314         $task->execute();
316         \core\task\manager::scheduled_task_failed($task);
317         // Should not get any task.
318         $task = \core\task\manager::get_next_scheduled_task($now);
319         $this->assertNull($task);
321         // Should get the second task (retry after delay).
322         $task = \core\task\manager::get_next_scheduled_task($now + 120);
323         $this->assertInstanceOf('\core\task\scheduled_test2_task', $task);
324         $task->execute();
326         \core\task\manager::scheduled_task_complete($task);
328         // Should not get any task.
329         $task = \core\task\manager::get_next_scheduled_task($now);
330         $this->assertNull($task);
332         // Check ordering.
333         $DB->delete_records('task_scheduled');
334         $record->lastruntime = 2;
335         $record->disabled = 0;
336         $record->classname = '\core\task\scheduled_test_task';
337         $DB->insert_record('task_scheduled', $record);
339         $record->lastruntime = 1;
340         $record->classname = '\core\task\scheduled_test2_task';
341         $DB->insert_record('task_scheduled', $record);
343         // Should get handed the second task.
344         $task = \core\task\manager::get_next_scheduled_task($now);
345         $this->assertInstanceOf('\core\task\scheduled_test2_task', $task);
346         $task->execute();
347         \core\task\manager::scheduled_task_complete($task);
349         // Should get handed the first task.
350         $task = \core\task\manager::get_next_scheduled_task($now);
351         $this->assertInstanceOf('\core\task\scheduled_test_task', $task);
352         $task->execute();
353         \core\task\manager::scheduled_task_complete($task);
355         // Should not get any task.
356         $task = \core\task\manager::get_next_scheduled_task($now);
357         $this->assertNull($task);
358     }
360     public function test_get_broken_scheduled_task() {
361         global $DB;
363         $this->resetAfterTest(true);
364         // Delete all existing scheduled tasks.
365         $DB->delete_records('task_scheduled');
366         // Add a scheduled task.
368         // A broken task that runs all the time.
369         $record = new stdClass();
370         $record->blocking = true;
371         $record->minute = '*';
372         $record->hour = '*';
373         $record->dayofweek = '*';
374         $record->day = '*';
375         $record->month = '*';
376         $record->component = 'test_scheduled_task';
377         $record->classname = '\core\task\scheduled_test_task_broken';
379         $DB->insert_record('task_scheduled', $record);
381         $now = time();
382         // Should not get any task.
383         $task = \core\task\manager::get_next_scheduled_task($now);
384         $this->assertDebuggingCalled();
385         $this->assertNull($task);
386     }
388     /**
389      * Tests the use of 'R' syntax in time fields of tasks to get
390      * tasks be configured with a non-uniform time.
391      */
392     public function test_random_time_specification() {
394         // Testing non-deterministic things in a unit test is not really
395         // wise, so we just test the values have changed within allowed bounds.
396         $testclass = new \core\task\scheduled_test_task();
398         // The test task defaults to '*'.
399         $this->assertIsString($testclass->get_minute());
400         $this->assertIsString($testclass->get_hour());
402         // Set a random value.
403         $testclass->set_minute('R');
404         $testclass->set_hour('R');
405         $testclass->set_day_of_week('R');
407         // Verify the minute has changed within allowed bounds.
408         $minute = $testclass->get_minute();
409         $this->assertIsInt($minute);
410         $this->assertGreaterThanOrEqual(0, $minute);
411         $this->assertLessThanOrEqual(59, $minute);
413         // Verify the hour has changed within allowed bounds.
414         $hour = $testclass->get_hour();
415         $this->assertIsInt($hour);
416         $this->assertGreaterThanOrEqual(0, $hour);
417         $this->assertLessThanOrEqual(23, $hour);
419         // Verify the dayofweek has changed within allowed bounds.
420         $dayofweek = $testclass->get_day_of_week();
421         $this->assertIsInt($dayofweek);
422         $this->assertGreaterThanOrEqual(0, $dayofweek);
423         $this->assertLessThanOrEqual(6, $dayofweek);
424     }
426     /**
427      * Test that the file_temp_cleanup_task removes directories and
428      * files as expected.
429      */
430     public function test_file_temp_cleanup_task() {
431         global $CFG;
432         $backuptempdir = make_backup_temp_directory('');
434         // Create directories.
435         $dir = $backuptempdir . DIRECTORY_SEPARATOR . 'backup01' . DIRECTORY_SEPARATOR . 'courses';
436         mkdir($dir, 0777, true);
438         // Create files to be checked and then deleted.
439         $file01 = $dir . DIRECTORY_SEPARATOR . 'sections.xml';
440         file_put_contents($file01, 'test data 001');
441         $file02 = $dir . DIRECTORY_SEPARATOR . 'modules.xml';
442         file_put_contents($file02, 'test data 002');
443         // Change the time modified for the first file, to a time that will be deleted by the task (greater than seven days).
444         touch($file01, time() - (8 * 24 * 3600));
446         $task = \core\task\manager::get_scheduled_task('\\core\\task\\file_temp_cleanup_task');
447         $this->assertInstanceOf('\core\task\file_temp_cleanup_task', $task);
448         $task->execute();
450         // Scan the directory. Only modules.xml should be left.
451         $filesarray = scandir($dir);
452         $this->assertEquals('modules.xml', $filesarray[2]);
453         $this->assertEquals(3, count($filesarray));
455         // Change the time modified on modules.xml.
456         touch($file02, time() - (8 * 24 * 3600));
457         // Change the time modified on the courses directory.
458         touch($backuptempdir . DIRECTORY_SEPARATOR . 'backup01' . DIRECTORY_SEPARATOR .
459                 'courses', time() - (8 * 24 * 3600));
460         // Run the scheduled task to remove the file and directory.
461         $task->execute();
462         $filesarray = scandir($backuptempdir . DIRECTORY_SEPARATOR . 'backup01');
463         // There should only be two items in the array, '.' and '..'.
464         $this->assertEquals(2, count($filesarray));
466         // Change the time modified on all of the files and directories.
467         $dir = new \RecursiveDirectoryIterator($CFG->tempdir);
468         // Show all child nodes prior to their parent.
469         $iter = new \RecursiveIteratorIterator($dir, \RecursiveIteratorIterator::CHILD_FIRST);
471         for ($iter->rewind(); $iter->valid(); $iter->next()) {
472             if ($iter->isDir() && !$iter->isDot()) {
473                 $node = $iter->getRealPath();
474                 touch($node, time() - (8 * 24 * 3600));
475             }
476         }
478         // Run the scheduled task again to remove all of the files and directories.
479         $task->execute();
480         $filesarray = scandir($CFG->tempdir);
481         // All of the files and directories should be deleted.
482         // There should only be three items in the array, '.', '..' and '.htaccess'.
483         $this->assertEquals([ '.', '..', '.htaccess' ], $filesarray);
484     }
486     /**
487      * Test that the function to clear the fail delay from a task works correctly.
488      */
489     public function test_clear_fail_delay() {
491         $this->resetAfterTest();
493         // Get an example task to use for testing. Task is set to run every minute by default.
494         $taskname = '\core\task\send_new_user_passwords_task';
496         // Pretend task started running and then failed 3 times.
497         $before = time();
498         $cronlockfactory = \core\lock\lock_config::get_lock_factory('cron');
499         for ($i = 0; $i < 3; $i ++) {
500             $task = \core\task\manager::get_scheduled_task($taskname);
501             $lock = $cronlockfactory->get_lock('\\' . get_class($task), 10);
502             $task->set_lock($lock);
503             \core\task\manager::scheduled_task_failed($task);
504         }
506         // Confirm task is now delayed by several minutes.
507         $task = \core\task\manager::get_scheduled_task($taskname);
508         $this->assertEquals(240, $task->get_fail_delay());
509         $this->assertGreaterThan($before + 230, $task->get_next_run_time());
511         // Clear the fail delay and re-get the task.
512         \core\task\manager::clear_fail_delay($task);
513         $task = \core\task\manager::get_scheduled_task($taskname);
515         // There should be no delay and it should run within the next minute.
516         $this->assertEquals(0, $task->get_fail_delay());
517         $this->assertLessThan($before + 70, $task->get_next_run_time());
518     }
520     /**
521      * Data provider for test_tool_health_category_find_missing_parents.
522      */
523     public static function provider_schedule_overrides(): array {
524         return array(
525             array(
526                 'scheduled_tasks' => array(
527                     '\core\task\scheduled_test_task' => array(
528                         'schedule' => '10 13 1 2 4',
529                         'disabled' => 0,
530                     ),
531                     '\core\task\scheduled_test2_task' => array(
532                         'schedule' => '* * * * *',
533                         'disabled' => 1,
534                     ),
535                 ),
536                 'task_full_classnames' => array(
537                     '\core\task\scheduled_test_task',
538                     '\core\task\scheduled_test2_task',
539                 ),
540                 'expected' => array(
541                     '\core\task\scheduled_test_task' => array(
542                         'min'   => '10',
543                         'hour'  => '13',
544                         'day'   => '1',
545                         'week'  => '2',
546                         'month' => '4',
547                         'disabled' => 0,
548                     ),
549                     '\core\task\scheduled_test2_task' => array(
550                         'min'   => '*',
551                         'hour'  => '*',
552                         'day'   => '*',
553                         'week'  => '*',
554                         'month' => '*',
555                         'disabled' => 1,
556                     ),
557                 )
558             ),
559             array(
560                 'scheduled_tasks' => array(
561                     '\core\task\*' => array(
562                         'schedule' => '1 2 3 4 5',
563                         'disabled' => 0,
564                     )
565                 ),
566                 'task_full_classnames' => array(
567                     '\core\task\scheduled_test_task',
568                     '\core\task\scheduled_test2_task',
569                 ),
570                 'expected' => array(
571                     '\core\task\scheduled_test_task' => array(
572                         'min'   => '1',
573                         'hour'  => '2',
574                         'day'   => '3',
575                         'week'  => '4',
576                         'month' => '5',
577                         'disabled' => 0,
578                     ),
579                     '\core\task\scheduled_test2_task' => array(
580                         'min'   => '1',
581                         'hour'  => '2',
582                         'day'   => '3',
583                         'week'  => '4',
584                         'month' => '5',
585                         'disabled' => 0,
586                     ),
587                 )
588             )
589         );
590     }
593     /**
594      * Test to ensure scheduled tasks are updated by values set in config.
595      *
596      * @param array $overrides
597      * @param array $tasks
598      * @param array $expected
599      * @dataProvider provider_schedule_overrides
600      */
601     public function test_scheduled_task_override_values(array $overrides, array $tasks, array $expected): void {
602         global $CFG, $DB;
604         $this->resetAfterTest();
606         // Add overrides to the config.
607         $CFG->scheduled_tasks = $overrides;
609         // Set up test scheduled task record.
610         $record = new stdClass();
611         $record->component = 'test_scheduled_task';
613         foreach ($tasks as $task) {
614             $record->classname = $task;
615             $DB->insert_record('task_scheduled', $record);
617             $scheduledtask = \core\task\manager::get_scheduled_task($task);
618             $expectedresults = $expected[$task];
620             // Check that the task is actually overridden.
621             $this->assertTrue($scheduledtask->is_overridden(), 'Is overridden');
623             // Check minute is correct.
624             $this->assertEquals($expectedresults['min'], $scheduledtask->get_minute(), 'Minute check');
626             // Check day is correct.
627             $this->assertEquals($expectedresults['day'], $scheduledtask->get_day(), 'Day check');
629             // Check hour is correct.
630             $this->assertEquals($expectedresults['hour'], $scheduledtask->get_hour(), 'Hour check');
632             // Check week is correct.
633             $this->assertEquals($expectedresults['week'], $scheduledtask->get_day_of_week(), 'Day of week check');
635             // Check week is correct.
636             $this->assertEquals($expectedresults['month'], $scheduledtask->get_month(), 'Month check');
638             // Check to see if the task is disabled.
639             $this->assertEquals($expectedresults['disabled'], $scheduledtask->get_disabled(), 'Disabled check');
640         }
641     }
643     /**
644      * Check that an overridden task is sent to be processed.
645      */
646     public function test_scheduled_task_overridden_task_can_run(): void {
647         global $CFG, $DB;
649         $this->resetAfterTest();
651         // Delete all existing scheduled tasks.
652         $DB->delete_records('task_scheduled');
654         // Add overrides to the config.
655         $CFG->scheduled_tasks = [
656             '\core\task\scheduled_test_task' => [
657                 'disabled' => 1
658             ],
659             '\core\task\scheduled_test2_task' => [
660                 'disabled' => 0
661             ],
662         ];
664         // A task that runs once per hour.
665         $record = new stdClass();
666         $record->component = 'test_scheduled_task';
667         $record->classname = '\core\task\scheduled_test_task';
668         $record->disabled = 0;
669         $DB->insert_record('task_scheduled', $record);
671         // And disabled test.
672         $record->classname = '\core\task\scheduled_test2_task';
673         $record->disabled = 1;
674         $DB->insert_record('task_scheduled', $record);
676         $now = time();
678         $scheduledtask = \core\task\manager::get_next_scheduled_task($now);
679         $this->assertInstanceOf('\core\task\scheduled_test2_task', $scheduledtask);
680         $scheduledtask->execute();
681         \core\task\manager::scheduled_task_complete($scheduledtask);
682     }
684     /**
685      * Assert that the specified tasks are equal.
686      *
687      * @param   \core\task\task_base $task
688      * @param   \core\task\task_base $comparisontask
689      */
690     public function assertTaskEquals(\core\task\task_base $task, \core\task\task_base $comparisontask): void {
691         // Convert both to an object.
692         $task = \core\task\manager::record_from_scheduled_task($task);
693         $comparisontask = \core\task\manager::record_from_scheduled_task($comparisontask);
695         // Reset the nextruntime field as it is intentionally dynamic.
696         $task->nextruntime = null;
697         $comparisontask->nextruntime = null;
699         $args = array_merge(
700             [
701                 $task,
702                 $comparisontask,
703             ],
704             array_slice(func_get_args(), 2)
705         );
707         call_user_func_array([$this, 'assertEquals'], $args);
708     }
710     /**
711      * Assert that the specified tasks are not equal.
712      *
713      * @param   \core\task\task_base $task
714      * @param   \core\task\task_base $comparisontask
715      */
716     public function assertTaskNotEquals(\core\task\task_base $task, \core\task\task_base $comparisontask): void {
717         // Convert both to an object.
718         $task = \core\task\manager::record_from_scheduled_task($task);
719         $comparisontask = \core\task\manager::record_from_scheduled_task($comparisontask);
721         // Reset the nextruntime field as it is intentionally dynamic.
722         $task->nextruntime = null;
723         $comparisontask->nextruntime = null;
725         $args = array_merge(
726             [
727                 $task,
728                 $comparisontask,
729             ],
730             array_slice(func_get_args(), 2)
731         );
733         call_user_func_array([$this, 'assertNotEquals'], $args);
734     }
736     /**
737      * Assert that the lastruntime column holds an original value after a scheduled task is reset.
738      */
739     public function test_reset_scheduled_tasks_for_component_keeps_original_lastruntime(): void {
740         global $DB;
741         $this->resetAfterTest(true);
743         // Set lastruntime for the scheduled task.
744         $DB->set_field('task_scheduled', 'lastruntime', 123456789, ['classname' => '\core\task\session_cleanup_task']);
746         // Reset the task.
747         \core\task\manager::reset_scheduled_tasks_for_component('moodle');
749         // Fetch the task again.
750         $taskafterreset = \core\task\manager::get_scheduled_task(core\task\session_cleanup_task::class);
752         // Confirm, that lastruntime is still in place.
753         $this->assertEquals(123456789, $taskafterreset->get_last_run_time());
754     }