Skip to content

Commit 62b39a0

Browse files
committed
refactor: begin to provide formal specs for test operations
Test operations until now have been built dynamically based on key checking on the arguments object passed in. This approach works but is very brittle. This commit introduces a new way of resolving the test operation through a more formal specification that all operations will eventually be migrated to.
1 parent 637f428 commit 62b39a0

File tree

1 file changed

+107
-72
lines changed

1 file changed

+107
-72
lines changed

test/functional/spec-runner/index.js

Lines changed: 107 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,35 @@ function resolveOperationArgs(operationName, operationArgs, context) {
468468
const CURSOR_COMMANDS = new Set(['find', 'aggregate', 'listIndexes', 'listCollections']);
469469
const ADMIN_COMMANDS = new Set(['listDatabases']);
470470

471+
const kOperations = new Map([
472+
[
473+
'createIndex',
474+
(operation, collection /*, context, options */) => {
475+
const fieldOrSpec = operation.arguments.keys;
476+
return collection.createIndex(fieldOrSpec);
477+
}
478+
],
479+
[
480+
'dropIndex',
481+
(operation, collection /*, context, options */) => {
482+
const indexName = operation.arguments.name;
483+
return collection.dropIndex(indexName);
484+
}
485+
],
486+
[
487+
'mapReduce',
488+
(operation, collection /*, context, options */) => {
489+
const args = operation.arguments;
490+
const map = args.map;
491+
const reduce = args.reduce;
492+
const options = {};
493+
if (args.out) options.out = args.out;
494+
495+
return collection.mapReduce(map, reduce, options);
496+
}
497+
]
498+
]);
499+
471500
/**
472501
*
473502
* @param {Object} operation the operation definition from the spec test
@@ -482,95 +511,101 @@ function testOperation(operation, obj, context, options) {
482511
let args = [];
483512
const operationName = translateOperationName(operation.name);
484513

485-
if (operation.arguments) {
486-
args = resolveOperationArgs(operationName, operation.arguments, context);
487-
488-
if (args == null) {
489-
args = [];
490-
Object.keys(operation.arguments).forEach(key => {
491-
if (key === 'callback') {
492-
args.push(() =>
493-
testOperations(operation.arguments.callback, context, { swallowOperationErrors: false })
494-
);
495-
return;
496-
}
497-
498-
if (['filter', 'fieldName', 'document', 'documents', 'pipeline'].indexOf(key) !== -1) {
499-
return args.unshift(operation.arguments[key]);
500-
}
514+
let opPromise;
515+
if (kOperations.has(operationName)) {
516+
opPromise = kOperations.get(operationName)(operation, obj, context, options);
517+
} else {
518+
if (operation.arguments) {
519+
args = resolveOperationArgs(operationName, operation.arguments, context);
520+
521+
if (args == null) {
522+
args = [];
523+
Object.keys(operation.arguments).forEach(key => {
524+
if (key === 'callback') {
525+
args.push(() =>
526+
testOperations(operation.arguments.callback, context, {
527+
swallowOperationErrors: false
528+
})
529+
);
530+
return;
531+
}
501532

502-
if ((key === 'map' || key === 'reduce') && operationName === 'mapReduce') {
503-
return args.unshift(operation.arguments[key]);
504-
}
533+
if (['filter', 'fieldName', 'document', 'documents', 'pipeline'].indexOf(key) !== -1) {
534+
return args.unshift(operation.arguments[key]);
535+
}
505536

506-
if (key === 'command') return args.unshift(operation.arguments[key]);
507-
if (key === 'requests') return args.unshift(extractBulkRequests(operation.arguments[key]));
508-
if (key === 'update' || key === 'replacement') return args.push(operation.arguments[key]);
509-
if (key === 'session') {
510-
if (isTransactionCommand(operationName)) return;
511-
opOptions.session = context[operation.arguments.session];
512-
return;
513-
}
537+
if ((key === 'map' || key === 'reduce') && operationName === 'mapReduce') {
538+
return args.unshift(operation.arguments[key]);
539+
}
514540

515-
if (key === 'returnDocument') {
516-
opOptions.returnOriginal = operation.arguments[key] === 'Before' ? true : false;
517-
return;
518-
}
541+
if (key === 'command') return args.unshift(operation.arguments[key]);
542+
if (key === 'requests')
543+
return args.unshift(extractBulkRequests(operation.arguments[key]));
544+
if (key === 'update' || key === 'replacement') return args.push(operation.arguments[key]);
545+
if (key === 'session') {
546+
if (isTransactionCommand(operationName)) return;
547+
opOptions.session = context[operation.arguments.session];
548+
return;
549+
}
519550

520-
if (key === 'options') {
521-
Object.assign(opOptions, operation.arguments[key]);
522-
if (opOptions.readPreference) {
523-
opOptions.readPreference = normalizeReadPreference(opOptions.readPreference.mode);
551+
if (key === 'returnDocument') {
552+
opOptions.returnOriginal = operation.arguments[key] === 'Before' ? true : false;
553+
return;
524554
}
525555

526-
return;
527-
}
556+
if (key === 'options') {
557+
Object.assign(opOptions, operation.arguments[key]);
558+
if (opOptions.readPreference) {
559+
opOptions.readPreference = normalizeReadPreference(opOptions.readPreference.mode);
560+
}
528561

529-
if (key === 'readPreference') {
530-
opOptions[key] = normalizeReadPreference(operation.arguments[key].mode);
531-
return;
532-
}
562+
return;
563+
}
533564

534-
opOptions[key] = operation.arguments[key];
535-
});
536-
}
537-
}
565+
if (key === 'readPreference') {
566+
opOptions[key] = normalizeReadPreference(operation.arguments[key].mode);
567+
return;
568+
}
538569

539-
if (
540-
args.length === 0 &&
541-
!isTransactionCommand(operationName) &&
542-
!isTestRunnerCommand(context, operationName)
543-
) {
544-
args.push({});
545-
}
570+
opOptions[key] = operation.arguments[key];
571+
});
572+
}
573+
}
546574

547-
if (Object.keys(opOptions).length > 0) {
548-
// NOTE: this is awful, but in order to provide options for some methods we need to add empty
549-
// query objects.
550-
if (operationName === 'distinct') {
575+
if (
576+
args.length === 0 &&
577+
!isTransactionCommand(operationName) &&
578+
!isTestRunnerCommand(context, operationName)
579+
) {
551580
args.push({});
552581
}
553582

554-
args.push(opOptions);
555-
}
583+
if (Object.keys(opOptions).length > 0) {
584+
// NOTE: this is awful, but in order to provide options for some methods we need to add empty
585+
// query objects.
586+
if (operationName === 'distinct') {
587+
args.push({});
588+
}
556589

557-
if (ADMIN_COMMANDS.has(operationName)) {
558-
obj = obj.db().admin();
559-
}
590+
args.push(opOptions);
591+
}
560592

561-
if (operation.name === 'listDatabaseNames' || operation.name === 'listCollectionNames') {
562-
opOptions.nameOnly = true;
563-
}
593+
if (ADMIN_COMMANDS.has(operationName)) {
594+
obj = obj.db().admin();
595+
}
564596

565-
let opPromise;
597+
if (operation.name === 'listDatabaseNames' || operation.name === 'listCollectionNames') {
598+
opOptions.nameOnly = true;
599+
}
566600

567-
if (CURSOR_COMMANDS.has(operationName)) {
568-
// `find` creates a cursor, so we need to call `toArray` on it
569-
const cursor = obj[operationName].apply(obj, args);
570-
opPromise = cursor.toArray();
571-
} else {
572-
// wrap this in a `Promise.try` because some operations might throw
573-
opPromise = Promise.try(() => obj[operationName].apply(obj, args));
601+
if (CURSOR_COMMANDS.has(operationName)) {
602+
// `find` creates a cursor, so we need to call `toArray` on it
603+
const cursor = obj[operationName].apply(obj, args);
604+
opPromise = cursor.toArray();
605+
} else {
606+
// wrap this in a `Promise.try` because some operations might throw
607+
opPromise = Promise.try(() => obj[operationName].apply(obj, args));
608+
}
574609
}
575610

576611
if (operation.error) {

0 commit comments

Comments
 (0)