Skip to content

Commit

Permalink
Assert::equal() added flags $matchOrder & $matchIdentity
Browse files Browse the repository at this point in the history
  • Loading branch information
dg committed Feb 6, 2023
1 parent 3ec6c66 commit 9d28a53
Show file tree
Hide file tree
Showing 3 changed files with 181 additions and 8 deletions.
30 changes: 22 additions & 8 deletions src/Framework/Assert.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,18 @@ public static function notSame(mixed $expected, mixed $actual, ?string $descript

/**
* Asserts that two values are equal and checks expectations. The identity of objects,
* the order of keys in the arrays and marginally different floats are ignored.
* the order of keys in the arrays and marginally different floats are ignored by default.
*/
public static function equal(mixed $expected, mixed $actual, ?string $description = null): void
public static function equal(
mixed $expected,
mixed $actual,
?string $description = null,
bool $matchOrder = false,
bool $matchIdentity = false,
): void
{
self::$counter++;
if (!self::isEqual($expected, $actual)) {
if (!self::isEqual($expected, $actual, $matchOrder, $matchIdentity)) {
self::fail(self::describe('%1 should be equal to %2', $description), $actual, $expected);
}
}
Expand All @@ -93,7 +99,7 @@ public static function notEqual(mixed $expected, mixed $actual, ?string $descrip
{
self::$counter++;
try {
$res = self::isEqual($expected, $actual);
$res = self::isEqual($expected, $actual, matchOrder: false, matchIdentity: false);
} catch (AssertException $e) {
}

Expand Down Expand Up @@ -602,6 +608,8 @@ public static function expandMatchingPatterns(string $pattern, string $actual):
private static function isEqual(
mixed $expected,
mixed $actual,
bool $matchOrder,
bool $matchIdentity,
int $level = 0,
?\SplObjectStorage $objects = null
): bool
Expand All @@ -618,7 +626,7 @@ private static function isEqual(
$diff = abs($expected - $actual);
return ($diff < self::Epsilon) || ($diff / max(abs($expected), abs($actual)) < self::Epsilon);

case is_object($expected) && is_object($actual) && $expected::class === $actual::class:
case !$matchIdentity && is_object($expected) && is_object($actual) && $expected::class === $actual::class:
$objects = $objects ? clone $objects : new \SplObjectStorage;
if (isset($objects[$expected])) {
return $objects[$expected] === $actual;
Expand All @@ -633,14 +641,20 @@ private static function isEqual(
// break omitted

case is_array($expected) && is_array($actual):
ksort($expected, SORT_STRING);
ksort($actual, SORT_STRING);
if ($matchOrder) {
reset($expected);
reset($actual);
} else {
ksort($expected, SORT_STRING);
ksort($actual, SORT_STRING);
}

if (array_keys($expected) !== array_keys($actual)) {
return false;
}

foreach ($expected as $value) {
if (!self::isEqual($value, current($actual), $level + 1, $objects)) {
if (!self::isEqual($value, current($actual), $matchOrder, $matchIdentity, $level + 1, $objects)) {
return false;
}

Expand Down
79 changes: 79 additions & 0 deletions tests/Framework/Assert.equal.matchIdentity.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php

declare(strict_types=1);

use Tester\Assert;

require __DIR__ . '/../bootstrap.php';


$obj1 = new stdClass;
$obj1->{'0'} = 'a';
$obj1->{'1'} = 'b';

$obj2 = new stdClass;
$obj2->{'1'} = 'b';
$obj2->{'0'} = 'a';

$obj3 = new stdClass;
$obj3->x = $obj3->y = new stdClass;

$obj4 = new stdClass;
$obj4->x = new stdClass;
$obj4->y = new stdClass;

$deep1 = $deep2 = new stdClass;
$deep1->x = $deep2->x = $deep1;

$float1 = 1 / 3;
$float2 = 1 - 2 / 3;

$equals = [
[1, 1],
['1', '1'],
[['1'], ['1']],
[['a', 'b'], [1 => 'b', 0 => 'a']],
[['a' => true, 'b' => false], ['b' => false, 'a' => true]],
[$float1, $float2],
[$float1 * 1e9, $float2 * 1e9],
[$float1 - $float2, 0.0],
[$float1 - $float2, $float2 - $float1],
[0.0, 0.0],
[INF, INF],
[[0 => 'a', 'str' => 'b'], ['str' => 'b', 0 => 'a']],
[$deep1, $deep2],
[Tester\Expect::type('int'), 1],
];

$notEquals = [
[1, 1.0],
[new stdClass, new stdClass],
[[new stdClass], [new stdClass]],
[$obj3, $obj4],
[INF, -INF],
[['a', 'b'], ['b', 'a']],
[NAN, NAN],
[Tester\Expect::type('int'), '1', 'string should be int'],
];



foreach ($equals as [$expected, $value]) {
Assert::equal($expected, $value, matchIdentity: true);
}

foreach ($notEquals as [$expected, $value]) {
Assert::exception(function () use ($expected, $value) {
Assert::equal($expected, $value, matchIdentity: true);
}, Tester\AssertException::class, '%a% should be %a%');
}

Assert::exception(function () {
$rec = [];
$rec[] = &$rec;
Assert::equal($rec, $rec, matchIdentity: true);
}, Exception::class, 'Nesting level too deep or recursive dependency.');

Assert::exception(function () {
Assert::equal(true, false, 'Custom description', matchIdentity: true);
}, Tester\AssertException::class, 'Custom description: %a% should be %a%');
80 changes: 80 additions & 0 deletions tests/Framework/Assert.equal.matchOrder.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?php

declare(strict_types=1);

use Tester\Assert;

require __DIR__ . '/../bootstrap.php';


$obj1 = new stdClass;
$obj1->{'0'} = 'a';
$obj1->{'1'} = 'b';

$obj2 = new stdClass;
$obj2->{'1'} = 'b';
$obj2->{'0'} = 'a';

$obj3 = new stdClass;
$obj3->x = $obj3->y = new stdClass;

$obj4 = new stdClass;
$obj4->x = new stdClass;
$obj4->y = new stdClass;

$deep1 = $deep2 = new stdClass;
$deep1->x = $deep2->x = $deep1;

$float1 = 1 / 3;
$float2 = 1 - 2 / 3;

$equals = [
[1, 1],
['1', '1'],
[['1'], ['1']],
[new stdClass, new stdClass],
[[new stdClass], [new stdClass]],
[$float1, $float2],
[$float1 * 1e9, $float2 * 1e9],
[$float1 - $float2, 0.0],
[$float1 - $float2, $float2 - $float1],
[0.0, 0.0],
[INF, INF],
[$deep1, $deep2],
[Tester\Expect::type('int'), 1],
];

$notEquals = [
[1, 1.0],
[['a', 'b'], [1 => 'b', 0 => 'a']],
[['a' => true, 'b' => false], ['b' => false, 'a' => true]],
[INF, -INF],
[$obj1, $obj2],
[$obj3, $obj4],
[[0 => 'a', 'str' => 'b'], ['str' => 'b', 0 => 'a']],
[['a', 'b'], ['b', 'a']],
[NAN, NAN],
[Tester\Expect::type('int'), '1', 'string should be int'],
];



foreach ($equals as [$expected, $value]) {
Assert::equal($expected, $value, matchOrder: true);
}

foreach ($notEquals as [$expected, $value]) {
Assert::exception(function () use ($expected, $value) {
Assert::equal($expected, $value, matchOrder: true);
}, Tester\AssertException::class, '%a% should be %a%');
}

Assert::exception(function () {
$rec = [];
$rec[] = &$rec;
Assert::equal($rec, $rec, matchOrder: true);
}, Exception::class, 'Nesting level too deep or recursive dependency.');

Assert::exception(function () {
Assert::equal(true, false, 'Custom description', matchOrder: true);
}, Tester\AssertException::class, 'Custom description: %a% should be %a%');

0 comments on commit 9d28a53

Please sign in to comment.