MDL-41754 (1) Progress tracking : moved from backup and restore to core
[moodle.git] / lib / tests / progress_test.php
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/>.
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  */
26 defined('MOODLE_INTERNAL') || die();
28 /**
29  * Progress tests.
30  */
31 class core_progress_testcase extends basic_testcase {
33     /**
34      * Tests for basic use with simple numeric progress.
35      */
36     public function test_basic() {
37         $progress = new core_mock_progress();
39         // Check values of empty progress things.
40         $this->assertFalse($progress->is_in_progress_section());
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());
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());
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());
61         // Check the new value.
62         $this->assert_min_max(0.2, 0.2, $progress);
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.2, 0.2, $progress);
69         // End the section. This should cause an update.
70         $progress->end_progress();
71         $this->assertTrue($progress->was_update_called());
73         // Because there are no sections left open, it thinks we finished.
74         $this->assert_min_max(1.0, 1.0, $progress);
76         // There was 1 progress call.
77         $this->assertEquals(1, $progress->get_progress_count());
78     }
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);
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());
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());
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);
110         // Exit the indeterminate section.
111         $progress->end_progress();
112         $this->assert_min_max(0.5, 0.5, $progress);
114         $progress->step_time();
115         $progress->progress(7);
116         $this->assert_min_max(0.7, 0.7, $progress);
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);
128         // Now enter another indeterminate section.
129         $progress->start_progress('and');
130         $this->assert_min_max(0.76, 0.78, $progress);
132         // Make some progress, should increment indeterminate count.
133         $progress->step_time();
134         $progress->progress();
135         $this->assertEquals(7, $progress->get_progress_count());
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());
144         // Leaving it will make no difference too.
145         $progress->end_progress();
147         // Leaving the indeterminate section will though.
148         $progress->end_progress();
149         $this->assert_min_max(0.78, 0.78, $progress);
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     }
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);
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);
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);
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     }
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     }
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);
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     }
224     /**
225      * Tests for any exceptions due to invalid calls.
226      */
227     public function test_exceptions() {
228         $progress = new core_mock_progress();
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         }
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         }
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         }
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         }
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         }
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();
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         }
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     }
320     /**
321      * Checks the current progress values are as expected.
322      *
323      * @param number $min Expected min progress
324      * @param number $max Expected max progress
325      * @param core_mock_progress $progress
326      */
327     private function assert_min_max($min, $max, core_mock_progress $progress) {
328         $this->assertEquals(array($min, $max),
329                 $progress->get_progress_proportion_range());
330     }
333 /**
334  * Helper class that records when update_progress is called and allows time
335  * stepping.
336  */
337 class core_mock_progress extends \core\progress\base {
338     private $updatecalled = false;
339     private $time = 1;
341     /**
342      * Checks if update was called since the last call to this function.
343      *
344      * @return boolean True if update was called
345      */
346     public function was_update_called() {
347         if ($this->updatecalled) {
348             $this->updatecalled = false;
349             return true;
350         }
351         return false;
352     }
354     /**
355      * Steps the current time by 1 second.
356      */
357     public function step_time() {
358         $this->time++;
359     }
361     protected function update_progress() {
362         $this->updatecalled = true;
363     }
365     protected function get_time() {
366         return $this->time;
367     }