-
Notifications
You must be signed in to change notification settings - Fork 266
PHPLIB-715: Use flexible numeric comparisons in DocumentMatchConstraint #958
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -35,10 +35,10 @@ | |
use function array_values; | ||
use function get_class; | ||
use function get_debug_type; | ||
use function in_array; | ||
use function is_array; | ||
use function is_float; | ||
use function is_int; | ||
use function is_object; | ||
use function is_scalar; | ||
use function method_exists; | ||
use function sprintf; | ||
|
||
|
@@ -59,9 +59,6 @@ class DocumentsMatchConstraint extends Constraint | |
/** @var boolean */ | ||
private $ignoreExtraKeysInEmbedded = false; | ||
|
||
/** @var array */ | ||
private $placeholders = []; | ||
|
||
/** | ||
* TODO: This is not currently used, but was preserved from the design of | ||
* TestCase::assertMatchesDocument(), which would sort keys and then compare | ||
|
@@ -88,14 +85,12 @@ class DocumentsMatchConstraint extends Constraint | |
* @param array|object $value | ||
* @param boolean $ignoreExtraKeysInRoot If true, ignore extra keys within the root document | ||
* @param boolean $ignoreExtraKeysInEmbedded If true, ignore extra keys within embedded documents | ||
* @param array $placeholders Placeholders for any value | ||
*/ | ||
public function __construct($value, bool $ignoreExtraKeysInRoot = false, bool $ignoreExtraKeysInEmbedded = false, array $placeholders = []) | ||
public function __construct($value, bool $ignoreExtraKeysInRoot = false, bool $ignoreExtraKeysInEmbedded = false) | ||
{ | ||
$this->value = $this->prepareBSON($value, true, $this->sortKeys); | ||
$this->ignoreExtraKeysInRoot = $ignoreExtraKeysInRoot; | ||
$this->ignoreExtraKeysInEmbedded = $ignoreExtraKeysInEmbedded; | ||
$this->placeholders = $placeholders; | ||
$this->comparatorFactory = Factory::getInstance(); | ||
} | ||
|
||
|
@@ -303,10 +298,6 @@ private function assertEquals(ArrayObject $expected, ArrayObject $actual, bool $ | |
throw new RuntimeException(sprintf('$actual is missing key: "%s"', $keyPrefix . $key)); | ||
} | ||
|
||
if (in_array($expectedValue, $this->placeholders, true)) { | ||
continue; | ||
} | ||
|
||
$actualValue = $actual[$key]; | ||
|
||
if ($expectedValue instanceof BSONDocument && isset($expectedValue['$$type'])) { | ||
|
@@ -322,26 +313,13 @@ private function assertEquals(ArrayObject $expected, ArrayObject $actual, bool $ | |
continue; | ||
} | ||
|
||
if (is_scalar($expectedValue) && is_scalar($actualValue)) { | ||
if ($expectedValue !== $actualValue) { | ||
throw new ComparisonFailure( | ||
$expectedValue, | ||
$actualValue, | ||
'', | ||
'', | ||
false, | ||
sprintf('Field path "%s": %s', $keyPrefix . $key, 'Failed asserting that two values are equal.') | ||
); | ||
} | ||
|
||
continue; | ||
} | ||
|
||
$expectedType = get_debug_type($expectedValue); | ||
$actualType = get_debug_type($actualValue); | ||
|
||
// Workaround for ObjectComparator printing the whole actual object | ||
if ($expectedType !== $actualType) { | ||
/* Early check to work around ObjectComparator printing the entire value | ||
* for a failed type comparison. Avoid doing this if either value is | ||
* numeric to allow for flexible numeric comparisons (e.g. 1 == 1.0). */ | ||
if ($expectedType !== $actualType && ! (self::isNumeric($expectedValue) || self::isNumeric($actualValue))) { | ||
throw new ComparisonFailure( | ||
$expectedValue, | ||
$actualValue, | ||
|
@@ -421,6 +399,11 @@ private function doToString() | |
return 'matches ' . $this->exporter()->export($this->value); | ||
} | ||
|
||
private static function isNumeric($value): bool | ||
{ | ||
return is_int($value) || is_float($value) || $value instanceof Int64; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Int64 is conditionally used in DocumentsMatchConstraintTest only when the platform is 32-bit. This probably makes no difference, but the fact that you reference the class here made me ask. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I shamelessly copied the logic from the unified spec tests where this was used for flexible numeric comparisons. |
||
} | ||
|
||
/** | ||
* Prepare a BSON document or array for comparison. | ||
* | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -43,6 +43,14 @@ public function testIgnoreExtraKeysInRoot(): void | |
$this->assertResult(false, $c, [1, ['a' => 1, 'b' => 2]], 'Extra keys in embedded are not permitted'); | ||
} | ||
|
||
public function testFlexibleNumericComparison(): void | ||
{ | ||
$c = new DocumentsMatchConstraint(['x' => 1, 'y' => 1.0]); | ||
$this->assertResult(true, $c, ['x' => 1.0, 'y' => 1.0], 'Float instead of expected int matches'); | ||
$this->assertResult(true, $c, ['x' => 1, 'y' => 1], 'Int instead of expected float matches'); | ||
$this->assertResult(false, $c, ['x' => 'foo', 'y' => 1.0], 'Different type does not match'); | ||
} | ||
|
||
public function testIgnoreExtraKeysInEmbedded(): void | ||
{ | ||
$c = new DocumentsMatchConstraint(['x' => 1, 'y' => ['a' => 1, 'b' => 2]], false, true); | ||
|
@@ -64,15 +72,6 @@ public function testIgnoreExtraKeysInEmbedded(): void | |
$this->assertResult(false, $c, [1, ['a' => 2]], 'Keys must have the correct value'); | ||
} | ||
|
||
public function testPlaceholders(): void | ||
{ | ||
$c = new DocumentsMatchConstraint(['x' => '42', 'y' => 42, 'z' => ['a' => 24]], false, false, [24, 42]); | ||
|
||
$this->assertResult(true, $c, ['x' => '42', 'y' => 'foo', 'z' => ['a' => 1]], 'Placeholders accept any value'); | ||
$this->assertResult(false, $c, ['x' => 42, 'y' => 'foo', 'z' => ['a' => 1]], 'Placeholder type must match'); | ||
$this->assertResult(true, $c, ['x' => '42', 'y' => 42, 'z' => ['a' => 24]], 'Exact match'); | ||
} | ||
|
||
/** | ||
* @dataProvider provideBSONTypes | ||
*/ | ||
|
@@ -146,13 +145,13 @@ public function errorMessageProvider() | |
['foo' => ['foo' => 'bar', 'bar' => 'baz']], | ||
], | ||
'Scalar value not equal' => [ | ||
'Field path "foo": Failed asserting that two values are equal.', | ||
'Field path "foo": Failed asserting that two strings are equal.', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Error messages change due to us no longer using a direct comparison for scalar values, and instead leaving those checks to PHPUnit comparators. |
||
new DocumentsMatchConstraint(['foo' => 'bar']), | ||
['foo' => 'baz'], | ||
], | ||
'Scalar type mismatch' => [ | ||
'Field path "foo": Failed asserting that two values are equal.', | ||
new DocumentsMatchConstraint(['foo' => 42]), | ||
'Field path "foo": \'42\' is not instance of expected type "bool".', | ||
new DocumentsMatchConstraint(['foo' => true]), | ||
['foo' => '42'], | ||
], | ||
'Type mismatch' => [ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,6 +12,7 @@ | |
use MongoDB\Driver\Server; | ||
use MongoDB\Driver\Session; | ||
use MongoDB\GridFS\Bucket; | ||
use MongoDB\MapReduceResult; | ||
use MongoDB\Model\IndexInfo; | ||
use MongoDB\Operation\FindOneAndReplace; | ||
use MongoDB\Operation\FindOneAndUpdate; | ||
|
@@ -206,6 +207,10 @@ public function assert(FunctionalTestCase $test, Context $context, bool $bubbleE | |
* is not used (e.g. Command Monitoring spec). */ | ||
if ($result instanceof Cursor) { | ||
$result = $result->toArray(); | ||
} elseif ($result instanceof MapReduceResult) { | ||
/* For mapReduce operations, we ignore the mapReduce metadata | ||
* and only return the result iterator for evaluation. */ | ||
$result = iterator_to_array($result->getIterator()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Was this change necessary? The only other change was There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It doesn't. We could update the logic to call There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This is fine. No sense adding significant refactoring for an outdated code path. |
||
} | ||
} catch (Exception $e) { | ||
$exception = $e; | ||
|
@@ -789,7 +794,7 @@ private function getResultAssertionTypeForCollection() | |
return ResultExpectation::ASSERT_SAME_DOCUMENTS; | ||
|
||
case 'mapReduce': | ||
return ResultExpectation::ASSERT_SAME_DOCUMENTS; | ||
return ResultExpectation::ASSERT_DOCUMENTS_MATCH; | ||
|
||
case 'replaceOne': | ||
case 'updateMany': | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,6 +23,14 @@ public function testMatchesDocument(): void | |
$this->assertResult(true, $c, ['y' => ['b' => 2, 'a' => 1], 'x' => 1], 'Root and embedded key order is not significant'); | ||
} | ||
|
||
public function testFlexibleNumericComparison(): void | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added these for the sake of completeness (and because I wanted to make sure that this works in the unified test runner). |
||
{ | ||
$c = new Matches(['x' => 1, 'y' => 1.0]); | ||
$this->assertResult(true, $c, ['x' => 1.0, 'y' => 1.0], 'Float instead of expected int matches'); | ||
$this->assertResult(true, $c, ['x' => 1, 'y' => 1], 'Int instead of expected float matches'); | ||
$this->assertResult(false, $c, ['x' => 'foo', 'y' => 1.0], 'Different type does not match'); | ||
} | ||
|
||
public function testDoNotAllowExtraRootKeys(): void | ||
{ | ||
$c = new Matches(['x' => 1], null, false); | ||
|
Uh oh!
There was an error while loading. Please reload this page.