Commit 35e6181a authored by Jeremy Mikola's avatar Jeremy Mikola

Merge ChangeStreamsProseTest into WatchFunctionalTest

parent 6afb08f4
......@@ -11,15 +11,31 @@ use MongoDB\Driver\Server;
use MongoDB\Driver\WriteConcern;
use MongoDB\Driver\Exception\CommandException;
use MongoDB\Operation\CreateCollection;
use MongoDB\Operation\DatabaseCommand;
use MongoDB\Operation\DropCollection;
use InvalidArgumentException;
use stdClass;
use UnexpectedValueException;
abstract class FunctionalTestCase extends TestCase
{
protected $manager;
private $configuredFailPoints = [];
public function setUp()
{
parent::setUp();
$this->manager = new Manager(static::getUri());
$this->configuredFailPoints = [];
}
public function tearDown()
{
$this->disableFailPoints();
parent::tearDown();
}
protected function assertCollectionCount($namespace, $count)
......@@ -49,6 +65,39 @@ abstract class FunctionalTestCase extends TestCase
$this->assertEquals((string) $expectedObjectId, (string) $actualObjectId);
}
/**
* Configure a fail point for the test.
*
* The fail point will automatically be disabled during tearDown() to avoid
* affecting a subsequent test.
*
* @param array|stdClass $command configureFailPoint command document
* @throws InvalidArgumentException if $command is not a configureFailPoint command
*/
protected function configureFailPoint($command)
{
if (is_array($command)) {
$command = (object) $command;
}
if ( ! $command instanceof stdClass) {
throw new InvalidArgumentException('$command is not an array or stdClass instance');
}
if (key($command) !== 'configureFailPoint') {
throw new InvalidArgumentException('$command is not a configureFailPoint command');
}
$operation = new DatabaseCommand('admin', $command);
$cursor = $operation->execute($this->getPrimaryServer());
$result = $cursor->toArray()[0];
$this->assertCommandSucceeded($result);
// Record the fail point so it can be disabled during tearDown()
$this->configuredFailPoints[] = $command->configureFailPoint;
}
/**
* Creates the test collection with the specified options.
*
......@@ -260,4 +309,24 @@ abstract class FunctionalTestCase extends TestCase
$this->markTestSkipped('Transactions require WiredTiger storage engine');
}
}
/**
* Disables any fail points that were configured earlier in the test.
*
* This tracks fail points set via configureFailPoint() and should be called
* during tearDown().
*/
private function disableFailPoints()
{
if (empty($this->configuredFailPoints)) {
return;
}
$server = $this->getPrimaryServer();
foreach ($this->configuredFailPoints as $failPoint) {
$operation = new DatabaseCommand('admin', ['configureFailPoint' => $failPoint, 'mode' => 'off']);
$operation->execute($server);
}
}
}
......@@ -9,6 +9,7 @@ use MongoDB\Driver\ReadPreference;
use MongoDB\Driver\Server;
use MongoDB\Driver\WriteConcern;
use MongoDB\Driver\Exception\LogicException;
use MongoDB\Driver\Exception\ServerException;
use MongoDB\Exception\ResumeTokenException;
use MongoDB\Operation\CreateCollection;
use MongoDB\Operation\DatabaseCommand;
......@@ -580,6 +581,45 @@ class WatchFunctionalTest extends FunctionalTestCase
$this->assertFalse($cursor->isDead());
}
/**
* Prose test: "ChangeStream will not attempt to resume after encountering
* error code 11601 (Interrupted), 136 (CappedPositionLost), or 237
* (CursorKilled) while executing a getMore command."
*
* @dataProvider provideNonResumableErrorCodes
*/
public function testNonResumableErrorCodes($errorCode)
{
if (version_compare($this->getServerVersion(), '4.0.0', '<')) {
$this->markTestSkipped('failCommand is not supported');
}
$this->configureFailPoint([
'configureFailPoint' => 'failCommand',
'mode' => ['times' => 1],
'data' => ['failCommands' => ['getMore'], 'errorCode' => $errorCode],
]);
$this->insertDocument(['x' => 1]);
$operation = new Watch($this->manager, $this->getDatabaseName(), $this->getCollectionName(), []);
$changeStream = $operation->execute($this->getPrimaryServer());
$changeStream->rewind();
$this->expectException(ServerException::class);
$this->expectExceptionCode($errorCode);
$changeStream->next();
}
public function provideNonResumableErrorCodes()
{
return [
[136], // CappedPositionLost
[237], // CursorKilled
[11601], // Interrupted
];
}
public function testNextResumeTokenNotFound()
{
if (version_compare($this->getServerVersion(), '4.1.8', '>=')) {
......
<?php
namespace MongoDB\Tests\SpecTests;
use MongoDB\Collection;
use MongoDB\Driver\Exception\ServerException;
/**
* Change Streams spec prose tests.
*
* @see https://github.com/mongodb/specifications/tree/master/source/change-streams
*/
class ChangeStreamsProseTest extends FunctionalTestCase
{
private $collection;
public function setUp()
{
parent::setUp();
$this->skipIfChangeStreamIsNotSupported();
$this->collection = new Collection($this->manager, $this->getDatabaseName(), $this->getCollectionName());
$this->dropCollection();
}
public function tearDown()
{
if (!$this->hasFailed()) {
$this->dropCollection();
}
parent::tearDown();
}
/**
* ChangeStream will not attempt to resume after encountering error code
* 11601 (Interrupted), 136 (CappedPositionLost), or 237 (CursorKilled)
* while executing a getMore command.
*
* @dataProvider provideNonResumableErrorCodes
*/
public function testProseTest5($errorCode)
{
if (version_compare($this->getServerVersion(), '4.0.0', '<')) {
$this->markTestSkipped('failCommand is not supported');
}
$this->configureFailPoint([
'configureFailPoint' => 'failCommand',
'mode' => ['times' => 1],
'data' => ['failCommands' => ['getMore'], 'errorCode' => $errorCode],
]);
$this->createCollection();
$changeStream = $this->collection->watch();
$changeStream->rewind();
$this->expectException(ServerException::class);
$this->expectExceptionCode($errorCode);
$changeStream->next();
}
public function provideNonResumableErrorCodes()
{
return [
[136], // CappedPositionLost
[237], // CursorKilled
[11601], // Interrupted
];
}
}
......@@ -4,7 +4,6 @@ namespace MongoDB\Tests\SpecTests;
use MongoDB\Client;
use MongoDB\Collection;
use MongoDB\Database;
use MongoDB\Driver\Server;
use MongoDB\Driver\WriteConcern;
use MongoDB\Driver\Exception\BulkWriteException;
......@@ -14,7 +13,6 @@ use MongoDB\Tests\FunctionalTestCase as BaseFunctionalTestCase;
use MongoDB\Tests\TestCase;
use PHPUnit\Framework\SkippedTest;
use ArrayIterator;
use InvalidArgumentException;
use IteratorIterator;
use LogicException;
use MultipleIterator;
......@@ -32,21 +30,18 @@ class FunctionalTestCase extends BaseFunctionalTestCase
const TOPOLOGY_REPLICASET = 'replicaset';
const TOPOLOGY_SHARDED = 'sharded';
private $configuredFailPoints = [];
private $context;
public function setUp()
{
parent::setUp();
$this->configuredFailPoints = [];
$this->context = null;
}
public function tearDown()
{
$this->context = null;
$this->disableFailPoints();
parent::tearDown();
}
......@@ -141,39 +136,6 @@ class FunctionalTestCase extends BaseFunctionalTestCase
$this->markTestSkipped(sprintf('Server version "%s" and topology "%s" do not meet test requirements: %s', $serverVersion, $topology, json_encode($runOn)));
}
/**
* Configure a fail point for the test.
*
* The fail point will automatically be disabled during tearDown() to avoid
* affecting a subsequent test.
*
* @param array|stdClass $command configureFailPoint command document
* @throws InvalidArgumentException if $command is not a configureFailPoint command
*/
protected function configureFailPoint($command)
{
if (is_array($command)) {
$command = (object) $command;
}
if ( ! $command instanceof stdClass) {
throw new InvalidArgumentException('$command is not an array or stdClass instance');
}
if (key($command) !== 'configureFailPoint') {
throw new InvalidArgumentException('$command is not a configureFailPoint command');
}
$database = new Database($this->manager, 'admin');
$cursor = $database->command($command);
$result = $cursor->toArray()[0];
$this->assertCommandSucceeded($result);
// Record the fail point so it can be disabled during tearDown()
$this->configuredFailPoints[] = $command->configureFailPoint;
}
/**
* Decode a JSON spec test.
*
......@@ -255,21 +217,6 @@ class FunctionalTestCase extends BaseFunctionalTestCase
return new Collection($this->manager, $context->databaseName, $context->outcomeCollectionName);
}
/**
* Disables any fail points that were configured earlier in the test.
*
* This tracks fail points set via configureFailPoint() and should be called
* during tearDown().
*/
private function disableFailPoints()
{
$database = new Database($this->manager, 'admin');
foreach ($this->configuredFailPoints as $failPoint) {
$database->command(['configureFailPoint' => $failPoint, 'mode' => 'off']);
}
}
/**
* Return the corresponding topology constants for the current topology.
*
......
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