From 3267365a8682d43c92c3429aa0ecb4b4a952f67d Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 19 Feb 2019 23:47:52 +0100 Subject: [PATCH] Assert::equal() added flags $matchOrder & $matchIdentity --- src/Framework/Assert.php | 30 +++++-- .../Framework/Assert.equal.matchIdentity.phpt | 79 ++++++++++++++++++ tests/Framework/Assert.equal.matchOrder.phpt | 80 +++++++++++++++++++ 3 files changed, 181 insertions(+), 8 deletions(-) create mode 100644 tests/Framework/Assert.equal.matchIdentity.phpt create mode 100644 tests/Framework/Assert.equal.matchOrder.phpt diff --git a/src/Framework/Assert.php b/src/Framework/Assert.php index 9fb9f253..4484cb8f 100644 --- a/src/Framework/Assert.php +++ b/src/Framework/Assert.php @@ -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); } } @@ -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) { } @@ -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 @@ -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; @@ -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; } diff --git a/tests/Framework/Assert.equal.matchIdentity.phpt b/tests/Framework/Assert.equal.matchIdentity.phpt new file mode 100644 index 00000000..65910ad4 --- /dev/null +++ b/tests/Framework/Assert.equal.matchIdentity.phpt @@ -0,0 +1,79 @@ +{'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%'); diff --git a/tests/Framework/Assert.equal.matchOrder.phpt b/tests/Framework/Assert.equal.matchOrder.phpt new file mode 100644 index 00000000..5e6a1345 --- /dev/null +++ b/tests/Framework/Assert.equal.matchOrder.phpt @@ -0,0 +1,80 @@ +{'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%');