Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 39 and 310]

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * This file contains the unit tests for the task logging system.
  19   *
  20   * @package   core
  21   * @category  phpunit
  22   * @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
  23   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  
  26  defined('MOODLE_INTERNAL') || die();
  27  require_once (__DIR__ . '/fixtures/task_fixtures.php');
  28  
  29  
  30  /**
  31   * This file contains the unit tests for the task logging system.
  32   *
  33   * @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
  34   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  35   */
  36  class core_task_logmanager extends advanced_testcase {
  37  
  38      /**
  39       * @var \moodle_database The original database prior to mocking
  40       */
  41      protected $DB;
  42  
  43      /**
  44       * Relevant tearDown for logging tests.
  45       */
  46      public function tearDown() {
  47          global $DB;
  48  
  49          // Ensure that any logging is always ended.
  50          \core\task\logmanager::finalise_log();
  51  
  52          if (null !== $this->DB) {
  53              $DB = $this->DB;
  54              $this->DB = null;
  55          }
  56      }
  57  
  58      /**
  59       * When the logmode is set to none, logging should not start.
  60       */
  61      public function test_logmode_none() {
  62          global $CFG;
  63          $this->resetAfterTest();
  64  
  65          $CFG->task_logmode = \core\task\logmanager::MODE_NONE;
  66  
  67          $initialbufferstate = ob_get_status();
  68  
  69          $task = $this->get_test_adhoc_task();
  70          \core\task\logmanager::start_logging($task);
  71  
  72          // There will be no additional output buffer.
  73          $this->assertEquals($initialbufferstate, ob_get_status());
  74      }
  75  
  76      /**
  77       * When the logmode is set to all that log capture is started.
  78       */
  79      public function test_start_logmode_all() {
  80          global $CFG;
  81          $this->resetAfterTest();
  82  
  83          $CFG->task_logmode = \core\task\logmanager::MODE_ALL;
  84  
  85          $initialbufferstate = ob_get_status();
  86  
  87          $task = $this->get_test_adhoc_task();
  88          \core\task\logmanager::start_logging($task);
  89  
  90          // Fetch the new output buffer state.
  91          $state = ob_get_status();
  92  
  93          // There will be no additional output buffer.
  94          $this->assertNotEquals($initialbufferstate, $state);
  95      }
  96  
  97      /**
  98       * When the logmode is set to fail that log capture is started.
  99       */
 100      public function test_start_logmode_fail() {
 101          global $CFG;
 102          $this->resetAfterTest();
 103  
 104          $CFG->task_logmode = \core\task\logmanager::MODE_FAILONLY;
 105  
 106          $initialbufferstate = ob_get_status();
 107  
 108          $task = $this->get_test_adhoc_task();
 109          \core\task\logmanager::start_logging($task);
 110  
 111          // Fetch the new output buffer state.
 112          $state = ob_get_status();
 113  
 114          // There will be no additional output buffer.
 115          $this->assertNotEquals($initialbufferstate, $state);
 116      }
 117  
 118      /**
 119       * When the logmode is set to fail, passing adhoc tests should not be logged.
 120       */
 121      public function test_logmode_fail_with_passing_adhoc_task() {
 122          global $CFG;
 123          $this->resetAfterTest();
 124  
 125          $CFG->task_logmode = \core\task\logmanager::MODE_FAILONLY;
 126  
 127          $logger = $this->get_mocked_logger();
 128  
 129          $initialbufferstate = ob_get_status();
 130  
 131          $task = $this->get_test_adhoc_task();
 132          \core\task\logmanager::start_logging($task);
 133  
 134          \core\task\manager::adhoc_task_complete($task);
 135  
 136          $this->assertEmpty($logger::$storelogfortask);
 137      }
 138  
 139      /**
 140       * When the logmode is set to fail, passing scheduled tests should not be logged.
 141       */
 142      public function test_logmode_fail_with_passing_scheduled_task() {
 143          global $CFG;
 144          $this->resetAfterTest();
 145  
 146          $CFG->task_logmode = \core\task\logmanager::MODE_FAILONLY;
 147  
 148          $logger = $this->get_mocked_logger();
 149  
 150          $initialbufferstate = ob_get_status();
 151  
 152          $task = $this->get_test_scheduled_task();
 153          \core\task\logmanager::start_logging($task);
 154  
 155          \core\task\manager::scheduled_task_complete($task);
 156  
 157          $this->assertEmpty($logger::$storelogfortask);
 158      }
 159  
 160      /**
 161       * When the logmode is set to fail, failing adhoc tests should be logged.
 162       */
 163      public function test_logmode_fail_with_failing_adhoc_task() {
 164          global $CFG;
 165  
 166          $this->resetAfterTest();
 167  
 168          // Mock the database. Marking jobs as failed updates a DB record which doesn't exist.
 169          $this->mock_database();
 170  
 171          $task = $this->get_test_adhoc_task();
 172  
 173          $CFG->task_logmode = \core\task\logmanager::MODE_FAILONLY;
 174  
 175          $logger = $this->get_mocked_logger();
 176  
 177          \core\task\logmanager::start_logging($task);
 178          \core\task\manager::adhoc_task_failed($task);
 179  
 180          $this->assertCount(1, $logger::$storelogfortask);
 181          $this->assertEquals($task, $logger::$storelogfortask[0][0]);
 182          $this->assertTrue($logger::$storelogfortask[0][2]);
 183      }
 184  
 185      /**
 186       * When the logmode is set to fail, failing scheduled tests should be logged.
 187       */
 188      public function test_logmode_fail_with_failing_scheduled_task() {
 189          global $CFG;
 190  
 191          $this->resetAfterTest();
 192  
 193          // Mock the database. Marking jobs as failed updates a DB record which doesn't exist.
 194          $this->mock_database();
 195  
 196          $task = $this->get_test_scheduled_task();
 197  
 198          $CFG->task_logmode = \core\task\logmanager::MODE_FAILONLY;
 199  
 200          $logger = $this->get_mocked_logger();
 201  
 202          \core\task\logmanager::start_logging($task);
 203          \core\task\manager::scheduled_task_failed($task);
 204  
 205          $this->assertCount(1, $logger::$storelogfortask);
 206          $this->assertEquals($task, $logger::$storelogfortask[0][0]);
 207          $this->assertTrue($logger::$storelogfortask[0][2]);
 208      }
 209  
 210      /**
 211       * When the logmode is set to fail, failing adhoc tests should be logged.
 212       */
 213      public function test_logmode_any_with_failing_adhoc_task() {
 214          global $CFG;
 215  
 216          $this->resetAfterTest();
 217  
 218          // Mock the database. Marking jobs as failed updates a DB record which doesn't exist.
 219          $this->mock_database();
 220  
 221          $task = $this->get_test_adhoc_task();
 222  
 223          $CFG->task_logmode = \core\task\logmanager::MODE_FAILONLY;
 224  
 225          $logger = $this->get_mocked_logger();
 226  
 227          \core\task\logmanager::start_logging($task);
 228          \core\task\manager::adhoc_task_failed($task);
 229  
 230          $this->assertCount(1, $logger::$storelogfortask);
 231          $this->assertEquals($task, $logger::$storelogfortask[0][0]);
 232          $this->assertTrue($logger::$storelogfortask[0][2]);
 233      }
 234  
 235      /**
 236       * When the logmode is set to fail, failing scheduled tests should be logged.
 237       */
 238      public function test_logmode_any_with_failing_scheduled_task() {
 239          global $CFG;
 240  
 241          $this->resetAfterTest();
 242  
 243          // Mock the database. Marking jobs as failed updates a DB record which doesn't exist.
 244          $this->mock_database();
 245  
 246          $task = $this->get_test_scheduled_task();
 247  
 248          $CFG->task_logmode = \core\task\logmanager::MODE_FAILONLY;
 249  
 250          $logger = $this->get_mocked_logger();
 251  
 252          \core\task\logmanager::start_logging($task);
 253          \core\task\manager::scheduled_task_failed($task);
 254  
 255          $this->assertCount(1, $logger::$storelogfortask);
 256          $this->assertEquals($task, $logger::$storelogfortask[0][0]);
 257          $this->assertTrue($logger::$storelogfortask[0][2]);
 258      }
 259  
 260      /**
 261       * When the logmode is set to fail, passing adhoc tests should be logged.
 262       */
 263      public function test_logmode_any_with_passing_adhoc_task() {
 264          global $CFG;
 265  
 266          $this->resetAfterTest();
 267  
 268          $this->mock_database();
 269  
 270          $task = $this->get_test_adhoc_task();
 271  
 272          $CFG->task_logmode = \core\task\logmanager::MODE_ALL;
 273  
 274          $logger = $this->get_mocked_logger();
 275  
 276          \core\task\logmanager::start_logging($task);
 277          \core\task\manager::adhoc_task_complete($task);
 278  
 279          $this->assertCount(1, $logger::$storelogfortask);
 280          $this->assertEquals($task, $logger::$storelogfortask[0][0]);
 281          $this->assertFalse($logger::$storelogfortask[0][2]);
 282      }
 283  
 284      /**
 285       * When the logmode is set to fail, passing scheduled tests should be logged.
 286       */
 287      public function test_logmode_any_with_passing_scheduled_task() {
 288          global $CFG;
 289  
 290          $this->resetAfterTest();
 291  
 292          $this->mock_database();
 293  
 294          $task = $this->get_test_scheduled_task();
 295  
 296          $CFG->task_logmode = \core\task\logmanager::MODE_ALL;
 297  
 298          $logger = $this->get_mocked_logger();
 299  
 300          \core\task\logmanager::start_logging($task);
 301          \core\task\manager::scheduled_task_complete($task);
 302  
 303          $this->assertCount(1, $logger::$storelogfortask);
 304          $this->assertEquals($task, $logger::$storelogfortask[0][0]);
 305          $this->assertFalse($logger::$storelogfortask[0][2]);
 306      }
 307  
 308      /**
 309       * Ensure that start_logging cannot be called in a nested fashion.
 310       */
 311      public function test_prevent_nested_logging() {
 312          $this->resetAfterTest();
 313  
 314          $task = $this->get_test_adhoc_task();
 315          \core\task\logmanager::start_logging($task);
 316  
 317          $this->expectException(\coding_exception::class);
 318          \core\task\logmanager::start_logging($task);
 319      }
 320  
 321      /**
 322       * Ensure that logging can be called after a previous log has finished.
 323       */
 324      public function test_repeated_usages() {
 325          $this->resetAfterTest();
 326  
 327          $logger = $this->get_mocked_logger();
 328  
 329          $task = $this->get_test_adhoc_task();
 330          \core\task\logmanager::start_logging($task);
 331          \core\task\logmanager::finalise_log();
 332  
 333          \core\task\logmanager::start_logging($task);
 334          \core\task\logmanager::finalise_log();
 335  
 336          $this->assertCount(2, $logger::$storelogfortask);
 337          $this->assertEquals($task, $logger::$storelogfortask[0][0]);
 338          $this->assertFalse($logger::$storelogfortask[0][2]);
 339          $this->assertEquals($task, $logger::$storelogfortask[1][0]);
 340          $this->assertFalse($logger::$storelogfortask[1][2]);
 341      }
 342  
 343      /**
 344       * Enusre that when finalise_log is called when logging is not active, nothing happens.
 345       */
 346      public function test_finalise_log_no_logging() {
 347          $initialbufferstate = ob_get_status();
 348  
 349          \core\task\logmanager::finalise_log();
 350  
 351          // There will be no additional output buffer.
 352          $this->assertEquals($initialbufferstate, ob_get_status());
 353      }
 354  
 355      /**
 356       * When log capture is enabled, calls to the flush function should cause log output to be both returned and captured.
 357       */
 358      public function test_flush_on_own_buffer() {
 359          $this->resetAfterTest();
 360  
 361          $logger = $this->get_mocked_logger();
 362  
 363          $testoutput = "I am the output under test.\n";
 364  
 365          $task = $this->get_test_adhoc_task();
 366          \core\task\logmanager::start_logging($task);
 367  
 368          echo $testoutput;
 369  
 370          $this->expectOutputString($testoutput);
 371          \core\task\logmanager::flush();
 372  
 373          // Finalise the log.
 374          \core\task\logmanager::finalise_log();
 375  
 376          $this->assertCount(1, $logger::$storelogfortask);
 377          $this->assertEquals($testoutput, file_get_contents($logger::$storelogfortask[0][1]));
 378      }
 379  
 380      /**
 381       * When log capture is enabled, calls to the flush function should not affect any subsequent ob_start.
 382       */
 383      public function test_flush_does_not_flush_inner_buffers() {
 384          $this->resetAfterTest();
 385  
 386          $logger = $this->get_mocked_logger();
 387  
 388          $testoutput = "I am the output under test.\n";
 389  
 390          $task = $this->get_test_adhoc_task();
 391          \core\task\logmanager::start_logging($task);
 392  
 393          ob_start();
 394          echo $testoutput;
 395          ob_end_clean();
 396  
 397          \core\task\logmanager::flush();
 398  
 399          // Finalise the log.
 400          \core\task\logmanager::finalise_log();
 401  
 402          $this->assertCount(1, $logger::$storelogfortask);
 403  
 404          // The task logger should not have captured the content of the inner buffer.
 405          $this->assertEquals('', file_get_contents($logger::$storelogfortask[0][1]));
 406      }
 407  
 408      /**
 409       * When log capture is enabled, calls to the flush function should not affect any subsequent ob_start.
 410       */
 411      public function test_inner_flushed_buffers_are_logged() {
 412          $this->resetAfterTest();
 413  
 414          $logger = $this->get_mocked_logger();
 415  
 416          $testoutput = "I am the output under test.\n";
 417  
 418          $task = $this->get_test_adhoc_task();
 419          \core\task\logmanager::start_logging($task);
 420  
 421          // We are going to flush the inner buffer. That means that we should expect the output immediately.
 422          $this->expectOutputString($testoutput);
 423  
 424          ob_start();
 425          echo $testoutput;
 426          ob_end_flush();
 427  
 428          // Finalise the log.
 429          \core\task\logmanager::finalise_log();
 430  
 431          $this->assertCount(1, $logger::$storelogfortask);
 432  
 433          // The task logger should not have captured the content of the inner buffer.
 434          $this->assertEquals($testoutput, file_get_contents($logger::$storelogfortask[0][1]));
 435      }
 436  
 437      /**
 438       * Get an example adhoc task to use for testing.
 439       *
 440       * @return  \core\task\adhoc_task
 441       */
 442      protected function get_test_adhoc_task() : \core\task\adhoc_task {
 443          $task = $this->getMockForAbstractClass(\core\task\adhoc_task::class);
 444  
 445          // Mock a lock on the task.
 446          $lock = $this->getMockBuilder(\core\lock\lock::class)
 447              ->disableOriginalConstructor()
 448              ->getMock();
 449          $task->set_lock($lock);
 450  
 451          return $task;
 452      }
 453  
 454      /**
 455       * Get an example scheduled task to use for testing.
 456       *
 457       * @return  \core\task\scheduled_task
 458       */
 459      protected function get_test_scheduled_task() : \core\task\scheduled_task {
 460          $task = $this->getMockForAbstractClass(\core\task\scheduled_task::class);
 461  
 462          // Mock a lock on the task.
 463          $lock = $this->getMockBuilder(\core\lock\lock::class)
 464              ->disableOriginalConstructor()
 465              ->getMock();
 466          $task->set_lock($lock);
 467  
 468          return $task;
 469      }
 470  
 471      /**
 472       * Create and configure a mocked task logger.
 473       *
 474       * @return  \core\task\task_logger
 475       */
 476      protected function get_mocked_logger() {
 477          global $CFG;
 478  
 479          // We will modify config for the alternate logging class therefore we mnust reset after the test.
 480          $this->resetAfterTest();
 481  
 482          // Note PHPUnit does not support mocking static functions.
 483          $CFG->task_log_class = \task_logging_test_mocked_logger::class;
 484          \task_logging_test_mocked_logger::test_reset();
 485  
 486          return $CFG->task_log_class;
 487      }
 488  
 489      /**
 490       * Mock the database.
 491       */
 492      protected function mock_database() {
 493          global $DB;
 494  
 495          // Store the old Database for restoration in reset.
 496          $this->DB = $DB;
 497  
 498          $DB = $this->getMockBuilder(\moodle_database::class)
 499              ->getMock();
 500  
 501          $DB->method('get_record')
 502              ->willReturn((object) []);
 503      }
 504  }
 505  
 506  /**
 507   * Mocked logger.
 508   *
 509   * @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
 510   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 511   */
 512  class task_logging_test_mocked_logger implements \core\task\task_logger {
 513  
 514      /**
 515       * @var bool Whether this is configured.
 516       */
 517      public static $isconfigured = true;
 518  
 519      /**
 520       * @var array Arguments that store_log_for_task was called with.
 521       */
 522      public static $storelogfortask = [];
 523  
 524      /**
 525       * @var bool Whether this logger has a report.
 526       */
 527      public static $haslogreport = true;
 528  
 529      /**
 530       * Reset the test class.
 531       */
 532      public static function test_reset() {
 533          self::$isconfigured = true;
 534          self::$storelogfortask = [];
 535          self::$haslogreport = true;
 536      }
 537  
 538      /**
 539       * Whether the task is configured and ready to log.
 540       *
 541       * @return  bool
 542       */
 543      public static function is_configured() : bool {
 544          return self::$isconfigured;
 545      }
 546  
 547      /**
 548       * Store the log for the specified task.
 549       *
 550       * @param   \core\task\task_base   $task The task that the log belongs to.
 551       * @param   string      $logpath The path to the log on disk
 552       * @param   bool        $failed Whether the task failed
 553       * @param   int         $dbreads The number of DB reads
 554       * @param   int         $dbwrites The number of DB writes
 555       * @param   float       $timestart The start time of the task
 556       * @param   float       $timeend The end time of the task
 557       */
 558      public static function store_log_for_task(\core\task\task_base $task, string $logpath, bool $failed,
 559              int $dbreads, int $dbwrites, float $timestart, float $timeend) {
 560          self::$storelogfortask[] = func_get_args();
 561      }
 562  
 563      /**
 564       * Whether this task logger has a report available.
 565       *
 566       * @return  bool
 567       */
 568      public static function has_log_report() : bool {
 569          return self::$haslogreport;
 570      }
 571  
 572      /**
 573       * Get any URL available for viewing relevant task log reports.
 574       *
 575       * @param   string      $classname The task class to fetch for
 576       * @return  \moodle_url
 577       */
 578      public static function get_url_for_task_class(string $classname) : \moodle_url {
 579          return new \moodle_url('');
 580      }
 581  
 582  }