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