MDL-38190 (1) Backup/restore: Progress tracking API
This commit is contained in:
@@ -64,6 +64,11 @@ class backup_controller extends backup implements loggable {
|
||||
protected $destination; // Destination chain object (fs_moodle, fs_os, db, email...)
|
||||
protected $logger; // Logging chain object (moodle, inline, fs, db, syslog)
|
||||
|
||||
/**
|
||||
* @var core_backup_progress Progress reporting object.
|
||||
*/
|
||||
protected $progress;
|
||||
|
||||
protected $checksum; // Cache @checksumable results for lighter @is_checksum_correct() uses
|
||||
|
||||
/**
|
||||
@@ -109,6 +114,10 @@ class backup_controller extends backup implements loggable {
|
||||
// Default logger chain (based on interactive/execution)
|
||||
$this->logger = backup_factory::get_logger_chain($this->interactive, $this->execution, $this->backupid);
|
||||
|
||||
// By default there is no progress reporter. Interfaces that wish to
|
||||
// display progress must set it.
|
||||
$this->progress = new core_backup_null_progress();
|
||||
|
||||
// Instantiate the output_controller singleton and active it if interactive and inmediate
|
||||
$oc = output_controller::get_instance();
|
||||
if ($this->interactive == backup::INTERACTIVE_YES && $this->execution == backup::EXECUTION_INMEDIATE) {
|
||||
@@ -302,6 +311,25 @@ class backup_controller extends backup implements loggable {
|
||||
return $this->logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the progress reporter, which can be used to report progress within
|
||||
* the backup or restore process.
|
||||
*
|
||||
* @return core_backup_progress Progress reporting object
|
||||
*/
|
||||
public function get_progress() {
|
||||
return $this->progress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the progress reporter.
|
||||
*
|
||||
* @param core_backup_progress $progress Progress reporting object
|
||||
*/
|
||||
public function set_progress(core_backup_progress $progress) {
|
||||
$this->progress = $progress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the backup
|
||||
* @return void Throws and exception of completes
|
||||
|
||||
@@ -57,6 +57,11 @@ class restore_controller extends backup implements loggable {
|
||||
|
||||
protected $logger; // Logging chain object (moodle, inline, fs, db, syslog)
|
||||
|
||||
/**
|
||||
* @var core_backup_progress Progress reporting object.
|
||||
*/
|
||||
protected $progress;
|
||||
|
||||
protected $checksum; // Cache @checksumable results for lighter @is_checksum_correct() uses
|
||||
|
||||
/**
|
||||
@@ -101,6 +106,10 @@ class restore_controller extends backup implements loggable {
|
||||
// Default logger chain (based on interactive/execution)
|
||||
$this->logger = backup_factory::get_logger_chain($this->interactive, $this->execution, $this->restoreid);
|
||||
|
||||
// By default there is no progress reporter. Interfaces that wish to
|
||||
// display progress must set it.
|
||||
$this->progress = new core_backup_null_progress();
|
||||
|
||||
// Instantiate the output_controller singleton and active it if interactive and inmediate
|
||||
$oc = output_controller::get_instance();
|
||||
if ($this->interactive == backup::INTERACTIVE_YES && $this->execution == backup::EXECUTION_INMEDIATE) {
|
||||
@@ -300,6 +309,25 @@ class restore_controller extends backup implements loggable {
|
||||
return $this->logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the progress reporter, which can be used to report progress within
|
||||
* the backup or restore process.
|
||||
*
|
||||
* @return core_backup_progress Progress reporting object
|
||||
*/
|
||||
public function get_progress() {
|
||||
return $this->progress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the progress reporter.
|
||||
*
|
||||
* @param core_backup_progress $progress Progress reporting object
|
||||
*/
|
||||
public function set_progress(core_backup_progress $progress) {
|
||||
$this->progress = $progress;
|
||||
}
|
||||
|
||||
public function execute_plan() {
|
||||
// Basic/initial prevention against time/memory limits
|
||||
set_time_limit(1 * 60 * 60); // 1 hour for 1 course initially granted
|
||||
|
||||
@@ -71,6 +71,8 @@ require_once($CFG->dirroot . '/backup/util/loggers/error_log_logger.class.php');
|
||||
require_once($CFG->dirroot . '/backup/util/loggers/file_logger.class.php');
|
||||
require_once($CFG->dirroot . '/backup/util/loggers/database_logger.class.php');
|
||||
require_once($CFG->dirroot . '/backup/util/loggers/output_indented_logger.class.php');
|
||||
require_once($CFG->dirroot . '/backup/util/progress/core_backup_progress.class.php');
|
||||
require_once($CFG->dirroot . '/backup/util/progress/core_backup_null_progress.class.php');
|
||||
require_once($CFG->dirroot . '/backup/util/settings/setting_dependency.class.php');
|
||||
require_once($CFG->dirroot . '/backup/util/settings/base_setting.class.php');
|
||||
require_once($CFG->dirroot . '/backup/util/settings/backup_setting.class.php');
|
||||
|
||||
@@ -60,6 +60,8 @@ require_once($CFG->dirroot . '/backup/util/loggers/error_log_logger.class.php');
|
||||
require_once($CFG->dirroot . '/backup/util/loggers/file_logger.class.php');
|
||||
require_once($CFG->dirroot . '/backup/util/loggers/database_logger.class.php');
|
||||
require_once($CFG->dirroot . '/backup/util/loggers/output_indented_logger.class.php');
|
||||
require_once($CFG->dirroot . '/backup/util/progress/core_backup_progress.class.php');
|
||||
require_once($CFG->dirroot . '/backup/util/progress/core_backup_null_progress.class.php');
|
||||
require_once($CFG->dirroot . '/backup/util/factories/backup_factory.class.php');
|
||||
require_once($CFG->dirroot . '/backup/util/factories/restore_factory.class.php');
|
||||
require_once($CFG->dirroot . '/backup/util/helper/backup_helper.class.php');
|
||||
|
||||
@@ -87,6 +87,16 @@ class backup_plan extends base_plan implements loggable {
|
||||
return $this->controller->get_logger();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the progress reporter, which can be used to report progress within
|
||||
* the backup or restore process.
|
||||
*
|
||||
* @return core_backup_progress Progress reporting object
|
||||
*/
|
||||
public function get_progress() {
|
||||
return $this->controller->get_progress();
|
||||
}
|
||||
|
||||
public function is_excluding_activities() {
|
||||
return $this->excludingdactivities;
|
||||
}
|
||||
|
||||
@@ -164,6 +164,14 @@ abstract class base_plan implements checksumable, executable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the progress reporter, which can be used to report progress within
|
||||
* the backup or restore process.
|
||||
*
|
||||
* @return core_backup_progress Progress reporting object
|
||||
*/
|
||||
public abstract function get_progress();
|
||||
|
||||
/**
|
||||
* Destroy all circular references. It helps PHP 5.2 a lot!
|
||||
*/
|
||||
|
||||
@@ -111,6 +111,16 @@ abstract class base_task implements checksumable, executable, loggable {
|
||||
return $this->plan->get_logger();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the progress reporter, which can be used to report progress within
|
||||
* the backup or restore process.
|
||||
*
|
||||
* @return core_backup_progress Progress reporting object
|
||||
*/
|
||||
public function get_progress() {
|
||||
return $this->plan->get_progress();
|
||||
}
|
||||
|
||||
public function log($message, $level, $a = null, $depth = null, $display = false) {
|
||||
backup_helper::log($message, $level, $a, $depth, $display, $this->get_logger());
|
||||
}
|
||||
|
||||
@@ -94,6 +94,16 @@ class restore_plan extends base_plan implements loggable {
|
||||
return $this->controller->get_logger();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the progress reporter, which can be used to report progress within
|
||||
* the backup or restore process.
|
||||
*
|
||||
* @return core_backup_progress Progress reporting object
|
||||
*/
|
||||
public function get_progress() {
|
||||
return $this->controller->get_progress();
|
||||
}
|
||||
|
||||
public function get_info() {
|
||||
return $this->controller->get_info();
|
||||
}
|
||||
|
||||
@@ -34,6 +34,10 @@ require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
|
||||
class mock_base_plan extends base_plan {
|
||||
public function build() {
|
||||
}
|
||||
|
||||
public function get_progress() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Progress handler that ignores progress entirely.
|
||||
*
|
||||
* @package core_backup
|
||||
* @copyright 2013 The Open University
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class core_backup_null_progress extends core_backup_progress {
|
||||
public function update_progress() {
|
||||
// Do nothing.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,307 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Base class for handling progress information during a backup and restore.
|
||||
*
|
||||
* Subclasses should generally override the current_progress function which
|
||||
* summarises all progress information.
|
||||
*
|
||||
* @package core_backup
|
||||
* @copyright 2013 The Open University
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
abstract class core_backup_progress {
|
||||
/**
|
||||
* @var int Constant indicating that the number of progress calls is unknown.
|
||||
*/
|
||||
const INDETERMINATE = -1;
|
||||
|
||||
/**
|
||||
* @var int The number of seconds that can pass without progress() calls.
|
||||
*/
|
||||
const TIME_LIMIT_WITHOUT_PROGRESS = 120;
|
||||
|
||||
/**
|
||||
* @var int Time of last progress call.
|
||||
*/
|
||||
protected $lastprogresstime;
|
||||
|
||||
/**
|
||||
* @var int Number of progress calls (restricted to ~ 1/second).
|
||||
*/
|
||||
protected $count;
|
||||
|
||||
/**
|
||||
* @var array Array of progress descriptions for each stack level.
|
||||
*/
|
||||
protected $descriptions = array();
|
||||
|
||||
/**
|
||||
* @var array Array of maximum progress values for each stack level.
|
||||
*/
|
||||
protected $maxes = array();
|
||||
|
||||
/**
|
||||
* @var array Array of current progress values.
|
||||
*/
|
||||
protected $currents = array();
|
||||
|
||||
/**
|
||||
* @var int Array of counts within parent progress entry (ignored for first)
|
||||
*/
|
||||
protected $parentcounts = array();
|
||||
|
||||
/**
|
||||
* Marks the start of an operation that will display progress.
|
||||
*
|
||||
* This can be called multiple times for nested progress sections. It must
|
||||
* be paired with calls to end_progress.
|
||||
*
|
||||
* The progress maximum may be INDETERMINATE if the current operation has
|
||||
* an unknown number of steps. (This is default.)
|
||||
*
|
||||
* Calling this function will always result in a new display, so this
|
||||
* should not be called exceedingly frequently.
|
||||
*
|
||||
* When it is complete by calling end_progress, each start_progress section
|
||||
* automatically adds progress to its parent, as defined by $parentcount.
|
||||
*
|
||||
* @param string $description Description to display
|
||||
* @param int $max Maximum value of progress for this section
|
||||
* @param int $parentcount How many progress points this section counts for
|
||||
* @throws coding_exception If max is invalid
|
||||
*/
|
||||
public function start_progress($description, $max = self::INDETERMINATE,
|
||||
$parentcount = 1) {
|
||||
if ($max != self::INDETERMINATE && $max <= 0) {
|
||||
throw new coding_exception(
|
||||
'start_progress() max value cannot be zero or negative');
|
||||
}
|
||||
if ($parentcount < 1) {
|
||||
throw new coding_exception(
|
||||
'start_progress() parent progress count must be at least 1');
|
||||
}
|
||||
if (!empty($this->descriptions)) {
|
||||
$prevmax = end($this->maxes);
|
||||
if ($prevmax !== self::INDETERMINATE) {
|
||||
$prevcurrent = end($this->currents);
|
||||
if ($prevcurrent + $parentcount > $prevmax) {
|
||||
throw new coding_exception(
|
||||
'start_progress() parent progress would exceed max');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ($parentcount != 1) {
|
||||
throw new coding_exception(
|
||||
'start_progress() progress count must be 1 when no parent');
|
||||
}
|
||||
}
|
||||
$this->descriptions[] = $description;
|
||||
$this->maxes[] = $max;
|
||||
$this->currents[] = 0;
|
||||
$this->parentcounts[] = $parentcount;
|
||||
$this->update_progress();
|
||||
$lastprogresstime = $this->get_time();
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the end of an operation that will display progress.
|
||||
*
|
||||
* This must be paired with each start_progress call.
|
||||
*
|
||||
* If there is a parent progress section, its progress will be increased
|
||||
* automatically to reflect the end of the child section.
|
||||
*
|
||||
* @throws coding_exception If progress hasn't been started
|
||||
*/
|
||||
public function end_progress() {
|
||||
if (!count($this->descriptions)) {
|
||||
throw new coding_exception('end_progress() without start_progress()');
|
||||
}
|
||||
array_pop($this->descriptions);
|
||||
array_pop($this->maxes);
|
||||
array_pop($this->currents);
|
||||
$parentcount = array_pop($this->parentcounts);
|
||||
if (!empty($this->descriptions)) {
|
||||
$lastmax = end($this->maxes);
|
||||
if ($lastmax != self::INDETERMINATE) {
|
||||
$lastvalue = end($this->currents);
|
||||
$this->currents[key($this->currents)] = $lastvalue + $parentcount;
|
||||
}
|
||||
}
|
||||
$this->update_progress();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates that progress has occurred.
|
||||
*
|
||||
* The progress value should indicate the total progress so far, from 0
|
||||
* to the value supplied for $max (inclusive) in start_progress.
|
||||
*
|
||||
* You do not need to call this function for every value. It is OK to skip
|
||||
* values. It is also OK to call this function as often as desired; it
|
||||
* doesn't do anything if called more than once per second.
|
||||
*
|
||||
* It must be INDETERMINATE if start_progress was called with $max set to
|
||||
* INDETERMINATE. Otherwise it must not be indeterminate.
|
||||
*
|
||||
* @param int $progress Progress so far
|
||||
* @throws coding_exception If progress value is invalid
|
||||
*/
|
||||
public function progress($progress = self::INDETERMINATE) {
|
||||
// Ignore too-frequent progress calls (more than once per second).
|
||||
$now = $this->get_time();
|
||||
if ($now === $this->lastprogresstime) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check we are inside a progress section.
|
||||
$max = end($this->maxes);
|
||||
if ($max === false) {
|
||||
throw new coding_exception(
|
||||
'progress() without start_progress');
|
||||
}
|
||||
|
||||
// Check and apply new progress.
|
||||
if ($progress === self::INDETERMINATE) {
|
||||
// Indeterminate progress.
|
||||
if ($max !== self::INDETERMINATE) {
|
||||
throw new coding_exception(
|
||||
'progress() INDETERMINATE, expecting value');
|
||||
}
|
||||
} else {
|
||||
// Determinate progress.
|
||||
$current = end($this->currents);
|
||||
if ($max === self::INDETERMINATE) {
|
||||
throw new coding_exception(
|
||||
'progress() with value, expecting INDETERMINATE');
|
||||
} else if ($progress < 0 || $progress > $max) {
|
||||
throw new coding_exception(
|
||||
'progress() value out of range');
|
||||
} else if ($progress < $current) {
|
||||
throw new coding_Exception(
|
||||
'progress() value may not go backwards');
|
||||
}
|
||||
$this->currents[key($this->currents)] = $progress;
|
||||
}
|
||||
|
||||
// Update progress.
|
||||
$this->count++;
|
||||
$this->lastprogresstime = $now;
|
||||
set_time_limit(self::TIME_LIMIT_WITHOUT_PROGRESS);
|
||||
$this->update_progress();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets time (this is provided so that unit tests can override it).
|
||||
*
|
||||
* @return int Current system time
|
||||
*/
|
||||
protected function get_time() {
|
||||
return time();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called whenever new progress should be displayed.
|
||||
*/
|
||||
protected abstract function update_progress();
|
||||
|
||||
/**
|
||||
* @return bool True if currently inside a progress section
|
||||
*/
|
||||
public function is_in_progress_section() {
|
||||
return !empty($this->descriptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Current progress section description
|
||||
*/
|
||||
public function get_current_description() {
|
||||
$description = end($this->descriptions);
|
||||
if ($description === false) {
|
||||
throw new coding_exception('Not inside progress section');
|
||||
}
|
||||
return $description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains current progress in a way suitable for drawing a progress bar.
|
||||
*
|
||||
* Progress is returned as a minimum and maximum value. If there is no
|
||||
* indeterminate progress, these values will be identical. If there is
|
||||
* intermediate progress, these values can be different. (For example, if
|
||||
* the top level progress sections is indeterminate, then the values will
|
||||
* always be 0.0 and 1.0.)
|
||||
*
|
||||
* @return array Minimum and maximum possible progress proportions
|
||||
*/
|
||||
public function get_progress_proportion_range() {
|
||||
// If there is no progress underway, we must have finished.
|
||||
if (empty($this->currents)) {
|
||||
return array(1.0, 1.0);
|
||||
}
|
||||
$count = count($this->currents);
|
||||
$min = 0.0;
|
||||
$max = 1.0;
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
// Get max value at that section - if it's indeterminate we can tell
|
||||
// no more.
|
||||
$sectionmax = $this->maxes[$i];
|
||||
if ($sectionmax === self::INDETERMINATE) {
|
||||
return array($min, $max);
|
||||
}
|
||||
|
||||
// Special case if current value is max (this should only happen
|
||||
// just before ending a section).
|
||||
$sectioncurrent = $this->currents[$i];
|
||||
if ($sectioncurrent === $sectionmax) {
|
||||
return array($max, $max);
|
||||
}
|
||||
|
||||
// Using the current value at that section, we know we are somewhere
|
||||
// between 'current' and the next 'current' value which depends on
|
||||
// the parentcount of the nested section (if any).
|
||||
$newmin = ($sectioncurrent / $sectionmax) * ($max - $min) + $min;
|
||||
$nextcurrent = $sectioncurrent + 1;
|
||||
if ($i + 1 < $count) {
|
||||
$weight = $this->parentcounts[$i + 1];
|
||||
$nextcurrent = $sectioncurrent + $weight;
|
||||
}
|
||||
$newmax = ($nextcurrent / $sectionmax) * ($max - $min) + $min;
|
||||
$min = $newmin;
|
||||
$max = $newmax;
|
||||
}
|
||||
|
||||
// If there was nothing indeterminate, we use the min value as current.
|
||||
return array($min, $min);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains current indeterminate progress in a way suitable for adding to
|
||||
* the progress display.
|
||||
*
|
||||
* This returns the number of indeterminate calls (at any level) during the
|
||||
* lifetime of this progress reporter, whether or not there is a current
|
||||
* indeterminate step. (The number will not be ridiculously high because
|
||||
* progress calls are limited to one per second.)
|
||||
*
|
||||
* @return int Number of indeterminate progress calls
|
||||
*/
|
||||
public function get_progress_count() {
|
||||
return $this->count;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,363 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Unit tests for the progress classes.
|
||||
*
|
||||
* @package core_backup
|
||||
* @category phpunit
|
||||
* @copyright 2013 The Open University
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
// Include all the needed stuff.
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot . '/backup/util/progress/core_backup_progress.class.php');
|
||||
|
||||
/**
|
||||
* Progress tests.
|
||||
*/
|
||||
class backup_progress_testcase extends basic_testcase {
|
||||
|
||||
/**
|
||||
* Tests for basic use with simple numeric progress.
|
||||
*/
|
||||
public function test_basic() {
|
||||
$progress = new core_backup_mock_progress();
|
||||
|
||||
// Check values of empty progress things.
|
||||
$this->assertFalse($progress->is_in_progress_section());
|
||||
|
||||
// Start progress counting, check basic values and check that update
|
||||
// gets called.
|
||||
$progress->start_progress('hello', 10);
|
||||
$this->assertTrue($progress->was_update_called());
|
||||
$this->assertTrue($progress->is_in_progress_section());
|
||||
$this->assertEquals('hello', $progress->get_current_description());
|
||||
|
||||
// Check numeric position and indeterminate count.
|
||||
$this->assert_min_max(0.0, 0.0, $progress);
|
||||
$this->assertEquals(0, $progress->get_progress_count());
|
||||
|
||||
// Make some progress and check that the time limit gets added.
|
||||
$progress->step_time();
|
||||
$progress->progress(2);
|
||||
$this->assertTrue($progress->was_update_called());
|
||||
$this->assertEquals(120, ini_get('max_execution_time'));
|
||||
|
||||
// Check the new value.
|
||||
$this->assert_min_max(0.2, 0.2, $progress);
|
||||
|
||||
// Do another progress run at same time, it should be ignored.
|
||||
$progress->progress(3);
|
||||
$this->assertFalse($progress->was_update_called());
|
||||
$this->assert_min_max(0.2, 0.2, $progress);
|
||||
|
||||
// End the section. This should cause an update.
|
||||
$progress->end_progress();
|
||||
$this->assertTrue($progress->was_update_called());
|
||||
|
||||
// Because there are no sections left open, it thinks we finished.
|
||||
$this->assert_min_max(1.0, 1.0, $progress);
|
||||
|
||||
// There was 1 progress call.
|
||||
$this->assertEquals(1, $progress->get_progress_count());
|
||||
|
||||
// Clear the time limit, otherwise phpunit complains.
|
||||
set_time_limit(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests progress that is nested and/or indeterminate.
|
||||
*/
|
||||
public function test_nested() {
|
||||
// Outer progress goes from 0 to 10.
|
||||
$progress = new core_backup_mock_progress();
|
||||
$progress->start_progress('hello', 10);
|
||||
|
||||
// Get up to 4, check position.
|
||||
$progress->step_time();
|
||||
$progress->progress(4);
|
||||
$this->assert_min_max(0.4, 0.4, $progress);
|
||||
$this->assertEquals('hello', $progress->get_current_description());
|
||||
|
||||
// Now start indeterminate progress.
|
||||
$progress->start_progress('world');
|
||||
$this->assert_min_max(0.4, 0.5, $progress);
|
||||
$this->assertEquals('world', $progress->get_current_description());
|
||||
|
||||
// Do some indeterminate progress and count it (once per second).
|
||||
$progress->step_time();
|
||||
$progress->progress();
|
||||
$this->assertEquals(2, $progress->get_progress_count());
|
||||
$progress->progress();
|
||||
$this->assertEquals(2, $progress->get_progress_count());
|
||||
$progress->step_time();
|
||||
$progress->progress();
|
||||
$this->assertEquals(3, $progress->get_progress_count());
|
||||
$this->assert_min_max(0.4, 0.5, $progress);
|
||||
|
||||
// Exit the indeterminate section.
|
||||
$progress->end_progress();
|
||||
$this->assert_min_max(0.5, 0.5, $progress);
|
||||
|
||||
$progress->step_time();
|
||||
$progress->progress(7);
|
||||
$this->assert_min_max(0.7, 0.7, $progress);
|
||||
|
||||
// Enter a numbered section (this time with a range of 5).
|
||||
$progress->start_progress('frogs', 5);
|
||||
$this->assert_min_max(0.7, 0.7, $progress);
|
||||
$progress->step_time();
|
||||
$progress->progress(1);
|
||||
$this->assert_min_max(0.72, 0.72, $progress);
|
||||
$progress->step_time();
|
||||
$progress->progress(3);
|
||||
$this->assert_min_max(0.76, 0.76, $progress);
|
||||
|
||||
// Now enter another indeterminate section.
|
||||
$progress->start_progress('and');
|
||||
$this->assert_min_max(0.76, 0.78, $progress);
|
||||
|
||||
// Make some progress, should increment indeterminate count.
|
||||
$progress->step_time();
|
||||
$progress->progress();
|
||||
$this->assertEquals(7, $progress->get_progress_count());
|
||||
|
||||
// Enter numbered section, won't make any difference to values.
|
||||
$progress->start_progress('zombies', 2);
|
||||
$progress->step_time();
|
||||
$progress->progress(1);
|
||||
$this->assert_min_max(0.76, 0.78, $progress);
|
||||
$this->assertEquals(8, $progress->get_progress_count());
|
||||
|
||||
// Leaving it will make no difference too.
|
||||
$progress->end_progress();
|
||||
|
||||
// Leaving the indeterminate section will though.
|
||||
$progress->end_progress();
|
||||
$this->assert_min_max(0.78, 0.78, $progress);
|
||||
|
||||
// Leave the two numbered sections.
|
||||
$progress->end_progress();
|
||||
$this->assert_min_max(0.8, 0.8, $progress);
|
||||
$progress->end_progress();
|
||||
$this->assertFalse($progress->is_in_progress_section());
|
||||
|
||||
set_time_limit(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the feature for 'weighting' nested progress.
|
||||
*/
|
||||
public function test_nested_weighted() {
|
||||
$progress = new core_backup_mock_progress();
|
||||
$progress->start_progress('', 10);
|
||||
|
||||
// First nested child has 2 units of its own and is worth 1 unit.
|
||||
$progress->start_progress('', 2);
|
||||
$progress->step_time();
|
||||
$progress->progress(1);
|
||||
$this->assert_min_max(0.05, 0.05, $progress);
|
||||
$progress->end_progress();
|
||||
$this->assert_min_max(0.1, 0.1, $progress);
|
||||
|
||||
// Next child has 2 units of its own but is worth 3 units.
|
||||
$progress->start_progress('weighted', 2, 3);
|
||||
$progress->step_time();
|
||||
$progress->progress(1);
|
||||
$this->assert_min_max(0.25, 0.25, $progress);
|
||||
$progress->end_progress();
|
||||
$this->assert_min_max(0.4, 0.4, $progress);
|
||||
|
||||
// Next indeterminate child is worth 6 units.
|
||||
$progress->start_progress('', core_backup_progress::INDETERMINATE, 6);
|
||||
$progress->step_time();
|
||||
$progress->progress();
|
||||
$this->assert_min_max(0.4, 1.0, $progress);
|
||||
$progress->end_progress();
|
||||
$this->assert_min_max(1.0, 1.0, $progress);
|
||||
|
||||
set_time_limit(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* I had some issues with real use in backup/restore, this test is intended
|
||||
* to be similar.
|
||||
*/
|
||||
public function test_realistic() {
|
||||
$progress = new core_backup_mock_progress();
|
||||
$progress->start_progress('parent', 100);
|
||||
$progress->start_progress('child', 1);
|
||||
$progress->progress(1);
|
||||
$this->assert_min_max(0.01, 0.01, $progress);
|
||||
$progress->end_progress();
|
||||
$this->assert_min_max(0.01, 0.01, $progress);
|
||||
|
||||
// Clear the time limit, otherwise phpunit complains.
|
||||
set_time_limit(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests for any exceptions due to invalid calls.
|
||||
*/
|
||||
public function test_exceptions() {
|
||||
$progress = new core_backup_mock_progress();
|
||||
|
||||
// Check errors when empty.
|
||||
try {
|
||||
$progress->progress();
|
||||
$this->fail();
|
||||
} catch (coding_exception $e) {
|
||||
$this->assertEquals(1, preg_match('~without start_progress~', $e->getMessage()));
|
||||
}
|
||||
try {
|
||||
$progress->end_progress();
|
||||
$this->fail();
|
||||
} catch (coding_exception $e) {
|
||||
$this->assertEquals(1, preg_match('~without start_progress~', $e->getMessage()));
|
||||
}
|
||||
try {
|
||||
$progress->get_current_description();
|
||||
$this->fail();
|
||||
} catch (coding_exception $e) {
|
||||
$this->assertEquals(1, preg_match('~Not inside progress~', $e->getMessage()));
|
||||
}
|
||||
try {
|
||||
$progress->start_progress('', 1, 7);
|
||||
$this->fail();
|
||||
} catch (coding_exception $e) {
|
||||
$this->assertEquals(1, preg_match('~must be 1~', $e->getMessage()));
|
||||
}
|
||||
|
||||
// Check invalid start (0).
|
||||
try {
|
||||
$progress->start_progress('hello', 0);
|
||||
$this->fail();
|
||||
} catch (coding_exception $e) {
|
||||
$this->assertEquals(1, preg_match('~cannot be zero or negative~', $e->getMessage()));
|
||||
}
|
||||
|
||||
// Indeterminate when value expected.
|
||||
$progress->start_progress('hello', 10);
|
||||
try {
|
||||
$progress->progress(core_backup_progress::INDETERMINATE);
|
||||
$this->fail();
|
||||
} catch (coding_exception $e) {
|
||||
$this->assertEquals(1, preg_match('~expecting value~', $e->getMessage()));
|
||||
}
|
||||
|
||||
// Value when indeterminate expected.
|
||||
$progress->start_progress('hello');
|
||||
try {
|
||||
$progress->progress(4);
|
||||
$this->fail();
|
||||
} catch (coding_exception $e) {
|
||||
$this->assertEquals(1, preg_match('~expecting INDETERMINATE~', $e->getMessage()));
|
||||
}
|
||||
|
||||
// Illegal values.
|
||||
$progress->start_progress('hello', 10);
|
||||
try {
|
||||
$progress->progress(-2);
|
||||
$this->fail();
|
||||
} catch (coding_exception $e) {
|
||||
$this->assertEquals(1, preg_match('~out of range~', $e->getMessage()));
|
||||
}
|
||||
try {
|
||||
$progress->progress(11);
|
||||
$this->fail();
|
||||
} catch (coding_exception $e) {
|
||||
$this->assertEquals(1, preg_match('~out of range~', $e->getMessage()));
|
||||
}
|
||||
|
||||
// You are allowed two with the same value...
|
||||
$progress->progress(4);
|
||||
$progress->step_time();
|
||||
$progress->progress(4);
|
||||
$progress->step_time();
|
||||
|
||||
// ...but not to go backwards.
|
||||
try {
|
||||
$progress->progress(3);
|
||||
$this->fail();
|
||||
} catch (coding_exception $e) {
|
||||
$this->assertEquals(1, preg_match('~backwards~', $e->getMessage()));
|
||||
}
|
||||
|
||||
// When you go forward, you can't go further than there is room.
|
||||
try {
|
||||
$progress->start_progress('', 1, 7);
|
||||
$this->fail();
|
||||
} catch (coding_exception $e) {
|
||||
$this->assertEquals(1, preg_match('~would exceed max~', $e->getMessage()));
|
||||
}
|
||||
|
||||
// Clear the time limit, otherwise phpunit complains.
|
||||
set_time_limit(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the current progress values are as expected.
|
||||
*
|
||||
* @param number $min Expected min progress
|
||||
* @param number $max Expected max progress
|
||||
* @param core_backup_mock_progress $progress
|
||||
*/
|
||||
private function assert_min_max($min, $max, core_backup_mock_progress $progress) {
|
||||
$this->assertEquals(array($min, $max),
|
||||
$progress->get_progress_proportion_range());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class that records when update_progress is called and allows time
|
||||
* stepping.
|
||||
*/
|
||||
class core_backup_mock_progress extends core_backup_progress {
|
||||
private $updatecalled = false;
|
||||
private $time = 1;
|
||||
|
||||
/**
|
||||
* Checks if update was called since the last call to this function.
|
||||
*
|
||||
* @return boolean True if update was called
|
||||
*/
|
||||
public function was_update_called() {
|
||||
if ($this->updatecalled) {
|
||||
$this->updatecalled = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Steps the current time by 1 second.
|
||||
*/
|
||||
public function step_time() {
|
||||
$this->time++;
|
||||
}
|
||||
|
||||
protected function update_progress() {
|
||||
$this->updatecalled = true;
|
||||
}
|
||||
|
||||
protected function get_time() {
|
||||
return $this->time;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user