Skip to content

Commit

Permalink
Replaced PHPUnit_Framework_Constraint_IsEqual::recursiveComparison() …
Browse files Browse the repository at this point in the history
…with a comparator sub-component

This sub-component has three final goals:

 * Allow end-users to override the comparison mechanism for specific
   data types. This is especially interesting when classes have internal,
   random property values (for example generated tokens or IDs) that
   should be ignored when comparing two objects.

   This goal is achieved by this commit. You can implement a custom
   extension of PHPUnit_Framework_Comparator and register it by passing
   it to PHPUnit_Framework_Comparator::register(). Look at the source of
   the existing comparators for more inspiration.

 * Currently, PHPUnit fails to print the difference between very large
   object trees. These trees are converted to strings using print_r(),
   which fails to export such large trees and leads to segmentation
   faults. You can reproduce this behaviour by passing two unequal
   Doctrine_Record[1] instances to assertEquals().

   This goal will be achieved in a future commit.

 * Currently, the diff output for large object trees is very hard to use,
   because the diff contains much more information than necessary to
   inspect the error. Because comparators throw
   PHPUnit_Framework_ComparisonFailure instances when they fail, they can
   also provide a lot of information about where exactly the comparison
   failed.

   This goal will also be achieved in a future commit.

[1] http://www.doctrine-project.org
  • Loading branch information
