Skip to content

PHPLIB-569: Atlas Data Lake spec tests #771

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 3 commits into from
Sep 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
33 changes: 33 additions & 0 deletions .evergreen/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,17 @@ functions:
${PREPARE_SHELL}
sh ${DRIVERS_TOOLS}/.evergreen/stop-orchestration.sh

"bootstrap mongohoused":
- command: shell.exec
params:
script: |
DRIVERS_TOOLS="${DRIVERS_TOOLS}" sh ${DRIVERS_TOOLS}/.evergreen/atlas_data_lake/build-mongohouse-local.sh
- command: shell.exec
params:
background: true
script: |
DRIVERS_TOOLS="${DRIVERS_TOOLS}" sh ${DRIVERS_TOOLS}/.evergreen/atlas_data_lake/run-mongohouse-local.sh

"run tests":
- command: shell.exec
type: test
Expand All @@ -196,6 +207,15 @@ functions:
${PREPARE_SHELL}
PHP_VERSION=${PHP_VERSION} AUTH=${AUTH} SSL=${SSL} MONGODB_URI="${MONGODB_URI}" sh ${PROJECT_DIRECTORY}/.evergreen/run-tests.sh

"run atlas data lake test":
- command: shell.exec
type: test
params:
working_dir: "src"
script: |
${PREPARE_SHELL}
PHP_VERSION=${PHP_VERSION} TESTS="atlas-data-lake" AUTH=${AUTH} SSL=${SSL} MONGODB_URI="${MONGODB_URI}" sh ${PROJECT_DIRECTORY}/.evergreen/run-tests.sh

"cleanup":
- command: shell.exec
params:
Expand Down Expand Up @@ -325,6 +345,12 @@ tasks:
TOPOLOGY: "sharded_cluster"
- func: "run tests"

- name: "test-atlas-data-lake"
commands:
- func: "bootstrap mongohoused"
- func: "run atlas data lake test"


# }}}


Expand Down Expand Up @@ -537,3 +563,10 @@ buildvariants:
- name: "test-standalone"
- name: "test-replica_set"
- name: "test-sharded_cluster"

- matrix_name: "atlas-data-lake-test"
matrix_spec: { "php-edge-versions": "latest-stable" }
display_name: "Atlas Data Lake test"
run_on: rhel70
tasks:
- name: "test-atlas-data-lake"
12 changes: 11 additions & 1 deletion .evergreen/run-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ set -o errexit # Exit the script with error if any of the commands fail
AUTH=${AUTH:-noauth}
SSL=${SSL:-nossl}
MONGODB_URI=${MONGODB_URI:-}
TESTS=${TESTS:-}

OS=$(uname -s | tr '[:upper:]' '[:lower:]')
[ -z "$MARCH" ] && MARCH=$(uname -m | tr '[:upper:]' '[:lower:]')
Expand All @@ -22,4 +23,13 @@ OLD_PATH=$PATH
PATH=/opt/php/${PHP_VERSION}-64bit/bin:$OLD_PATH

# Run the tests, and store the results in a Evergreen compatible JSON results file
php vendor/bin/phpunit
case "$TESTS" in
atlas-data-lake*)
MONGODB_URI="mongodb://mhuser:pencil@127.0.0.1:27017"
php vendor/bin/phpunit --testsuite "Atlas Data Lake Test Suite"
;;

*)
php vendor/bin/phpunit
;;
esac
7 changes: 6 additions & 1 deletion phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
beStrictAboutChangesToGlobalState="true"
colors="true"
bootstrap="tests/bootstrap.php"
defaultTestSuite="Default Test Suite"
>

<php>
Expand All @@ -16,8 +17,12 @@
</php>

<testsuites>
<testsuite name="MongoDB CRUD Test Suite">
<testsuite name="Default Test Suite">
<directory>./tests/</directory>
</testsuite>

<testsuite name="Atlas Data Lake Test Suite">
<file>tests/SpecTests/AtlasDataLakeSpecTest.php</file>
</testsuite>
</testsuites>
</phpunit>
250 changes: 250 additions & 0 deletions tests/SpecTests/AtlasDataLakeSpecTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
<?php

namespace MongoDB\Tests\SpecTests;

use MongoDB\Client;
use MongoDB\Driver\Command;
use MongoDB\Driver\Cursor;
use MongoDB\Tests\CommandObserver;
use stdClass;
use Symfony\Bridge\PhpUnit\SetUpTearDownTrait;
use function basename;
use function current;
use function explode;
use function file_get_contents;
use function glob;
use function parse_url;

