From fe47d48540813f2b7c56975501990c797bcdf118 Mon Sep 17 00:00:00 2001 From: Andreas Braun Date: Thu, 22 Jun 2023 08:50:04 +0200 Subject: [PATCH] PHPLIB-1147, PHPLIB-1148: Int64 improvements (#1112) * PHPLIB-1147: Create Int64 instances directly * PHPLIB-1148: Add comparator for Int64 objects * Simplify comparison of Int64 --- phpunit.evergreen.xml | 2 +- phpunit.xml.dist | 2 +- tests/Comparator/Int64Comparator.php | 45 ++++ tests/Comparator/Int64ComparatorTest.php | 211 ++++++++++++++++++ .../FunctionalTestCase.php | 11 - .../Prose22_RangeExplicitEncryptionTest.php | 7 +- .../ClientSideEncryptionSpecTest.php | 14 +- tests/SpecTests/DocumentsMatchConstraint.php | 8 - .../DocumentsMatchConstraintTest.php | 6 +- .../Constraint/IsBsonTypeTest.php | 6 +- tests/UnifiedSpecTests/Constraint/Matches.php | 13 -- tests/bootstrap.php | 9 + 12 files changed, 279 insertions(+), 55 deletions(-) create mode 100644 tests/Comparator/Int64Comparator.php create mode 100644 tests/Comparator/Int64ComparatorTest.php create mode 100644 tests/bootstrap.php diff --git a/phpunit.evergreen.xml b/phpunit.evergreen.xml index da26eba50..56b35f6f8 100644 --- a/phpunit.evergreen.xml +++ b/phpunit.evergreen.xml @@ -6,7 +6,7 @@ beStrictAboutOutputDuringTests="true" beStrictAboutChangesToGlobalState="true" colors="true" - bootstrap="vendor/autoload.php" + bootstrap="tests/bootstrap.php" defaultTestSuite="Default Test Suite" > diff --git a/phpunit.xml.dist b/phpunit.xml.dist index f8a13387c..c4f6cb2b7 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -6,7 +6,7 @@ beStrictAboutOutputDuringTests="true" beStrictAboutChangesToGlobalState="true" colors="true" - bootstrap="vendor/autoload.php" + bootstrap="tests/bootstrap.php" defaultTestSuite="Default Test Suite" > diff --git a/tests/Comparator/Int64Comparator.php b/tests/Comparator/Int64Comparator.php new file mode 100644 index 000000000..c985e0c03 --- /dev/null +++ b/tests/Comparator/Int64Comparator.php @@ -0,0 +1,45 @@ +isComparable($actual)) + || ($actual instanceof Int64 && $this->isComparable($expected)); + } + + public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false): void + { + if ($expected == $actual) { + return; + } + + throw new ComparisonFailure( + $expected, + $actual, + '', + '', + false, + sprintf( + 'Failed asserting that %s matches expected %s.', + $this->exporter->export($actual), + $this->exporter->export($expected) + ) + ); + } + + private function isComparable($value): bool + { + return $value instanceof Int64 || is_numeric($value); + } +} diff --git a/tests/Comparator/Int64ComparatorTest.php b/tests/Comparator/Int64ComparatorTest.php new file mode 100644 index 000000000..9a887acd3 --- /dev/null +++ b/tests/Comparator/Int64ComparatorTest.php @@ -0,0 +1,211 @@ +assertSame($expectedResult, (new Int64Comparator())->accepts($expectedValue, $actualValue)); + } + + public static function provideAcceptsValues(): Generator + { + yield 'Expects Int64, Actual Int64' => [ + 'expectedResult' => true, + 'expectedValue' => new Int64(123), + 'actualValue' => new Int64(123), + ]; + + yield 'Expects Int64, Actual int' => [ + 'expectedResult' => true, + 'expectedValue' => new Int64(123), + 'actualValue' => 123, + ]; + + yield 'Expects Int64, Actual int string' => [ + 'expectedResult' => true, + 'expectedValue' => new Int64(123), + 'actualValue' => '123', + ]; + + yield 'Expects Int64, Actual float' => [ + 'expectedResult' => true, + 'expectedValue' => new Int64(123), + 'actualValue' => 123.0, + ]; + + yield 'Expects Int64, Actual float string' => [ + 'expectedResult' => true, + 'expectedValue' => new Int64(123), + 'actualValue' => '123.0', + ]; + + yield 'Expects Int64, Actual non-numeric string' => [ + 'expectedResult' => false, + 'expectedValue' => new Int64(123), + 'actualValue' => 'foo', + ]; + + yield 'Expects int, Actual Int64' => [ + 'expectedResult' => true, + 'expectedValue' => 123, + 'actualValue' => new Int64(123), + ]; + + yield 'Expects int string, Actual Int64' => [ + 'expectedResult' => true, + 'expectedValue' => '123', + 'actualValue' => new Int64(123), + ]; + + yield 'Expects float, Actual Int64' => [ + 'expectedResult' => true, + 'expectedValue' => 123.0, + 'actualValue' => new Int64(123), + ]; + + yield 'Expects float string, Actual Int64' => [ + 'expectedResult' => true, + 'expectedValue' => '123.0', + 'actualValue' => new Int64(123), + ]; + + yield 'Expects non-numeric string, Actual Int64' => [ + 'expectedResult' => false, + 'expectedValue' => 'foo', + 'actualValue' => new Int64(123), + ]; + + yield 'Expects float, Actual float' => [ + 'expectedResult' => false, + 'expectedValue' => 123.0, + 'actualValue' => 123.0, + ]; + + yield 'Expects numeric string, Actual numeric string' => [ + 'expectedResult' => false, + 'expectedValue' => '123', + 'actualValue' => '123', + ]; + } + + /** + * @dataProvider provideMatchingAssertions + * @doesNotPerformAssertions + */ + public function testMatchingAssertions($expected, $actual): void + { + (new Int64Comparator())->assertEquals($expected, $actual); + } + + public static function provideMatchingAssertions(): Generator + { + yield 'Expected Int64, Actual Int64' => [ + 'expected' => new Int64(8589934592), + 'actual' => new Int64(8589934592), + ]; + + yield 'Expected Int64, Actual int' => [ + 'expected' => new Int64(8589934592), + 'actual' => 8589934592, + ]; + + yield 'Expected Int64, Actual int string' => [ + 'expected' => new Int64(8589934592), + 'actual' => '8589934592', + ]; + + yield 'Expected Int64, Actual float' => [ + 'expected' => new Int64(8589934592), + 'actual' => 8589934592.0, + ]; + + yield 'Expected Int64, Actual float string' => [ + 'expected' => new Int64(8589934592), + 'actual' => '8589934592.0', + ]; + + yield 'Expected int, Actual Int64' => [ + 'expected' => 8589934592, + 'actual' => new Int64(8589934592), + ]; + + yield 'Expected int string, Actual Int64' => [ + 'expected' => '8589934592', + 'actual' => new Int64(8589934592), + ]; + + yield 'Expected float, Actual Int64' => [ + 'expected' => 8589934592.0, + 'actual' => new Int64(8589934592), + ]; + + yield 'Expected float string, Actual Int64' => [ + 'expected' => '8589934592.0', + 'actual' => new Int64(8589934592), + ]; + } + + /** @dataProvider provideFailingValues */ + public function testFailingAssertions($expected, $actual): void + { + $this->expectException(ComparisonFailure::class); + + (new Int64Comparator())->assertEquals($expected, $actual); + } + + public static function provideFailingValues(): Generator + { + yield 'Expected Int64, Actual Int64' => [ + 'expected' => new Int64(8589934592), + 'actual' => new Int64(456), + ]; + + yield 'Expected Int64, Actual int' => [ + 'expected' => new Int64(8589934592), + 'actual' => 456, + ]; + + yield 'Expected Int64, Actual int string' => [ + 'expected' => new Int64(8589934592), + 'actual' => '456', + ]; + + yield 'Expected Int64, Actual float' => [ + 'expected' => new Int64(8589934592), + 'actual' => 8589934592.1, + ]; + + yield 'Expected Int64, Actual float string' => [ + 'expected' => new Int64(8589934592), + 'actual' => '8589934592.1', + ]; + + yield 'Expected int, Actual Int64' => [ + 'expected' => 8589934592, + 'actual' => new Int64(456), + ]; + + yield 'Expected int string, Actual Int64' => [ + 'expected' => '8589934592', + 'actual' => new Int64(456), + ]; + + yield 'Expected float, Actual Int64' => [ + 'expected' => 8589934592.1, + 'actual' => new Int64(456), + ]; + + yield 'Expected float string, Actual Int64' => [ + 'expected' => '8589934592.1', + 'actual' => new Int64(456), + ]; + } +} diff --git a/tests/SpecTests/ClientSideEncryption/FunctionalTestCase.php b/tests/SpecTests/ClientSideEncryption/FunctionalTestCase.php index 08368b0ac..a48ea03ad 100644 --- a/tests/SpecTests/ClientSideEncryption/FunctionalTestCase.php +++ b/tests/SpecTests/ClientSideEncryption/FunctionalTestCase.php @@ -2,7 +2,6 @@ namespace MongoDB\Tests\SpecTests\ClientSideEncryption; -use MongoDB\BSON\Int64; use MongoDB\Client; use MongoDB\Driver\WriteConcern; use MongoDB\Tests\SpecTests\FunctionalTestCase as BaseFunctionalTestCase; @@ -14,8 +13,6 @@ use function is_executable; use function is_readable; use function sprintf; -use function strlen; -use function unserialize; use const DIRECTORY_SEPARATOR; use const PATH_SEPARATOR; @@ -69,14 +66,6 @@ protected static function insertKeyVaultData(Client $client, ?array $keyVaultDat $collection->insertMany($keyVaultData); } - protected static function createInt64(string $value): Int64 - { - $array = sprintf('a:1:{s:7:"integer";s:%d:"%s";}', strlen($value), $value); - $int64 = sprintf('C:%d:"%s":%d:{%s}', strlen(Int64::class), Int64::class, strlen($array), $array); - - return unserialize($int64); - } - private function createTestCollection(?stdClass $encryptedFields = null, ?stdClass $jsonSchema = null): void { $context = $this->getContext(); diff --git a/tests/SpecTests/ClientSideEncryption/Prose22_RangeExplicitEncryptionTest.php b/tests/SpecTests/ClientSideEncryption/Prose22_RangeExplicitEncryptionTest.php index d1c437745..46de1c49f 100644 --- a/tests/SpecTests/ClientSideEncryption/Prose22_RangeExplicitEncryptionTest.php +++ b/tests/SpecTests/ClientSideEncryption/Prose22_RangeExplicitEncryptionTest.php @@ -8,6 +8,7 @@ use MongoDB\BSON\Binary; use MongoDB\BSON\Decimal128; use MongoDB\BSON\Document; +use MongoDB\BSON\Int64; use MongoDB\BSON\UTCDateTime; use MongoDB\Driver\ClientEncryption; use MongoDB\Driver\Exception\EncryptionException; @@ -168,8 +169,8 @@ public static function provideTypeAndRangeOpts(): Generator yield 'Long' => [ 'Long', [ - 'min' => self::createInt64('0'), - 'max' => self::createInt64('200'), + 'min' => new Int64(0), + 'max' => new Int64(200), 'sparsity' => 1, ], ]; @@ -467,7 +468,7 @@ private static function getCastCallableForType(string $type): callable case 'Long': return function (int $value) { - return self::createInt64((string) $value); + return new Int64($value); }; default: diff --git a/tests/SpecTests/ClientSideEncryptionSpecTest.php b/tests/SpecTests/ClientSideEncryptionSpecTest.php index e75ef306d..6d30a526e 100644 --- a/tests/SpecTests/ClientSideEncryptionSpecTest.php +++ b/tests/SpecTests/ClientSideEncryptionSpecTest.php @@ -43,9 +43,7 @@ use function json_decode; use function sprintf; use function str_repeat; -use function strlen; use function substr; -use function unserialize; use const DIRECTORY_SEPARATOR; use const PATH_SEPARATOR; @@ -1862,14 +1860,6 @@ public static function provideRewrapManyDataKeySrcAndDstProviders() } } - private function createInt64(string $value): Int64 - { - $array = sprintf('a:1:{s:7:"integer";s:%d:"%s";}', strlen($value), $value); - $int64 = sprintf('C:%d:"%s":%d:{%s}', strlen(Int64::class), Int64::class, strlen($array), $array); - - return unserialize($int64); - } - private function createTestCollection(?stdClass $encryptedFields = null, ?stdClass $jsonSchema = null): void { $context = $this->getContext(); @@ -1936,7 +1926,7 @@ private function encryptCorpusValue(string $fieldName, stdClass $data, ClientEnc /* Note: workaround issue where mongocryptd refuses to encrypt * 32-bit integers if schemaMap defines a "long" BSON type. */ $value = $data->type === 'long' && ! $data->value instanceof Int64 - ? $this->createInt64($data->value) + ? new Int64($data->value) : $data->value; $encrypted = $clientEncryption->encrypt($value, $encryptionOptions); @@ -1987,7 +1977,7 @@ private function prepareCorpusData(string $fieldName, stdClass $data, ClientEncr /* Note: workaround issue where mongocryptd refuses to encrypt * 32-bit integers if schemaMap defines a "long" BSON type. */ if ($data->type === 'long' && ! $data->value instanceof Int64) { - $data->value = $this->createInt64($data->value); + $data->value = new Int64($data->value); } return $data; diff --git a/tests/SpecTests/DocumentsMatchConstraint.php b/tests/SpecTests/DocumentsMatchConstraint.php index 3dde8e705..cf5f98ee7 100644 --- a/tests/SpecTests/DocumentsMatchConstraint.php +++ b/tests/SpecTests/DocumentsMatchConstraint.php @@ -30,8 +30,6 @@ use function PHPUnit\Framework\logicalOr; use function sprintf; -use const PHP_INT_SIZE; - /** * Constraint that checks if one document matches another. * @@ -308,12 +306,6 @@ private function prepareBSON($bson, bool $isRoot, bool $sortKeys = false) $bson[$key] = $this->prepareBSON($value, false, $sortKeys); continue; } - - /* Convert Int64 objects to integers on 64-bit platforms for - * compatibility reasons. */ - if ($value instanceof Int64 && PHP_INT_SIZE != 4) { - $bson[$key] = (int) ((string) $value); - } } return $bson; diff --git a/tests/SpecTests/DocumentsMatchConstraintTest.php b/tests/SpecTests/DocumentsMatchConstraintTest.php index bd61a9354..c14f001c6 100644 --- a/tests/SpecTests/DocumentsMatchConstraintTest.php +++ b/tests/SpecTests/DocumentsMatchConstraintTest.php @@ -4,6 +4,7 @@ use MongoDB\BSON\Binary; use MongoDB\BSON\Decimal128; +use MongoDB\BSON\Int64; use MongoDB\BSON\Javascript; use MongoDB\BSON\MaxKey; use MongoDB\BSON\MinKey; @@ -18,7 +19,6 @@ use function MongoDB\BSON\fromJSON; use function MongoDB\BSON\toPHP; -use function unserialize; use const PHP_INT_SIZE; @@ -85,8 +85,8 @@ public function provideBSONTypes() $undefined = toPHP(fromJSON('{ "x": {"$undefined": true} }'))->x; $symbol = toPHP(fromJSON('{ "x": {"$symbol": "test"} }'))->x; $dbPointer = toPHP(fromJSON('{ "x": {"$dbPointer": {"$ref": "db.coll", "$id" : { "$oid" : "5a2e78accd485d55b405ac12" } }} }'))->x; - $int64 = unserialize('C:18:"MongoDB\BSON\Int64":28:{a:1:{s:7:"integer";s:1:"1";}}'); - $long = PHP_INT_SIZE == 4 ? unserialize('C:18:"MongoDB\BSON\Int64":38:{a:1:{s:7:"integer";s:10:"4294967296";}}') : 4294967296; + $int64 = new Int64(1); + $long = PHP_INT_SIZE == 4 ? new Int64('4294967296') : 4294967296; return [ 'double' => ['double', 1.4], diff --git a/tests/UnifiedSpecTests/Constraint/IsBsonTypeTest.php b/tests/UnifiedSpecTests/Constraint/IsBsonTypeTest.php index 6bace1633..d14b09e60 100644 --- a/tests/UnifiedSpecTests/Constraint/IsBsonTypeTest.php +++ b/tests/UnifiedSpecTests/Constraint/IsBsonTypeTest.php @@ -4,6 +4,7 @@ use MongoDB\BSON\Binary; use MongoDB\BSON\Decimal128; +use MongoDB\BSON\Int64; use MongoDB\BSON\Javascript; use MongoDB\BSON\MaxKey; use MongoDB\BSON\MinKey; @@ -22,7 +23,6 @@ use function fopen; use function MongoDB\BSON\fromJSON; use function MongoDB\BSON\toPHP; -use function unserialize; use const PHP_INT_SIZE; @@ -39,8 +39,8 @@ public function provideTypes() $undefined = toPHP(fromJSON('{ "x": {"$undefined": true} }'))->x; $symbol = toPHP(fromJSON('{ "x": {"$symbol": "test"} }'))->x; $dbPointer = toPHP(fromJSON('{ "x": {"$dbPointer": {"$ref": "db.coll", "$id" : { "$oid" : "5a2e78accd485d55b405ac12" } }} }'))->x; - $int64 = unserialize('C:18:"MongoDB\BSON\Int64":28:{a:1:{s:7:"integer";s:1:"1";}}'); - $long = PHP_INT_SIZE == 4 ? unserialize('C:18:"MongoDB\BSON\Int64":38:{a:1:{s:7:"integer";s:10:"4294967296";}}') : 4294967296; + $int64 = new Int64(1); + $long = PHP_INT_SIZE == 4 ? new Int64('4294967296') : 4294967296; return [ 'double' => ['double', 1.4], diff --git a/tests/UnifiedSpecTests/Constraint/Matches.php b/tests/UnifiedSpecTests/Constraint/Matches.php index 579240d69..4f1586625 100644 --- a/tests/UnifiedSpecTests/Constraint/Matches.php +++ b/tests/UnifiedSpecTests/Constraint/Matches.php @@ -40,8 +40,6 @@ use function strpos; use function strrchr; -use const PHP_INT_SIZE; - /** * Constraint that checks if one value matches another. * @@ -425,17 +423,6 @@ private static function prepare($bson) return $bson; } - /* Convert Int64 objects to integers on 64-bit platforms for - * compatibility reasons. */ - if ($bson instanceof Int64 && PHP_INT_SIZE != 4) { - return (int) ((string) $bson); - } - - /* TODO: Convert Int64 objects to integers on 32-bit platforms if they - * can be expressed as such. This is necessary to handle flexible - * numeric comparisons if the server returns 32-bit value as a 64-bit - * integer (e.g. cursor ID). */ - // Serializable can produce an array or object, so recurse on its output if ($bson instanceof Serializable) { return self::prepare($bson->bsonSerialize()); diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 000000000..d10cd460d --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,9 @@ +register(new Int64Comparator());