Skip to content

Commit 474ec83

Browse files
jmikolaalcaeus
andauthored
PHPLIB-569: Atlas Data Lake spec tests (#771)
* PHPLIB-569: Atlas Data Lake spec tests Tests synced with mongodb/specifications@bdc283c * PHPLIB-569: Run ADL tests on Evergreen * Simplify test suite configuration Co-authored-by: Andreas Braun <git@alcaeus.org>
1 parent 792a4a2 commit 474ec83

File tree

11 files changed

+580
-2
lines changed

11 files changed

+580
-2
lines changed

.evergreen/config.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,17 @@ functions:
187187
${PREPARE_SHELL}
188188
sh ${DRIVERS_TOOLS}/.evergreen/stop-orchestration.sh
189189
190+
"bootstrap mongohoused":
191+
- command: shell.exec
192+
params:
193+
script: |
194+
DRIVERS_TOOLS="${DRIVERS_TOOLS}" sh ${DRIVERS_TOOLS}/.evergreen/atlas_data_lake/build-mongohouse-local.sh
195+
- command: shell.exec
196+
params:
197+
background: true
198+
script: |
199+
DRIVERS_TOOLS="${DRIVERS_TOOLS}" sh ${DRIVERS_TOOLS}/.evergreen/atlas_data_lake/run-mongohouse-local.sh
200+
190201
"run tests":
191202
- command: shell.exec
192203
type: test
@@ -196,6 +207,15 @@ functions:
196207
${PREPARE_SHELL}
197208
PHP_VERSION=${PHP_VERSION} AUTH=${AUTH} SSL=${SSL} MONGODB_URI="${MONGODB_URI}" sh ${PROJECT_DIRECTORY}/.evergreen/run-tests.sh
198209
210+
"run atlas data lake test":
211+
- command: shell.exec
212+
type: test
213+
params:
214+
working_dir: "src"
215+
script: |
216+
${PREPARE_SHELL}
217+
PHP_VERSION=${PHP_VERSION} TESTS="atlas-data-lake" AUTH=${AUTH} SSL=${SSL} MONGODB_URI="${MONGODB_URI}" sh ${PROJECT_DIRECTORY}/.evergreen/run-tests.sh
218+
199219
"cleanup":
200220
- command: shell.exec
201221
params:
@@ -325,6 +345,12 @@ tasks:
325345
TOPOLOGY: "sharded_cluster"
326346
- func: "run tests"
327347

348+
- name: "test-atlas-data-lake"
349+
commands:
350+
- func: "bootstrap mongohoused"
351+
- func: "run atlas data lake test"
352+
353+
328354
# }}}
329355

330356

@@ -537,3 +563,10 @@ buildvariants:
537563
- name: "test-standalone"
538564
- name: "test-replica_set"
539565
- name: "test-sharded_cluster"
566+
567+
- matrix_name: "atlas-data-lake-test"
568+
matrix_spec: { "php-edge-versions": "latest-stable" }
569+
display_name: "Atlas Data Lake test"
570+
run_on: rhel70
571+
tasks:
572+
- name: "test-atlas-data-lake"

.evergreen/run-tests.sh

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ set -o errexit # Exit the script with error if any of the commands fail
1212
AUTH=${AUTH:-noauth}
1313
SSL=${SSL:-nossl}
1414
MONGODB_URI=${MONGODB_URI:-}
15+
TESTS=${TESTS:-}
1516

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

2425
# Run the tests, and store the results in a Evergreen compatible JSON results file
25-
php vendor/bin/phpunit
26+
case "$TESTS" in
27+
atlas-data-lake*)
28+
MONGODB_URI="mongodb://mhuser:pencil@127.0.0.1:27017"
29+
php vendor/bin/phpunit --testsuite "Atlas Data Lake Test Suite"
30+
;;
31+
32+
*)
33+
php vendor/bin/phpunit
34+
;;
35+
esac

phpunit.xml.dist

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
beStrictAboutChangesToGlobalState="true"
88
colors="true"
99
bootstrap="tests/bootstrap.php"
10+
defaultTestSuite="Default Test Suite"
1011
>
1112

1213
<php>
@@ -16,8 +17,12 @@
1617
</php>
1718

