Skip to content

PHPLIB-493: Unified test runner POC #783

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

Merged
merged 41 commits into from
Oct 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
735f256
Reduce isShardedCluster to a one-liner
jmikola Sep 9, 2020
1845c2e
wip
jmikola Sep 9, 2020
37da353
wip
jmikola Sep 12, 2020
28c2ba5
wip
jmikola Sep 22, 2020
a589d5f
wip
jmikola Sep 23, 2020
236a031
Clean up constraints and add test coverage
jmikola Sep 28, 2020
7883988
Code fixes and fall back to assertInternalType for PHPUnit 6.x
jmikola Sep 28, 2020
e8c609f
phpcs fixes
jmikola Sep 28, 2020
1d8e7be
Use Matches for CollectionData and validate operator syntax
jmikola Sep 29, 2020
7045abf
Fix var usage
jmikola Sep 29, 2020
e7c58b2
Fix static method call and allow empty string in $$matchesHexBytes
jmikola Sep 29, 2020
a6007cd
CS fixes
jmikola Sep 29, 2020
a9f80c7
Include PHPUnit functions via autoload-dev
jmikola Sep 29, 2020
3f21c01
CR feedback
jmikola Sep 30, 2020
67e21ad
IsBsonType helpers and new IsStream constraint
jmikola Oct 5, 2020
dfc2b5a
Changes to get CRUD POC tests running
jmikola Oct 5, 2020
f88f2ca
Implement expectEvents and change stream tests
jmikola Oct 6, 2020
82c95f5
Implement GridFS, transactions, and collate events by client
jmikola Oct 6, 2020
9455db5
Remove stream entities and update GridFS operations
jmikola Oct 7, 2020
032cb4d
Todo items for Matches constraint
jmikola Oct 7, 2020
e548e8b
Assert basic structure of test files in data provider
jmikola Oct 7, 2020
2609297
Use assertion for testFailingTests
jmikola Oct 7, 2020
0fb5829
Implement session tests
jmikola Oct 7, 2020
f0f70ae
Consider PHPUnit Warnings for expectError in GridFS tests
jmikola Oct 7, 2020
2e0d254
Disable fail points after tests
jmikola Oct 7, 2020
6932497
Fix targetedFailPoint operation
jmikola Oct 7, 2020
e78cbb8
Fix MatchesTest and make EntityMap optional
jmikola Oct 7, 2020
8853503
Note cycling references and killAllSessions before each test
jmikola Oct 7, 2020
2434700
Sync unified spec tests
jmikola Oct 7, 2020
dbf4228
Fix phpcs validation
jmikola Oct 7, 2020
59f4c72
assertHasOnlyKeys requires array or stdClass
jmikola Oct 8, 2020
c49b411
Add missing valid-fail test
jmikola Oct 8, 2020
64b4118
Fix phpcs error
jmikola Oct 8, 2020
e544081
Remove nullable return type hint for PHP 7.0
jmikola Oct 8, 2020
97c9f39
Update session test
jmikola Oct 9, 2020
b572a4c
Sync returnDocument-enum-invalid.json
jmikola Oct 10, 2020
abc7426
Handle issues from code review
alcaeus Oct 13, 2020
4bb37be
fix phpcs
alcaeus Oct 13, 2020
9a89118
fix tests
alcaeus Oct 13, 2020
ea4a99b
Fix wrong continue statement
alcaeus Oct 13, 2020
f21efac
Handle readPreferenceTags in URI options
alcaeus Oct 14, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"jean85/pretty-package-versions": "^1.2"
},
"require-dev": {
"phpunit/phpunit": "^6.4",
"phpunit/phpunit": "^6.5",
"sebastian/comparator": "^2.0 || ^3.0",
"squizlabs/php_codesniffer": "^3.5, <3.5.5",
"symfony/phpunit-bridge": "^4.4@dev"
Expand All @@ -26,7 +26,10 @@
"files": [ "src/functions.php" ]
},
"autoload-dev": {
"psr-4": { "MongoDB\\Tests\\": "tests/" }
"psr-4": { "MongoDB\\Tests\\": "tests/" },
"// Manually include assertion functions for PHPUnit 8.x and earlier ":"",
"// See: https://github.com/sebastianbergmann/phpunit/issues/3746 ":"",
"files": [ "vendor/phpunit/phpunit/src/Framework/Assert/Functions.php" ]
},
"extra": {
"branch-alias": {
Expand Down
6 changes: 1 addition & 5 deletions tests/FunctionalTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -308,11 +308,7 @@ protected function isReplicaSet()

protected function isShardedCluster()
{
if ($this->getPrimaryServer()->getType() == Server::TYPE_MONGOS) {
return true;
}

return false;
return $this->getPrimaryServer()->getType() == Server::TYPE_MONGOS;
}

protected function isShardedClusterUsingReplicasets()
Expand Down
90 changes: 90 additions & 0 deletions tests/UnifiedSpecTests/CollectionData.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<?php

namespace MongoDB\Tests\UnifiedSpecTests;

use ArrayIterator;
use IteratorIterator;
use MongoDB\Client;
use MongoDB\Driver\ReadConcern;
use MongoDB\Driver\ReadPreference;
use MongoDB\Driver\WriteConcern;
use MongoDB\Tests\UnifiedSpecTests\Constraint\Matches;
use MultipleIterator;
use stdClass;
use function assertContainsOnly;
use function assertInternalType;
use function assertNotNull;
use function assertThat;
use function sprintf;

class CollectionData
{
/** @var string */
private $collectionName;

/** @var string */
private $databaseName;

/** @var array */
private $documents;

public function __construct(stdClass $o)
{
assertInternalType('string', $o->collectionName);
$this->collectionName = $o->collectionName;

assertInternalType('string', $o->databaseName);
$this->databaseName = $o->databaseName;

assertInternalType('array', $o->documents);
assertContainsOnly('object', $o->documents);
$this->documents = $o->documents;
}

public function prepareInitialData(Client $client)
{
$database = $client->selectDatabase(
$this->databaseName,
['writeConcern' => new WriteConcern(WriteConcern::MAJORITY)]
);

$database->dropCollection($this->collectionName);

if (empty($this->documents)) {
$database->createCollection($this->collectionName);

return;
}

$database->selectCollection($this->collectionName)->insertMany($this->documents);
}

public function assertOutcome(Client $client)
{
$collection = $client->selectCollection(
$this->databaseName,
$this->collectionName,
[
'readConcern' => new ReadConcern(ReadConcern::LOCAL),
'readPreference' => new ReadPreference(ReadPreference::PRIMARY),
]
);

$cursor = $collection->find([], ['sort' => ['_id' => 1]]);

$mi = new MultipleIterator(MultipleIterator::MIT_NEED_ANY);
$mi->attachIterator(new ArrayIterator($this->documents));
$mi->attachIterator(new IteratorIterator($cursor));

foreach ($mi as $i => $documents) {
list($expectedDocument, $actualDocument) = $documents;
assertNotNull($expectedDocument);
assertNotNull($actualDocument);

/* Prohibit extra root keys and disable operators to enforce exact
* matching of documents. Key order variation is still allowed. */
$constraint = new Matches($expectedDocument, null, false, false);
assertThat($actualDocument, $constraint, sprintf('documents[%d] match', $i));
}
}
}
210 changes: 210 additions & 0 deletions tests/UnifiedSpecTests/Constraint/IsBsonType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
<?php

namespace MongoDB\Tests\UnifiedSpecTests\Constraint;

use LogicException;
use MongoDB\BSON\BinaryInterface;
use MongoDB\BSON\DBPointer;
use MongoDB\BSON\Decimal128Interface;
use MongoDB\BSON\Int64;
use MongoDB\BSON\JavascriptInterface;
use MongoDB\BSON\MaxKeyInterface;
use MongoDB\BSON\MinKeyInterface;
use MongoDB\BSON\ObjectIdInterface;
use MongoDB\BSON\RegexInterface;
use MongoDB\BSON\Serializable;
use MongoDB\BSON\Symbol;
use MongoDB\BSON\TimestampInterface;
use MongoDB\BSON\Type;
use MongoDB\BSON\Undefined;
use MongoDB\BSON\UTCDateTimeInterface;
use MongoDB\Model\BSONArray;
use MongoDB\Model\BSONDocument;
use PHPUnit\Framework\Constraint\Constraint;
use PHPUnit\Framework\Constraint\LogicalOr;
use RuntimeException;
use Symfony\Bridge\PhpUnit\ConstraintTrait;
use function array_keys;
use function array_map;
use function count;
use function in_array;
use function is_array;
use function is_bool;
use function is_float;
use function is_int;
use function is_object;
use function is_string;
use function range;
use function sprintf;
use const PHP_INT_SIZE;

final class IsBsonType extends Constraint
{
use ConstraintTrait;

/** @var array */
private static $types = [
'double',
'string',
'object',
'array',
'binData',
'undefined',
'objectId',
'bool',
'date',
'null',
'regex',
'dbPointer',
'javascript',
'symbol',
'javascriptWithScope',
'int',
'timestamp',
'long',
'decimal',
'minKey',
'maxKey',
];

/** @var string */
private $type;

public function __construct(string $type)
{
if (! in_array($type, self::$types)) {
throw new RuntimeException(sprintf('Type specified for %s <%s> is not a valid type', self::class, $type));
}

$this->type = $type;
}

public static function any() : LogicalOr
{
return self::anyOf(...self::$types);
}

public static function anyOf(string ...$types) : Constraint
{
if (count($types) === 1) {
return new self(...$types);
}

return LogicalOr::fromConstraints(...array_map(function ($type) {
return new self($type);
}, $types));
}

private function doMatches($other) : bool
{
switch ($this->type) {
case 'double':
return is_float($other);
case 'string':
return is_string($other);
case 'object':
return self::isObject($other);
case 'array':
return self::isArray($other);
case 'binData':
return $other instanceof BinaryInterface;
case 'undefined':
return $other instanceof Undefined;
case 'objectId':
return $other instanceof ObjectIdInterface;
case 'bool':
return is_bool($other);
case 'date':
return $other instanceof UTCDateTimeInterface;
case 'null':
return $other === null;
case 'regex':
return $other instanceof RegexInterface;
case 'dbPointer':
return $other instanceof DBPointer;
case 'javascript':
return $other instanceof JavascriptInterface && $other->getScope() === null;
case 'symbol':
return $other instanceof Symbol;
case 'javascriptWithScope':
return $other instanceof JavascriptInterface && $other->getScope() !== null;
case 'int':
return is_int($other);
case 'timestamp':
return $other instanceof TimestampInterface;
case 'long':
if (PHP_INT_SIZE == 4) {
return $other instanceof Int64;
}

return is_int($other);
case 'decimal':
return $other instanceof Decimal128Interface;
case 'minKey':
return $other instanceof MinKeyInterface;
case 'maxKey':
return $other instanceof MaxKeyInterface;
default:
// This should already have been caught in the constructor
throw new LogicException('Unsupported type: ' . $this->type);
}
}

private function doToString() : string
{
return sprintf('is of BSON type "%s"', $this->type);
}

private static function isArray($other) : bool
{
if ($other instanceof BSONArray) {
return true;
}

// Serializable can produce an array or object, so recurse on its output
if ($other instanceof Serializable) {
return self::isArray($other->bsonSerialize());
}

if (! is_array($other)) {
return false;
}

// Empty and indexed arrays serialize as BSON arrays
return self::isArrayEmptyOrIndexed($other);
}

private static function isObject($other) : bool
{
if ($other instanceof BSONDocument) {
return true;
}

// Serializable can produce an array or object, so recurse on its output
if ($other instanceof Serializable) {
return self::isObject($other->bsonSerialize());
}

// Non-empty, associative arrays serialize as BSON objects
if (is_array($other)) {
return ! self::isArrayEmptyOrIndexed($other);
}

if (! is_object($other)) {
return false;
}

/* Serializable has already been handled, so any remaining instances of
* Type will not serialize as BSON objects */
return ! $other instanceof Type;
}

private static function isArrayEmptyOrIndexed(array $a) : bool
{
if (empty($a)) {
return true;
}

return array_keys($a) === range(0, count($a) - 1);
}
}
Loading