Commit 4b4a7d99 authored by Jeremy Mikola's avatar Jeremy Mikola

PHPLIB-365: Transactions test runner

Mark incomplete transaction tests to be fixed before 4.2-compat release.

Changes JSON decoding of spec tests to preserve array/object types.

Bump driver version on Travis CI to include libmongoc 1.14 and PHPC-1373.
parent cb595b6d
......@@ -14,7 +14,7 @@ cache:
env:
global:
- DRIVER_VERSION=1.6.0alpha1
- DRIVER_VERSION=1.6.0alpha2
- SERVER_DISTRO=ubuntu1604
- SERVER_VERSION=4.0.9
- DEPLOYMENT=STANDALONE
......
......@@ -18,7 +18,7 @@ class ClientFunctionalTest extends FunctionalTestCase
{
parent::setUp();
$this->client = new Client($this->getUri());
$this->client = new Client(static::getUri());
$this->client->dropDatabase($this->getDatabaseName());
}
......
......@@ -26,7 +26,7 @@ class ClientTest extends TestCase
public function testConstructorDriverOptionTypeChecks(array $driverOptions)
{
$this->expectException(InvalidArgumentException::class);
new Client($this->getUri(), [], $driverOptions);
new Client(static::getUri(), [], $driverOptions);
}
public function provideInvalidConstructorDriverOptions()
......@@ -42,9 +42,9 @@ class ClientTest extends TestCase
public function testToString()
{
$client = new Client($this->getUri());
$client = new Client(static::getUri());
$this->assertSame($this->getUri(), (string) $client);
$this->assertSame(static::getUri(), (string) $client);
}
public function testSelectCollectionInheritsOptions()
......@@ -59,7 +59,7 @@ class ClientTest extends TestCase
'typeMap' => ['root' => 'array'],
];
$client = new Client($this->getUri(), $uriOptions, $driverOptions);
$client = new Client(static::getUri(), $uriOptions, $driverOptions);
$collection = $client->selectCollection($this->getDatabaseName(), $this->getCollectionName());
$debug = $collection->__debugInfo();
......@@ -82,7 +82,7 @@ class ClientTest extends TestCase
'writeConcern' => new WriteConcern(WriteConcern::MAJORITY),
];
$client = new Client($this->getUri());
$client = new Client(static::getUri());
$collection = $client->selectCollection($this->getDatabaseName(), $this->getCollectionName(), $collectionOptions);
$debug = $collection->__debugInfo();
......@@ -100,7 +100,7 @@ class ClientTest extends TestCase
{
$uriOptions = ['w' => WriteConcern::MAJORITY];
$client = new Client($this->getUri(), $uriOptions);
$client = new Client(static::getUri(), $uriOptions);
$database = $client->{$this->getDatabaseName()};
$debug = $database->__debugInfo();
......@@ -121,7 +121,7 @@ class ClientTest extends TestCase
'typeMap' => ['root' => 'array'],
];
$client = new Client($this->getUri(), $uriOptions, $driverOptions);
$client = new Client(static::getUri(), $uriOptions, $driverOptions);
$database = $client->selectDatabase($this->getDatabaseName());
$debug = $database->__debugInfo();
......@@ -144,7 +144,7 @@ class ClientTest extends TestCase
'writeConcern' => new WriteConcern(WriteConcern::MAJORITY),
];
$client = new Client($this->getUri());
$client = new Client(static::getUri());
$database = $client->selectDatabase($this->getDatabaseName(), $databaseOptions);
$debug = $database->__debugInfo();
......
......@@ -1227,7 +1227,7 @@ class DocumentationExamplesTest extends FunctionalTestCase
{
$this->skipIfTransactionsAreNotSupported();
$client = new Client($this->getUri());
$client = new Client(static::getUri());
/* The WC is required: https://docs.mongodb.com/manual/core/transactions/#transactions-and-locks */
$client->hr->dropCollection('employees', ['writeConcern' => new \MongoDB\Driver\WriteConcern('majority')]);
......@@ -1387,7 +1387,7 @@ class DocumentationExamplesTest extends FunctionalTestCase
{
$this->skipIfTransactionsAreNotSupported();
$client = new Client($this->getUri());
$client = new Client(static::getUri());
/* The WC is required: https://docs.mongodb.com/manual/core/transactions/#transactions-and-locks */
$client->hr->dropCollection('employees', ['writeConcern' => new \MongoDB\Driver\WriteConcern('majority')]);
......@@ -1410,7 +1410,7 @@ class DocumentationExamplesTest extends FunctionalTestCase
$this->skipIfCausalConsistencyIsNotSupported();
// Prep
$client = new Client($this->getUri());
$client = new Client(static::getUri());
$items = $client->selectDatabase(
'test',
[ 'writeConcern' => new WriteConcern(WriteConcern::MAJORITY) ]
......
......@@ -17,11 +17,9 @@ use UnexpectedValueException;
abstract class FunctionalTestCase extends TestCase
{
protected $manager;
public function setUp()
{
$this->manager = new Manager($this->getUri());
$this->manager = new Manager(static::getUri());
}
protected function assertCollectionCount($namespace, $count)
......
......@@ -17,7 +17,7 @@ class FindAndModifyFunctionalTest extends FunctionalTestCase
*/
public function testManagerReadConcernIsOmitted()
{
$manager = new Manager($this->getUri(), ['readConcernLevel' => 'majority']);
$manager = new Manager(static::getUri(), ['readConcernLevel' => 'majority']);
$server = $manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
(new CommandObserver)->observe(
......
......@@ -75,7 +75,7 @@ class WatchFunctionalTest extends FunctionalTestCase
/* In order to trigger a dropped connection, we'll use a new client with
* a socket timeout that is less than the change stream's maxAwaitTimeMS
* option. */
$manager = new Manager($this->getUri(), ['socketTimeoutMS' => 50]);
$manager = new Manager(static::getUri(), ['socketTimeoutMS' => 50]);
$primaryServer = $manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
$operation = new Watch($manager, $this->getDatabaseName(), $this->getCollectionName(), [], $this->defaultOptions);
......@@ -222,7 +222,7 @@ class WatchFunctionalTest extends FunctionalTestCase
/* In order to trigger a dropped connection, we'll use a new client with
* a socket timeout that is less than the change stream's maxAwaitTimeMS
* option. */
$manager = new Manager($this->getUri(), ['socketTimeoutMS' => 50]);
$manager = new Manager(static::getUri(), ['socketTimeoutMS' => 50]);
$primaryServer = $manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
$operation = new Watch($manager, $this->getDatabaseName(), $this->getCollectionName(), [], $this->defaultOptions);
......
<?php
namespace MongoDB\Tests\SpecTests;
use MongoDB\BSON\Timestamp;
use MongoDB\Driver\Monitoring\CommandFailedEvent;
use MongoDB\Driver\Monitoring\CommandStartedEvent;
use MongoDB\Driver\Monitoring\CommandSucceededEvent;
use MongoDB\Driver\Monitoring\CommandSubscriber;
use ArrayIterator;
use LogicException;
use MultipleIterator;
use stdClass;
/**
* Spec test CommandStartedEvent expectations.
*/
class CommandExpectations implements CommandSubscriber
{
private $commandStartedEvents = [];
private $expectedCommandStartedEvents = [];
public static function fromTransactions(array $expectedEvents)
{
$o = new self;
foreach ($expectedEvents as $expectedEvent) {
if (!isset($expectedEvent->command_started_event)) {
throw new LogicException('$expectedEvent->command_started_event field is not set');
}
$o->expectedCommandStartedEvents[] = $expectedEvent->command_started_event;
}
return $o;
}
/**
* Not used.
*
* @see https://www.php.net/manual/en/mongodb-driver-monitoring-commandsubscriber.commandfailed.php
*/
public function commandFailed(CommandFailedEvent $event)
{
}
/**
* Tracks outgoing commands for spec test APM assertions.
*
* @see https://www.php.net/manual/en/mongodb-driver-monitoring-commandsubscriber.commandstarted.php
*/
public function commandStarted(CommandStartedEvent $event)
{
$this->commandStartedEvents[] = $event;
}
/**
* Not used.
*
* @see https://www.php.net/manual/en/mongodb-driver-monitoring-commandsubscriber.commandsucceeded.php
*/
public function commandSucceeded(CommandSucceededEvent $event)
{
}
/**
* Start command monitoring.
*/
public function startMonitoring()
{
\MongoDB\Driver\Monitoring\addSubscriber($this);
}
/**
* Stop command monitoring.
*/
public function stopMonitoring()
{
\MongoDB\Driver\Monitoring\removeSubscriber($this);
}
/**
* Assert that the command expectations match the monitored events.
*
* @param FunctionalTestCase $test Test instance
* @param Context $context Execution context
*/
public function assert(FunctionalTestCase $test, Context $context)
{
$test->assertCount(count($this->expectedCommandStartedEvents), $this->commandStartedEvents);
$mi = new MultipleIterator(MultipleIterator::MIT_NEED_ANY);
$mi->attachIterator(new ArrayIterator($this->expectedCommandStartedEvents));
$mi->attachIterator(new ArrayIterator($this->commandStartedEvents));
foreach ($mi as $events) {
list($expectedEvent, $actualEvent) = $events;
$test->assertInternalType('object', $expectedEvent);
$test->assertInstanceOf(CommandStartedEvent::class, $actualEvent);
if (isset($expectedEvent->command_name)) {
$test->assertSame($expectedEvent->command_name, $actualEvent->getCommandName());
}
if (isset($expectedEvent->database_name)) {
$test->assertSame($expectedEvent->database_name, $actualEvent->getDatabaseName());
}
if (isset($expectedEvent->command)) {
$expectedCommand = $expectedEvent->command;
$context->replaceCommandSessionPlaceholder($expectedCommand);
$test->assertSameCommand($expectedCommand, $actualEvent->getCommand());
}
}
}
private function __construct()
{
}
}
<?php
namespace MongoDB\Tests\SpecTests;
use MongoDB\Client;
use MongoDB\Driver\ReadConcern;
use MongoDB\Driver\ReadPreference;
use MongoDB\Driver\WriteConcern;
use LogicException;
use stdClass;
/**
* Execution context for spec tests.
*
* This object tracks state that would be difficult to store on the test itself
* due to the design of PHPUnit's data providers and setUp/tearDown methods.
*/
final class Context
{
public $client;
public $collectionName;
public $databaseName;
public $defaultWriteOptions = [];
public $outcomeFindOptions = [];
public $outcomeCollectionName;
public $session0;
public $session0Lsid;
public $session1;
public $session1Lsid;
private function __construct($databaseName, $collectionName)
{
$this->databaseName = $databaseName;
$this->collectionName = $collectionName;
$this->outcomeCollectionName = $collectionName;
}
public static function fromRetryableWrites(stdClass $test, $databaseName, $collectionName)
{
$o = new self($databaseName, $collectionName);
$clientOptions = isset($test->clientOptions) ? (array) $test->clientOptions : [];
// TODO: Remove this once retryWrites=true by default (see: PHPC-1324)
$clientOptions['retryWrites'] = true;
if (isset($test->outcome->collection->name)) {
$o->outcomeCollectionName = $test->outcome->collection->name;
}
$o->client = new Client(FunctionalTestCase::getUri(), $clientOptions);
return $o;
}
public static function fromTransactions(stdClass $test, $databaseName, $collectionName)
{
$o = new self($databaseName, $collectionName);
$o->defaultWriteOptions = [
'writeConcern' => new WriteConcern(WriteConcern::MAJORITY),
];
$o->outcomeFindOptions = [
'readConcern' => new ReadConcern('local'),
'readPreference' => new ReadPreference('primary'),
];
$clientOptions = isset($test->clientOptions) ? (array) $test->clientOptions : [];
/* Transaction spec tests expect a new client for each test so that
* txnNumber values are deterministic. Append a random option to avoid
* re-using a previously persisted libmongoc client object. */
$clientOptions += ['p' => mt_rand()];
$o->client = new Client(FunctionalTestCase::getUri(), $clientOptions);
$session0Options = isset($test->sessionOptions->session0) ? (array) $test->sessionOptions->session0 : [];
$session1Options = isset($test->sessionOptions->session1) ? (array) $test->sessionOptions->session1 : [];
$o->session0 = $o->client->startSession($o->prepareSessionOptions($session0Options));
$o->session1 = $o->client->startSession($o->prepareSessionOptions($session1Options));
$o->session0Lsid = $o->session0->getLogicalSessionId();
$o->session1Lsid = $o->session1->getLogicalSessionId();
return $o;
}
public function getCollection(array $collectionOptions = [])
{
return $this->client->selectCollection(
$this->databaseName,
$this->collectionName,
$this->prepareOptions($collectionOptions)
);
}
public function getDatabase(array $databaseOptions = [])
{
return $this->client->selectDatabase(
$this->databaseName,
$this->prepareOptions($databaseOptions)
);
}
/**
* Prepare options readConcern, readPreference, and writeConcern options by
* creating value objects.
*
* @param array $options
* @return array
* @throws LogicException if any option keys are unsupported
*/
public function prepareOptions(array $options)
{
if (isset($options['readConcern']) && !($options['readConcern'] instanceof ReadConcern)) {
$readConcern = (array) $options['readConcern'];
$diff = array_diff_key($readConcern, ['level' => 1]);
if (!empty($diff)) {
throw new LogicException('Unsupported readConcern args: ' . implode(',', array_keys($diff)));
}
$options['readConcern'] = new ReadConcern($readConcern['level']);
}
if (isset($options['readPreference']) && !($options['readPreference'] instanceof ReadPreference)) {
$readPreference = (array) $options['readPreference'];
$diff = array_diff_key($readPreference, ['mode' => 1]);
if (!empty($diff)) {
throw new LogicException('Unsupported readPreference args: ' . implode(',', array_keys($diff)));
}
$options['readPreference'] = new ReadPreference($readPreference['mode']);
}
if (isset($options['writeConcern']) && !($options['writeConcern'] instanceof writeConcern)) {
$writeConcern = (array) $options['writeConcern'];
$diff = array_diff_key($writeConcern, ['w' => 1, 'wtimeout' => 1, 'j' => 1]);
if (!empty($diff)) {
throw new LogicException('Unsupported writeConcern args: ' . implode(',', array_keys($diff)));
}
$w = $writeConcern['w'];
$wtimeout = isset($writeConcern['wtimeout']) ? $writeConcern['wtimeout'] : 0;
$j = isset($writeConcern['j']) ? $writeConcern['j'] : null;
$options['writeConcern'] = isset($j)
? new WriteConcern($w, $wtimeout, $j)
: new WriteConcern($w, $wtimeout);
}
return $options;
}
/**
* Replace a session placeholder in an operation arguments array.
*
* Note: this method will modify the $args parameter.
*
* @param array $args Operation arguments
* @throws LogicException if the session placeholder is unsupported
*/
public function replaceArgumentSessionPlaceholder(array &$args)
{
if (!isset($args['session'])) {
return;
}
switch ($args['session']) {
case 'session0':
$args['session'] = $this->session0;
break;
case 'session1':
$args['session'] = $this->session1;
break;
default:
throw new LogicException('Unsupported session placeholder: ' . $args['session']);
}
}
/**
* Replace a logical session ID placeholder in a command document.
*
* Note: this method will modify the $command parameter.
*
* @param stdClass $command Command document
* @throws LogicException if the session placeholder is unsupported
*/
public function replaceCommandSessionPlaceholder(stdClass $command)
{
if (!isset($command->lsid)) {
return;
}
switch ($command->lsid) {
case 'session0':
$command->lsid = $this->session0Lsid;
break;
case 'session1':
$command->lsid = $this->session1Lsid;
break;
default:
throw new LogicException('Unsupported session placeholder: ' . $command->lsid);
}
}
private function prepareSessionOptions(array $options)
{
if (isset($options['defaultTransactionOptions'])) {
$options['defaultTransactionOptions'] = $this->prepareOptions((array) $options['defaultTransactionOptions']);
}
return $options;
}
}
<?php
namespace MongoDB\Tests\SpecTests;
use MongoDB\Driver\Exception\BulkWriteException;
use MongoDB\Driver\Exception\CommandException;
use MongoDB\Driver\Exception\RuntimeException;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Tests\TestCase;
use Exception;
use stdClass;
/**
* Spec test operation error expectation.
*/
final class ErrorExpectation
{
/**
* @see https://github.com/mongodb/mongo/blob/master/src/mongo/base/error_codes.err
*/
private static $codeNameMap = [
'Interrupted' => 11601,
'WriteConflict' => 112,
'NoSuchTransaction' => 251,
'OperationNotSupportedInTransaction' => 263,
];
private $codeName;
private $isExpected = false;
private $excludedLabels = [];
private $includedLabels = [];
private $messageContains;
private function __construct()
{
}
public static function fromRetryableWrites(stdClass $outcome)
{
$o = new self;
if (isset($outcome->error)) {
$o->isExpected = $outcome->error;
}
return $o;
}
/**
* @throws InvalidArgumentException
*/
public static function fromTransactions(stdClass $operation)
{
$o = new self;
if (isset($operation->error)) {
$o->isExpected = $operation->error;
}
$result = isset($operation->result) ? $operation->result : null;
if (isset($result->errorContains)) {
$o->messageContains = $result->errorContains;
$o->isExpected = true;
}
if (isset($result->errorCodeName)) {
$o->codeName = $result->errorCodeName;
$o->isExpected = true;
}
if (isset($result->errorLabelsContain)) {
if (!self::isArrayOfStrings($result->errorLabelsContain)) {
throw InvalidArgumentException::invalidType('errorLabelsContain', $result->errorLabelsContain, 'string[]');
}
$o->includedLabels = $result->errorLabelsContain;
$o->isExpected = true;
}
if (isset($result->errorLabelsOmit)) {
if (!self::isArrayOfStrings($result->errorLabelsOmit)) {
throw InvalidArgumentException::invalidType('errorLabelsOmit', $result->errorLabelsOmit, 'string[]');
}
$o->excludedLabels = $result->errorLabelsOmit;
$o->isExpected = true;
}
return $o;
}
/**
* Assert that the error expectation matches the actual outcome.
*
* @param TestCase $test Test instance for performing assertions
* @param Exception|null $actual Exception (if any) from the actual outcome
*/
public function assert(TestCase $test, Exception $actual = null)
{
if (!$this->isExpected) {
if ($actual !== null) {
$test->fail(sprintf("Operation threw unexpected %s: %s\n%s", get_class($actual), $actual->getMessage(), $actual->getTraceAsString()));
}
return;
}
$test->assertNotNull($actual);
if (isset($this->messageContains)) {
$test->assertContains($this->messageContains, $actual->getMessage(), '', true /* case-insensitive */);
}
if (isset($this->codeName)) {
$this->assertCodeName($test, $actual);
}
if (!empty($this->excludedLabels) or !empty($this->includedLabels)) {
$test->assertInstanceOf(RuntimeException::class, $actual);
foreach ($this->excludedLabels as $label) {
$test->assertFalse($actual->hasErrorLabel($label), 'Exception should not have error label: ' . $label);
}
foreach ($this->includedLabels as $label) {
$test->assertTrue($actual->hasErrorLabel($label), 'Exception should have error label: ' . $label);
}
}
}
public function isExpected()
{
return $this->isExpected;
}
/**
* Assert that the error code name expectation matches the actual outcome.
*
* @param TestCase $test Test instance for performing assertions
* @param Exception|null $actual Exception (if any) from the actual outcome
*/
private function assertCodeName(TestCase $test, Exception $actual = null)
{
/* BulkWriteException does not expose codeName for server errors. Work
* around this be comparing the error code against a map.
*
* TODO: Remove this once PHPC-1386 is resolved. */
if ($actual instanceof BulkWriteException) {
$test->assertArrayHasKey($this->codeName, self::$codeNameMap);
$test->assertSame(self::$codeNameMap[$this->codeName], $actual->getCode());
return;
}
$test->assertInstanceOf(CommandException::class, $actual);
$result = $actual->getResultDocument();
$test->assertObjectHasAttribute('codeName', $result);
$test->assertAttributeSame($this->codeName, 'codeName', $result);
}
private static function isArrayOfStrings($array)
{
if (!is_array($array)) {
return false;
}
foreach ($array as $string) {
if (!is_string($string)) {
return false;
}
}
return true;
}
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
......@@ -2,6 +2,9 @@
namespace MongoDB\Tests\SpecTests;
use LogicException;
use stdClass;
/**
* Retryable writes spec tests.
*
......@@ -9,38 +12,47 @@ namespace MongoDB\Tests\SpecTests;
*/
class RetryableWritesSpecTest extends FunctionalTestCase
{
public function assertSameCommand(stdClass $expectedCommand, stdClass $actualCommand)
{
throw new LogicException('Retryable writes spec tests do not assert CommandStartedEvents');
}
/**
* Execute an individual test case from the specification.
*
* @dataProvider provideTests
* @param string $name Test name
* @param array $test Individual "tests[]" document
* @param array $runOn Top-level "runOn" document
* @param stdClass $test Individual "tests[]" document
* @param array $runOn Top-level "runOn" array with server requirements
* @param array $data Top-level "data" array to initialize collection
*/
public function testRetryableWrites($name, array $test, array $runOn = null, array $data)
public function testRetryableWrites($name, stdClass $test, array $runOn = null, array $data)
{
$this->setName($name);
// TODO: Revise this once a test environment with multiple mongos nodes is available (see: PHPLIB-430)
if (isset($test->useMultipleMongoses) && $test->useMultipleMongoses && $this->isShardedCluster()) {
$this->markTestSkipped('"useMultipleMongoses" is not supported');
}
if (isset($runOn)) {
$this->checkServerRequirements($runOn);
}
// TODO: Remove this once retryWrites=true by default (see: PHPC-1324)
$test['clientOptions']['retryWrites'] = true;
$context = Context::fromRetryableWrites($test, $this->getDatabaseName(), $this->getCollectionName());
$this->setContext($context);
$this->initTestSubjects($test);
$this->initOutcomeCollection($test);
$this->initDataFixtures($data);
$this->dropTestAndOutcomeCollections();
$this->insertDataFixtures($data);
if (isset($test['failPoint'])) {
$this->configureFailPoint($test['failPoint']);
if (isset($test->failPoint)) {
$this->configureFailPoint($test->failPoint);
}
$this->assertOperation($test['operation'], $test['outcome']);
Operation::fromRetryableWrites($test->operation, $test->outcome)->assert($this, $context);
if (isset($test['outcome']['collection']['data'])) {
$this->assertOutcomeCollectionData($test['outcome']['collection']['data']);
if (isset($test->outcome->collection->data)) {
$this->assertOutcomeCollectionData($test->outcome->collection->data);
}
}
......@@ -49,13 +61,13 @@ class RetryableWritesSpecTest extends FunctionalTestCase
$testArgs = [];
foreach (glob(__DIR__ . '/retryable-writes/*.json') as $filename) {
$json = json_decode(file_get_contents($filename), true);
$json = $this->decodeJson(file_get_contents($filename));
$group = basename($filename, '.json');
$runOn = isset($json['runOn']) ? $json['runOn'] : null;
$data = isset($json['data']) ? $json['data'] : [];
$runOn = isset($json->runOn) ? $json->runOn : null;
$data = isset($json->data) ? $json->data : [];
foreach ($json['tests'] as $test) {
$name = $group . ': ' . $test['description'];
foreach ($json->tests as $test) {
$name = $group . ': ' . $test->description;
$testArgs[] = [$name, $test, $runOn, $data];
}
}
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
{
"runOn": [
{
"minServerVersion": "4.0",
"topology": [
"replicaset"
]
},
{
"minServerVersion": "4.1.8",
"topology": [
"sharded"
]
}
],
"database_name": "transaction-tests",
"collection_name": "test",
"data": [
{
"_id": 1,
"count": 0
}
],
"tests": [
{
"description": "causal consistency",
"clientOptions": {
"retryWrites": false
},
"operations": [
{
"name": "updateOne",
"object": "collection",
"arguments": {
"session": "session0",
"filter": {
"_id": 1
},
"update": {
"$inc": {
"count": 1
}
},
"upsert": false
},
"result": {
"matchedCount": 1,
"modifiedCount": 1,
"upsertedCount": 0
}
},
{
"name": "startTransaction",
"object": "session0"
},
{
"name": "updateOne",
"object": "collection",
"arguments": {
"session": "session0",
"filter": {
"_id": 1
},
"update": {
"$inc": {
"count": 1
}
},
"upsert": false
},
"result": {
"matchedCount": 1,
"modifiedCount": 1,
"upsertedCount": 0
}
},
{
"name": "commitTransaction",
"object": "session0"
}
],
"expectations": [
{
"command_started_event": {
"command": {
"update": "test",
"updates": [
{
"q": {
"_id": 1
},
"u": {
"$inc": {
"count": 1
}
},
"multi": false,
"upsert": false
}
],
"ordered": true,
"lsid": "session0",
"readConcern": null,
"txnNumber": null,
"startTransaction": null,
"autocommit": null,
"writeConcern": null
},
"command_name": "update",
"database_name": "transaction-tests"
}
},
{
"command_started_event": {
"command": {
"update": "test",
"updates": [
{
"q": {
"_id": 1
},
"u": {
"$inc": {
"count": 1
}
},
"multi": false,
"upsert": false
}
],
"ordered": true,
"readConcern": {
"afterClusterTime": 42
},
"lsid": "session0",
"txnNumber": {
"$numberLong": "1"
},
"startTransaction": true,
"autocommit": false,
"writeConcern": null
},
"command_name": "update",
"database_name": "transaction-tests"
}
},
{
"command_started_event": {
"command": {
"commitTransaction": 1,
"lsid": "session0",
"txnNumber": {
"$numberLong": "1"
},
"startTransaction": null,
"autocommit": false,
"writeConcern": null
},
"command_name": "commitTransaction",
"database_name": "admin"
}
}
],
"outcome": {
"collection": {
"data": [
{
"_id": 1,
"count": 2
}
]
}
}
},
{
"description": "causal consistency disabled",
"clientOptions": {
"retryWrites": false
},
"sessionOptions": {
"session0": {
"causalConsistency": false
}
},
"operations": [
{
"name": "insertOne",
"object": "collection",
"arguments": {
"session": "session0",
"document": {
"_id": 2
}
},
"result": {
"insertedId": 2
}
},
{
"name": "startTransaction",
"object": "session0"
},
{
"name": "updateOne",
"object": "collection",
"arguments": {
"session": "session0",
"filter": {
"_id": 1
},
"update": {
"$inc": {
"count": 1
}
},
"upsert": false
},
"result": {
"matchedCount": 1,
"modifiedCount": 1,
"upsertedCount": 0
}
},
{
"name": "commitTransaction",
"object": "session0"
}
],
"expectations": [
{
"command_started_event": {
"command": {
"insert": "test",
"documents": [
{
"_id": 2
}
],
"ordered": true,
"readConcern": null,
"lsid": "session0",
"txnNumber": null,
"autocommit": null,
"writeConcern": null
},
"command_name": "insert",
"database_name": "transaction-tests"
}
},
{
"command_started_event": {
"command": {
"update": "test",
"updates": [
{
"q": {
"_id": 1
},
"u": {
"$inc": {
"count": 1
}
},
"multi": false,
"upsert": false
}
],
"ordered": true,
"readConcern": null,
"lsid": "session0",
"txnNumber": {
"$numberLong": "1"
},
"startTransaction": true,
"autocommit": false,
"writeConcern": null
},
"command_name": "update",
"database_name": "transaction-tests"
}
},
{
"command_started_event": {
"command": {
"commitTransaction": 1,
"lsid": "session0",
"txnNumber": {
"$numberLong": "1"
},
"startTransaction": null,
"autocommit": false,
"writeConcern": null
},
"command_name": "commitTransaction",
"database_name": "admin"
}
}
],
"outcome": {
"collection": {
"data": [
{
"_id": 1,
"count": 1
},
{
"_id": 2
}
]
}
}
}
]
}
This diff is collapsed.
{
"runOn": [
{
"minServerVersion": "4.0.2",
"topology": [
"replicaset"
]
},
{
"minServerVersion": "4.1.8",
"topology": [
"sharded"
]
}
],
"database_name": "transaction-tests",
"collection_name": "test",
"data": [
{
"_id": 1
},
{
"_id": 2
},
{
"_id": 3
},
{
"_id": 4
}
],
"tests": [
{
"description": "count",
"operations": [
{
"name": "startTransaction",
"object": "session0"
},
{
"name": "count",
"object": "collection",
"arguments": {
"session": "session0",
"filter": {
"_id": 1
}
},
"result": {
"errorCodeName": "OperationNotSupportedInTransaction",
"errorLabelsOmit": [
"TransientTransactionError",
"UnknownTransactionCommitResult"
]
}
},
{
"name": "abortTransaction",
"object": "session0"
}
],
"expectations": [
{
"command_started_event": {
"command": {
"count": "test",
"query": {
"_id": 1
},
"readConcern": null,
"lsid": "session0",
"txnNumber": {
"$numberLong": "1"
},
"startTransaction": true,
"autocommit": false,
"writeConcern": null
},
"command_name": "count",
"database_name": "transaction-tests"
}
},
{
"command_started_event": {
"command": {
"abortTransaction": 1,
"lsid": "session0",
"txnNumber": {
"$numberLong": "1"
},
"startTransaction": null,
"autocommit": false,
"writeConcern": null
},
"command_name": "abortTransaction",
"database_name": "admin"
}
}
],
"outcome": {
"collection": {
"data": [
{
"_id": 1
},
{
"_id": 2
},
{
"_id": 3
},
{
"_id": 4
}
]
}
}
}
]
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
......@@ -15,58 +15,14 @@ use Traversable;
abstract class TestCase extends BaseTestCase
{
public function expectException($exception)
{
if (method_exists(BaseTestCase::class, 'expectException')) {
parent::expectException($exception);
return;
}
parent::setExpectedException($exception);
}
public function expectExceptionMessage($exceptionMessage)
{
if (method_exists(BaseTestCase::class, 'expectExceptionMessage')) {
parent::expectExceptionMessage($exceptionMessage);
return;
}
parent::setExpectedException($this->getExpectedException(), $exceptionMessage);
}
public function expectExceptionMessageRegExp($exceptionMessageRegExp)
{
if (method_exists(BaseTestCase::class, 'expectExceptionMessageRegExp')) {
parent::expectExceptionMessageRegExp($exceptionMessageRegExp);
return;
}
parent::setExpectedExceptionRegExp($this->getExpectedException(), $exceptionMessageRegExp);
}
public function provideInvalidArrayValues()
{
return $this->wrapValuesForDataProvider($this->getInvalidArrayValues());
}
public function provideInvalidDocumentValues()
{
return $this->wrapValuesForDataProvider($this->getInvalidDocumentValues());
}
protected function assertDeprecated(callable $execution)
/**
* Return the connection URI.
*
* @return string
*/
public static function getUri()
{
$errors = [];
set_error_handler(function($errno, $errstr) use (&$errors) {
$errors[] = $errstr;
}, E_USER_DEPRECATED);
try {
call_user_func($execution);
} finally {
restore_error_handler();
}
$this->assertCount(1, $errors);
return getenv('MONGODB_URI') ?: 'mongodb://127.0.0.1:27017';
}
/**
......@@ -78,7 +34,7 @@ abstract class TestCase extends BaseTestCase
* @param array|object $expectedDocument
* @param array|object $actualDocument
*/
protected function assertMatchesDocument($expectedDocument, $actualDocument)
public function assertMatchesDocument($expectedDocument, $actualDocument)
{
$normalizedExpectedDocument = $this->normalizeBSON($expectedDocument);
$normalizedActualDocument = $this->normalizeBSON($actualDocument);
......@@ -112,7 +68,7 @@ abstract class TestCase extends BaseTestCase
* @param array|object $expectedDocument
* @param array|object $actualDocument
*/
protected function assertSameDocument($expectedDocument, $actualDocument)
public function assertSameDocument($expectedDocument, $actualDocument)
{
$this->assertEquals(
\MongoDB\BSON\toJSON(\MongoDB\BSON\fromPHP($this->normalizeBSON($expectedDocument))),
......@@ -120,7 +76,7 @@ abstract class TestCase extends BaseTestCase
);
}
protected function assertSameDocuments(array $expectedDocuments, $actualDocuments)
public function assertSameDocuments(array $expectedDocuments, $actualDocuments)
{
if ($actualDocuments instanceof Traversable) {
$actualDocuments = iterator_to_array($actualDocuments);
......@@ -140,6 +96,60 @@ abstract class TestCase extends BaseTestCase
);
}
public function expectException($exception)
{
if (method_exists(BaseTestCase::class, 'expectException')) {
parent::expectException($exception);
return;
}
parent::setExpectedException($exception);
}
public function expectExceptionMessage($exceptionMessage)
{
if (method_exists(BaseTestCase::class, 'expectExceptionMessage')) {
parent::expectExceptionMessage($exceptionMessage);
return;
}
parent::setExpectedException($this->getExpectedException(), $exceptionMessage);
}
public function expectExceptionMessageRegExp($exceptionMessageRegExp)
{
if (method_exists(BaseTestCase::class, 'expectExceptionMessageRegExp')) {
parent::expectExceptionMessageRegExp($exceptionMessageRegExp);
return;
}
parent::setExpectedExceptionRegExp($this->getExpectedException(), $exceptionMessageRegExp);
}
public function provideInvalidArrayValues()
{
return $this->wrapValuesForDataProvider($this->getInvalidArrayValues());
}
public function provideInvalidDocumentValues()
{
return $this->wrapValuesForDataProvider($this->getInvalidDocumentValues());
}
protected function assertDeprecated(callable $execution)
{
$errors = [];
set_error_handler(function($errno, $errstr) use (&$errors) {
$errors[] = $errstr;
}, E_USER_DEPRECATED);
try {
call_user_func($execution);
} finally {
restore_error_handler();
}
$this->assertCount(1, $errors);
}
/**
* Return the test collection name.
*
......@@ -262,16 +272,6 @@ abstract class TestCase extends BaseTestCase
return sprintf('%s.%s', $this->getDatabaseName(), $this->getCollectionName());
}
/**
* Return the connection URI.
*
* @return string
*/
protected function getUri()
{
return getenv('MONGODB_URI') ?: 'mongodb://127.0.0.1:27017';
}
/**
* Wrap a list of values for use as a single-argument data provider.
*
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment