Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.0.x will end 8 May 2023 (12 months).
  • Bug fixes for security issues in 4.0.x will end 13 November 2023 (18 months).
  • PHP version: minimum PHP 7.3.0 Note: the minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is also supported.

Differences Between: [Versions 311 and 400] [Versions 400 and 401] [Versions 400 and 402] [Versions 400 and 403]

   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  namespace core\task;
  18  
  19  defined('MOODLE_INTERNAL') || die();
  20  require_once (__DIR__ . '/../fixtures/task_fixtures.php');
  21  
  22  /**
  23   * Test class for scheduled task.
  24   *
  25   * @package core
  26   * @category test
  27   * @copyright 2013 Damyon Wiese
  28   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  29   */
  30  class scheduled_task_test extends \advanced_testcase {
  31  
  32      /**
  33       * Test the cron scheduling method
  34       */
  35      public function test_eval_cron_field() {
  36          $testclass = new scheduled_test_task();
  37  
  38          $this->assertEquals(20, count($testclass->eval_cron_field('*/3', 0, 59)));
  39          $this->assertEquals(31, count($testclass->eval_cron_field('1,*/2', 0, 59)));
  40          $this->assertEquals(15, count($testclass->eval_cron_field('1-10,5-15', 0, 59)));
  41          $this->assertEquals(13, count($testclass->eval_cron_field('1-10,5-15/2', 0, 59)));
  42          $this->assertEquals(3, count($testclass->eval_cron_field('1,2,3,1,2,3', 0, 59)));
  43          $this->assertEquals(0, count($testclass->eval_cron_field('-1,10,80', 0, 59)));
  44          $this->assertEquals(0, count($testclass->eval_cron_field('-1', 0, 59)));
  45      }
  46  
  47      public function test_get_next_scheduled_time() {
  48          global $CFG;
  49          $this->resetAfterTest();
  50  
  51          $this->setTimezone('Europe/London');
  52  
  53          // Let's specify the hour we are going to use initially for the test.
  54          // (note that we pick 01:00 that is tricky for Europe/London, because
  55          // it's exactly the Daylight Saving Time Begins hour.
  56          $testhour = 1;
  57  
  58          // Test job run at 1 am.
  59          $testclass = new scheduled_test_task();
  60  
  61          // All fields default to '*'.
  62          $testclass->set_hour($testhour);
  63          $testclass->set_minute('0');
  64          // Next valid time should be 1am of the next day.
  65          $nexttime = $testclass->get_next_scheduled_time();
  66  
  67          $oneamdate = new \DateTime('now', new \DateTimeZone('Europe/London'));
  68          $oneamdate->setTime($testhour, 0, 0);
  69  
  70          // Once a year (currently last Sunday of March), when changing to Daylight Saving Time,
  71          // Europe/London 01:00 simply doesn't exists because, exactly at 01:00 the clock
  72          // is advanced by one hour and becomes 02:00. When that happens, the DateInterval
  73          // calculations cannot be to advance by 1 day, but by one less hour. That is exactly when
  74          // the next scheduled run will happen (next day 01:00).
  75          $isdaylightsaving = false;
  76          if ($testhour < (int)$oneamdate->format('H')) {
  77              $isdaylightsaving = true;
  78          }
  79  
  80          // Make it 1 am tomorrow if the time is after 1am.
  81          if ($oneamdate->getTimestamp() < time()) {
  82              $oneamdate->add(new \DateInterval('P1D'));
  83              if ($isdaylightsaving) {
  84                  // If today is Europe/London Daylight Saving Time Begins, expectation is 1 less hour.
  85                  $oneamdate->sub(new \DateInterval('PT1H'));
  86              }
  87          }
  88          $oneam = $oneamdate->getTimestamp();
  89  
  90          $this->assertEquals($oneam, $nexttime, 'Next scheduled time is 1am.');
  91  
  92          // Disabled flag does not affect next time.
  93          $testclass->set_disabled(true);
  94          $nexttime = $testclass->get_next_scheduled_time();
  95          $this->assertEquals($oneam, $nexttime, 'Next scheduled time is 1am.');
  96  
  97          // Now test for job run every 10 minutes.
  98          $testclass = new scheduled_test_task();
  99  
 100          // All fields default to '*'.
 101          $testclass->set_minute('*/10');
 102          // Next valid time should be next 10 minute boundary.
 103          $nexttime = $testclass->get_next_scheduled_time();
 104  
 105          $minutes = ((intval(date('i') / 10)) + 1) * 10;
 106          $nexttenminutes = mktime(date('H'), $minutes, 0);
 107  
 108          $this->assertEquals($nexttenminutes, $nexttime, 'Next scheduled time is in 10 minutes.');
 109  
 110          // Disabled flag does not affect next time.
 111          $testclass->set_disabled(true);
 112          $nexttime = $testclass->get_next_scheduled_time();
 113          $this->assertEquals($nexttenminutes, $nexttime, 'Next scheduled time is in 10 minutes.');
 114  
 115          // Test hourly job executed on Sundays only.
 116          $testclass = new scheduled_test_task();
 117          $testclass->set_minute('0');
 118          $testclass->set_day_of_week('7');
 119  
 120          $nexttime = $testclass->get_next_scheduled_time();
 121  
 122          $this->assertEquals(7, date('N', $nexttime));
 123          $this->assertEquals(0, date('i', $nexttime));
 124  
 125          // Test monthly job.
 126          $testclass = new scheduled_test_task();
 127          $testclass->set_minute('32');
 128          $testclass->set_hour('0');
 129          $testclass->set_day('1');
 130  
 131          $nexttime = $testclass->get_next_scheduled_time();
 132  
 133          $this->assertEquals(32, date('i', $nexttime));
 134          $this->assertEquals(0, date('G', $nexttime));
 135          $this->assertEquals(1, date('j', $nexttime));
 136      }
 137  
 138      public function test_timezones() {
 139          global $CFG, $USER;
 140  
 141          // The timezones used in this test are chosen because they do not use DST - that would break the test.
 142          $this->resetAfterTest();
 143  
 144          $this->setTimezone('Asia/Kabul');
 145  
 146          $testclass = new scheduled_test_task();
 147  
 148          // Scheduled tasks should always use servertime - so this is 03:30 GMT.
 149          $testclass->set_hour('1');
 150          $testclass->set_minute('0');
 151  
 152          // Next valid time should be 1am of the next day.
 153          $nexttime = $testclass->get_next_scheduled_time();
 154  
 155          // GMT+05:45.
 156          $USER->timezone = 'Asia/Kathmandu';
 157          $userdate = userdate($nexttime);
 158  
 159          // Should be displayed in user timezone.
 160          // I used http://www.timeanddate.com/worldclock/fixedtime.html?msg=Moodle+Test&iso=20160502T01&p1=113
 161          // setting my location to Kathmandu to verify this time.
 162          $this->assertStringContainsString('2:15 AM', \core_text::strtoupper($userdate));
 163      }
 164  
 165      public function test_reset_scheduled_tasks_for_component_customised(): void {
 166          $this->resetAfterTest(true);
 167  
 168          $tasks = manager::load_scheduled_tasks_for_component('moodle');
 169  
 170          // Customise a task.
 171          $task = reset($tasks);
 172          $task->set_minute('1');
 173          $task->set_hour('2');
 174          $task->set_day('3');
 175          $task->set_month('4');
 176          $task->set_day_of_week('5');
 177          $task->set_customised('1');
 178          manager::configure_scheduled_task($task);
 179  
 180          // Now call reset.
 181          manager::reset_scheduled_tasks_for_component('moodle');
 182  
 183          // Fetch the task again.
 184          $taskafterreset = manager::get_scheduled_task(get_class($task));
 185  
 186          // The task should still be the same as the customised.
 187          $this->assertTaskEquals($task, $taskafterreset);
 188      }
 189  
 190      public function test_reset_scheduled_tasks_for_component_deleted(): void {
 191          global $DB;
 192          $this->resetAfterTest(true);
 193  
 194          // Delete a task to simulate the fact that its new.
 195          $tasklist = manager::load_scheduled_tasks_for_component('moodle');
 196  
 197          // Note: This test must use a task which does not use any random values.
 198          $task = manager::get_scheduled_task(session_cleanup_task::class);
 199  
 200          $DB->delete_records('task_scheduled', array('classname' => '\\' . trim(get_class($task), '\\')));
 201          $this->assertFalse(manager::get_scheduled_task(session_cleanup_task::class));
 202  
 203          // Now call reset on all the tasks.
 204          manager::reset_scheduled_tasks_for_component('moodle');
 205  
 206          // Assert that the second task was added back.
 207          $taskafterreset = manager::get_scheduled_task(session_cleanup_task::class);
 208          $this->assertNotFalse($taskafterreset);
 209  
 210          $this->assertTaskEquals($task, $taskafterreset);
 211          $this->assertCount(count($tasklist), manager::load_scheduled_tasks_for_component('moodle'));
 212      }
 213  
 214      public function test_reset_scheduled_tasks_for_component_changed_in_source(): void {
 215          $this->resetAfterTest(true);
 216  
 217          // Delete a task to simulate the fact that its new.
 218          // Note: This test must use a task which does not use any random values.
 219          $task = manager::get_scheduled_task(session_cleanup_task::class);
 220  
 221          // Get a copy of the task before maing changes for later comparison.
 222          $taskbeforechange = manager::get_scheduled_task(session_cleanup_task::class);
 223  
 224          // Edit a task to simulate a change in its definition (as if it was not customised).
 225          $task->set_minute('1');
 226          $task->set_hour('2');
 227          $task->set_day('3');
 228          $task->set_month('4');
 229          $task->set_day_of_week('5');
 230          manager::configure_scheduled_task($task);
 231  
 232          // Fetch the task out for comparison.
 233          $taskafterchange = manager::get_scheduled_task(session_cleanup_task::class);
 234  
 235          // The task should now be different to the original.
 236          $this->assertTaskNotEquals($taskbeforechange, $taskafterchange);
 237  
 238          // Now call reset.
 239          manager::reset_scheduled_tasks_for_component('moodle');
 240  
 241          // Fetch the task again.
 242          $taskafterreset = manager::get_scheduled_task(session_cleanup_task::class);
 243  
 244          // The task should now be the same as the original.
 245          $this->assertTaskEquals($taskbeforechange, $taskafterreset);
 246      }
 247  
 248      /**
 249       * Tests that the reset function deletes old tasks.
 250       */
 251      public function test_reset_scheduled_tasks_for_component_delete() {
 252          global $DB;
 253          $this->resetAfterTest(true);
 254  
 255          $count = $DB->count_records('task_scheduled', array('component' => 'moodle'));
 256          $allcount = $DB->count_records('task_scheduled');
 257  
 258          $task = new scheduled_test_task();
 259          $task->set_component('moodle');
 260          $record = manager::record_from_scheduled_task($task);
 261          $DB->insert_record('task_scheduled', $record);
 262          $this->assertTrue($DB->record_exists('task_scheduled', array('classname' => '\core\task\scheduled_test_task',
 263              'component' => 'moodle')));
 264  
 265          $task = new scheduled_test2_task();
 266          $task->set_component('moodle');
 267          $record = manager::record_from_scheduled_task($task);
 268          $DB->insert_record('task_scheduled', $record);
 269          $this->assertTrue($DB->record_exists('task_scheduled', array('classname' => '\core\task\scheduled_test2_task',
 270              'component' => 'moodle')));
 271  
 272          $aftercount = $DB->count_records('task_scheduled', array('component' => 'moodle'));
 273          $afterallcount = $DB->count_records('task_scheduled');
 274  
 275          $this->assertEquals($count + 2, $aftercount);
 276          $this->assertEquals($allcount + 2, $afterallcount);
 277  
 278          // Now check that the right things were deleted.
 279          manager::reset_scheduled_tasks_for_component('moodle');
 280  
 281          $this->assertEquals($count, $DB->count_records('task_scheduled', array('component' => 'moodle')));
 282          $this->assertEquals($allcount, $DB->count_records('task_scheduled'));
 283          $this->assertFalse($DB->record_exists('task_scheduled', array('classname' => '\core\task\scheduled_test2_task',
 284              'component' => 'moodle')));
 285          $this->assertFalse($DB->record_exists('task_scheduled', array('classname' => '\core\task\scheduled_test_task',
 286              'component' => 'moodle')));
 287      }
 288  
 289      public function test_get_next_scheduled_task() {
 290          global $DB;
 291  
 292          $this->resetAfterTest(true);
 293          // Delete all existing scheduled tasks.
 294          $DB->delete_records('task_scheduled');
 295          // Add a scheduled task.
 296  
 297          // A task that runs once per hour.
 298          $record = new \stdClass();
 299          $record->blocking = true;
 300          $record->minute = '0';
 301          $record->hour = '0';
 302          $record->dayofweek = '*';
 303          $record->day = '*';
 304          $record->month = '*';
 305          $record->component = 'test_scheduled_task';
 306          $record->classname = '\core\task\scheduled_test_task';
 307  
 308          $DB->insert_record('task_scheduled', $record);
 309          // And another one to test failures.
 310          $record->classname = '\core\task\scheduled_test2_task';
 311          $DB->insert_record('task_scheduled', $record);
 312          // And disabled test.
 313          $record->classname = '\core\task\scheduled_test3_task';
 314          $record->disabled = 1;
 315          $DB->insert_record('task_scheduled', $record);
 316  
 317          $now = time();
 318  
 319          // Should get handed the first task.
 320          $task = manager::get_next_scheduled_task($now);
 321          $this->assertInstanceOf('\core\task\scheduled_test_task', $task);
 322          $task->execute();
 323  
 324          manager::scheduled_task_complete($task);
 325          // Should get handed the second task.
 326          $task = manager::get_next_scheduled_task($now);
 327          $this->assertInstanceOf('\core\task\scheduled_test2_task', $task);
 328          $task->execute();
 329  
 330          manager::scheduled_task_failed($task);
 331          // Should not get any task.
 332          $task = manager::get_next_scheduled_task($now);
 333          $this->assertNull($task);
 334  
 335          // Should get the second task (retry after delay).
 336          $task = manager::get_next_scheduled_task($now + 120);
 337          $this->assertInstanceOf('\core\task\scheduled_test2_task', $task);
 338          $task->execute();
 339  
 340          manager::scheduled_task_complete($task);
 341  
 342          // Should not get any task.
 343          $task = manager::get_next_scheduled_task($now);
 344          $this->assertNull($task);
 345  
 346          // Check ordering.
 347          $DB->delete_records('task_scheduled');
 348          $record->lastruntime = 2;
 349          $record->disabled = 0;
 350          $record->classname = '\core\task\scheduled_test_task';
 351          $DB->insert_record('task_scheduled', $record);
 352  
 353          $record->lastruntime = 1;
 354          $record->classname = '\core\task\scheduled_test2_task';
 355          $DB->insert_record('task_scheduled', $record);
 356  
 357          // Should get handed the second task.
 358          $task = manager::get_next_scheduled_task($now);
 359          $this->assertInstanceOf('\core\task\scheduled_test2_task', $task);
 360          $task->execute();
 361          manager::scheduled_task_complete($task);
 362  
 363          // Should get handed the first task.
 364          $task = manager::get_next_scheduled_task($now);
 365          $this->assertInstanceOf('\core\task\scheduled_test_task', $task);
 366          $task->execute();
 367          manager::scheduled_task_complete($task);
 368  
 369          // Should not get any task.
 370          $task = manager::get_next_scheduled_task($now);
 371          $this->assertNull($task);
 372      }
 373  
 374      public function test_get_broken_scheduled_task() {
 375          global $DB;
 376  
 377          $this->resetAfterTest(true);
 378          // Delete all existing scheduled tasks.
 379          $DB->delete_records('task_scheduled');
 380          // Add a scheduled task.
 381  
 382          // A broken task that runs all the time.
 383          $record = new \stdClass();
 384          $record->blocking = true;
 385          $record->minute = '*';
 386          $record->hour = '*';
 387          $record->dayofweek = '*';
 388          $record->day = '*';
 389          $record->month = '*';
 390          $record->component = 'test_scheduled_task';
 391          $record->classname = '\core\task\scheduled_test_task_broken';
 392  
 393          $DB->insert_record('task_scheduled', $record);
 394  
 395          $now = time();
 396          // Should not get any task.
 397          $task = manager::get_next_scheduled_task($now);
 398          $this->assertDebuggingCalled();
 399          $this->assertNull($task);
 400      }
 401  
 402      /**
 403       * Tests the use of 'R' syntax in time fields of tasks to get
 404       * tasks be configured with a non-uniform time.
 405       */
 406      public function test_random_time_specification() {
 407  
 408          // Testing non-deterministic things in a unit test is not really
 409          // wise, so we just test the values have changed within allowed bounds.
 410          $testclass = new scheduled_test_task();
 411  
 412          // The test task defaults to '*'.
 413          $this->assertIsString($testclass->get_minute());
 414          $this->assertIsString($testclass->get_hour());
 415  
 416          // Set a random value.
 417          $testclass->set_minute('R');
 418          $testclass->set_hour('R');
 419          $testclass->set_day_of_week('R');
 420  
 421          // Verify the minute has changed within allowed bounds.
 422          $minute = $testclass->get_minute();
 423          $this->assertIsInt($minute);
 424          $this->assertGreaterThanOrEqual(0, $minute);
 425          $this->assertLessThanOrEqual(59, $minute);
 426  
 427          // Verify the hour has changed within allowed bounds.
 428          $hour = $testclass->get_hour();
 429          $this->assertIsInt($hour);
 430          $this->assertGreaterThanOrEqual(0, $hour);
 431          $this->assertLessThanOrEqual(23, $hour);
 432  
 433          // Verify the dayofweek has changed within allowed bounds.
 434          $dayofweek = $testclass->get_day_of_week();
 435          $this->assertIsInt($dayofweek);
 436          $this->assertGreaterThanOrEqual(0, $dayofweek);
 437          $this->assertLessThanOrEqual(6, $dayofweek);
 438      }
 439  
 440      /**
 441       * Test that the file_temp_cleanup_task removes directories and
 442       * files as expected.
 443       */
 444      public function test_file_temp_cleanup_task() {
 445          global $CFG;
 446          $backuptempdir = make_backup_temp_directory('');
 447  
 448          // Create directories.
 449          $dir = $backuptempdir . DIRECTORY_SEPARATOR . 'backup01' . DIRECTORY_SEPARATOR . 'courses';
 450          mkdir($dir, 0777, true);
 451  
 452          // Create files to be checked and then deleted.
 453          $file01 = $dir . DIRECTORY_SEPARATOR . 'sections.xml';
 454          file_put_contents($file01, 'test data 001');
 455          $file02 = $dir . DIRECTORY_SEPARATOR . 'modules.xml';
 456          file_put_contents($file02, 'test data 002');
 457          // Change the time modified for the first file, to a time that will be deleted by the task (greater than seven days).
 458          touch($file01, time() - (8 * 24 * 3600));
 459  
 460          $task = manager::get_scheduled_task('\\core\\task\\file_temp_cleanup_task');
 461          $this->assertInstanceOf('\core\task\file_temp_cleanup_task', $task);
 462          $task->execute();
 463  
 464          // Scan the directory. Only modules.xml should be left.
 465          $filesarray = scandir($dir);
 466          $this->assertEquals('modules.xml', $filesarray[2]);
 467          $this->assertEquals(3, count($filesarray));
 468  
 469          // Change the time modified on modules.xml.
 470          touch($file02, time() - (8 * 24 * 3600));
 471          // Change the time modified on the courses directory.
 472          touch($backuptempdir . DIRECTORY_SEPARATOR . 'backup01' . DIRECTORY_SEPARATOR .
 473                  'courses', time() - (8 * 24 * 3600));
 474          // Run the scheduled task to remove the file and directory.
 475          $task->execute();
 476          $filesarray = scandir($backuptempdir . DIRECTORY_SEPARATOR . 'backup01');
 477          // There should only be two items in the array, '.' and '..'.
 478          $this->assertEquals(2, count($filesarray));
 479  
 480          // Change the time modified on all of the files and directories.
 481          $dir = new \RecursiveDirectoryIterator($CFG->tempdir);
 482          // Show all child nodes prior to their parent.
 483          $iter = new \RecursiveIteratorIterator($dir, \RecursiveIteratorIterator::CHILD_FIRST);
 484  
 485          for ($iter->rewind(); $iter->valid(); $iter->next()) {
 486              if ($iter->isDir() && !$iter->isDot()) {
 487                  $node = $iter->getRealPath();
 488                  touch($node, time() - (8 * 24 * 3600));
 489              }
 490          }
 491  
 492          // Run the scheduled task again to remove all of the files and directories.
 493          $task->execute();
 494          $filesarray = scandir($CFG->tempdir);
 495          // All of the files and directories should be deleted.
 496          // There should only be three items in the array, '.', '..' and '.htaccess'.
 497          $this->assertEquals([ '.', '..', '.htaccess' ], $filesarray);
 498      }
 499  
 500      /**
 501       * Test that the function to clear the fail delay from a task works correctly.
 502       */
 503      public function test_clear_fail_delay() {
 504  
 505          $this->resetAfterTest();
 506  
 507          // Get an example task to use for testing. Task is set to run every minute by default.
 508          $taskname = '\core\task\send_new_user_passwords_task';
 509  
 510          // Pretend task started running and then failed 3 times.
 511          $before = time();
 512          $cronlockfactory = \core\lock\lock_config::get_lock_factory('cron');
 513          for ($i = 0; $i < 3; $i ++) {
 514              $task = manager::get_scheduled_task($taskname);
 515              $lock = $cronlockfactory->get_lock('\\' . get_class($task), 10);
 516              $task->set_lock($lock);
 517              manager::scheduled_task_failed($task);
 518          }
 519  
 520          // Confirm task is now delayed by several minutes.
 521          $task = manager::get_scheduled_task($taskname);
 522          $this->assertEquals(240, $task->get_fail_delay());
 523          $this->assertGreaterThan($before + 230, $task->get_next_run_time());
 524  
 525          // Clear the fail delay and re-get the task.
 526          manager::clear_fail_delay($task);
 527          $task = manager::get_scheduled_task($taskname);
 528  
 529          // There should be no delay and it should run within the next minute.
 530          $this->assertEquals(0, $task->get_fail_delay());
 531          $this->assertLessThan($before + 70, $task->get_next_run_time());
 532      }
 533  
 534      /**
 535       * Data provider for test_scheduled_task_override_values.
 536       */
 537      public static function provider_schedule_overrides(): array {
 538          return array(
 539              array(
 540                  'scheduled_tasks' => array(
 541                      '\core\task\scheduled_test_task' => array(
 542                          'schedule' => '10 13 1 2 4',
 543                          'disabled' => 0,
 544                      ),
 545                      '\core\task\scheduled_test2_task' => array(
 546                          'schedule' => '* * * * *',
 547                          'disabled' => 1,
 548                      ),
 549                  ),
 550                  'task_full_classnames' => array(
 551                      '\core\task\scheduled_test_task',
 552                      '\core\task\scheduled_test2_task',
 553                  ),
 554                  'expected' => array(
 555                      '\core\task\scheduled_test_task' => array(
 556                          'min'   => '10',
 557                          'hour'  => '13',
 558                          'day'   => '1',
 559                          'month' => '2',
 560                          'week'  => '4',
 561                          'disabled' => 0,
 562                      ),
 563                      '\core\task\scheduled_test2_task' => array(
 564                          'min'   => '*',
 565                          'hour'  => '*',
 566                          'day'   => '*',
 567                          'month' => '*',
 568                          'week'  => '*',
 569                          'disabled' => 1,
 570                      ),
 571                  )
 572              ),
 573              array(
 574                  'scheduled_tasks' => array(
 575                      '\core\task\*' => array(
 576                          'schedule' => '1 2 3 4 5',
 577                          'disabled' => 0,
 578                      )
 579                  ),
 580                  'task_full_classnames' => array(
 581                      '\core\task\scheduled_test_task',
 582                      '\core\task\scheduled_test2_task',
 583                  ),
 584                  'expected' => array(
 585                      '\core\task\scheduled_test_task' => array(
 586                          'min'   => '1',
 587                          'hour'  => '2',
 588                          'day'   => '3',
 589                          'month' => '4',
 590                          'week'  => '5',
 591                          'disabled' => 0,
 592                      ),
 593                      '\core\task\scheduled_test2_task' => array(
 594                          'min'   => '1',
 595                          'hour'  => '2',
 596                          'day'   => '3',
 597                          'month' => '4',
 598                          'week'  => '5',
 599                          'disabled' => 0,
 600                      ),
 601                  )
 602              )
 603          );
 604      }
 605  
 606  
 607      /**
 608       * Test to ensure scheduled tasks are updated by values set in config.
 609       *
 610       * @param array $overrides
 611       * @param array $tasks
 612       * @param array $expected
 613       * @dataProvider provider_schedule_overrides
 614       */
 615      public function test_scheduled_task_override_values(array $overrides, array $tasks, array $expected): void {
 616          global $CFG, $DB;
 617  
 618          $this->resetAfterTest();
 619  
 620          // Add overrides to the config.
 621          $CFG->scheduled_tasks = $overrides;
 622  
 623          // Set up test scheduled task record.
 624          $record = new \stdClass();
 625          $record->component = 'test_scheduled_task';
 626  
 627          foreach ($tasks as $task) {
 628              $record->classname = $task;
 629              $DB->insert_record('task_scheduled', $record);
 630  
 631              $scheduledtask = manager::get_scheduled_task($task);
 632              $expectedresults = $expected[$task];
 633  
 634              // Check that the task is actually overridden.
 635              $this->assertTrue($scheduledtask->is_overridden(), 'Is overridden');
 636  
 637              // Check minute is correct.
 638              $this->assertEquals($expectedresults['min'], $scheduledtask->get_minute(), 'Minute check');
 639  
 640              // Check day is correct.
 641              $this->assertEquals($expectedresults['day'], $scheduledtask->get_day(), 'Day check');
 642  
 643              // Check hour is correct.
 644              $this->assertEquals($expectedresults['hour'], $scheduledtask->get_hour(), 'Hour check');
 645  
 646              // Check week is correct.
 647              $this->assertEquals($expectedresults['week'], $scheduledtask->get_day_of_week(), 'Day of week check');
 648  
 649              // Check week is correct.
 650              $this->assertEquals($expectedresults['month'], $scheduledtask->get_month(), 'Month check');
 651  
 652              // Check to see if the task is disabled.
 653              $this->assertEquals($expectedresults['disabled'], $scheduledtask->get_disabled(), 'Disabled check');
 654          }
 655      }
 656  
 657      /**
 658       * Check that an overridden task is sent to be processed.
 659       */
 660      public function test_scheduled_task_overridden_task_can_run(): void {
 661          global $CFG, $DB;
 662  
 663          $this->resetAfterTest();
 664  
 665          // Delete all existing scheduled tasks.
 666          $DB->delete_records('task_scheduled');
 667  
 668          // Add overrides to the config.
 669          $CFG->scheduled_tasks = [
 670              '\core\task\scheduled_test_task' => [
 671                  'disabled' => 1
 672              ],
 673              '\core\task\scheduled_test2_task' => [
 674                  'disabled' => 0
 675              ],
 676          ];
 677  
 678          // A task that runs once per hour.
 679          $record = new \stdClass();
 680          $record->component = 'test_scheduled_task';
 681          $record->classname = '\core\task\scheduled_test_task';
 682          $record->disabled = 0;
 683          $DB->insert_record('task_scheduled', $record);
 684  
 685          // And disabled test.
 686          $record->classname = '\core\task\scheduled_test2_task';
 687          $record->disabled = 1;
 688          $DB->insert_record('task_scheduled', $record);
 689  
 690          $now = time();
 691  
 692          $scheduledtask = manager::get_next_scheduled_task($now);
 693          $this->assertInstanceOf('\core\task\scheduled_test2_task', $scheduledtask);
 694          $scheduledtask->execute();
 695          manager::scheduled_task_complete($scheduledtask);
 696      }
 697  
 698      /**
 699       * Assert that the specified tasks are equal.
 700       *
 701       * @param   \core\task\task_base $task
 702       * @param   \core\task\task_base $comparisontask
 703       */
 704      public function assertTaskEquals(task_base $task, task_base $comparisontask): void {
 705          // Convert both to an object.
 706          $task = manager::record_from_scheduled_task($task);
 707          $comparisontask = manager::record_from_scheduled_task($comparisontask);
 708  
 709          // Reset the nextruntime field as it is intentionally dynamic.
 710          $task->nextruntime = null;
 711          $comparisontask->nextruntime = null;
 712  
 713          $args = array_merge(
 714              [
 715                  $task,
 716                  $comparisontask,
 717              ],
 718              array_slice(func_get_args(), 2)
 719          );
 720  
 721          call_user_func_array([$this, 'assertEquals'], $args);
 722      }
 723  
 724      /**
 725       * Assert that the specified tasks are not equal.
 726       *
 727       * @param   \core\task\task_base $task
 728       * @param   \core\task\task_base $comparisontask
 729       */
 730      public function assertTaskNotEquals(task_base $task, task_base $comparisontask): void {
 731          // Convert both to an object.
 732          $task = manager::record_from_scheduled_task($task);
 733          $comparisontask = manager::record_from_scheduled_task($comparisontask);
 734  
 735          // Reset the nextruntime field as it is intentionally dynamic.
 736          $task->nextruntime = null;
 737          $comparisontask->nextruntime = null;
 738  
 739          $args = array_merge(
 740              [
 741                  $task,
 742                  $comparisontask,
 743              ],
 744              array_slice(func_get_args(), 2)
 745          );
 746  
 747          call_user_func_array([$this, 'assertNotEquals'], $args);
 748      }
 749  
 750      /**
 751       * Assert that the lastruntime column holds an original value after a scheduled task is reset.
 752       */
 753      public function test_reset_scheduled_tasks_for_component_keeps_original_lastruntime(): void {
 754          global $DB;
 755          $this->resetAfterTest(true);
 756  
 757          // Set lastruntime for the scheduled task.
 758          $DB->set_field('task_scheduled', 'lastruntime', 123456789, ['classname' => '\core\task\session_cleanup_task']);
 759  
 760          // Reset the task.
 761          manager::reset_scheduled_tasks_for_component('moodle');
 762  
 763          // Fetch the task again.
 764          $taskafterreset = manager::get_scheduled_task(session_cleanup_task::class);
 765  
 766          // Confirm, that lastruntime is still in place.
 767          $this->assertEquals(123456789, $taskafterreset->get_last_run_time());
 768      }
 769  
 770      /**
 771       * Data provider for {@see test_is_component_enabled}
 772       *
 773       * @return array[]
 774       */
 775      public function is_component_enabled_provider(): array {
 776          return [
 777              'Enabled component' => ['auth_cas', true],
 778              'Disabled component' => ['auth_ldap', false],
 779              'Invalid component' => ['auth_invalid', false],
 780          ];
 781      }
 782  
 783      /**
 784       * Tests whether tasks belonging to components consider the component to be enabled
 785       *
 786       * @param string $component
 787       * @param bool $expected
 788       *
 789       * @dataProvider is_component_enabled_provider
 790       */
 791      public function test_is_component_enabled(string $component, bool $expected): void {
 792          $this->resetAfterTest();
 793  
 794          // Set cas as the only enabled auth component.
 795          set_config('auth', 'cas');
 796  
 797          $task = new scheduled_test_task();
 798          $task->set_component($component);
 799  
 800          $this->assertEquals($expected, $task->is_component_enabled());
 801      }
 802  
 803      /**
 804       * Test whether tasks belonging to core components considers the component to be enabled
 805       */
 806      public function test_is_component_enabled_core(): void {
 807          $task = new scheduled_test_task();
 808          $this->assertTrue($task->is_component_enabled());
 809      }
 810  }