Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 39 and 401]

   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;
  18  
  19  defined('MOODLE_INTERNAL') || die();
  20  
  21  /**
  22   * Unit tests for the progress classes.
  23   *
  24   * @package core
  25   * @category test
  26   * @copyright 2013 The Open University
  27   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  28   */
  29  class progress_test extends \basic_testcase {
  30  
  31      /**
  32       * Tests for basic use with simple numeric progress.
  33       */
  34      public function test_basic() {
  35          $progress = new core_mock_progress();
  36  
  37          // Check values of empty progress things.
  38          $this->assertFalse($progress->is_in_progress_section());
  39  
  40          // Start progress counting, check basic values and check that update
  41          // gets called.
  42          $progress->start_progress('hello', 10);
  43          $this->assertTrue($progress->was_update_called());
  44          $this->assertTrue($progress->is_in_progress_section());
  45          $this->assertEquals('hello', $progress->get_current_description());
  46  
  47          // Check numeric position and indeterminate count.
  48          $this->assert_min_max(0.0, 0.0, $progress);
  49          $this->assertEquals(0, $progress->get_progress_count());
  50  
  51          // Make some progress and check that the time limit gets added.
  52          $progress->step_time();
  53          \core_php_time_limit::get_and_clear_unit_test_data();
  54          $progress->progress(2);
  55          $this->assertTrue($progress->was_update_called());
  56          $this->assertEquals(array(\core\progress\base::TIME_LIMIT_WITHOUT_PROGRESS),
  57                  \core_php_time_limit::get_and_clear_unit_test_data());
  58  
  59          // Check the new value.
  60          $this->assert_min_max(0.2, 0.2, $progress);
  61  
  62          // Do another progress run at same time, it should be ignored.
  63          $progress->progress(3);
  64          $this->assertFalse($progress->was_update_called());
  65          $this->assert_min_max(0.3, 0.3, $progress);
  66  
  67          // End the section. This should cause an update.
  68          $progress->end_progress();
  69          $this->assertTrue($progress->was_update_called());
  70  
  71          // Because there are no sections left open, it thinks we finished.
  72          $this->assert_min_max(1.0, 1.0, $progress);
  73  
  74          // There was 1 progress call.
  75          $this->assertEquals(1, $progress->get_progress_count());
  76      }
  77  
  78      /**
  79       * Tests progress that is nested and/or indeterminate.
  80       */
  81      public function test_nested() {
  82          // Outer progress goes from 0 to 10.
  83          $progress = new core_mock_progress();
  84          $progress->start_progress('hello', 10);
  85  
  86          // Get up to 4, check position.
  87          $progress->step_time();
  88          $progress->progress(4);
  89          $this->assert_min_max(0.4, 0.4, $progress);
  90          $this->assertEquals('hello', $progress->get_current_description());
  91  
  92          // Now start indeterminate progress.
  93          $progress->start_progress('world');
  94          $this->assert_min_max(0.4, 0.5, $progress);
  95          $this->assertEquals('world', $progress->get_current_description());
  96  
  97          // Do some indeterminate progress and count it (once per second).
  98          $progress->step_time();
  99          $progress->progress();
 100          $this->assertEquals(2, $progress->get_progress_count());
 101          $progress->progress();
 102          $this->assertEquals(2, $progress->get_progress_count());
 103          $progress->step_time();
 104          $progress->progress();
 105          $this->assertEquals(3, $progress->get_progress_count());
 106          $this->assert_min_max(0.4, 0.5, $progress);
 107  
 108          // Exit the indeterminate section.
 109          $progress->end_progress();
 110          $this->assert_min_max(0.5, 0.5, $progress);
 111  
 112          $progress->step_time();
 113          $progress->progress(7);
 114          $this->assert_min_max(0.7, 0.7, $progress);
 115  
 116          // Enter a numbered section (this time with a range of 5).
 117          $progress->start_progress('frogs', 5);
 118          $this->assert_min_max(0.7, 0.7, $progress);
 119          $progress->step_time();
 120          $progress->progress(1);
 121          $this->assert_min_max(0.72, 0.72, $progress);
 122          $progress->step_time();
 123          $progress->progress(3);
 124          $this->assert_min_max(0.76, 0.76, $progress);
 125  
 126          // Now enter another indeterminate section.
 127          $progress->start_progress('and');
 128          $this->assert_min_max(0.76, 0.78, $progress);
 129  
 130          // Make some progress, should increment indeterminate count.
 131          $progress->step_time();
 132          $progress->progress();
 133          $this->assertEquals(7, $progress->get_progress_count());
 134  
 135          // Enter numbered section, won't make any difference to values.
 136          $progress->start_progress('zombies', 2);
 137          $progress->step_time();
 138          $progress->progress(1);
 139          $this->assert_min_max(0.76, 0.78, $progress);
 140          $this->assertEquals(8, $progress->get_progress_count());
 141  
 142          // Leaving it will make no difference too.
 143          $progress->end_progress();
 144  
 145          // Leaving the indeterminate section will though.
 146          $progress->end_progress();
 147          $this->assert_min_max(0.78, 0.78, $progress);
 148  
 149          // Leave the two numbered sections.
 150          $progress->end_progress();
 151          $this->assert_min_max(0.8, 0.8, $progress);
 152          $progress->end_progress();
 153          $this->assertFalse($progress->is_in_progress_section());
 154      }
 155  
 156      /**
 157       * Tests the feature for 'weighting' nested progress.
 158       */
 159      public function test_nested_weighted() {
 160          $progress = new core_mock_progress();
 161          $progress->start_progress('', 10);
 162  
 163          // First nested child has 2 units of its own and is worth 1 unit.
 164          $progress->start_progress('', 2);
 165          $progress->step_time();
 166          $progress->progress(1);
 167          $this->assert_min_max(0.05, 0.05, $progress);
 168          $progress->end_progress();
 169          $this->assert_min_max(0.1, 0.1, $progress);
 170  
 171          // Next child has 2 units of its own but is worth 3 units.
 172          $progress->start_progress('weighted', 2, 3);
 173          $progress->step_time();
 174          $progress->progress(1);
 175          $this->assert_min_max(0.25, 0.25, $progress);
 176          $progress->end_progress();
 177          $this->assert_min_max(0.4, 0.4, $progress);
 178  
 179          // Next indeterminate child is worth 6 units.
 180          $progress->start_progress('', \core\progress\base::INDETERMINATE, 6);
 181          $progress->step_time();
 182          $progress->progress();
 183          $this->assert_min_max(0.4, 1.0, $progress);
 184          $progress->end_progress();
 185          $this->assert_min_max(1.0, 1.0, $progress);
 186      }
 187  
 188      /**
 189       * I had some issues with real use in backup/restore, this test is intended
 190       * to be similar.
 191       */
 192      public function test_realistic() {
 193          $progress = new core_mock_progress();
 194          $progress->start_progress('parent', 100);
 195          $progress->start_progress('child', 1);
 196          $progress->progress(1);
 197          $this->assert_min_max(0.01, 0.01, $progress);
 198          $progress->end_progress();
 199          $this->assert_min_max(0.01, 0.01, $progress);
 200      }
 201  
 202      /**
 203       * To avoid causing problems, progress needs to work for sections that have
 204       * zero entries.
 205       */
 206      public function test_zero() {
 207          $progress = new core_mock_progress();
 208          $progress->start_progress('parent', 100);
 209          $progress->progress(1);
 210          $this->assert_min_max(0.01, 0.01, $progress);
 211          $progress->start_progress('child', 0);
 212  
 213          // For 'zero' progress, the progress section as immediately complete
 214          // within the parent count, so it moves up to 2%.
 215          $this->assert_min_max(0.02, 0.02, $progress);
 216          $progress->progress(0);
 217          $this->assert_min_max(0.02, 0.02, $progress);
 218          $progress->end_progress();
 219          $this->assert_min_max(0.02, 0.02, $progress);
 220      }
 221  
 222      /**
 223       * Tests for any exceptions due to invalid calls.
 224       */
 225      public function test_exceptions() {
 226          $progress = new core_mock_progress();
 227  
 228          // Check errors when empty.
 229          try {
 230              $progress->progress();
 231              $this->fail();
 232          } catch (\coding_exception $e) {
 233              $this->assertEquals(1, preg_match('~without start_progress~', $e->getMessage()));
 234          }
 235          try {
 236              $progress->end_progress();
 237              $this->fail();
 238          } catch (\coding_exception $e) {
 239              $this->assertEquals(1, preg_match('~without start_progress~', $e->getMessage()));
 240          }
 241          try {
 242              $progress->get_current_description();
 243              $this->fail();
 244          } catch (\coding_exception $e) {
 245              $this->assertEquals(1, preg_match('~Not inside progress~', $e->getMessage()));
 246          }
 247          try {
 248              $progress->start_progress('', 1, 7);
 249              $this->fail();
 250          } catch (\coding_exception $e) {
 251              $this->assertEquals(1, preg_match('~must be 1~', $e->getMessage()));
 252          }
 253  
 254          // Check invalid start (-2).
 255          try {
 256              $progress->start_progress('hello', -2);
 257              $this->fail();
 258          } catch (\coding_exception $e) {
 259              $this->assertEquals(1, preg_match('~cannot be negative~', $e->getMessage()));
 260          }
 261  
 262          // Indeterminate when value expected.
 263          $progress->start_progress('hello', 10);
 264          try {
 265              $progress->progress(\core\progress\base::INDETERMINATE);
 266              $this->fail();
 267          } catch (\coding_exception $e) {
 268              $this->assertEquals(1, preg_match('~expecting value~', $e->getMessage()));
 269          }
 270  
 271          // Value when indeterminate expected.
 272          $progress->start_progress('hello');
 273          try {
 274              $progress->progress(4);
 275              $this->fail();
 276          } catch (\coding_exception $e) {
 277              $this->assertEquals(1, preg_match('~expecting INDETERMINATE~', $e->getMessage()));
 278          }
 279  
 280          // Illegal values.
 281          $progress->start_progress('hello', 10);
 282          try {
 283              $progress->progress(-2);
 284              $this->fail();
 285          } catch (\coding_exception $e) {
 286              $this->assertEquals(1, preg_match('~out of range~', $e->getMessage()));
 287          }
 288          try {
 289              $progress->progress(11);
 290              $this->fail();
 291          } catch (\coding_exception $e) {
 292              $this->assertEquals(1, preg_match('~out of range~', $e->getMessage()));
 293          }
 294  
 295          // You are allowed two with the same value...
 296          $progress->progress(4);
 297          $progress->step_time();
 298          $progress->progress(4);
 299          $progress->step_time();
 300  
 301          // ...but not to go backwards.
 302          try {
 303              $progress->progress(3);
 304              $this->fail();
 305          } catch (\coding_exception $e) {
 306              $this->assertEquals(1, preg_match('~backwards~', $e->getMessage()));
 307          }
 308  
 309          // When you go forward, you can't go further than there is room.
 310          try {
 311              $progress->start_progress('', 1, 7);
 312              $this->fail();
 313          } catch (\coding_exception $e) {
 314              $this->assertEquals(1, preg_match('~would exceed max~', $e->getMessage()));
 315          }
 316      }
 317  
 318      public function test_progress_change() {
 319  
 320          $progress = new core_mock_progress();
 321  
 322          $progress->start_progress('hello', 50);
 323  
 324  
 325          for ($n = 1; $n <= 10; $n++) {
 326              $progress->increment_progress();
 327          }
 328  
 329          // Check numeric position and indeterminate count.
 330          $this->assert_min_max(0.2, 0.2, $progress);
 331          $this->assertEquals(1, $progress->get_progress_count());
 332  
 333          // Make some progress and check that the time limit gets added.
 334          $progress->step_time();
 335  
 336          for ($n = 1; $n <= 20; $n++) {
 337              $progress->increment_progress();
 338          }
 339  
 340          $this->assertTrue($progress->was_update_called());
 341  
 342          // Check the new value.
 343          $this->assert_min_max(0.6, 0.6, $progress);
 344          $this->assertEquals(2, $progress->get_progress_count());
 345  
 346          for ($n = 1; $n <= 10; $n++) {
 347              $progress->increment_progress();
 348          }
 349          $this->assertFalse($progress->was_update_called());
 350          $this->assert_min_max(0.8, 0.8, $progress);
 351          $this->assertEquals(2, $progress->get_progress_count());
 352  
 353          // Do another progress run at same time, it should be ignored.
 354          $progress->increment_progress(5);
 355          $this->assertFalse($progress->was_update_called());
 356          $this->assert_min_max(0.9, 0.9, $progress);
 357          $this->assertEquals(2, $progress->get_progress_count());
 358  
 359          for ($n = 1; $n <= 3; $n++) {
 360              $progress->step_time();
 361              $progress->increment_progress(1);
 362          }
 363          $this->assertTrue($progress->was_update_called());
 364          $this->assert_min_max(0.96, 0.96, $progress);
 365          $this->assertEquals(5, $progress->get_progress_count());
 366  
 367  
 368          // End the section. This should cause an update.
 369          $progress->end_progress();
 370          $this->assertTrue($progress->was_update_called());
 371          $this->assertEquals(5, $progress->get_progress_count());
 372  
 373          // Because there are no sections left open, it thinks we finished.
 374          $this->assert_min_max(1.0, 1.0, $progress);
 375      }
 376  
 377      /**
 378       * Checks the current progress values are as expected.
 379       *
 380       * @param number $min Expected min progress
 381       * @param number $max Expected max progress
 382       * @param core_mock_progress $progress
 383       */
 384      private function assert_min_max($min, $max, core_mock_progress $progress) {
 385          $this->assertEquals(array($min, $max),
 386                  $progress->get_progress_proportion_range());
 387      }
 388  }
 389  
 390  /**
 391   * Helper class that records when update_progress is called and allows time
 392   * stepping.
 393   */
 394  class core_mock_progress extends \core\progress\base {
 395      private $updatecalled = false;
 396      private $time = 1;
 397  
 398      /**
 399       * Checks if update was called since the last call to this function.
 400       *
 401       * @return boolean True if update was called
 402       */
 403      public function was_update_called() {
 404          if ($this->updatecalled) {
 405              $this->updatecalled = false;
 406              return true;
 407          }
 408          return false;
 409      }
 410  
 411      /**
 412       * Steps the current time by 1 second.
 413       */
 414      public function step_time() {
 415          $this->time++;
 416      }
 417  
 418      protected function update_progress() {
 419          $this->updatecalled = true;
 420      }
 421  
 422      protected function get_time() {
 423          return $this->time;
 424      }
 425  }