Bernhard Schussek committed Jun 14, 2011
1 parent 5937d9a commit d38ea8f
Show file tree
Hide file tree
Showing 19 changed files with 1,338 additions and 178 deletions.
10 changes: 10 additions & 0 deletions PHPUnit/Autoload.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,16 @@ function phpunit_autoload($class = NULL) {
'phpunit_extensions_ticketlistener_trac' => '/Extensions/TicketListener/Trac.php',
'phpunit_framework_assert' => '/Framework/Assert.php',
'phpunit_framework_assertionfailederror' => '/Framework/AssertionFailedError.php',
'phpunit_framework_comparator' => '/Framework/Comparator.php',
'phpunit_framework_comparator_array' => '/Framework/Comparator/Array.php',
'phpunit_framework_comparator_domdocument' => '/Framework/Comparator/DOMDocument.php',
'phpunit_framework_comparator_double' => '/Framework/Comparator/Double.php',
'phpunit_framework_comparator_exception' => '/Framework/Comparator/Exception.php',
'phpunit_framework_comparator_object' => '/Framework/Comparator/Object.php',
'phpunit_framework_comparator_resource' => '/Framework/Comparator/Resource.php',
'phpunit_framework_comparator_scalar' => '/Framework/Comparator/Scalar.php',
'phpunit_framework_comparator_splobjectstorage' => '/Framework/Comparator/SplObjectStorage.php',
'phpunit_framework_comparator_type' => '/Framework/Comparator/Type.php',
'phpunit_framework_comparisonfailure' => '/Framework/ComparisonFailure.php',
'phpunit_framework_comparisonfailure_array' => '/Framework/ComparisonFailure/Array.php',
'phpunit_framework_comparisonfailure_object' => '/Framework/ComparisonFailure/Object.php',
Expand Down
143 changes: 143 additions & 0 deletions PHPUnit/Framework/Comparator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
<?php
/**
* PHPUnit
*
* Copyright (c) 2002-2011, Sebastian Bergmann <sebastian@phpunit.de>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* * Neither the name of Sebastian Bergmann nor the names of his
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* @package PHPUnit
* @subpackage Framework
* @author Bernhard Schussek <bschussek@gmail.com>
* @copyright 2002-2011 Sebastian Bergmann <sebastian@phpunit.de>
* @license http://www.opensource.org/licenses/bsd-license.php BSD License
* @link http://www.phpunit.de/
* @since File available since Release 3.6.0
*/

/**
* Abstract base class for comparators which compare values for equality.
*
* @package PHPUnit
* @subpackage Framework
* @author Bernhard Schussek <bschussek@gmail.com>
* @copyright 2002-2011 Sebastian Bergmann <sebastian@phpunit.de>
* @license http://www.opensource.org/licenses/bsd-license.php BSD License
* @version Release: @package_version@
* @link http://www.phpunit.de/
* @since Class available since Release 3.6.0
*/
abstract class PHPUnit_Framework_Comparator
{
/**
* @var array
*/
protected static $comparators = array();

/**
* Returns the correct comparator for comparing two values.
*
* @param mixed $a The first value to compare
* @param mixed $b The second value to compare
* @return PHPUnit_Framework_Comparator
* @since Method available since Release 3.0.0
*/
public static function getInstance($a, $b)
{
foreach (self::$comparators as $comparator) {
if ($comparator->accepts($a, $b)) {
return $comparator;
}
}

throw new InvalidArgumentException(sprintf('No comparator is registered for comparing the types "%s" and "%s"', gettype($a), gettype($b)));
}

/**
* Registers a new comparator.
*
* This comparator will be returned by getInstance() if its accept() method
* returns TRUE for the compared values. It has higher priority than the
* existing comparators, meaning that its accept() method will be tested
* before those of the other comparators.
*
* @param PHPUnit_Framework_Comparator $comparator The registered comparator
* @since Method available since Release 3.0.0
*/
public static function register(PHPUnit_Framework_Comparator $comparator)
{
array_unshift(self::$comparators, $comparator);
}

/**
* Unregisters a comparator.
*
* This comparator will no longer be returned by getInstance().
*
* @param PHPUnit_Framework_Comparator $comparator The unregistered comparator
* @since Method available since Release 3.0.0
*/
public static function unregister(PHPUnit_Framework_Comparator $comparator)
{
if (false !== ($key = array_search($comparator, self::$comparators, true)))
{
unset(self::$comparators[$key]);
}
}

/**
* Returns whether the comparator can compare two values.
*
* @param mixed $a The first value to compare
* @param mixed $b The second value to compare
* @return boolean
* @since Method available since Release 3.0.0
*/
abstract public function accepts($a, $b);

/**
* Asserts that two values are equal.
*
* @param mixed $a The first value to compare
* @param mixed $b The second value to compare
* @param float $delta The allowed numerical distance between two values to
* consider them equal
* @param bool $canonicalize If set to TRUE, arrays are sorted before
* comparison
* @param bool $ignoreCase If set to TRUE, upper- and lowercasing is
* ignored when comparing string values
* @throws PHPUnit_Framework_ComparisonFailure Thrown when the comparison
* fails. Contains information about the
* specific errors that lead to the failure.
* @since Method available since Release 3.0.0
*/
abstract public function assertEquals($a, $b, $delta = 0, $canonicalize = FALSE, $ignoreCase = FALSE);
}
174 changes: 174 additions & 0 deletions PHPUnit/Framework/Comparator/Array.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
<?php
/**
* PHPUnit
*
* Copyright (c) 2002-2011, Sebastian Bergmann <sebastian@phpunit.de>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* * Neither the name of Sebastian Bergmann nor the names of his
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* @package PHPUnit
* @subpackage Framework
* @author Bernhard Schussek <bschussek@gmail.com>
* @copyright 2002-2011 Sebastian Bergmann <sebastian@phpunit.de>
* @license http://www.opensource.org/licenses/bsd-license.php BSD License
* @link http://www.phpunit.de/
* @since File available since Release 3.6.0
*/

/**
* Compares arrays for equality.
*
* @package PHPUnit
* @subpackage Framework_Comparator
* @author Bernhard Schussek <bschussek@gmail.com>
* @copyright 2002-2011 Sebastian Bergmann <sebastian@phpunit.de>
* @license http://www.opensource.org/licenses/bsd-license.php BSD License
* @version Release: @package_version@
* @link http://www.phpunit.de/
* @since Class available since Release 3.6.0
*/
class PHPUnit_Framework_Comparator_Array extends PHPUnit_Framework_Comparator
{
/**
* Returns whether the comparator can compare two values.
*
* @param mixed $a The first value to compare
* @param mixed $b The second value to compare
* @return boolean
* @since Method available since Release 3.6.0
*/
public function accepts($a, $b)
{
return is_array($a) && is_array($b);
}

/**
* Asserts that two values are equal.
*
* @param mixed $a The first value to compare
* @param mixed $b The second value to compare
* @param float $delta The allowed numerical distance between two values to
* consider them equal
* @param bool $canonicalize If set to TRUE, arrays are sorted before
* comparison
* @param bool $ignoreCase If set to TRUE, upper- and lowercasing is
* ignored when comparing string values
* @throws PHPUnit_Framework_ComparisonFailure Thrown when the comparison
* fails. Contains information about the
* specific errors that lead to the failure.
* @since Method available since Release 3.6.0
*/
public function assertEquals($a, $b, $delta = 0, $canonicalize = FALSE, $ignoreCase = FALSE, array &$processed = array())
{
$remaining = $b;

if ($canonicalize) {
sort($a);
sort($b);
}

foreach ($a as $key => $value) {
if (!isset($b[$key])) {
throw new PHPUnit_Framework_ComparisonFailure($a/*->dumpExcerpt($key, $comparator)*/, $b);
}

try {
self::getInstance($value, $b[$key])->assertEquals($value, $b[$key], $delta, $canonicalize, $ignoreCase, $processed);
}

catch (PHPUnit_Framework_ComparisonFailure $e) {
throw new PHPUnit_Framework_ComparisonFailure($a/*->dumpExcerpt($key, $e->getExpected())*/, $b/*->dumpExcerpt($key, $e->getActual())*/);
}

unset($remaining[$key]);
}

foreach ($remaining as $key => $value) {
throw new PHPUnit_Framework_ComparisonFailure($a, $b/*->dumpExcerpt($key, $value)*/);
}
}

protected function dumpAll()
{
$result = $this->getType().' (';

if (!empty($this->value)) {
$result .= "\n";

foreach ($this->value as $k => $v) {
$result .= sprintf(" %s => %s,\n", var_export($k, true), $this->indent($v));
}
}

$result .= ')';

return $result;
}

protected function dumpExcerpt($key = null, $value = null)
{
$result = $this->getType().' (';

if (!empty($this->value)) {
$truncated = false;
$result .= "\n";

foreach ($this->value as $k => $v) {
if ((is_null($key) || $key !== $k) && !$truncated) {
$result .= " ...\n";
$truncated = true;
}
else if ($k === $key) {
$value = null === $value
? $v
: ($value instanceof PHPUnit_Framework_SelfDescribing ? $value->toString() : $value);
$result .= sprintf(" %s => %s,\n", var_export($k, true), $this->indent($value));
$truncated = false;
}
}
}

$result .= ')';

return $result;
}

protected function indent($lines)
{
$lines = explode("\n", $lines);

foreach ($lines as $key => $line) {
$lines[$key] = ' '.$line;
}

return trim(implode("\n", $lines));
}
}
Loading

0 comments on commit d38ea8f

Please sign in to comment.