Skip to content

Commit

Permalink
Remove legacy transaction-convenient-api spec tests
Browse files Browse the repository at this point in the history
  • Loading branch information
jmikola committed Feb 13, 2024
1 parent 8a6f2b5 commit aab06d6
Show file tree
Hide file tree
Showing 11 changed files with 2 additions and 4,041 deletions.
247 changes: 2 additions & 245 deletions tests/SpecTests/TransactionsSpecTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,192 +2,18 @@

namespace MongoDB\Tests\SpecTests;

use MongoDB\BSON\Int64;
use MongoDB\BSON\Timestamp;
use MongoDB\Driver\Command;
use MongoDB\Driver\Exception\ServerException;
use MongoDB\Driver\Server;
use stdClass;

use function array_unique;
use function basename;
use function count;
use function dirname;
use function file_get_contents;
use function get_object_vars;
use function glob;

/**
* Transactions Convenient API spec tests.
* Transactions spec prose tests.
*
* @see https://github.com/mongodb/specifications/tree/master/source/transactions-convenient-api
* @see https://github.com/mongodb/specifications/blob/master/source/transactions/tests/README.rst#mongos-pinning-prose-tests
*/
class TransactionsSpecTest extends FunctionalTestCase
{
public const INTERRUPTED = 11601;

public function setUp(): void
{
parent::setUp();

static::killAllSessions();

$this->skipIfTransactionsAreNotSupported();
}

public function tearDown(): void
{
if ($this->hasFailed()) {
static::killAllSessions();
}

parent::tearDown();
}

/**
* Assert that the expected and actual command documents match.
*
* Note: this method may modify the $expected object.
*
* @see https://github.com/mongodb/specifications/blob/master/source/transactions/tests/README.rst#command-started-events
* @param stdClass $expected Expected command document
* @param stdClass $actual Actual command document
*/
public static function assertCommandMatches(stdClass $expected, stdClass $actual): void
{
if (isset($expected->getMore) && $expected->getMore === 42) {
static::assertObjectHasAttribute('getMore', $actual);
static::assertThat($actual->getMore, static::logicalOr(
static::isInstanceOf(Int64::class),
static::isType('integer'),
));
unset($expected->getMore);
}

if (isset($expected->recoveryToken) && $expected->recoveryToken === 42) {
static::assertObjectHasAttribute('recoveryToken', $actual);
static::assertIsObject($actual->recoveryToken);
unset($expected->recoveryToken);
}

if (isset($expected->readConcern->afterClusterTime) && $expected->readConcern->afterClusterTime === 42) {
static::assertObjectHasAttribute('readConcern', $actual);
static::assertIsObject($actual->readConcern);
static::assertObjectHasAttribute('afterClusterTime', $actual->readConcern);
static::assertInstanceOf(Timestamp::class, $actual->readConcern->afterClusterTime);
unset($expected->readConcern->afterClusterTime);

/* If "afterClusterTime" was the only assertion for "readConcern",
* unset the field to avoid expecting an empty document later. */
if (get_object_vars($expected->readConcern) === []) {
unset($expected->readConcern);
}
}

/* TODO: Determine if forcing a new libmongoc client in Context is
* preferable to skipping the txnNumber assertion. */
//unset($expected['txnNumber']);

static::assertCommandOmittedFields($expected, $actual);

static::assertDocumentsMatch($expected, $actual);
}

/** @dataProvider provideTransactionsConvenientApiTests */
public function testTransactionsConvenientApi(stdClass $test, ?array $runOn, array $data, ?string $databaseName = null, ?string $collectionName = null): void
{
$this->runTransactionTest($test, $runOn, $data, $databaseName, $collectionName);
}

public function provideTransactionsConvenientApiTests(): array
{
return $this->provideTests('transactions-convenient-api');
}

/**
* Execute an individual test case from the specification.
*
* @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
* @param string $databaseName Name of database under test
* @param string $collectionName Name of collection under test
*/
private function runTransactionTest(stdClass $test, ?array $runOn, array $data, ?string $databaseName = null, ?string $collectionName = null): void
{
if (isset($runOn)) {
$this->checkServerRequirements($runOn);
}

if (isset($test->skipReason)) {
$this->markTestSkipped($test->skipReason);
}

$databaseName ??= $this->getDatabaseName();
$collectionName ??= $this->getCollectionName();

// Serverless uses a load balancer fronting a single proxy (PHPLIB-757)
$useMultipleMongoses = $this->isMongos() || ($this->isLoadBalanced() && ! $this->isServerless())
? ($test->useMultipleMongoses ?? false)
: false;

$context = Context::fromTransactions($test, $databaseName, $collectionName, $useMultipleMongoses);
$this->setContext($context);

$this->dropTestAndOutcomeCollections();
$this->createTestCollection();
$this->insertDataFixtures($data);
$this->preventStaleDbVersionError($test->operations);

if (isset($test->failPoint)) {
$this->configureFailPoint($test->failPoint);
}

if (isset($test->expectations)) {
$commandExpectations = CommandExpectations::fromTransactions($context->getClient(), $test->expectations);
$commandExpectations->startMonitoring();
}

foreach ($test->operations as $operation) {
Operation::fromTransactions($operation)->assert($this, $context);
}

$context->session0->endSession();
$context->session1->endSession();

if (isset($commandExpectations)) {
$commandExpectations->stopMonitoring();
$commandExpectations->assert($this, $context);
}

if (isset($test->outcome->collection->data)) {
$this->assertOutcomeCollectionData($test->outcome->collection->data);
}
}

private function provideTests(string $dir): array
{
$testArgs = [];

foreach (glob(__DIR__ . '/' . $dir . '/*.json') as $filename) {
$json = $this->decodeJson(file_get_contents($filename));
$group = basename(dirname($filename)) . '/' . basename($filename, '.json');
$runOn = $json->runOn ?? null;
$data = $json->data ?? [];
// phpcs:disable Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
$databaseName = $json->database_name ?? null;
$collectionName = $json->collection_name ?? null;
// phpcs:enable Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps

foreach ($json->tests as $test) {
$name = $group . ': ' . $test->description;
$testArgs[$name] = [$test, $runOn, $data, $databaseName, $collectionName];
}
}

return $testArgs;
}

/**
* Prose test 1: Test that starting a new transaction on a pinned
* ClientSession unpins the session and normal server selection is performed
Expand Down Expand Up @@ -265,73 +91,4 @@ public function testRunningNonTransactionOperationOnPinnedSessionUnpinsSession()

$session->endSession();
}

/**
* Create the collection, since it cannot be created within a transaction.
*/
protected function createTestCollection(): void
{
$context = $this->getContext();

$database = $context->getDatabase();
$database->createCollection($context->collectionName, $context->defaultWriteOptions);
}

/**
* Kill all sessions on the cluster.
*
* This will clean up any open transactions that may remain from a
* previously failed test. For sharded clusters, this command will be run
* on all mongos nodes.
*/
private static function killAllSessions(): void
{
// killAllSessions is not supported on serverless, see CLOUDP-84298
if (static::isServerless()) {
return;
}

$manager = static::createTestManager();
$primary = $manager->selectServer();

$servers = $primary->getType() === Server::TYPE_MONGOS
? $manager->getServers()
: [$primary];

foreach ($servers as $server) {
try {
// Skip servers that do not support sessions
if (! isset($server->getInfo()['logicalSessionTimeoutMinutes'])) {
continue;
}

$server->executeCommand('admin', new Command(['killAllSessions' => []]));
} catch (ServerException $e) {
// Interrupted error is safe to ignore (see: SERVER-38335)
if ($e->getCode() != self::INTERRUPTED) {
throw $e;
}
}
}
}

/**
* Work around potential error executing distinct on sharded clusters.
*
* @see https://github.com/mongodb/specifications/tree/master/source/transactions/tests#why-do-tests-that-run-distinct-sometimes-fail-with-staledbversion
*/
private function preventStaleDbVersionError(array $operations): void
{
if (! $this->isShardedCluster()) {
return;
}

foreach ($operations as $operation) {
if ($operation->name === 'distinct') {
$this->getContext()->getCollection()->distinct('foo');

return;
}
}
}
}
Loading

0 comments on commit aab06d6

Please sign in to comment.