diff --git a/core/includes/batch.inc b/core/includes/batch.inc index 2152725..334c957 100644 --- a/core/includes/batch.inc +++ b/core/includes/batch.inc @@ -14,6 +14,7 @@ * @see batch_get() */ +use \Drupal\Component\Batch\Batch; use \Symfony\Component\HttpFoundation\JsonResponse; /** @@ -329,27 +330,7 @@ function _batch_process() { * @see _batch_process() */ function _batch_api_percentage($total, $current) { - if (!$total || $total == $current) { - // If $total doesn't evaluate as true or is equal to the current set, then - // we're finished, and we can return "100". - $percentage = "100"; - } - else { - // We add a new digit at 200, 2000, etc. (since, for example, 199/200 - // would round up to 100% if we didn't). - $decimal_places = max(0, floor(log10($total / 2.0)) - 1); - do { - // Calculate the percentage to the specified number of decimal places. - $percentage = sprintf('%01.' . $decimal_places . 'f', round($current / $total * 100, $decimal_places)); - // When $current is an integer, the above calculation will always be - // correct. However, if $current is a floating point number (in the case - // of a multi-step batch operation that is not yet complete), $percentage - // may be erroneously rounded up to 100%. To prevent that, we add one - // more decimal place and try again. - $decimal_places++; - } while ($percentage == '100'); - } - return $percentage; + return Batch::percentage($total, $current); } /** diff --git a/core/lib/Drupal/Component/Batch/Batch.php b/core/lib/Drupal/Component/Batch/Batch.php new file mode 100644 index 0000000..7d863cb --- /dev/null +++ b/core/lib/Drupal/Component/Batch/Batch.php @@ -0,0 +1,56 @@ + 'Batch percentages', - 'description' => 'Unit tests of progress percentage rounding.', - 'group' => 'Batch API', - ); - } - - function setUp() { - // Set up an array of test cases, where the expected values are the keys, - // and the values are arrays with the keys 'total' and 'current', - // corresponding with the function parameters of _batch_api_percentage(). - $this->testCases = array( - // 1/2 is 50%. - '50' => array('total' => 2, 'current' => 1), - // Though we should never encounter a case where the current set is set - // 0, if we did, we should get 0%. - '0' => array('total' => 3, 'current' => 0), - // 1/3 is closer to 33% than to 34%. - '33' => array('total' => 3, 'current' => 1), - // 2/3 is closer to 67% than to 66%. - '67' => array('total' => 3, 'current' => 2), - // 1/199 should round up to 1%. - '1' => array('total' => 199, 'current' => 1), - // 198/199 should round down to 99%. - '99' => array('total' => 199, 'current' => 198), - // 199/200 would have rounded up to 100%, which would give the false - // impression of being finished, so we add another digit and should get - // 99.5%. - '99.5' => array('total' => 200, 'current' => 199), - // The same logic holds for 1/200: we should get 0.5%. - '0.5' => array('total' => 200, 'current' => 1), - // Numbers that come out evenly, such as 50/200, should be forced to have - // extra digits for consistancy. - '25.0' => array('total' => 200, 'current' => 50), - // Regardless of number of digits we're using, 100% should always just be - // 100%. - '100' => array('total' => 200, 'current' => 200), - // 1998/1999 should similarly round down to 99.9%. - '99.9' => array('total' => 1999, 'current' => 1998), - // 1999/2000 should add another digit and go to 99.95%. - '99.95' => array('total' => 2000, 'current' => 1999), - // 19999/20000 should add yet another digit and go to 99.995%. - '99.995' => array('total' => 20000, 'current' => 19999), - // The next five test cases simulate a batch with a single operation - // ('total' equals 1) that takes several steps to complete. Within the - // operation, we imagine that there are 501 items to process, and 100 are - // completed during each step. The percentages we get back should be - // rounded the usual way for the first few passes (i.e., 20%, 40%, etc.), - // but for the last pass through, when 500 out of 501 items have been - // processed, we do not want to round up to 100%, since that would - // erroneously indicate that the processing is complete. - '20' => array('total' => 1, 'current' => 100/501), - '40' => array('total' => 1, 'current' => 200/501), - '60' => array('total' => 1, 'current' => 300/501), - '80' => array('total' => 1, 'current' => 400/501), - '99.8' => array('total' => 1, 'current' => 500/501), - ); - require_once DRUPAL_ROOT . '/core/includes/batch.inc'; - parent::setUp(); - } - - /** - * Tests the _batch_api_percentage() function. - */ - function testBatchPercentages() { - foreach ($this->testCases as $expected_result => $arguments) { - // PHP sometimes casts numeric strings that are array keys to integers, - // cast them back here. - $expected_result = (string) $expected_result; - $total = $arguments['total']; - $current = $arguments['current']; - $actual_result = _batch_api_percentage($total, $current); - if ($actual_result === $expected_result) { - $this->pass(format_string('Expected the batch api percentage at the state @numerator/@denominator to be @expected%, and got @actual%.', array('@numerator' => $current, '@denominator' => $total, '@expected' => $expected_result, '@actual' => $actual_result))); - } - else { - $this->fail(format_string('Expected the batch api percentage at the state @numerator/@denominator to be @expected%, but got @actual%.', array('@numerator' => $current, '@denominator' => $total, '@expected' => $expected_result, '@actual' => $actual_result))); - } - } - } -} diff --git a/core/tests/Drupal/Tests/Component/Batch/PercentagesUnitTest.php b/core/tests/Drupal/Tests/Component/Batch/PercentagesUnitTest.php new file mode 100644 index 0000000..a63d2b4 --- /dev/null +++ b/core/tests/Drupal/Tests/Component/Batch/PercentagesUnitTest.php @@ -0,0 +1,93 @@ + 'Batch percentages', + 'description' => 'Unit tests of progress percentage rounding.', + 'group' => 'Batch API', + ); + } + + /** + * Tests the _batch_api_percentage() function. + * + * @dataProvider provider + */ + function testBatchPercentages($total, $current, $expected_result) { + $actual_result = Batch::percentage($total, $current); + $this->assertEquals($actual_result, $expected_result, sprintf('The expected the batch api percentage at the state %s/%s is %s%% and got %s%%.', $current, $total, $expected_result, $actual_result)); + } + + /** + * Provide data for batch unit tests. + */ + public function provider() { + // Set up an array of test cases. + return array( + // array(total, current, expected). + // 1/2 is 50%. + array(2, 1, '50'), + // Though we should never encounter a case where the current set is set + // 0, if we did, we should get 0%. + array(3, 0, '0'), + // 1/3 is closer to 33% than to 34%. + array(3, 1, '33'), + // 2/3 is closer to 67% than to 66%. + array(3, 2, '67'), + // 1/199 should round up to 1%. + array(199, 1, '1'), + // 198/199 should round down to 99%. + array(199, 198, '99'), + // 199/200 would have rounded up to 100%, which would give the false + // impression of being finished, so we add another digit and should get + // 99.5%. + array(200, 199, '99.5'), + // The same logic holds for 1/200: we should get 0.5%. + array(200, 1, '0.5'), + // Numbers that come out evenly, such as 50/200, should be forced to have + // extra digits for consistancy. + array(200, 50, '25.0'), + // Regardless of number of digits we're using, 100% should always just be + // 100%. + array(200, 200, '100'), + // 1998/1999 should similarly round down to 99.9%. + array(1999, 1998, '99.9'), + // 1999/2000 should add another digit and go to 99.95%. + array(2000, 1999, '99.95'), + // 19999/20000 should add yet another digit and go to 99.995%. + array(20000, 19999, '99.995'), + // The next five test cases simulate a batch with a single operation + // ('total' equals 1) that takes several steps to complete. Within the + // operation, we imagine that there are 501 items to process, and 100 are + // completed during each step. The percentages we get back should be + // rounded the usual way for the first few passes (i.e., 20%, 40%, etc.), + // but for the last pass through, when 500 out of 501 items have been + // processed, we do not want to round up to 100%, since that would + // erroneously indicate that the processing is complete. + array('total' => 1, 'current' => 100/501, '20'), + array('total' => 1, 'current' => 200/501, '40'), + array('total' => 1, 'current' => 300/501, '60'), + array('total' => 1, 'current' => 400/501, '80'), + array('total' => 1, 'current' => 500/501, '99.8'), + ); + } +}