Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.

namespace core\task;

defined('MOODLE_INTERNAL') || die();
require_once(__DIR__ . '/../fixtures/task_fixtures.php');

/**
 * Test class for scheduled task.
 *
 * @package core
 * @category test
 * @copyright 2013 Damyon Wiese
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
> * @coversDefaultClass \core\task\scheduled_task
*/ class scheduled_task_test extends \advanced_testcase { /**
> * Data provider for {@see test_eval_cron_field} * Test the cron scheduling method > * */ > * @return array public function test_eval_cron_field() { > */ $testclass = new scheduled_test_task(); > public static function eval_cron_provider(): array { > return [ $this->assertEquals(20, count($testclass->eval_cron_field('*/3', 0, 59))); > // At every 3rd <unit>. $this->assertEquals(31, count($testclass->eval_cron_field('1,*/2', 0, 59))); > ['*/3', 0, 29, [0, 3, 6, 9, 12, 15, 18, 21, 24, 27]], $this->assertEquals(15, count($testclass->eval_cron_field('1-10,5-15', 0, 59))); > // At <unit> 1 and every 2nd <unit>. $this->assertEquals(13, count($testclass->eval_cron_field('1-10,5-15/2', 0, 59))); > ['1,*/2', 0, 29, [0, 1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28]], $this->assertEquals(3, count($testclass->eval_cron_field('1,2,3,1,2,3', 0, 59))); > // At every <unit> from 1 through 10 and every <unit> from 5 through 15. $this->assertEquals(0, count($testclass->eval_cron_field('-1,10,80', 0, 59))); > ['1-10,5-15', 0, 29, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]], $this->assertEquals(0, count($testclass->eval_cron_field('-1', 0, 59))); > // At every <unit> from 1 through 10 and every 2nd <unit> from 5 through 15. } > ['1-10,5-15/2', 0, 29, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15]], > // At every <unit> from 1 through 10 and every 2nd <unit> from 5 through 29. public function test_get_next_scheduled_time() { > ['1-10,5/2', 0, 29, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29]], global $CFG; > // At <unit> 1, 2, 3. $this->resetAfterTest(); > ['1,2,3,1,2,3', 0, 29, [1, 2, 3]], > // Invalid. $this->setTimezone('Europe/London'); > ['-1,10,80', 0, 29, []], > // Invalid. // Let's specify the hour we are going to use initially for the test. > ['-1', 0, 29, []], // (note that we pick 01:00 that is tricky for Europe/London, because > ]; // it's exactly the Daylight Saving Time Begins hour. > } $testhour = 1; > > /**
// Test job run at 1 am.
> * $testclass = new scheduled_test_task(); > * @param string $field > * @param int $min // All fields default to '*'. > * @param int $max $testclass->set_hour($testhour); > * @param int[] $expected $testclass->set_minute('0'); > * // Next valid time should be 1am of the next day. > * @dataProvider eval_cron_provider $nexttime = $testclass->get_next_scheduled_time(); > * > * @covers ::eval_cron_field
< public function test_eval_cron_field() {
> public function test_eval_cron_field(string $field, int $min, int $max, array $expected): void {
< $this->assertEquals(20, count($testclass->eval_cron_field('*/3', 0, 59))); < $this->assertEquals(31, count($testclass->eval_cron_field('1,*/2', 0, 59))); < $this->assertEquals(15, count($testclass->eval_cron_field('1-10,5-15', 0, 59))); < $this->assertEquals(13, count($testclass->eval_cron_field('1-10,5-15/2', 0, 59))); < $this->assertEquals(3, count($testclass->eval_cron_field('1,2,3,1,2,3', 0, 59))); < $this->assertEquals(0, count($testclass->eval_cron_field('-1,10,80', 0, 59))); < $this->assertEquals(0, count($testclass->eval_cron_field('-1', 0, 59)));
> $this->assertEquals($expected, $testclass->eval_cron_field($field, $min, $max));
$isdaylightsaving = false; if ($testhour < (int)$oneamdate->format('H')) { $isdaylightsaving = true; } // Make it 1 am tomorrow if the time is after 1am. if ($oneamdate->getTimestamp() < time()) { $oneamdate->add(new \DateInterval('P1D')); if ($isdaylightsaving) { // If today is Europe/London Daylight Saving Time Begins, expectation is 1 less hour. $oneamdate->sub(new \DateInterval('PT1H')); } } $oneam = $oneamdate->getTimestamp(); $this->assertEquals($oneam, $nexttime, 'Next scheduled time is 1am.'); // Disabled flag does not affect next time. $testclass->set_disabled(true); $nexttime = $testclass->get_next_scheduled_time(); $this->assertEquals($oneam, $nexttime, 'Next scheduled time is 1am.'); // Now test for job run every 10 minutes. $testclass = new scheduled_test_task(); // All fields default to '*'. $testclass->set_minute('*/10'); // Next valid time should be next 10 minute boundary. $nexttime = $testclass->get_next_scheduled_time(); $minutes = ((intval(date('i') / 10)) + 1) * 10; $nexttenminutes = mktime(date('H'), $minutes, 0); $this->assertEquals($nexttenminutes, $nexttime, 'Next scheduled time is in 10 minutes.'); // Disabled flag does not affect next time. $testclass->set_disabled(true); $nexttime = $testclass->get_next_scheduled_time(); $this->assertEquals($nexttenminutes, $nexttime, 'Next scheduled time is in 10 minutes.'); // Test hourly job executed on Sundays only. $testclass = new scheduled_test_task(); $testclass->set_minute('0'); $testclass->set_day_of_week('7'); $nexttime = $testclass->get_next_scheduled_time(); $this->assertEquals(7, date('N', $nexttime)); $this->assertEquals(0, date('i', $nexttime)); // Test monthly job. $testclass = new scheduled_test_task(); $testclass->set_minute('32'); $testclass->set_hour('0'); $testclass->set_day('1'); $nexttime = $testclass->get_next_scheduled_time(); $this->assertEquals(32, date('i', $nexttime)); $this->assertEquals(0, date('G', $nexttime)); $this->assertEquals(1, date('j', $nexttime));
> } } > > /** public function test_timezones() { > * Data provider for get_next_scheduled_time_detail. global $CFG, $USER; > * > * Note all times in here are in default Australia/Perth time zone. // The timezones used in this test are chosen because they do not use DST - that would break the test. > * $this->resetAfterTest(); > * @return array[] Function parameters for each run > */ $this->setTimezone('Asia/Kabul'); > public static function get_next_scheduled_time_detail_provider(): array { > return [ $testclass = new scheduled_test_task(); > // Every minute = next minute. > ['2023-11-01 15:15', '*', '*', '*', '*', '*', '2023-11-01 15:16'], // Scheduled tasks should always use servertime - so this is 03:30 GMT. > // Specified minute (coming up) = same hour, that minute. $testclass->set_hour('1'); > ['2023-11-01 15:15', '18', '*', '*', '*', '*', '2023-11-01 15:18'], $testclass->set_minute('0'); > // Specified minute (passed) = next hour, that minute. > ['2023-11-01 15:15', '11', '*', '*', '*', '*', '2023-11-01 16:11'], // Next valid time should be 1am of the next day. > // Range of minutes = same hour, next matching value. $nexttime = $testclass->get_next_scheduled_time(); > ['2023-11-01 15:15', '*/15', '*', '*', '*', '*', '2023-11-01 15:30'], > // Specified hour, any minute = first minute that hour. // GMT+05:45. > ['2023-11-01 15:15', '*', '20', '*', '*', '*', '2023-11-01 20:00'], $USER->timezone = 'Asia/Kathmandu'; > // Specified hour, specified minute = that time. $userdate = userdate($nexttime); > ['2023-11-01 15:15', '13', '20', '*', '*', '*', '2023-11-01 20:13'], > // Any minute, range of hours = next hour in range, 00:00. // Should be displayed in user timezone. > ['2023-11-01 15:15', '*', '*/6', '*', '*', '*', '2023-11-01 18:00'], // I used http://www.timeanddate.com/worldclock/fixedtime.html?msg=Moodle+Test&iso=20160502T01&p1=113 > // Specified minute, range of hours = next hour where minute not passed, that minute. // setting my location to Kathmandu to verify this time. > ['2023-11-01 18:15', '10', '*/6', '*', '*', '*', '2023-11-02 00:10'], $this->assertStringContainsString('2:15 AM', \core_text::strtoupper($userdate)); > // Specified day, any hour/minute. } > ['2023-11-01 15:15', '*', '*', '3', '*', '*', '2023-11-03 00:00'], > // Specified day (next month), any hour/minute. public function test_reset_scheduled_tasks_for_component_customised(): void { > ['2023-11-05 15:15', '*', '*', '3', '*', '*', '2023-12-03 00:00'], $this->resetAfterTest(true); > // Specified day, specified hour. > ['2023-11-01 15:15', '*', '17', '3', '*', '*', '2023-11-03 17:00'], $tasks = manager::load_scheduled_tasks_for_component('moodle'); > // Specified day, specified minute. > ['2023-11-01 15:15', '17', '*', '3', '*', '*', '2023-11-03 00:17'], // Customise a task. > // 30th of every month, February. $task = reset($tasks); > ['2023-01-31 15:15', '15', '10', '30', '*', '*', '2023-03-30 10:15'], $task->set_minute('1'); > // Friday, any time. 2023-11-01 is a Wednesday, so it will run in 2 days. $task->set_hour('2'); > ['2023-11-01 15:15', '*', '*', '*', '5', '*', '2023-11-03 00:00'], $task->set_day('3'); > // Friday, any time (but it's already Friday). $task->set_month('4'); > ['2023-11-03 15:15', '*', '*', '*', '5', '*', '2023-11-03 15:16'], $task->set_day_of_week('5'); > // Sunday (week rollover). $task->set_customised('1'); > ['2023-11-01 15:15', '*', '*', '*', '0', '*', '2023-11-05 00:00'], manager::configure_scheduled_task($task); > // Specified days and day of week (days come first). > ['2023-11-01 15:15', '*', '*', '2,4,6', '5', '*', '2023-11-02 00:00'], // Now call reset. > // Specified days and day of week (day of week comes first). manager::reset_scheduled_tasks_for_component('moodle'); > ['2023-11-01 15:15', '*', '*', '4,6,8', '5', '*', '2023-11-03 00:00'], > // Specified months. // Fetch the task again. > ['2023-11-01 15:15', '*', '*', '*', '*', '6,8,10,12', '2023-12-01 00:00'], $taskafterreset = manager::get_scheduled_task(get_class($task)); > // Specified months (crossing year). > ['2023-11-01 15:15', '*', '*', '*', '*', '6,8,10', '2024-06-01 00:00'], // The task should still be the same as the customised. > // Specified months and day of week (i.e. first Sunday in December). $this->assertTaskEquals($task, $taskafterreset); > ['2023-11-01 15:15', '*', '*', '*', '0', '6,8,10,12', '2023-12-03 00:00'], } > // It's already December, but the next Friday is not until next month. > ['2023-12-30 15:15', '*', '*', '*', '5', '6,8,10,12', '2024-06-07 00:00'], public function test_reset_scheduled_tasks_for_component_deleted(): void { > // Around end of year. global $DB; > ['2023-12-31 23:00', '10', '3', '*', '*', '*', '2024-01-01 03:10'], $this->resetAfterTest(true); > // Some impossible requirements... > ['2023-12-31 23:00', '*', '*', '30', '*', '2', scheduled_task::NEVER_RUN_TIME], // Delete a task to simulate the fact that its new. > ['2023-12-31 23:00', '*', '*', '31', '*', '9,4,6,11', scheduled_task::NEVER_RUN_TIME], $tasklist = manager::load_scheduled_tasks_for_component('moodle'); > // Normal years and leap years. > ['2021-01-01 23:00', '*', '*', '28', '*', '2', '2021-02-28 00:00'], // Note: This test must use a task which does not use any random values. > ['2021-01-01 23:00', '*', '*', '29', '*', '2', '2024-02-29 00:00'], $task = manager::get_scheduled_task(session_cleanup_task::class); > // Missing leap year over century. Longest possible gap between runs. > ['2096-03-01 00:00', '59', '23', '29', '*', '2', '2104-02-29 23:59'], $DB->delete_records('task_scheduled', array('classname' => '\\' . trim(get_class($task), '\\'))); > ]; $this->assertFalse(manager::get_scheduled_task(session_cleanup_task::class)); > } > // Now call reset on all the tasks. > /** manager::reset_scheduled_tasks_for_component('moodle'); > * Tests get_next_scheduled_time using a large number of example scenarios. > * // Assert that the second task was added back. > * @param string $now Current time (strtotime format) $taskafterreset = manager::get_scheduled_task(session_cleanup_task::class); > * @param string $minute Minute restriction list for task $this->assertNotFalse($taskafterreset); > * @param string $hour Hour restriction list for task > * @param string $day Day restriction list for task $this->assertTaskEquals($task, $taskafterreset); > * @param string $dayofweek Day of week restriction list for task $this->assertCount(count($tasklist), manager::load_scheduled_tasks_for_component('moodle')); > * @param string $month Month restriction list for task } > * @param string|int $expected Expected run time (strtotime format or time int) > * @dataProvider get_next_scheduled_time_detail_provider public function test_reset_scheduled_tasks_for_component_changed_in_source(): void { > * @covers ::get_next_scheduled_time $this->resetAfterTest(true); > */ > public function test_get_next_scheduled_time_detail(string $now, string $minute, string $hour, // Delete a task to simulate the fact that its new. > string $day, string $dayofweek, string $month, $expected): void { // Note: This test must use a task which does not use any random values. > // Create test task with specified times. $task = manager::get_scheduled_task(session_cleanup_task::class); > $task = new scheduled_test_task(); > $task->set_minute($minute); // Get a copy of the task before maing changes for later comparison. > $task->set_hour($hour); $taskbeforechange = manager::get_scheduled_task(session_cleanup_task::class); > $task->set_day($day); > $task->set_day_of_week($dayofweek); // Edit a task to simulate a change in its definition (as if it was not customised). > $task->set_month($month); $task->set_minute('1'); > $task->set_hour('2'); > // Check function results. $task->set_day('3'); > $nowtime = strtotime($now); $task->set_month('4'); > if (is_int($expected)) { $task->set_day_of_week('5'); > $expectedtime = $expected; manager::configure_scheduled_task($task); > } else { > $expectedtime = strtotime($expected); // Fetch the task out for comparison. > } $taskafterchange = manager::get_scheduled_task(session_cleanup_task::class); > $actualtime = $task->get_next_scheduled_time($nowtime); > $this->assertEquals($expectedtime, $actualtime, 'Expected ' . $expected . ', actual ' . date('Y-m-d H:i', $actualtime)); // The task should now be different to the original. > } $this->assertTaskNotEquals($taskbeforechange, $taskafterchange); > > /** // Now call reset. > * Tests get_next_scheduled_time around DST changes, with regard to the continuity of frequent manager::reset_scheduled_tasks_for_component('moodle'); > * tasks. > * // Fetch the task again. > * We want frequent tasks to keep progressing as normal and not randomly stop for an hour, or $taskafterreset = manager::get_scheduled_task(session_cleanup_task::class); > * suddenly decide they need to happen in the past. > * // The task should now be the same as the original. > * @covers ::get_next_scheduled_time $this->assertTaskEquals($taskbeforechange, $taskafterreset); > */ } > public function test_get_next_scheduled_time_dst_continuity(): void { > $this->resetAfterTest(); /** > $this->setTimezone('Europe/London'); * Tests that the reset function deletes old tasks. > */ > // Test task is set to run every 20 minutes (:00, :20, :40). public function test_reset_scheduled_tasks_for_component_delete() { > $task = new scheduled_test_task(); global $DB; > $task->set_minute('*/20'); $this->resetAfterTest(true); > > // DST change forwards. Check times in GMT to ensure it progresses as normal. $count = $DB->count_records('task_scheduled', array('component' => 'moodle')); > $before = strtotime('2023-03-26 00:59 GMT'); $allcount = $DB->count_records('task_scheduled'); > $this->assertEquals(strtotime('2023-03-26 00:59 Europe/London'), $before); > $one = $task->get_next_scheduled_time($before); $task = new scheduled_test_task(); > $this->assertEquals(strtotime('2023-03-26 01:00 GMT'), $one); $task->set_component('moodle'); > $this->assertEquals(strtotime('2023-03-26 02:00 Europe/London'), $one); $record = manager::record_from_scheduled_task($task); > $two = $task->get_next_scheduled_time($one); $DB->insert_record('task_scheduled', $record); > $this->assertEquals(strtotime('2023-03-26 01:20 GMT'), $two); $this->assertTrue($DB->record_exists('task_scheduled', array('classname' => '\core\task\scheduled_test_task', > $three = $task->get_next_scheduled_time($two); 'component' => 'moodle'))); > $this->assertEquals(strtotime('2023-03-26 01:40 GMT'), $three); > $four = $task->get_next_scheduled_time($three); $task = new scheduled_test2_task(); > $this->assertEquals(strtotime('2023-03-26 02:00 GMT'), $four); $task->set_component('moodle'); > $record = manager::record_from_scheduled_task($task); > // DST change backwards. $DB->insert_record('task_scheduled', $record); > $before = strtotime('2023-10-29 00:59 GMT'); $this->assertTrue($DB->record_exists('task_scheduled', array('classname' => '\core\task\scheduled_test2_task', > // The 'before' time is 01:59 Europe/London, but we won't explicitly test that because 'component' => 'moodle'))); > // there are two 01:59s so it might fail depending on implementation. > $one = $task->get_next_scheduled_time($before); $aftercount = $DB->count_records('task_scheduled', array('component' => 'moodle')); > $this->assertEquals(strtotime('2023-10-29 01:00 GMT'), $one); $afterallcount = $DB->count_records('task_scheduled'); > // We cannot compare against the Eerope/London time (01:00) because there are two 01:00s. > $two = $task->get_next_scheduled_time($one); $this->assertEquals($count + 2, $aftercount); > $this->assertEquals(strtotime('2023-10-29 01:20 GMT'), $two); $this->assertEquals($allcount + 2, $afterallcount); > $three = $task->get_next_scheduled_time($two); > $this->assertEquals(strtotime('2023-10-29 01:40 GMT'), $three); // Now check that the right things were deleted. > $four = $task->get_next_scheduled_time($three); manager::reset_scheduled_tasks_for_component('moodle'); > $this->assertEquals(strtotime('2023-10-29 02:00 GMT'), $four); > // This time is now unambiguous in Europe/London. $this->assertEquals($count, $DB->count_records('task_scheduled', array('component' => 'moodle'))); > $this->assertEquals(strtotime('2023-10-29 02:00 Europe/London'), $four);
$this->assertEquals($allcount, $DB->count_records('task_scheduled')); $this->assertFalse($DB->record_exists('task_scheduled', array('classname' => '\core\task\scheduled_test2_task', 'component' => 'moodle'))); $this->assertFalse($DB->record_exists('task_scheduled', array('classname' => '\core\task\scheduled_test_task', 'component' => 'moodle'))); } public function test_get_next_scheduled_task() { global $DB; $this->resetAfterTest(true); // Delete all existing scheduled tasks. $DB->delete_records('task_scheduled'); // Add a scheduled task. // A task that runs once per hour. $record = new \stdClass(); $record->blocking = true; $record->minute = '0'; $record->hour = '0'; $record->dayofweek = '*'; $record->day = '*'; $record->month = '*'; $record->component = 'test_scheduled_task'; $record->classname = '\core\task\scheduled_test_task'; $DB->insert_record('task_scheduled', $record); // And another one to test failures. $record->classname = '\core\task\scheduled_test2_task'; $DB->insert_record('task_scheduled', $record); // And disabled test. $record->classname = '\core\task\scheduled_test3_task'; $record->disabled = 1; $DB->insert_record('task_scheduled', $record); $now = time(); // Should get handed the first task. $task = manager::get_next_scheduled_task($now); $this->assertInstanceOf('\core\task\scheduled_test_task', $task); $task->execute(); manager::scheduled_task_complete($task); // Should get handed the second task. $task = manager::get_next_scheduled_task($now); $this->assertInstanceOf('\core\task\scheduled_test2_task', $task); $task->execute(); manager::scheduled_task_failed($task); // Should not get any task. $task = manager::get_next_scheduled_task($now); $this->assertNull($task); // Should get the second task (retry after delay). $task = manager::get_next_scheduled_task($now + 120); $this->assertInstanceOf('\core\task\scheduled_test2_task', $task); $task->execute(); manager::scheduled_task_complete($task); // Should not get any task. $task = manager::get_next_scheduled_task($now); $this->assertNull($task); // Check ordering. $DB->delete_records('task_scheduled'); $record->lastruntime = 2; $record->disabled = 0; $record->classname = '\core\task\scheduled_test_task'; $DB->insert_record('task_scheduled', $record); $record->lastruntime = 1; $record->classname = '\core\task\scheduled_test2_task'; $DB->insert_record('task_scheduled', $record); // Should get handed the second task. $task = manager::get_next_scheduled_task($now); $this->assertInstanceOf('\core\task\scheduled_test2_task', $task); $task->execute(); manager::scheduled_task_complete($task); // Should get handed the first task. $task = manager::get_next_scheduled_task($now); $this->assertInstanceOf('\core\task\scheduled_test_task', $task); $task->execute(); manager::scheduled_task_complete($task); // Should not get any task. $task = manager::get_next_scheduled_task($now); $this->assertNull($task); } public function test_get_broken_scheduled_task() { global $DB; $this->resetAfterTest(true); // Delete all existing scheduled tasks. $DB->delete_records('task_scheduled'); // Add a scheduled task. // A broken task that runs all the time. $record = new \stdClass(); $record->blocking = true; $record->minute = '*'; $record->hour = '*'; $record->dayofweek = '*'; $record->day = '*'; $record->month = '*'; $record->component = 'test_scheduled_task'; $record->classname = '\core\task\scheduled_test_task_broken'; $DB->insert_record('task_scheduled', $record); $now = time(); // Should not get any task. $task = manager::get_next_scheduled_task($now); $this->assertDebuggingCalled(); $this->assertNull($task); } /** * Tests the use of 'R' syntax in time fields of tasks to get * tasks be configured with a non-uniform time. */ public function test_random_time_specification() { // Testing non-deterministic things in a unit test is not really // wise, so we just test the values have changed within allowed bounds. $testclass = new scheduled_test_task(); // The test task defaults to '*'. $this->assertIsString($testclass->get_minute()); $this->assertIsString($testclass->get_hour()); // Set a random value. $testclass->set_minute('R'); $testclass->set_hour('R'); $testclass->set_day_of_week('R'); // Verify the minute has changed within allowed bounds. $minute = $testclass->get_minute(); $this->assertIsInt($minute); $this->assertGreaterThanOrEqual(0, $minute); $this->assertLessThanOrEqual(59, $minute); // Verify the hour has changed within allowed bounds. $hour = $testclass->get_hour(); $this->assertIsInt($hour); $this->assertGreaterThanOrEqual(0, $hour); $this->assertLessThanOrEqual(23, $hour); // Verify the dayofweek has changed within allowed bounds. $dayofweek = $testclass->get_day_of_week(); $this->assertIsInt($dayofweek); $this->assertGreaterThanOrEqual(0, $dayofweek); $this->assertLessThanOrEqual(6, $dayofweek); } /** * Test that the file_temp_cleanup_task removes directories and * files as expected. */ public function test_file_temp_cleanup_task() { global $CFG; $backuptempdir = make_backup_temp_directory(''); // Create directories. $dir = $backuptempdir . DIRECTORY_SEPARATOR . 'backup01' . DIRECTORY_SEPARATOR . 'courses'; mkdir($dir, 0777, true); // Create files to be checked and then deleted. $file01 = $dir . DIRECTORY_SEPARATOR . 'sections.xml'; file_put_contents($file01, 'test data 001'); $file02 = $dir . DIRECTORY_SEPARATOR . 'modules.xml'; file_put_contents($file02, 'test data 002'); // Change the time modified for the first file, to a time that will be deleted by the task (greater than seven days). touch($file01, time() - (8 * 24 * 3600)); $task = manager::get_scheduled_task('\\core\\task\\file_temp_cleanup_task'); $this->assertInstanceOf('\core\task\file_temp_cleanup_task', $task); $task->execute(); // Scan the directory. Only modules.xml should be left. $filesarray = scandir($dir); $this->assertEquals('modules.xml', $filesarray[2]); $this->assertEquals(3, count($filesarray)); // Change the time modified on modules.xml. touch($file02, time() - (8 * 24 * 3600)); // Change the time modified on the courses directory. touch($backuptempdir . DIRECTORY_SEPARATOR . 'backup01' . DIRECTORY_SEPARATOR . 'courses', time() - (8 * 24 * 3600)); // Run the scheduled task to remove the file and directory. $task->execute(); $filesarray = scandir($backuptempdir . DIRECTORY_SEPARATOR . 'backup01'); // There should only be two items in the array, '.' and '..'. $this->assertEquals(2, count($filesarray)); // Change the time modified on all of the files and directories. $dir = new \RecursiveDirectoryIterator($CFG->tempdir); // Show all child nodes prior to their parent. $iter = new \RecursiveIteratorIterator($dir, \RecursiveIteratorIterator::CHILD_FIRST); for ($iter->rewind(); $iter->valid(); $iter->next()) { if ($iter->isDir() && !$iter->isDot()) { $node = $iter->getRealPath(); touch($node, time() - (8 * 24 * 3600)); } } // Run the scheduled task again to remove all of the files and directories. $task->execute(); $filesarray = scandir($CFG->tempdir); // All of the files and directories should be deleted. // There should only be three items in the array, '.', '..' and '.htaccess'. $this->assertEquals([ '.', '..', '.htaccess' ], $filesarray); } /** * Test that the function to clear the fail delay from a task works correctly. */ public function test_clear_fail_delay() { $this->resetAfterTest(); // Get an example task to use for testing. Task is set to run every minute by default. $taskname = '\core\task\send_new_user_passwords_task'; // Pretend task started running and then failed 3 times. $before = time(); $cronlockfactory = \core\lock\lock_config::get_lock_factory('cron'); for ($i = 0; $i < 3; $i ++) { $task = manager::get_scheduled_task($taskname); $lock = $cronlockfactory->get_lock('\\' . get_class($task), 10); $task->set_lock($lock); manager::scheduled_task_failed($task); } // Confirm task is now delayed by several minutes. $task = manager::get_scheduled_task($taskname); $this->assertEquals(240, $task->get_fail_delay()); $this->assertGreaterThan($before + 230, $task->get_next_run_time()); // Clear the fail delay and re-get the task. manager::clear_fail_delay($task); $task = manager::get_scheduled_task($taskname); // There should be no delay and it should run within the next minute. $this->assertEquals(0, $task->get_fail_delay()); $this->assertLessThan($before + 70, $task->get_next_run_time()); } /** * Data provider for test_scheduled_task_override_values. */ public static function provider_schedule_overrides(): array { return array( array( 'scheduled_tasks' => array( '\core\task\scheduled_test_task' => array( 'schedule' => '10 13 1 2 4', 'disabled' => 0, ), '\core\task\scheduled_test2_task' => array( 'schedule' => '* * * * *', 'disabled' => 1, ), ), 'task_full_classnames' => array( '\core\task\scheduled_test_task', '\core\task\scheduled_test2_task', ), 'expected' => array( '\core\task\scheduled_test_task' => array( 'min' => '10', 'hour' => '13', 'day' => '1', 'month' => '2', 'week' => '4', 'disabled' => 0, ), '\core\task\scheduled_test2_task' => array( 'min' => '*', 'hour' => '*', 'day' => '*', 'month' => '*', 'week' => '*', 'disabled' => 1, ), ) ), array( 'scheduled_tasks' => array( '\core\task\*' => array( 'schedule' => '1 2 3 4 5', 'disabled' => 0, ) ), 'task_full_classnames' => array( '\core\task\scheduled_test_task', '\core\task\scheduled_test2_task', ), 'expected' => array( '\core\task\scheduled_test_task' => array( 'min' => '1', 'hour' => '2', 'day' => '3', 'month' => '4', 'week' => '5', 'disabled' => 0, ), '\core\task\scheduled_test2_task' => array( 'min' => '1', 'hour' => '2', 'day' => '3', 'month' => '4', 'week' => '5', 'disabled' => 0, ), ) ) ); } /** * Test to ensure scheduled tasks are updated by values set in config. * * @param array $overrides * @param array $tasks * @param array $expected * @dataProvider provider_schedule_overrides */ public function test_scheduled_task_override_values(array $overrides, array $tasks, array $expected): void { global $CFG, $DB; $this->resetAfterTest(); // Add overrides to the config. $CFG->scheduled_tasks = $overrides; // Set up test scheduled task record. $record = new \stdClass(); $record->component = 'test_scheduled_task'; foreach ($tasks as $task) { $record->classname = $task; $DB->insert_record('task_scheduled', $record); $scheduledtask = manager::get_scheduled_task($task); $expectedresults = $expected[$task]; // Check that the task is actually overridden. $this->assertTrue($scheduledtask->is_overridden(), 'Is overridden'); // Check minute is correct. $this->assertEquals($expectedresults['min'], $scheduledtask->get_minute(), 'Minute check'); // Check day is correct. $this->assertEquals($expectedresults['day'], $scheduledtask->get_day(), 'Day check'); // Check hour is correct. $this->assertEquals($expectedresults['hour'], $scheduledtask->get_hour(), 'Hour check'); // Check week is correct. $this->assertEquals($expectedresults['week'], $scheduledtask->get_day_of_week(), 'Day of week check'); // Check week is correct. $this->assertEquals($expectedresults['month'], $scheduledtask->get_month(), 'Month check'); // Check to see if the task is disabled. $this->assertEquals($expectedresults['disabled'], $scheduledtask->get_disabled(), 'Disabled check'); } } /** * Check that an overridden task is sent to be processed. */ public function test_scheduled_task_overridden_task_can_run(): void { global $CFG, $DB; $this->resetAfterTest(); // Delete all existing scheduled tasks. $DB->delete_records('task_scheduled'); // Add overrides to the config. $CFG->scheduled_tasks = [ '\core\task\scheduled_test_task' => [ 'disabled' => 1 ], '\core\task\scheduled_test2_task' => [ 'disabled' => 0 ], ]; // A task that runs once per hour. $record = new \stdClass(); $record->component = 'test_scheduled_task'; $record->classname = '\core\task\scheduled_test_task'; $record->disabled = 0; $DB->insert_record('task_scheduled', $record); // And disabled test. $record->classname = '\core\task\scheduled_test2_task'; $record->disabled = 1; $DB->insert_record('task_scheduled', $record); $now = time(); $scheduledtask = manager::get_next_scheduled_task($now); $this->assertInstanceOf('\core\task\scheduled_test2_task', $scheduledtask); $scheduledtask->execute(); manager::scheduled_task_complete($scheduledtask); } /** * Assert that the specified tasks are equal. * * @param \core\task\task_base $task * @param \core\task\task_base $comparisontask */ public function assertTaskEquals(task_base $task, task_base $comparisontask): void { // Convert both to an object. $task = manager::record_from_scheduled_task($task); $comparisontask = manager::record_from_scheduled_task($comparisontask); // Reset the nextruntime field as it is intentionally dynamic. $task->nextruntime = null; $comparisontask->nextruntime = null; $args = array_merge( [ $task, $comparisontask, ], array_slice(func_get_args(), 2) ); call_user_func_array([$this, 'assertEquals'], $args); } /** * Assert that the specified tasks are not equal. * * @param \core\task\task_base $task * @param \core\task\task_base $comparisontask */ public function assertTaskNotEquals(task_base $task, task_base $comparisontask): void { // Convert both to an object. $task = manager::record_from_scheduled_task($task); $comparisontask = manager::record_from_scheduled_task($comparisontask); // Reset the nextruntime field as it is intentionally dynamic. $task->nextruntime = null; $comparisontask->nextruntime = null; $args = array_merge( [ $task, $comparisontask, ], array_slice(func_get_args(), 2) ); call_user_func_array([$this, 'assertNotEquals'], $args); } /** * Assert that the lastruntime column holds an original value after a scheduled task is reset. */ public function test_reset_scheduled_tasks_for_component_keeps_original_lastruntime(): void { global $DB; $this->resetAfterTest(true); // Set lastruntime for the scheduled task. $DB->set_field('task_scheduled', 'lastruntime', 123456789, ['classname' => '\core\task\session_cleanup_task']); // Reset the task. manager::reset_scheduled_tasks_for_component('moodle'); // Fetch the task again. $taskafterreset = manager::get_scheduled_task(session_cleanup_task::class); // Confirm, that lastruntime is still in place. $this->assertEquals(123456789, $taskafterreset->get_last_run_time()); } /** * Data provider for {@see test_is_component_enabled} * * @return array[] */ public function is_component_enabled_provider(): array { return [ 'Enabled component' => ['auth_cas', true], 'Disabled component' => ['auth_ldap', false], 'Invalid component' => ['auth_invalid', false], ]; } /** * Tests whether tasks belonging to components consider the component to be enabled * * @param string $component * @param bool $expected * * @dataProvider is_component_enabled_provider */ public function test_is_component_enabled(string $component, bool $expected): void { $this->resetAfterTest(); // Set cas as the only enabled auth component. set_config('auth', 'cas'); $task = new scheduled_test_task(); $task->set_component($component); $this->assertEquals($expected, $task->is_component_enabled()); } /** * Test whether tasks belonging to core components considers the component to be enabled */ public function test_is_component_enabled_core(): void { $task = new scheduled_test_task(); $this->assertTrue($task->is_component_enabled()); } }