Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 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 310 and 311] [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403] [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   * Unit tests for backups cron helper.
  19   *
  20   * @package   core_backup
  21   * @category  phpunit
  22   * @copyright 2012 Frédéric Massart <fred@moodle.com>
  23   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  
  26  defined('MOODLE_INTERNAL') || die();
  27  
  28  global $CFG;
  29  require_once($CFG->dirroot . '/backup/util/helper/backup_cron_helper.class.php');
  30  require_once($CFG->dirroot . '/backup/util/interfaces/checksumable.class.php');
  31  require_once("$CFG->dirroot/backup/backup.class.php");
  32  
  33  /**
  34   * Unit tests for backup cron helper
  35   */
  36  class backup_cron_helper_testcase extends advanced_testcase {
  37      /**
  38       * Test {@link backup_cron_automated_helper::calculate_next_automated_backup}.
  39       */
  40      public function test_next_automated_backup() {
  41          global $CFG;
  42  
  43          $this->resetAfterTest();
  44          set_config('backup_auto_active', '1', 'backup');
  45  
  46          $this->setTimezone('Australia/Perth');
  47  
  48          // Notes
  49          // - backup_auto_weekdays starts on Sunday
  50          // - Tests cannot be done in the past
  51          // - Only the DST on the server side is handled.
  52  
  53          // Every Tue and Fri at 11pm.
  54          set_config('backup_auto_weekdays', '0010010', 'backup');
  55          set_config('backup_auto_hour', '23', 'backup');
  56          set_config('backup_auto_minute', '0', 'backup');
  57  
  58          $timezone = 99; // Ignored, everything is calculated in server timezone!!!
  59  
  60          $now = strtotime('next Monday 17:00:00');
  61          $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now);
  62          $this->assertEquals('2-23:00', date('w-H:i', $next));
  63  
  64          $now = strtotime('next Tuesday 18:00:00');
  65          $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now);
  66          $this->assertEquals('2-23:00', date('w-H:i', $next));
  67  
  68          $now = strtotime('next Wednesday 17:00:00');
  69          $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now);
  70          $this->assertEquals('5-23:00', date('w-H:i', $next));
  71  
  72          $now = strtotime('next Thursday 17:00:00');
  73          $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now);
  74          $this->assertEquals('5-23:00', date('w-H:i', $next));
  75  
  76          $now = strtotime('next Friday 17:00:00');
  77          $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now);
  78          $this->assertEquals('5-23:00', date('w-H:i', $next));
  79  
  80          $now = strtotime('next Saturday 17:00:00');
  81          $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now);
  82          $this->assertEquals('2-23:00', date('w-H:i', $next));
  83  
  84          $now = strtotime('next Sunday 17:00:00');
  85          $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now);
  86          $this->assertEquals('2-23:00', date('w-H:i', $next));
  87  
  88          // Every Sun and Sat at 12pm.
  89          set_config('backup_auto_weekdays', '1000001', 'backup');
  90          set_config('backup_auto_hour', '0', 'backup');
  91          set_config('backup_auto_minute', '0', 'backup');
  92  
  93          $now = strtotime('next Monday 17:00:00');
  94          $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now);
  95          $this->assertEquals('6-00:00', date('w-H:i', $next));
  96  
  97          $now = strtotime('next Tuesday 17:00:00');
  98          $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now);
  99          $this->assertEquals('6-00:00', date('w-H:i', $next));
 100  
 101          $now = strtotime('next Wednesday 17:00:00');
 102          $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now);
 103          $this->assertEquals('6-00:00', date('w-H:i', $next));
 104  
 105          $now = strtotime('next Thursday 17:00:00');
 106          $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now);
 107          $this->assertEquals('6-00:00', date('w-H:i', $next));
 108  
 109          $now = strtotime('next Friday 17:00:00');
 110          $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now);
 111          $this->assertEquals('6-00:00', date('w-H:i', $next));
 112  
 113          $now = strtotime('next Saturday 17:00:00');
 114          $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now);
 115          $this->assertEquals('0-00:00', date('w-H:i', $next));
 116  
 117          $now = strtotime('next Sunday 17:00:00');
 118          $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now);
 119          $this->assertEquals('6-00:00', date('w-H:i', $next));
 120  
 121          // Every Sun at 4am.
 122          set_config('backup_auto_weekdays', '1000000', 'backup');
 123          set_config('backup_auto_hour', '4', 'backup');
 124          set_config('backup_auto_minute', '0', 'backup');
 125  
 126          $now = strtotime('next Monday 17:00:00');
 127          $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now);
 128          $this->assertEquals('0-04:00', date('w-H:i', $next));
 129  
 130          $now = strtotime('next Tuesday 17:00:00');
 131          $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now);
 132          $this->assertEquals('0-04:00', date('w-H:i', $next));
 133  
 134          $now = strtotime('next Wednesday 17:00:00');
 135          $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now);
 136          $this->assertEquals('0-04:00', date('w-H:i', $next));
 137  
 138          $now = strtotime('next Thursday 17:00:00');
 139          $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now);
 140          $this->assertEquals('0-04:00', date('w-H:i', $next));
 141  
 142          $now = strtotime('next Friday 17:00:00');
 143          $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now);
 144          $this->assertEquals('0-04:00', date('w-H:i', $next));
 145  
 146          $now = strtotime('next Saturday 17:00:00');
 147          $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now);
 148          $this->assertEquals('0-04:00', date('w-H:i', $next));
 149  
 150          $now = strtotime('next Sunday 17:00:00');
 151          $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now);
 152          $this->assertEquals('0-04:00', date('w-H:i', $next));
 153  
 154          // Every day but Wed at 8:30pm.
 155          set_config('backup_auto_weekdays', '1110111', 'backup');
 156          set_config('backup_auto_hour', '20', 'backup');
 157          set_config('backup_auto_minute', '30', 'backup');
 158  
 159          $now = strtotime('next Monday 17:00:00');
 160          $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now);
 161          $this->assertEquals('1-20:30', date('w-H:i', $next));
 162  
 163          $now = strtotime('next Tuesday 17:00:00');
 164          $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now);
 165          $this->assertEquals('2-20:30', date('w-H:i', $next));
 166  
 167          $now = strtotime('next Wednesday 17:00:00');
 168          $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now);
 169          $this->assertEquals('4-20:30', date('w-H:i', $next));
 170  
 171          $now = strtotime('next Thursday 17:00:00');
 172          $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now);
 173          $this->assertEquals('4-20:30', date('w-H:i', $next));
 174  
 175          $now = strtotime('next Friday 17:00:00');
 176          $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now);
 177          $this->assertEquals('5-20:30', date('w-H:i', $next));
 178  
 179          $now = strtotime('next Saturday 17:00:00');
 180          $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now);
 181          $this->assertEquals('6-20:30', date('w-H:i', $next));
 182  
 183          $now = strtotime('next Sunday 17:00:00');
 184          $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now);
 185          $this->assertEquals('0-20:30', date('w-H:i', $next));
 186  
 187          // Sun, Tue, Thu, Sat at 12pm.
 188          set_config('backup_auto_weekdays', '1010101', 'backup');
 189          set_config('backup_auto_hour', '0', 'backup');
 190          set_config('backup_auto_minute', '0', 'backup');
 191  
 192          $now = strtotime('next Monday 13:00:00');
 193          $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now);
 194          $this->assertEquals('2-00:00', date('w-H:i', $next));
 195  
 196          $now = strtotime('next Tuesday 13:00:00');
 197          $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now);
 198          $this->assertEquals('4-00:00', date('w-H:i', $next));
 199  
 200          $now = strtotime('next Wednesday 13:00:00');
 201          $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now);
 202          $this->assertEquals('4-00:00', date('w-H:i', $next));
 203  
 204          $now = strtotime('next Thursday 13:00:00');
 205          $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now);
 206          $this->assertEquals('6-00:00', date('w-H:i', $next));
 207  
 208          $now = strtotime('next Friday 13:00:00');
 209          $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now);
 210          $this->assertEquals('6-00:00', date('w-H:i', $next));
 211  
 212          $now = strtotime('next Saturday 13:00:00');
 213          $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now);
 214          $this->assertEquals('0-00:00', date('w-H:i', $next));
 215  
 216          $now = strtotime('next Sunday 13:00:00');
 217          $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now);
 218          $this->assertEquals('2-00:00', date('w-H:i', $next));
 219  
 220          // None.
 221          set_config('backup_auto_weekdays', '0000000', 'backup');
 222          set_config('backup_auto_hour', '15', 'backup');
 223          set_config('backup_auto_minute', '30', 'backup');
 224  
 225          $now = strtotime('next Sunday 13:00:00');
 226          $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now);
 227          $this->assertEquals('0', $next);
 228  
 229          // Playing with timezones.
 230          set_config('backup_auto_weekdays', '1111111', 'backup');
 231          set_config('backup_auto_hour', '20', 'backup');
 232          set_config('backup_auto_minute', '00', 'backup');
 233  
 234          $this->setTimezone('Australia/Perth');
 235          $now = strtotime('18:00:00');
 236          $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now);
 237          $this->assertEquals(date('w-20:00'), date('w-H:i', $next));
 238  
 239          $this->setTimezone('Europe/Brussels');
 240          $now = strtotime('18:00:00');
 241          $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now);
 242          $this->assertEquals(date('w-20:00'), date('w-H:i', $next));
 243  
 244          $this->setTimezone('America/New_York');
 245          $now = strtotime('18:00:00');
 246          $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now);
 247          $this->assertEquals(date('w-20:00'), date('w-H:i', $next));
 248      }
 249  
 250      /**
 251       * Test {@link backup_cron_automated_helper::get_backups_to_delete}.
 252       */
 253      public function test_get_backups_to_delete() {
 254          $this->resetAfterTest();
 255          // Active only backup_auto_max_kept config to 2 days.
 256          set_config('backup_auto_max_kept', '2', 'backup');
 257          set_config('backup_auto_delete_days', '0', 'backup');
 258          set_config('backup_auto_min_kept', '0', 'backup');
 259  
 260          // No backups to delete.
 261          $backupfiles = array(
 262              '1000000000' => 'file1.mbz',
 263              '1000432000' => 'file3.mbz'
 264          );
 265          $deletedbackups = testable_backup_cron_automated_helper::testable_get_backups_to_delete($backupfiles, 1000432000);
 266          $this->assertFalse($deletedbackups);
 267  
 268          // Older backup to delete.
 269          $backupfiles['1000172800'] = 'file2.mbz';
 270          $deletedbackups = testable_backup_cron_automated_helper::testable_get_backups_to_delete($backupfiles, 1000432000);
 271          $this->assertEquals(1, count($deletedbackups));
 272          $this->assertArrayHasKey('1000000000', $backupfiles);
 273          $this->assertEquals('file1.mbz', $backupfiles['1000000000']);
 274  
 275          // Activate backup_auto_max_kept to 5 days and backup_auto_delete_days to 10 days.
 276          set_config('backup_auto_max_kept', '5', 'backup');
 277          set_config('backup_auto_delete_days', '10', 'backup');
 278          set_config('backup_auto_min_kept', '0', 'backup');
 279  
 280          // No backups to delete. Timestamp is 1000000000 + 10 days.
 281          $backupfiles['1000432001'] = 'file4.mbz';
 282          $backupfiles['1000864000'] = 'file5.mbz';
 283          $deletedbackups = testable_backup_cron_automated_helper::testable_get_backups_to_delete($backupfiles, 1000864000);
 284          $this->assertFalse($deletedbackups);
 285  
 286          // One old backup to delete. Timestamp is 1000000000 + 10 days + 1 second.
 287          $deletedbackups = testable_backup_cron_automated_helper::testable_get_backups_to_delete($backupfiles, 1000864001);
 288          $this->assertEquals(1, count($deletedbackups));
 289          $this->assertArrayHasKey('1000000000', $backupfiles);
 290          $this->assertEquals('file1.mbz', $backupfiles['1000000000']);
 291  
 292          // Two old backups to delete. Timestamp is 1000000000 + 12 days + 1 second.
 293          $deletedbackups = testable_backup_cron_automated_helper::testable_get_backups_to_delete($backupfiles, 1001036801);
 294          $this->assertEquals(2, count($deletedbackups));
 295          $this->assertArrayHasKey('1000000000', $backupfiles);
 296          $this->assertEquals('file1.mbz', $backupfiles['1000000000']);
 297          $this->assertArrayHasKey('1000172800', $backupfiles);
 298          $this->assertEquals('file2.mbz', $backupfiles['1000172800']);
 299  
 300          // Activate backup_auto_max_kept to 5 days, backup_auto_delete_days to 10 days and backup_auto_min_kept to 2.
 301          set_config('backup_auto_max_kept', '5', 'backup');
 302          set_config('backup_auto_delete_days', '10', 'backup');
 303          set_config('backup_auto_min_kept', '2', 'backup');
 304  
 305          // Three instead of four old backups are deleted. Timestamp is 1000000000 + 16 days.
 306          $deletedbackups = testable_backup_cron_automated_helper::testable_get_backups_to_delete($backupfiles, 1001382400);
 307          $this->assertEquals(3, count($deletedbackups));
 308          $this->assertArrayHasKey('1000000000', $backupfiles);
 309          $this->assertEquals('file1.mbz', $backupfiles['1000000000']);
 310          $this->assertArrayHasKey('1000172800', $backupfiles);
 311          $this->assertEquals('file2.mbz', $backupfiles['1000172800']);
 312          $this->assertArrayHasKey('1000432000', $backupfiles);
 313          $this->assertEquals('file3.mbz', $backupfiles['1000432000']);
 314  
 315          // Three instead of all five backups are deleted. Timestamp is 1000000000 + 60 days.
 316          $deletedbackups = testable_backup_cron_automated_helper::testable_get_backups_to_delete($backupfiles, 1005184000);
 317          $this->assertEquals(3, count($deletedbackups));
 318          $this->assertArrayHasKey('1000000000', $backupfiles);
 319          $this->assertEquals('file1.mbz', $backupfiles['1000000000']);
 320          $this->assertArrayHasKey('1000172800', $backupfiles);
 321          $this->assertEquals('file2.mbz', $backupfiles['1000172800']);
 322          $this->assertArrayHasKey('1000432000', $backupfiles);
 323          $this->assertEquals('file3.mbz', $backupfiles['1000432000']);
 324      }
 325  
 326      /**
 327       * Test {@link backup_cron_automated_helper::is_course_modified}.
 328       */
 329      public function test_is_course_modified() {
 330          $this->resetAfterTest();
 331          $this->preventResetByRollback();
 332  
 333          set_config('enabled_stores', 'logstore_standard', 'tool_log');
 334          set_config('buffersize', 0, 'logstore_standard');
 335          set_config('logguests', 1, 'logstore_standard');
 336  
 337          $course = $this->getDataGenerator()->create_course();
 338  
 339          // New courses should be backed up.
 340          $this->assertTrue(testable_backup_cron_automated_helper::testable_is_course_modified($course->id, 0));
 341  
 342          $timepriortobackup = time();
 343          $this->waitForSecond();
 344          $otherarray = [
 345              'format' => backup::FORMAT_MOODLE,
 346              'mode' => backup::MODE_GENERAL,
 347              'interactive' => backup::INTERACTIVE_YES,
 348              'type' => backup::TYPE_1COURSE,
 349          ];
 350          $event = \core\event\course_backup_created::create([
 351              'objectid' => $course->id,
 352              'context'  => context_course::instance($course->id),
 353              'other'    => $otherarray
 354          ]);
 355          $event->trigger();
 356  
 357          // If the only action since last backup was a backup then no backup.
 358          $this->assertFalse(testable_backup_cron_automated_helper::testable_is_course_modified($course->id, $timepriortobackup));
 359  
 360          $course->groupmode = SEPARATEGROUPS;
 361          $course->groupmodeforce = true;
 362          update_course($course);
 363  
 364          // Updated courses should be backed up.
 365          $this->assertTrue(testable_backup_cron_automated_helper::testable_is_course_modified($course->id, $timepriortobackup));
 366      }
 367  
 368      /**
 369       * Create courses and backup records for tests.
 370       *
 371       * @return array Created courses.
 372       */
 373      private function course_setup() {
 374          global $DB;
 375  
 376          // Create test courses.
 377          $course1 = $this->getDataGenerator()->create_course(array('timecreated' => 1553402000)); // Newest.
 378          $course2 = $this->getDataGenerator()->create_course(array('timecreated' => 1552179600));
 379          $course3 = $this->getDataGenerator()->create_course(array('timecreated' => 1552179600));
 380          $course4 = $this->getDataGenerator()->create_course(array('timecreated' => 1552179600));
 381  
 382          // Create backup course records for the courses that need them.
 383          $backupcourse3 = new stdClass;
 384          $backupcourse3->courseid = $course3->id;
 385          $backupcourse3->laststatus = testable_backup_cron_automated_helper::BACKUP_STATUS_OK;
 386          $backupcourse3->nextstarttime = 1554858160;
 387          $DB->insert_record('backup_courses', $backupcourse3);
 388  
 389          $backupcourse4 = new stdClass;
 390          $backupcourse4->courseid = $course4->id;
 391          $backupcourse4->laststatus = testable_backup_cron_automated_helper::BACKUP_STATUS_OK;
 392          $backupcourse4->nextstarttime = 1554858160;
 393          $DB->insert_record('backup_courses', $backupcourse4);
 394  
 395          return array($course1, $course2, $course3, $course4);
 396      }
 397  
 398      /**
 399       * Test the selection and ordering of courses to be backed up.
 400       */
 401      public function test_get_courses() {
 402          $this->resetAfterTest();
 403  
 404          list($course1, $course2, $course3, $course4) = $this->course_setup();
 405  
 406          $now = 1559215025;
 407  
 408          // Get the courses in order.
 409          $courseset = testable_backup_cron_automated_helper::testable_get_courses($now);
 410  
 411          $coursearray = array();
 412          foreach ($courseset as $course) {
 413              if ($course->id != SITEID) { // Skip system course for test.
 414                  $coursearray[] = $course->id;
 415              }
 416  
 417          }
 418          $courseset->close();
 419  
 420          // First should be course 1, it is the more recently modified without a backup.
 421          $this->assertEquals($course1->id, $coursearray[0]);
 422  
 423          // Second should be course 2, it is the next more recently modified without a backup.
 424          $this->assertEquals($course2->id, $coursearray[1]);
 425  
 426          // Third should be course 3, it is the course with the oldest backup.
 427          $this->assertEquals($course3->id, $coursearray[2]);
 428  
 429          // Fourth should be course 4, it is the course with the newest backup.
 430          $this->assertEquals($course4->id, $coursearray[3]);
 431      }
 432  
 433      /**
 434       * Test the selection and ordering of courses to be backed up.
 435       * Where it is not yet time to start backups for courses with existing backups.
 436       */
 437      public function test_get_courses_starttime() {
 438          $this->resetAfterTest();
 439  
 440          list($course1, $course2, $course3, $course4) = $this->course_setup();
 441  
 442          $now = 1554858000;
 443  
 444          // Get the courses in order.
 445          $courseset = testable_backup_cron_automated_helper::testable_get_courses($now);
 446  
 447          $coursearray = array();
 448          foreach ($courseset as $course) {
 449              if ($course->id != SITEID) { // Skip system course for test.
 450                  $coursearray[] = $course->id;
 451              }
 452  
 453          }
 454          $courseset->close();
 455  
 456          // Should only be two courses.
 457          // First should be course 1, it is the more recently modified without a backup.
 458          $this->assertEquals($course1->id, $coursearray[0]);
 459  
 460          // Second should be course 2, it is the next more recently modified without a backup.
 461          $this->assertEquals($course2->id, $coursearray[1]);
 462      }
 463  
 464  }
 465  
 466  
 467  
 468  /**
 469   * Provides access to protected methods we want to explicitly test
 470   *
 471   * @copyright 2015 Jean-Philippe Gaudreau <jp.gaudreau@umontreal.ca>
 472   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 473   */
 474  class testable_backup_cron_automated_helper extends backup_cron_automated_helper {
 475  
 476      /**
 477       * Provides access to protected method get_backups_to_remove.
 478       *
 479       * @param array $backupfiles Existing backup files
 480       * @param int $now Starting time of the process
 481       * @return array Backup files to remove
 482       */
 483      public static function testable_get_backups_to_delete($backupfiles, $now) {
 484          return parent::get_backups_to_delete($backupfiles, $now);
 485      }
 486  
 487      /**
 488       * Provides access to protected method get_backups_to_remove.
 489       *
 490       * @param int $courseid course id to check
 491       * @param int $since timestamp, from which to check
 492       *
 493       * @return bool true if the course was modified, false otherwise. This also returns false if no readers are enabled. This is
 494       * intentional, since we cannot reliably determine if any modification was made or not.
 495       */
 496      public static function testable_is_course_modified($courseid, $since) {
 497          return parent::is_course_modified($courseid, $since);
 498      }
 499  
 500      /**
 501       * Provides access to protected method get_courses.
 502       *
 503       * @param int $now Timestamp to use.
 504       * @return moodle_recordset The returned courses as a Moodle recordest.
 505       */
 506      public static function testable_get_courses($now) {
 507          return parent::get_courses($now);
 508      }
 509  
 510  }