Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.

Differences Between: [Versions 311 and 403] [Versions 400 and 403] [Versions 401 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  /**
  24   * Test class for adhoc tasks.
  25   *
  26   * @package core
  27   * @category test
  28   * @copyright 2013 Damyon Wiese
  29   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  30   * @coversDefaultClass \core\task\manager
  31   */
  32  class adhoc_task_test extends \advanced_testcase {
  33  
  34      /**
  35       * Test getting name of task that implements it's own get_name method
  36       *
  37       * @covers \core\task\adhoc_task::get_name
  38       */
  39      public function test_get_name(): void {
  40          $task = new \core\task\adhoc_test_task();
  41          $this->assertEquals('Test adhoc class', $task->get_name());
  42      }
  43  
  44      /**
  45       * Test getting name of task that uses the default implementation of get_name
  46       *
  47       * @covers \core\task\adhoc_task::get_name
  48       */
  49      public function test_get_name_default(): void {
  50          $task = new \mod_fake\task\adhoc_component_task();
  51          $this->assertEquals('Adhoc component task', $task->get_name());
  52      }
  53  
  54      /**
  55       * Test basic adhoc task execution.
  56       *
  57       * @covers ::get_next_adhoc_task
  58       */
  59      public function test_get_next_adhoc_task_now() {
  60          $this->resetAfterTest(true);
  61  
  62          // Create an adhoc task.
  63          $task = new adhoc_test_task();
  64  
  65          // Queue it.
  66          manager::queue_adhoc_task($task);
  67  
  68          $now = time();
  69          // Get it from the scheduler.
  70          $task = manager::get_next_adhoc_task($now);
  71          $this->assertInstanceOf('\\core\\task\\adhoc_test_task', $task);
  72          $task->execute();
  73          manager::adhoc_task_complete($task);
  74      }
  75  
  76      /**
  77       * Test basic adhoc task execution.
  78       *
  79       * @covers ::get_next_adhoc_task
  80       */
  81      public function test_get_next_adhoc_task_class() {
  82          $this->resetAfterTest(true);
  83  
  84          // Create an adhoc task.
  85          $task = new \core\task\adhoc_test_task();
  86  
  87          // Queue it.
  88          manager::queue_adhoc_task($task);
  89  
  90          $now = time();
  91          $classname = get_class($task);
  92  
  93          // The task will not be returned.
  94          $this->assertNull(manager::get_next_adhoc_task($now, true, "{$classname}notexists"));
  95  
  96          // Get it from the scheduler.
  97          $task = manager::get_next_adhoc_task($now, true, $classname);
  98          $this->assertInstanceOf('\\core\\task\\adhoc_test_task', $task);
  99          $task->execute();
 100          manager::adhoc_task_complete($task);
 101      }
 102  
 103      /**
 104       * Test adhoc task failure retry backoff.
 105       *
 106       * @covers ::get_next_adhoc_task
 107       * @covers ::get_adhoc_task
 108       */
 109      public function test_get_next_adhoc_task_fail_retry() {
 110          $this->resetAfterTest(true);
 111  
 112          // Create an adhoc task.
 113          $task = new adhoc_test_task();
 114          manager::queue_adhoc_task($task);
 115  
 116          $now = time();
 117  
 118          // Get it from the scheduler, execute it, and mark it as failed.
 119          $task = manager::get_next_adhoc_task($now);
 120          $taskid = $task->get_id();
 121          $task->execute();
 122          manager::adhoc_task_failed($task);
 123  
 124          // The task will not be returned immediately.
 125          $this->assertNull(manager::get_next_adhoc_task($now));
 126  
 127          // Should get the adhoc task (retry after delay). Fail it again.
 128          $task = manager::get_next_adhoc_task($now + 120);
 129          $this->assertInstanceOf('\\core\\task\\adhoc_test_task', $task);
 130          $this->assertEquals($taskid, $task->get_id());
 131          $task->execute();
 132          manager::adhoc_task_failed($task);
 133  
 134          // Should get the adhoc task immediately.
 135          $task = manager::get_adhoc_task($taskid);
 136          $this->assertInstanceOf('\\core\\task\\adhoc_test_task', $task);
 137          $this->assertEquals($taskid, $task->get_id());
 138          $task->execute();
 139          manager::adhoc_task_complete($task);
 140  
 141          // Should not get any task.
 142          $this->assertNull(manager::get_next_adhoc_task($now));
 143      }
 144  
 145      /**
 146       * Test future adhoc task execution.
 147       * @covers ::get_next_adhoc_task
 148       */
 149      public function test_get_next_adhoc_task_future() {
 150          $this->resetAfterTest(true);
 151  
 152          $now = time();
 153          // Create an adhoc task in future.
 154          $task = new adhoc_test_task();
 155          $task->set_next_run_time($now + 1000);
 156          manager::queue_adhoc_task($task);
 157  
 158          // Fetching the next task should not return anything.
 159          $this->assertNull(manager::get_next_adhoc_task($now));
 160  
 161          // Fetching in the future should return the task.
 162          $task = manager::get_next_adhoc_task($now + 1020);
 163          $this->assertInstanceOf('\\core\\task\\adhoc_test_task', $task);
 164          $task->execute();
 165          manager::adhoc_task_complete($task);
 166      }
 167  
 168      /**
 169       * Test queueing an adhoc task belonging to a component, where we set the task component accordingly
 170       * @covers ::queue_adhoc_task
 171       */
 172      public function test_queue_adhoc_task_for_component(): void {
 173          $this->resetAfterTest();
 174  
 175          $task = new \mod_forum\task\send_user_digests();
 176          $task->set_component('mod_test');
 177  
 178          manager::queue_adhoc_task($task);
 179          $this->assertDebuggingNotCalled();
 180      }
 181  
 182      /**
 183       * Test queueing an adhoc task belonging to a component, where we do not set the task component
 184       * @covers ::queue_adhoc_task
 185       */
 186      public function test_queue_task_for_component_without_set_component(): void {
 187          $this->resetAfterTest();
 188  
 189          $task = new \mod_forum\task\send_user_digests();
 190  
 191          manager::queue_adhoc_task($task);
 192          $this->assertDebuggingNotCalled();
 193  
 194          // Assert the missing component was set.
 195          $this->assertEquals('mod_forum', $task->get_component());
 196      }
 197  
 198      /**
 199       * Test queueing an adhoc task belonging to an invalid component, where we do not set the task component
 200       * @covers ::queue_adhoc_task
 201       */
 202      public function test_queue_task_for_invalid_component_without_set_component(): void {
 203          $this->resetAfterTest();
 204  
 205          $task = new \mod_fake\task\adhoc_component_task();
 206  
 207          manager::queue_adhoc_task($task);
 208          $this->assertDebuggingCalled('Component not set and the class namespace does not match a valid component (mod_fake).');
 209      }
 210  
 211      /**
 212       * Test empty set of adhoc tasks
 213       * @covers ::get_adhoc_tasks
 214       */
 215      public function test_get_adhoc_tasks_empty_set() {
 216          $this->resetAfterTest(true);
 217  
 218          $this->assertEquals([], manager::get_adhoc_tasks('\\core\\task\\adhoc_test_task'));
 219      }
 220  
 221      /**
 222       * Test correct set of adhoc tasks is returned for class.
 223       * @covers ::get_adhoc_tasks
 224       */
 225      public function test_get_adhoc_tasks_result_set() {
 226          $this->resetAfterTest(true);
 227  
 228          for ($i = 0; $i < 3; $i++) {
 229              $task = new adhoc_test_task();
 230              manager::queue_adhoc_task($task);
 231          }
 232  
 233          for ($i = 0; $i < 3; $i++) {
 234              $task = new adhoc_test2_task();
 235              manager::queue_adhoc_task($task);
 236          }
 237  
 238          $adhoctests = manager::get_adhoc_tasks('\\core\\task\\adhoc_test_task');
 239          $adhoctest2s = manager::get_adhoc_tasks('\\core\\task\\adhoc_test2_task');
 240  
 241          $this->assertCount(3, $adhoctests);
 242          $this->assertCount(3, $adhoctest2s);
 243  
 244          foreach ($adhoctests as $task) {
 245              $this->assertInstanceOf('\\core\\task\\adhoc_test_task', $task);
 246          }
 247  
 248          foreach ($adhoctest2s as $task) {
 249              $this->assertInstanceOf('\\core\\task\\adhoc_test2_task', $task);
 250          }
 251      }
 252  
 253      /**
 254       * Ensure that the reschedule_or_queue_adhoc_task function will schedule a new task if no tasks exist.
 255       * @covers ::reschedule_or_queue_adhoc_task
 256       */
 257      public function test_reschedule_or_queue_adhoc_task_no_existing() {
 258          $this->resetAfterTest(true);
 259  
 260          // Schedule adhoc task.
 261          $task = new adhoc_test_task();
 262          $task->set_custom_data(['courseid' => 10]);
 263          manager::reschedule_or_queue_adhoc_task($task);
 264          $this->assertEquals(1, count(manager::get_adhoc_tasks('core\task\adhoc_test_task')));
 265      }
 266  
 267      /**
 268       * Ensure that the reschedule_or_queue_adhoc_task function will schedule a new task if a task for the same user does
 269       * not exist.
 270       * @covers ::reschedule_or_queue_adhoc_task
 271       */
 272      public function test_reschedule_or_queue_adhoc_task_different_user() {
 273          $this->resetAfterTest(true);
 274          $user = \core_user::get_user_by_username('admin');
 275  
 276          // Schedule adhoc task.
 277          $task = new adhoc_test_task();
 278          $task->set_custom_data(['courseid' => 10]);
 279          manager::reschedule_or_queue_adhoc_task($task);
 280  
 281          // Schedule adhoc task for a different user.
 282          $task = new adhoc_test_task();
 283          $task->set_custom_data(['courseid' => 10]);
 284          $task->set_userid($user->id);
 285          manager::reschedule_or_queue_adhoc_task($task);
 286  
 287          $this->assertEquals(2, count(manager::get_adhoc_tasks('core\task\adhoc_test_task')));
 288      }
 289  
 290      /**
 291       * Ensure that the reschedule_or_queue_adhoc_task function will schedule a new task if a task with different custom
 292       * data exists.
 293       * @covers ::reschedule_or_queue_adhoc_task
 294       */
 295      public function test_reschedule_or_queue_adhoc_task_different_data() {
 296          $this->resetAfterTest(true);
 297  
 298          // Schedule adhoc task.
 299          $task = new adhoc_test_task();
 300          $task->set_custom_data(['courseid' => 10]);
 301          manager::reschedule_or_queue_adhoc_task($task);
 302  
 303          // Schedule adhoc task for a different user.
 304          $task = new adhoc_test_task();
 305          $task->set_custom_data(['courseid' => 11]);
 306          manager::reschedule_or_queue_adhoc_task($task);
 307  
 308          $this->assertEquals(2, count(manager::get_adhoc_tasks('core\task\adhoc_test_task')));
 309      }
 310  
 311      /**
 312       * Ensure that the reschedule_or_queue_adhoc_task function will not make any change for matching data if no time was
 313       * specified.
 314       * @covers ::reschedule_or_queue_adhoc_task
 315       */
 316      public function test_reschedule_or_queue_adhoc_task_match_no_change() {
 317          $this->resetAfterTest(true);
 318  
 319          // Schedule adhoc task.
 320          $task = new adhoc_test_task();
 321          $task->set_custom_data(['courseid' => 10]);
 322          $task->set_next_run_time(time() + DAYSECS);
 323          manager::reschedule_or_queue_adhoc_task($task);
 324  
 325          $before = manager::get_adhoc_tasks('core\task\adhoc_test_task');
 326  
 327          // Schedule the task again but do not specify a time.
 328          $task = new adhoc_test_task();
 329          $task->set_custom_data(['courseid' => 10]);
 330          manager::reschedule_or_queue_adhoc_task($task);
 331  
 332          $this->assertEquals(1, count(manager::get_adhoc_tasks('core\task\adhoc_test_task')));
 333          $this->assertEquals($before, manager::get_adhoc_tasks('core\task\adhoc_test_task'));
 334      }
 335  
 336      /**
 337       * Ensure that the reschedule_or_queue_adhoc_task function will update the run time if there are planned changes.
 338       * @covers ::reschedule_or_queue_adhoc_task
 339       */
 340      public function test_reschedule_or_queue_adhoc_task_match_update_runtime() {
 341          $this->resetAfterTest(true);
 342          $initialruntime = time() + DAYSECS;
 343          $newruntime = time() + WEEKSECS;
 344  
 345          // Schedule adhoc task.
 346          $task = new adhoc_test_task();
 347          $task->set_custom_data(['courseid' => 10]);
 348          $task->set_next_run_time($initialruntime);
 349          manager::reschedule_or_queue_adhoc_task($task);
 350  
 351          $before = manager::get_adhoc_tasks('core\task\adhoc_test_task');
 352  
 353          // Schedule the task again.
 354          $task = new adhoc_test_task();
 355          $task->set_custom_data(['courseid' => 10]);
 356          $task->set_next_run_time($newruntime);
 357          manager::reschedule_or_queue_adhoc_task($task);
 358  
 359          $tasks = manager::get_adhoc_tasks('core\task\adhoc_test_task');
 360          $this->assertEquals(1, count($tasks));
 361          $this->assertNotEquals($before, $tasks);
 362          $firsttask = reset($tasks);
 363          $this->assertEquals($newruntime, $firsttask->get_next_run_time());
 364      }
 365  
 366      /**
 367       * Test queue_adhoc_task "if not scheduled".
 368       * @covers ::queue_adhoc_task
 369       */
 370      public function test_queue_adhoc_task_if_not_scheduled() {
 371          $this->resetAfterTest(true);
 372          $user = \core_user::get_user_by_username('admin');
 373  
 374          // Schedule adhoc task.
 375          $task = new adhoc_test_task();
 376          $task->set_custom_data(array('courseid' => 10));
 377          $this->assertNotEmpty(manager::queue_adhoc_task($task, true));
 378          $this->assertEquals(1, count(manager::get_adhoc_tasks('core\task\adhoc_test_task')));
 379  
 380          // Schedule adhoc task with a user.
 381          $task = new adhoc_test_task();
 382          $task->set_custom_data(array('courseid' => 10));
 383          $task->set_userid($user->id);
 384          $this->assertNotEmpty(manager::queue_adhoc_task($task, true));
 385          $this->assertEquals(2, count(manager::get_adhoc_tasks('core\task\adhoc_test_task')));
 386  
 387          // Schedule same adhoc task with different custom data.
 388          $task = new adhoc_test_task();
 389          $task->set_custom_data(array('courseid' => 1));
 390          $this->assertNotEmpty(manager::queue_adhoc_task($task, true));
 391          $this->assertEquals(3, count(manager::get_adhoc_tasks('core\task\adhoc_test_task')));
 392  
 393          // Schedule same adhoc task with same custom data.
 394          $task = new adhoc_test_task();
 395          $task->set_custom_data(array('courseid' => 1));
 396          $this->assertEmpty(manager::queue_adhoc_task($task, true));
 397          $this->assertEquals(3, count(manager::get_adhoc_tasks('core\task\adhoc_test_task')));
 398  
 399          // Schedule same adhoc task with same custom data and a user.
 400          $task = new adhoc_test_task();
 401          $task->set_custom_data(array('courseid' => 1));
 402          $task->set_userid($user->id);
 403          $this->assertNotEmpty(manager::queue_adhoc_task($task, true));
 404          $this->assertEquals(4, count(manager::get_adhoc_tasks('core\task\adhoc_test_task')));
 405  
 406          // Schedule same adhoc task without custom data.
 407          // Note: This task was created earlier.
 408          $task = new adhoc_test_task();
 409          $this->assertNotEmpty(manager::queue_adhoc_task($task, true));
 410          $this->assertEquals(5, count(manager::get_adhoc_tasks('core\task\adhoc_test_task')));
 411  
 412          // Schedule same adhoc task without custom data (again).
 413          $task5 = new adhoc_test_task();
 414          $this->assertEmpty(manager::queue_adhoc_task($task5, true));
 415          $this->assertEquals(5, count(manager::get_adhoc_tasks('core\task\adhoc_test_task')));
 416  
 417          // Schedule same adhoc task without custom data but with a userid.
 418          $task6 = new adhoc_test_task();
 419          $user = \core_user::get_user_by_username('admin');
 420          $task6->set_userid($user->id);
 421          $this->assertNotEmpty(manager::queue_adhoc_task($task6, true));
 422          $this->assertEquals(6, count(manager::get_adhoc_tasks('core\task\adhoc_test_task')));
 423  
 424          // Schedule same adhoc task again without custom data but with a userid.
 425          $task6 = new adhoc_test_task();
 426          $user = \core_user::get_user_by_username('admin');
 427          $task6->set_userid($user->id);
 428          $this->assertEmpty(manager::queue_adhoc_task($task6, true));
 429          $this->assertEquals(6, count(manager::get_adhoc_tasks('core\task\adhoc_test_task')));
 430      }
 431  
 432      /**
 433       * Test that when no userid is specified, it returns empty from the DB
 434       * too.
 435       * @covers \core\task\adhoc_task::get_userid
 436       */
 437      public function test_adhoc_task_user_empty() {
 438          $this->resetAfterTest(true);
 439  
 440          // Create an adhoc task in future.
 441          $task = new adhoc_test_task();
 442          manager::queue_adhoc_task($task);
 443  
 444          // Get it back from the scheduler.
 445          $now = time();
 446          $task = manager::get_next_adhoc_task($now);
 447          manager::adhoc_task_complete($task);
 448  
 449          $this->assertEmpty($task->get_userid());
 450      }
 451  
 452      /**
 453       * Test that when a userid is specified, that userid is subsequently
 454       * returned.
 455       *
 456       * @covers \core\task\adhoc_task::set_userid
 457       * @covers \core\task\adhoc_task::get_userid
 458       */
 459      public function test_adhoc_task_user_set() {
 460          $this->resetAfterTest(true);
 461  
 462          // Create an adhoc task in future.
 463          $task = new adhoc_test_task();
 464          $user = \core_user::get_user_by_username('admin');
 465          $task->set_userid($user->id);
 466          manager::queue_adhoc_task($task);
 467  
 468          // Get it back from the scheduler.
 469          $now = time();
 470          $task = manager::get_next_adhoc_task($now);
 471          manager::adhoc_task_complete($task);
 472  
 473          $this->assertEquals($user->id, $task->get_userid());
 474      }
 475  
 476      /**
 477       * Test get_concurrency_limit() method to return 0 by default.
 478       *
 479       * @covers \core\task\adhoc_task::get_concurrency_limit
 480       */
 481      public function test_get_concurrency_limit() {
 482          $this->resetAfterTest(true);
 483          $task = new adhoc_test_task();
 484          $concurrencylimit = $task->get_concurrency_limit();
 485          $this->assertEquals(0, $concurrencylimit);
 486      }
 487  
 488      /**
 489       * Test get_concurrency_limit() method to return a default value set in config.
 490       * @covers \core\task\adhoc_task::get_concurrency_limit
 491       */
 492      public function test_get_concurrency_limit_default() {
 493          $this->resetAfterTest(true);
 494          set_config('task_concurrency_limit_default', 10);
 495          $task = new adhoc_test_task();
 496          $concurrencylimit = $task->get_concurrency_limit();
 497          $this->assertEquals(10, $concurrencylimit);
 498      }
 499  
 500      /**
 501       * Test get_concurrency_limit() method to return a value for specific task class.
 502       * @covers \core\task\adhoc_task::get_concurrency_limit
 503       */
 504      public function test_get_concurrency_limit_for_task() {
 505          global $CFG;
 506          $this->resetAfterTest(true);
 507          set_config('task_concurrency_limit_default', 10);
 508          $CFG->task_concurrency_limit = array('core\task\adhoc_test_task' => 5);
 509          $task = new adhoc_test_task();
 510          $concurrencylimit = $task->get_concurrency_limit();
 511          $this->assertEquals(5, $concurrencylimit);
 512      }
 513  
 514      /**
 515       * Test adhoc task sorting.
 516       * @covers ::get_next_adhoc_task
 517       */
 518      public function test_get_next_adhoc_task_sorting() {
 519          $this->resetAfterTest(true);
 520  
 521          // Create adhoc tasks.
 522          $task1 = new adhoc_test_task();
 523          $task1->set_next_run_time(1510000000);
 524          $task1->set_custom_data_as_string('Task 1');
 525          manager::queue_adhoc_task($task1);
 526  
 527          $task2 = new adhoc_test_task();
 528          $task2->set_next_run_time(1520000000);
 529          $task2->set_custom_data_as_string('Task 2');
 530          manager::queue_adhoc_task($task2);
 531  
 532          $task3 = new adhoc_test_task();
 533          $task3->set_next_run_time(1520000000);
 534          $task3->set_custom_data_as_string('Task 3');
 535          manager::queue_adhoc_task($task3);
 536  
 537          // Shuffle tasks.
 538          $task1->set_next_run_time(1540000000);
 539          manager::reschedule_or_queue_adhoc_task($task1);
 540  
 541          $task3->set_next_run_time(1530000000);
 542          manager::reschedule_or_queue_adhoc_task($task3);
 543  
 544          $task2->set_next_run_time(1530000000);
 545          manager::reschedule_or_queue_adhoc_task($task2);
 546  
 547          // Confirm, that tasks are sorted by nextruntime and then by id (ascending).
 548          $task = manager::get_next_adhoc_task(time());
 549          $this->assertEquals('Task 2', $task->get_custom_data_as_string());
 550          manager::adhoc_task_complete($task);
 551  
 552          $task = manager::get_next_adhoc_task(time());
 553          $this->assertEquals('Task 3', $task->get_custom_data_as_string());
 554          manager::adhoc_task_complete($task);
 555  
 556          $task = manager::get_next_adhoc_task(time());
 557          $this->assertEquals('Task 1', $task->get_custom_data_as_string());
 558          manager::adhoc_task_complete($task);
 559      }
 560  
 561      /**
 562       * Test adhoc task run from CLI.
 563       * @covers ::run_adhoc_from_cli
 564       */
 565      public function test_run_adhoc_from_cli() {
 566          $this->resetAfterTest(true);
 567  
 568          $taskid = 1;
 569  
 570          if (!manager::is_runnable()) {
 571              $this->markTestSkipped("Cannot run tasks");
 572          }
 573  
 574          ob_start();
 575          manager::run_adhoc_from_cli($taskid);
 576          $output = ob_get_contents();
 577          ob_end_clean();
 578  
 579          $this->assertMatchesRegularExpression(
 580              sprintf('!admin/cli/adhoc_task.php\W+--id=%d\W+--force!', $taskid),
 581              $output
 582          );
 583      }
 584  
 585      /**
 586       * Test adhoc class run from CLI.
 587       * @covers ::run_all_adhoc_from_cli
 588       */
 589      public function test_run_all_adhoc_from_cli() {
 590          $this->resetAfterTest(true);
 591  
 592          $classname = 'fake';
 593  
 594          if (!manager::is_runnable()) {
 595              $this->markTestSkipped("Cannot run tasks");
 596          }
 597  
 598          ob_start();
 599          manager::run_all_adhoc_from_cli(false, $classname);
 600          $output = ob_get_contents();
 601          ob_end_clean();
 602  
 603          $this->assertMatchesRegularExpression(
 604              sprintf('!admin/cli/adhoc_task.php\W+--classname=%s\W+--force!', $classname),
 605              $output
 606          );
 607      }
 608  }