/**
* Atlas Data Lake spec tests.
*
* @see https://github.com/mongodb/specifications/tree/master/source/atlas-data-lake-testing/tests
*/
class AtlasDataLakeSpecTest extends FunctionalTestCase
{
use SetUpTearDownTrait;

private function doSetUp()
{
parent::setUp();

if (! $this->isAtlasDataLake()) {
$this->markTestSkipped('Server is not Atlas Data Lake');
}
}

/**
* Assert that the expected and actual command documents match.
*
* @param stdClass $expected Expected command document
* @param stdClass $actual Actual command document
*/
public static function assertCommandMatches(stdClass $expected, stdClass $actual)
{
foreach ($expected as $key => $value) {
if ($value === null) {
static::assertObjectNotHasAttribute($key, $actual);
unset($expected->{$key});
}
}

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

/**
* Execute an individual test case from the specification.
*
* @dataProvider provideTests
* @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
*/
public function testAtlasDataLake(stdClass $test, array $runOn = null, array $data, $databaseName = null, $collectionName = null)
{
if (isset($runOn)) {
$this->checkServerRequirements($runOn);
}

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

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

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

/* Note: Atlas Data Lake is read-only, so do not attempt to drop the
* collection under test or insert data fixtures. Necesarry data
* fixtures are already specified in the mongohoused configuration. */

if (isset($test->failPoint)) {
throw new LogicException('ADL tests are not expected to configure fail points');
}

if (isset($test->expectations)) {
$commandExpectations = CommandExpectations::fromCrud((array) $test->expectations);
$commandExpectations->startMonitoring();
}

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

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

if (isset($test->outcome->collection->data)) {
throw new LogicException('ADL tests are not expected to assert collection data');
}
}

public function provideTests()
{
$testArgs = [];

foreach (glob(__DIR__ . '/atlas_data_lake/*.json') as $filename) {
$json = $this->decodeJson(file_get_contents($filename));
$group = basename($filename, '.json');
$runOn = $json->runOn ?? null;
$data = $json->data ?? [];
$databaseName = $json->database_name ?? null;
$collectionName = $json->collection_name ?? null;

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

return $testArgs;
}

/**
* Prose test 1: Connect without authentication
*/
public function testKillCursors()
{
$cursorId = null;
$cursorNamespace = null;

(new CommandObserver())->observe(
function () {
$client = new Client(static::getUri());
$client->test->driverdata->find([], ['batchSize' => 2, 'limit' => 3]);
},
function (array $event) use (&$cursorId, &$cursorNamespace) {
if ($event['started']->getCommandName() === 'find') {
$this->assertArrayHasKey('succeeded', $event);

$reply = $event['succeeded']->getReply();
$this->assertObjectHasAttribute('cursor', $reply);
$this->assertIsObject($reply->cursor);
$this->assertObjectHasAttribute('id', $reply->cursor);
$this->assertIsInt($reply->cursor->id);
$this->assertObjectHasAttribute('ns', $reply->cursor);
$this->assertIsString($reply->cursor->ns);

/* Note: MongoDB\Driver\CursorId is not used here; however,
* we shouldn't have to worry about encoutnering a 64-bit
* cursor IDs on a 32-bit platform mongohoused allocates IDs
* sequentially (starting from 1). */
$cursorId = $reply->cursor->id;
$cursorNamespace = $reply->cursor->ns;

return;
}

/* After the initial find command, expect that killCursors is
* next and that a cursor ID and namespace were collected. */
$this->assertSame('killCursors', $event['started']->getCommandName());
$this->assertIsInt($cursorId);
$this->assertIsString($cursorNamespace);

list($databaseName, $collectionName) = explode('.', $cursorNamespace, 2);
$command = $event['started']->getCommand();

/* Assert that the killCursors command uses the namespace and
* cursor ID from the find command reply. */
$this->assertSame($databaseName, $event['started']->getDatabaseName());
$this->assertSame($databaseName, $command->{'$db'});
$this->assertObjectHasAttribute('killCursors', $command);
$this->assertSame($collectionName, $command->killCursors);
$this->assertObjectHasAttribute('cursors', $command);
$this->assertIsArray($command->cursors);
$this->assertArrayHasKey(0, $command->cursors);
$this->assertSame($cursorId, $command->cursors[0]);

/* Assert that the killCursors command reply indicates that the
* expected cursor ID was killed. */
$reply = $event['succeeded']->getReply();
$this->assertObjectHasAttribute('cursorsKilled', $reply);
$this->assertIsArray($reply->cursorsKilled);
$this->assertArrayHasKey(0, $reply->cursorsKilled);
$this->assertSame($cursorId, $reply->cursorsKilled[0]);
}
);
}

/**
* Prose test 2: Connect without authentication
*/
public function testConnectWithoutAuth()
{
/* Parse URI to remove userinfo component. The query string is left
* as-is and must not include authMechanism or credentials. */
$parts = parse_url(static::getUri());
$port = isset($parts['port']) ? ':' . $parts['port'] : '';
$path = $parts['path'] ?? '/';
$query = isset($parts['query']) ? '?' . $parts['query'] : '';

$uri = $parts['scheme'] . '://' . $parts['host'] . $port . $path . $query;

$client = new Client($uri);
$cursor = $client->selectDatabase($this->getDatabaseName())->command(['ping' => 1]);

$this->assertInstanceOf(Cursor::class, $cursor);
$this->assertCommandSucceeded(current($cursor->toArray()));
}

/**
* Prose test 3: Connect with SCRAM-SHA-1 authentication
*/
public function testConnectwithSCRAMSHA1()
{
$client = new Client(static::getUri(), ['authMechanism' => 'SCRAM-SHA-1']);
$cursor = $client->selectDatabase($this->getDatabaseName())->command(['ping' => 1]);

$this->assertInstanceOf(Cursor::class, $cursor);
$this->assertCommandSucceeded(current($cursor->toArray()));
}

/**
* Prose test 4: Connect with SCRAM-SHA-256 authentication
*/
public function testConnectwithSCRAMSHA256()
{
$client = new Client(static::getUri(), ['authMechanism' => 'SCRAM-SHA-256']);
$cursor = $client->selectDatabase($this->getDatabaseName())->command(['ping' => 1]);

$this->assertInstanceOf(Cursor::class, $cursor);
$this->assertCommandSucceeded(current($cursor->toArray()));
}

private function isAtlasDataLake() : bool
{
$cursor = $this->manager->executeCommand(
$this->getDatabaseName(),
new Command(['buildInfo' => 1])
);

$document = current($cursor->toArray());

return ! empty($document->dataLake);
}
}
Loading