1819
<testsuites>
19-
<testsuite name="MongoDB CRUD Test Suite">
20+
<testsuite name="Default Test Suite">
2021
<directory>./tests/</directory>
2122
</testsuite>
23+
24+
<testsuite name="Atlas Data Lake Test Suite">
25+
<file>tests/SpecTests/AtlasDataLakeSpecTest.php</file>
26+
</testsuite>
2227
</testsuites>
2328
</phpunit>
Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
<?php
2+
3+
namespace MongoDB\Tests\SpecTests;
4+
5+
use MongoDB\Client;
6+
use MongoDB\Driver\Command;
7+
use MongoDB\Driver\Cursor;
8+
use MongoDB\Tests\CommandObserver;
9+
use stdClass;
10+
use Symfony\Bridge\PhpUnit\SetUpTearDownTrait;
11+
use function basename;
12+
use function current;
13+
use function explode;
14+
use function file_get_contents;
15+
use function glob;
16+
use function parse_url;
17+
18+
/**
19+
* Atlas Data Lake spec tests.
20+
*
21+
* @see https://github.com/mongodb/specifications/tree/master/source/atlas-data-lake-testing/tests
22+
*/
23+
class AtlasDataLakeSpecTest extends FunctionalTestCase
24+
{
25+
use SetUpTearDownTrait;
26+
27+
private function doSetUp()
28+
{
29+
parent::setUp();
30+
31+
if (! $this->isAtlasDataLake()) {
32+
$this->markTestSkipped('Server is not Atlas Data Lake');
33+
}
34+
}
35+
36+
/**
37+
* Assert that the expected and actual command documents match.
38+
*
39+
* @param stdClass $expected Expected command document
40+
* @param stdClass $actual Actual command document
41+
*/
42+
public static function assertCommandMatches(stdClass $expected, stdClass $actual)
43+
{
44+
foreach ($expected as $key => $value) {
45+
if ($value === null) {
46+
static::assertObjectNotHasAttribute($key, $actual);
47+
unset($expected->{$key});
48+
}
49+
}
50+
51+
static::assertDocumentsMatch($expected, $actual);
52+
}
53+
54+
/**
55+
* Execute an individual test case from the specification.
56+
*
57+
* @dataProvider provideTests
58+
* @param stdClass $test Individual "tests[]" document
59+
* @param array $runOn Top-level "runOn" array with server requirements
60+
* @param array $data Top-level "data" array to initialize collection
61+
* @param string $databaseName Name of database under test
62+
* @param string $collectionName Name of collection under test
63+
*/
64+
public function testAtlasDataLake(stdClass $test, array $runOn = null, array $data, $databaseName = null, $collectionName = null)
65+
{
66+
if (isset($runOn)) {
67+
$this->checkServerRequirements($runOn);
68+
}
69+
70+
if (isset($test->skipReason)) {
71+
$this->markTestSkipped($test->skipReason);
72+
}
73+
74+
$databaseName = $databaseName ?? $this->getDatabaseName();
75+
$collectionName = $collectionName ?? $this->getCollectionName();
76+
77+
$context = Context::fromCrud($test, $databaseName, $collectionName);
78+
$this->setContext($context);
79+
80+
/* Note: Atlas Data Lake is read-only, so do not attempt to drop the
81+
* collection under test or insert data fixtures. Necesarry data
82+
* fixtures are already specified in the mongohoused configuration. */
83+
84+
if (isset($test->failPoint)) {
85+
throw new LogicException('ADL tests are not expected to configure fail points');
86+
}
87+
88+
if (isset($test->expectations)) {
89+
$commandExpectations = CommandExpectations::fromCrud((array) $test->expectations);
90+
$commandExpectations->startMonitoring();
91+
}
92+
93+
foreach ($test->operations as $operation) {
94+
Operation::fromCrud($operation)->assert($this, $context);
95+
}
96+
97+
if (isset($commandExpectations)) {
98+
$commandExpectations->stopMonitoring();
99+
$commandExpectations->assert($this, $context);
100+
}
101+
102+
if (isset($test->outcome->collection->data)) {
103+
throw new LogicException('ADL tests are not expected to assert collection data');
104+
}
105+
}
106+
107+
public function provideTests()
108+
{
109+
$testArgs = [];
110+
111+
foreach (glob(__DIR__ . '/atlas_data_lake/*.json') as $filename) {
112+
$json = $this->decodeJson(file_get_contents($filename));
113+
$group = basename($filename, '.json');
114+
$runOn = $json->runOn ?? null;
115+
$data = $json->data ?? [];
116+
$databaseName = $json->database_name ?? null;
117+
$collectionName = $json->collection_name ?? null;
118+
119+
foreach ($json->tests as $test) {
120+
$name = $group . ': ' . $test->description;
121+
$testArgs[$name] = [$test, $runOn, $data, $databaseName, $collectionName];
122+
}
123+
}
124+
125+
return $testArgs;
126+
}
127+
128+
/**
129+
* Prose test 1: Connect without authentication
130+
*/
131+
public function testKillCursors()
132+
{
133+
$cursorId = null;
134+
$cursorNamespace = null;
135+
136+
(new CommandObserver())->observe(
137+
function () {
138+
$client = new Client(static::getUri());
139+
$client->test->driverdata->find([], ['batchSize' => 2, 'limit' => 3]);
140+
},
141+
function (array $event) use (&$cursorId, &$cursorNamespace) {
142+
if ($event['started']->getCommandName() === 'find') {
143+
$this->assertArrayHasKey('succeeded', $event);
144+
145+
$reply = $event['succeeded']->getReply();
146+
$this->assertObjectHasAttribute('cursor', $reply);
147+
$this->assertIsObject($reply->cursor);
148+
$this->assertObjectHasAttribute('id', $reply->cursor);
149+
$this->assertIsInt($reply->cursor->id);
150+
$this->assertObjectHasAttribute('ns', $reply->cursor);
151+
$this->assertIsString($reply->cursor->ns);
152+
153+
/* Note: MongoDB\Driver\CursorId is not used here; however,
154+
* we shouldn't have to worry about encoutnering a 64-bit
155+
* cursor IDs on a 32-bit platform mongohoused allocates IDs
156+
* sequentially (starting from 1). */
157+
$cursorId = $reply->cursor->id;
158+
$cursorNamespace = $reply->cursor->ns;
159+
160+
return;
161+
}
162+
163+
/* After the initial find command, expect that killCursors is
164+
* next and that a cursor ID and namespace were collected. */
165+
$this->assertSame('killCursors', $event['started']->getCommandName());
166+
$this->assertIsInt($cursorId);
167+
$this->assertIsString($cursorNamespace);
168+
169+
list($databaseName, $collectionName) = explode('.', $cursorNamespace, 2);
170+
$command = $event['started']->getCommand();
171+
172+
/* Assert that the killCursors command uses the namespace and
173+
* cursor ID from the find command reply. */
174+
$this->assertSame($databaseName, $event['started']->getDatabaseName());
175+
$this->assertSame($databaseName, $command->{'$db'});
176+
$this->assertObjectHasAttribute('killCursors', $command);
177+
$this->assertSame($collectionName, $command->killCursors);
178+
$this->assertObjectHasAttribute('cursors', $command);
179+
$this->assertIsArray($command->cursors);
180+
$this->assertArrayHasKey(0, $command->cursors);
181+
$this->assertSame($cursorId, $command->cursors[0]);
182+
183+
/* Assert that the killCursors command reply indicates that the
184+
* expected cursor ID was killed. */
185+
$reply = $event['succeeded']->getReply();
186+
$this->assertObjectHasAttribute('cursorsKilled', $reply);
187+
$this->assertIsArray($reply->cursorsKilled);
188+
$this->assertArrayHasKey(0, $reply->cursorsKilled);
189+
$this->assertSame($cursorId, $reply->cursorsKilled[0]);
190+
}
191+
);
192+
}
193+
194+
/**
195+
* Prose test 2: Connect without authentication
196+
*/
197+
public function testConnectWithoutAuth()
198+
{
199+
/* Parse URI to remove userinfo component. The query string is left
200+
* as-is and must not include authMechanism or credentials. */
201+
$parts = parse_url(static::getUri());
202+
$port = isset($parts['port']) ? ':' . $parts['port'] : '';
203+
$path = $parts['path'] ?? '/';
204+
$query = isset($parts['query']) ? '?' . $parts['query'] : '';
205+
206+
$uri = $parts['scheme'] . '://' . $parts['host'] . $port . $path . $query;
207+
208+
$client = new Client($uri);
209+
$cursor = $client->selectDatabase($this->getDatabaseName())->command(['ping' => 1]);
210+
211+
$this->assertInstanceOf(Cursor::class, $cursor);
212+
$this->assertCommandSucceeded(current($cursor->toArray()));
213+
}
214+
215+
/**
216+
* Prose test 3: Connect with SCRAM-SHA-1 authentication
217+
*/
218+
public function testConnectwithSCRAMSHA1()
219+
{
220+
$client = new Client(static::getUri(), ['authMechanism' => 'SCRAM-SHA-1']);
221+
$cursor = $client->selectDatabase($this->getDatabaseName())->command(['ping' => 1]);
222+
223+
$this->assertInstanceOf(Cursor::class, $cursor);
224+
$this->assertCommandSucceeded(current($cursor->toArray()));
225+
}
226+
227+
/**
228+
* Prose test 4: Connect with SCRAM-SHA-256 authentication
229+
*/
230+
public function testConnectwithSCRAMSHA256()
231+
{
232+
$client = new Client(static::getUri(), ['authMechanism' => 'SCRAM-SHA-256']);
233+
$cursor = $client->selectDatabase($this->getDatabaseName())->command(['ping' => 1]);
234+
235+
$this->assertInstanceOf(Cursor::class, $cursor);
236+
$this->assertCommandSucceeded(current($cursor->toArray()));
237+
}
238+
239+
private function isAtlasDataLake() : bool
240+
{
241+
$cursor = $this->manager->executeCommand(
242+
$this->getDatabaseName(),
243+
new Command(['buildInfo' => 1])
244+
);
245+
246+
$document = current($cursor->toArray());
247+
248+
return ! empty($document->dataLake);
249+
}
250+
}

0 commit comments

Comments
 (0)