def parse_collection_options(opts): if 'readPreference' in opts: opts['read_preference'] = parse_read_preference( opts.pop('readPreference')) if 'writeConcern' in opts: opts['write_concern'] = WriteConcern(**dict(opts.pop('writeConcern'))) if 'readConcern' in opts: opts['read_concern'] = ReadConcern(**dict(opts.pop('readConcern'))) return opts
def parse_collection_options(opts): if 'readPreference' in opts: opts['read_preference'] = parse_read_preference( opts.pop('readPreference')) if 'writeConcern' in opts: opts['write_concern'] = WriteConcern( **dict(opts.pop('writeConcern'))) if 'readConcern' in opts: opts['read_concern'] = ReadConcern( **dict(opts.pop('readConcern'))) return opts
def parse_options(opts): if 'readPreference' in opts: opts['read_preference'] = parse_read_preference( opts.pop('readPreference')) if 'writeConcern' in opts: opts['write_concern'] = WriteConcern( **dict(opts.pop('writeConcern'))) if 'readConcern' in opts: opts['read_concern'] = ReadConcern(**dict(opts.pop('readConcern'))) if 'maxTimeMS' in opts: opts['max_time_ms'] = opts.pop('maxTimeMS') if 'maxCommitTimeMS' in opts: opts['max_commit_time_ms'] = opts.pop('maxCommitTimeMS') return dict(opts)
def run_scenario(self): listener = OvertCommandListener() # New client, to avoid interference from pooled sessions. # Convert test['clientOptions'] to dict to avoid a Jython bug using "**" # with ScenarioDict. client = rs_client(event_listeners=[listener], **dict(test['clientOptions'])) # Close the client explicitly to avoid having too many threads open. self.addCleanup(client.close) # Kill all sessions before and after each test to prevent an open # transaction (from a test failure) from blocking collection/database # operations during test set up and tear down. def kill_all_sessions(): try: client.admin.command('killAllSessions', []) except OperationFailure: # "operation was interrupted" by killing the command's # own session. pass kill_all_sessions() self.addCleanup(kill_all_sessions) database_name = scenario_def['database_name'] collection_name = scenario_def['collection_name'] write_concern_db = client.get_database( database_name, write_concern=WriteConcern(w='majority')) write_concern_coll = write_concern_db[collection_name] write_concern_coll.drop() write_concern_db.create_collection(collection_name) if scenario_def['data']: # Load data. write_concern_coll.insert_many(scenario_def['data']) # Create session0 and session1. sessions = {} session_ids = {} for i in range(2): session_name = 'session%d' % i opts = camel_to_snake_args(test['sessionOptions'][session_name]) if 'default_transaction_options' in opts: txn_opts = opts['default_transaction_options'] if 'readConcern' in txn_opts: read_concern = ReadConcern(**dict(txn_opts['readConcern'])) else: read_concern = None if 'writeConcern' in txn_opts: write_concern = WriteConcern( **dict(txn_opts['writeConcern'])) else: write_concern = None if 'readPreference' in txn_opts: read_pref = parse_read_preference( txn_opts['readPreference']) else: read_pref = None txn_opts = client_session.TransactionOptions( read_concern=read_concern, write_concern=write_concern, read_preference=read_pref, ) opts['default_transaction_options'] = txn_opts s = client.start_session(**dict(opts)) sessions[session_name] = s # Store lsid so we can access it after end_session, in check_events. session_ids[session_name] = s.session_id self.addCleanup(end_sessions, sessions) if 'failPoint' in test: self.set_fail_point(test['failPoint']) self.addCleanup(self.set_fail_point, { 'configureFailPoint': 'failCommand', 'mode': 'off' }) listener.results.clear() collection = client[database_name][collection_name] for op in test['operations']: expected_result = op.get('result') if expect_error(expected_result): with self.assertRaises(PyMongoError, msg=op['name']) as context: self.run_operation(sessions, collection, op.copy()) if expect_error_message(expected_result): self.assertIn(expected_result['errorContains'].lower(), str(context.exception).lower()) if expect_error_code(expected_result): self.assertEqual(expected_result['errorCodeName'], context.exception.details.get('codeName')) if expect_error_labels_contain(expected_result): self.assertErrorLabelsContain( context.exception, expected_result['errorLabelsContain']) if expect_error_labels_omit(expected_result): self.assertErrorLabelsOmit( context.exception, expected_result['errorLabelsOmit']) else: result = self.run_operation(sessions, collection, op.copy()) if 'result' in op: if op['name'] == 'runCommand': self.check_command_result(expected_result, result) else: self.check_result(expected_result, result) for s in sessions.values(): s.end_session() self.check_events(test, listener, session_ids) # Assert final state is expected. expected_c = test['outcome'].get('collection') if expected_c is not None: # Read from the primary to ensure causal consistency. primary_coll = collection.with_options( read_preference=ReadPreference.PRIMARY) self.assertEqual(list(primary_coll.find()), expected_c['data'])
def run_scenario(self, scenario_def, test): self.maybe_skip_scenario(test) listener = OvertCommandListener() # Create a new client, to avoid interference from pooled sessions. # Convert test['clientOptions'] to dict to avoid a Jython bug using # "**" with ScenarioDict. client_options = dict(test['clientOptions']) use_multi_mongos = test['useMultipleMongoses'] if client_context.is_mongos and use_multi_mongos: client = rs_client(client_context.mongos_seeds(), event_listeners=[listener], **client_options) else: client = rs_client(event_listeners=[listener], **client_options) # Close the client explicitly to avoid having too many threads open. self.addCleanup(client.close) # Kill all sessions before and after each test to prevent an open # transaction (from a test failure) from blocking collection/database # operations during test set up and tear down. self.kill_all_sessions() self.addCleanup(self.kill_all_sessions) database_name = scenario_def['database_name'] write_concern_db = client_context.client.get_database( database_name, write_concern=WriteConcern(w='majority')) if 'bucket_name' in scenario_def: # Create a bucket for the retryable reads GridFS tests. collection_name = scenario_def['bucket_name'] client_context.client.drop_database(database_name) if scenario_def['data']: data = scenario_def['data'] # Load data. write_concern_db['fs.chunks'].insert_many(data['fs.chunks']) write_concern_db['fs.files'].insert_many(data['fs.files']) else: collection_name = scenario_def['collection_name'] write_concern_coll = write_concern_db[collection_name] write_concern_coll.drop() write_concern_db.create_collection(collection_name) if scenario_def['data']: # Load data. write_concern_coll.insert_many(scenario_def['data']) # SPEC-1245 workaround StaleDbVersion on distinct for c in self.mongos_clients: c[database_name][collection_name].distinct("x") # Create session0 and session1. sessions = {} session_ids = {} for i in range(2): session_name = 'session%d' % i opts = camel_to_snake_args(test['sessionOptions'][session_name]) if 'default_transaction_options' in opts: txn_opts = opts['default_transaction_options'] if 'readConcern' in txn_opts: read_concern = ReadConcern(**dict(txn_opts['readConcern'])) else: read_concern = None if 'writeConcern' in txn_opts: write_concern = WriteConcern( **dict(txn_opts['writeConcern'])) else: write_concern = None if 'readPreference' in txn_opts: read_pref = parse_read_preference( txn_opts['readPreference']) else: read_pref = None txn_opts = client_session.TransactionOptions( read_concern=read_concern, write_concern=write_concern, read_preference=read_pref, ) opts['default_transaction_options'] = txn_opts s = client.start_session(**dict(opts)) sessions[session_name] = s # Store lsid so we can access it after end_session, in check_events. session_ids[session_name] = s.session_id self.addCleanup(end_sessions, sessions) if 'failPoint' in test: self.set_fail_point(test['failPoint']) self.addCleanup(self.set_fail_point, { 'configureFailPoint': 'failCommand', 'mode': 'off' }) listener.results.clear() collection = client[database_name][collection_name] self.run_operations(sessions, collection, test['operations']) end_sessions(sessions) self.check_events(test, listener, session_ids) # Disable fail points. if 'failPoint' in test: self.set_fail_point({ 'configureFailPoint': 'failCommand', 'mode': 'off' }) # Assert final state is expected. expected_c = test['outcome'].get('collection') if expected_c is not None: # Read from the primary with local read concern to ensure causal # consistency. primary_coll = collection.with_options( read_preference=ReadPreference.PRIMARY, read_concern=ReadConcern('local')) self.assertEqual(list(primary_coll.find()), expected_c['data'])
def run_scenario(self): listener = OvertCommandListener() # New client, to avoid interference from pooled sessions. # Convert test['clientOptions'] to dict to avoid a Jython bug using "**" # with ScenarioDict. client = rs_client(event_listeners=[listener], **dict(test['clientOptions'])) # Kill all sessions before and after each test to prevent an open # transaction (from a test failure) from blocking collection/database # operations during test set up and tear down. def kill_all_sessions(): try: client.admin.command('killAllSessions', []) except OperationFailure: # "operation was interrupted" by killing the command's # own session. pass kill_all_sessions() self.addCleanup(kill_all_sessions) database_name = scenario_def['database_name'] collection_name = scenario_def['collection_name'] write_concern_db = client.get_database( database_name, write_concern=WriteConcern(w='majority')) write_concern_coll = write_concern_db[collection_name] write_concern_coll.drop() write_concern_db.create_collection(collection_name) if scenario_def['data']: # Load data. write_concern_coll.insert_many(scenario_def['data']) # Create session0 and session1. sessions = {} session_ids = {} for i in range(2): session_name = 'session%d' % i opts = camel_to_snake_args(test['sessionOptions'][session_name]) if 'default_transaction_options' in opts: txn_opts = opts['default_transaction_options'] if 'readConcern' in txn_opts: read_concern = ReadConcern( **dict(txn_opts['readConcern'])) else: read_concern = None if 'writeConcern' in txn_opts: write_concern = WriteConcern( **dict(txn_opts['writeConcern'])) else: write_concern = None if 'readPreference' in txn_opts: read_pref = parse_read_preference( txn_opts['readPreference']) else: read_pref = None txn_opts = client_session.TransactionOptions( read_concern=read_concern, write_concern=write_concern, read_preference=read_pref, ) opts['default_transaction_options'] = txn_opts s = client.start_session(**dict(opts)) sessions[session_name] = s # Store lsid so we can access it after end_session, in check_events. session_ids[session_name] = s.session_id self.addCleanup(end_sessions, sessions) if 'failPoint' in test: self.set_fail_point(test['failPoint']) self.addCleanup(self.set_fail_point, { 'configureFailPoint': 'failCommand', 'mode': 'off'}) listener.results.clear() collection = client[database_name][collection_name] for op in test['operations']: expected_result = op.get('result') if expect_error(expected_result): with self.assertRaises(PyMongoError, msg=op['name']) as context: self.run_operation(sessions, collection, op.copy()) if expect_error_message(expected_result): self.assertIn(expected_result['errorContains'].lower(), str(context.exception).lower()) if expect_error_code(expected_result): self.assertEqual(expected_result['errorCodeName'], context.exception.details.get('codeName')) if expect_error_labels_contain(expected_result): self.assertErrorLabelsContain( context.exception, expected_result['errorLabelsContain']) if expect_error_labels_omit(expected_result): self.assertErrorLabelsOmit( context.exception, expected_result['errorLabelsOmit']) else: result = self.run_operation(sessions, collection, op.copy()) if 'result' in op: if op['name'] == 'runCommand': self.check_command_result(expected_result, result) else: self.check_result(expected_result, result) for s in sessions.values(): s.end_session() self.check_events(test, listener, session_ids) # Assert final state is expected. expected_c = test['outcome'].get('collection') if expected_c is not None: # Read from the primary to ensure causal consistency. primary_coll = collection.with_options( read_preference=ReadPreference.PRIMARY) self.assertEqual(list(primary_coll.find()), expected_c['data'])
def run_scenario(self): dbname = scenario_def['database_name'] collname = scenario_def['collection_name'] coll = self.client[dbname][collname] coll.drop() coll.insert_many(scenario_def['data']) self.listener.results.clear() name = camel_to_snake(test['operation']['name']) if 'read_preference' in test['operation']: coll = coll.with_options(read_preference=parse_read_preference( test['operation']['read_preference'])) if 'collectionOptions' in test['operation']: colloptions = test['operation']['collectionOptions'] if 'writeConcern' in colloptions: concern = colloptions['writeConcern'] coll = coll.with_options(write_concern=WriteConcern(**concern)) test_args = test['operation']['arguments'] if 'options' in test_args: options = test_args.pop('options') test_args.update(options) args = {} for arg in test_args: args[camel_to_snake(arg)] = test_args[arg] if name == 'bulk_write': bulk_args = [] for request in args['requests']: opname = request['name'] klass = opname[0:1].upper() + opname[1:] arg = getattr(pymongo, klass)(**request['arguments']) bulk_args.append(arg) try: coll.bulk_write(bulk_args, args.get('ordered', True)) except OperationFailure: pass elif name == 'find': if 'sort' in args: args['sort'] = list(args['sort'].items()) for arg in 'skip', 'limit': if arg in args: args[arg] = int(args[arg]) try: # Iterate the cursor. tuple(coll.find(**args)) except OperationFailure: pass # Wait for the killCursors thread to run if necessary. if 'limit' in args and client_context.version[:2] < (3, 1): self.client._kill_cursors_executor.wake() started = self.listener.results['started'] succeeded = self.listener.results['succeeded'] wait_until(lambda: started[-1].command_name == 'killCursors', "publish a start event for killCursors.") wait_until(lambda: succeeded[-1].command_name == 'killCursors', "publish a succeeded event for killCursors.") else: try: getattr(coll, name)(**args) except OperationFailure: pass res = self.listener.results for expectation in test['expectations']: event_type = next(iter(expectation)) if event_type == "command_started_event": event = res['started'][0] if len(res['started']) else None if event is not None: # The tests substitute 42 for any number other than 0. if (event.command_name == 'getMore' and event.command['getMore']): event.command['getMore'] = 42 elif event.command_name == 'killCursors': event.command['cursors'] = [42] elif event_type == "command_succeeded_event": event = (res['succeeded'].pop(0) if len(res['succeeded']) else None) if event is not None: reply = event.reply # The tests substitute 42 for any number other than 0, # and "" for any error message. if 'writeErrors' in reply: for doc in reply['writeErrors']: # Remove any new fields the server adds. The tests # only have index, code, and errmsg. diff = set(doc) - set(['index', 'code', 'errmsg']) for field in diff: doc.pop(field) doc['code'] = 42 doc['errmsg'] = "" elif 'cursor' in reply: if reply['cursor']['id']: reply['cursor']['id'] = 42 elif event.command_name == 'killCursors': # Make the tests continue to pass when the killCursors # command is actually in use. if 'cursorsKilled' in reply: reply.pop('cursorsKilled') reply['cursorsUnknown'] = [42] # Found succeeded event. Pop related started event. res['started'].pop(0) elif event_type == "command_failed_event": event = res['failed'].pop(0) if len(res['failed']) else None if event is not None: # Found failed event. Pop related started event. res['started'].pop(0) else: self.fail("Unknown event type") if event is None: event_name = event_type.split('_')[1] self.fail("Expected %s event for %s command. Actual " "results:%s" % (event_name, expectation[event_type]['command_name'], format_actual_results(res))) for attr, expected in expectation[event_type].items(): if 'options' in expected: options = expected.pop('options') expected.update(options) actual = getattr(event, attr) if isinstance(expected, dict): for key, val in expected.items(): self.assertEqual(val, actual[key]) else: self.assertEqual(actual, expected)
def run_scenario(self): listener = OvertCommandListener() # New client, to avoid interference from pooled sessions. # Convert test['clientOptions'] to dict to avoid a Jython bug using "**" # with ScenarioDict. client = rs_client(event_listeners=[listener], **dict(test['clientOptions'])) try: client.admin.command('killAllSessions', []) except OperationFailure: # "operation was interrupted" by killing the command's own session. pass write_concern_db = client.get_database( 'transaction-tests', write_concern=WriteConcern(w='majority')) write_concern_db.test.drop() write_concern_db.create_collection('test') if scenario_def['data']: # Load data. write_concern_db.test.insert_many(scenario_def['data']) # Create session0 and session1. sessions = {} session_ids = {} for i in range(2): session_name = 'session%d' % i opts = camel_to_snake_args(test['sessionOptions'][session_name]) if 'default_transaction_options' in opts: txn_opts = opts['default_transaction_options'] if 'readConcern' in txn_opts: read_concern = ReadConcern(**dict(txn_opts['readConcern'])) else: read_concern = None if 'writeConcern' in txn_opts: write_concern = WriteConcern( **dict(txn_opts['writeConcern'])) else: write_concern = None if 'readPreference' in txn_opts: read_pref = parse_read_preference( txn_opts['readPreference']) else: read_pref = None txn_opts = client_session.TransactionOptions( read_concern=read_concern, write_concern=write_concern, read_preference=read_pref, ) opts['default_transaction_options'] = txn_opts s = client.start_session(**dict(opts)) sessions[session_name] = s # Store lsid so we can access it after end_session, in check_events. session_ids[session_name] = s.session_id self.addCleanup(end_sessions, sessions) listener.results.clear() collection = client['transaction-tests'].test for op in test['operations']: expected_result = op.get('result') if expect_error_message(expected_result): with self.assertRaises(PyMongoError, msg=op.get('name')) as context: self.run_operation(sessions, collection, op.copy()) self.assertIn(expected_result['errorContains'].lower(), str(context.exception).lower()) elif expect_error_code(expected_result): with self.assertRaises(OperationFailure) as context: self.run_operation(sessions, collection, op.copy()) self.assertEqual(expected_result['errorCodeName'], context.exception.details.get('codeName')) else: result = self.run_operation(sessions, collection, op.copy()) if 'result' in op: self.check_result(expected_result, result) for s in sessions.values(): s.end_session() self.check_events(test, listener, session_ids) # Assert final state is expected. expected_c = test['outcome'].get('collection') if expected_c is not None: # Read from the primary to ensure causal consistency. primary_coll = collection.with_options( read_preference=ReadPreference.PRIMARY) self.assertEqual(list(primary_coll.find()), expected_c['data'])
def run_operation(self, sessions, collection, operation): session = None name = camel_to_snake(operation['name']) self.transaction_test_debug(name) session_name = operation['arguments'].pop('session', None) if session_name: session = sessions[session_name] # Combine arguments with options and handle special cases. arguments = operation['arguments'] arguments.update(arguments.pop("options", {})) pref = write_c = read_c = None if 'readPreference' in arguments: pref = parse_read_preference(arguments.pop('readPreference')) if 'writeConcern' in arguments: write_c = WriteConcern(**dict(arguments.pop('writeConcern'))) if 'readConcern' in arguments: read_c = ReadConcern(**dict(arguments.pop('readConcern'))) if name == 'start_transaction': cmd = partial(session.start_transaction, write_concern=write_c, read_concern=read_c, read_preference=pref) elif name in ('commit_transaction', 'abort_transaction'): cmd = getattr(session, name) else: collection = collection.with_options(write_concern=write_c, read_concern=read_c, read_preference=pref) cmd = getattr(collection, name) arguments['session'] = session for arg_name in list(arguments): c2s = camel_to_snake(arg_name) # PyMongo accepts sort as list of tuples. Asserting len=1 # because ordering dicts from JSON in 2.6 is unwieldy. if arg_name == "sort": sort_dict = arguments[arg_name] assert len(sort_dict) == 1, 'test can only have 1 sort key' arguments[arg_name] = list(iteritems(sort_dict)) # Named "key" instead not fieldName. if arg_name == "fieldName": arguments["key"] = arguments.pop(arg_name) # Aggregate uses "batchSize", while find uses batch_size. elif arg_name == "batchSize" and name == "aggregate": continue # Requires boolean returnDocument. elif arg_name == "returnDocument": arguments[c2s] = arguments[arg_name] == "After" elif c2s == "requests": # Parse each request into a bulk write model. requests = [] for request in arguments["requests"]: bulk_model = camel_to_upper_camel(request["name"]) bulk_class = getattr(operations, bulk_model) bulk_arguments = camel_to_snake_args(request["arguments"]) requests.append(bulk_class(**dict(bulk_arguments))) arguments["requests"] = requests else: arguments[c2s] = arguments.pop(arg_name) result = cmd(**dict(arguments)) if name == "aggregate": if arguments["pipeline"] and "$out" in arguments["pipeline"][-1]: # Read from the primary to ensure causal consistency. out = collection.database.get_collection( arguments["pipeline"][-1]["$out"], read_preference=ReadPreference.PRIMARY) return out.find() if isinstance(result, Cursor) or isinstance(result, CommandCursor): return list(result) return result
def run_scenario(self): if test.get('skipReason'): raise unittest.SkipTest(test.get('skipReason')) listener = OvertCommandListener() # Create a new client, to avoid interference from pooled sessions. # Convert test['clientOptions'] to dict to avoid a Jython bug using # "**" with ScenarioDict. client_options = dict(test['clientOptions']) use_multi_mongos = test['useMultipleMongoses'] if client_context.is_mongos and use_multi_mongos: client = rs_client(client_context.mongos_seeds(), event_listeners=[listener], **client_options) else: client = rs_client(event_listeners=[listener], **client_options) # Close the client explicitly to avoid having too many threads open. self.addCleanup(client.close) # Kill all sessions before and after each test to prevent an open # transaction (from a test failure) from blocking collection/database # operations during test set up and tear down. self.kill_all_sessions() self.addCleanup(self.kill_all_sessions) database_name = scenario_def['database_name'] collection_name = scenario_def['collection_name'] # Don't use the test client to load data. write_concern_db = client_context.client.get_database( database_name, write_concern=WriteConcern(w='majority')) write_concern_coll = write_concern_db[collection_name] write_concern_coll.drop() write_concern_db.create_collection(collection_name) if scenario_def['data']: # Load data. write_concern_coll.insert_many(scenario_def['data']) # SPEC-1245 workaround StaleDbVersion on distinct for c in self.mongos_clients: c[database_name][collection_name].distinct("x") # Create session0 and session1. sessions = {} session_ids = {} for i in range(2): session_name = 'session%d' % i opts = camel_to_snake_args(test['sessionOptions'][session_name]) if 'default_transaction_options' in opts: txn_opts = opts['default_transaction_options'] if 'readConcern' in txn_opts: read_concern = ReadConcern( **dict(txn_opts['readConcern'])) else: read_concern = None if 'writeConcern' in txn_opts: write_concern = WriteConcern( **dict(txn_opts['writeConcern'])) else: write_concern = None if 'readPreference' in txn_opts: read_pref = parse_read_preference( txn_opts['readPreference']) else: read_pref = None txn_opts = client_session.TransactionOptions( read_concern=read_concern, write_concern=write_concern, read_preference=read_pref, ) opts['default_transaction_options'] = txn_opts s = client.start_session(**dict(opts)) sessions[session_name] = s # Store lsid so we can access it after end_session, in check_events. session_ids[session_name] = s.session_id self.addCleanup(end_sessions, sessions) if 'failPoint' in test: self.set_fail_point(test['failPoint']) self.addCleanup(self.set_fail_point, { 'configureFailPoint': 'failCommand', 'mode': 'off'}) listener.results.clear() collection = client[database_name][collection_name] self.run_operations(sessions, collection, test['operations']) for s in sessions.values(): s.end_session() self.check_events(test, listener, session_ids) # Disable fail points. self.set_fail_point({ 'configureFailPoint': 'failCommand', 'mode': 'off'}) # Assert final state is expected. expected_c = test['outcome'].get('collection') if expected_c is not None: # Read from the primary with local read concern to ensure causal # consistency. primary_coll = collection.with_options( read_preference=ReadPreference.PRIMARY, read_concern=ReadConcern('local')) self.assertEqual(list(primary_coll.find()), expected_c['data'])
def run_scenario(self): dbname = scenario_def['database_name'] collname = scenario_def['collection_name'] coll = self.client[dbname][collname] coll.drop() coll.insert_many(scenario_def['data']) self.listener.results.clear() name = camel_to_snake(test['operation']['name']) if 'read_preference' in test['operation']: coll = coll.with_options(read_preference=parse_read_preference( test['operation']['read_preference'])) if 'collectionOptions' in test['operation']: colloptions = test['operation']['collectionOptions'] if 'writeConcern' in colloptions: concern = colloptions['writeConcern'] coll = coll.with_options( write_concern=WriteConcern(**concern)) test_args = test['operation']['arguments'] if 'options' in test_args: options = test_args.pop('options') test_args.update(options) args = {} for arg in test_args: args[camel_to_snake(arg)] = test_args[arg] if name == 'bulk_write': bulk_args = [] for request in args['requests']: opname = request['name'] klass = opname[0:1].upper() + opname[1:] arg = getattr(pymongo, klass)(**request['arguments']) bulk_args.append(arg) try: coll.bulk_write(bulk_args, args.get('ordered', True)) except OperationFailure: pass elif name == 'find': if 'sort' in args: args['sort'] = list(args['sort'].items()) for arg in 'skip', 'limit': if arg in args: args[arg] = int(args[arg]) try: # Iterate the cursor. tuple(coll.find(**args)) except OperationFailure: pass # Wait for the killCursors thread to run if necessary. if 'limit' in args and client_context.version[:2] < (3, 1): self.client._kill_cursors_executor.wake() started = self.listener.results['started'] succeeded = self.listener.results['succeeded'] wait_until( lambda: started[-1].command_name == 'killCursors', "publish a start event for killCursors.") wait_until( lambda: succeeded[-1].command_name == 'killCursors', "publish a succeeded event for killCursors.") else: try: getattr(coll, name)(**args) except OperationFailure: pass res = self.listener.results for expectation in test['expectations']: event_type = next(iter(expectation)) if event_type == "command_started_event": event = res['started'][0] if len(res['started']) else None if event is not None: # The tests substitute 42 for any number other than 0. if (event.command_name == 'getMore' and event.command['getMore']): event.command['getMore'] = 42 elif event.command_name == 'killCursors': event.command['cursors'] = [42] elif event_type == "command_succeeded_event": event = ( res['succeeded'].pop(0) if len(res['succeeded']) else None) if event is not None: reply = event.reply # The tests substitute 42 for any number other than 0, # and "" for any error message. if 'writeErrors' in reply: for doc in reply['writeErrors']: # Remove any new fields the server adds. The tests # only have index, code, and errmsg. diff = set(doc) - set(['index', 'code', 'errmsg']) for field in diff: doc.pop(field) doc['code'] = 42 doc['errmsg'] = "" elif 'cursor' in reply: if reply['cursor']['id']: reply['cursor']['id'] = 42 elif event.command_name == 'killCursors': # Make the tests continue to pass when the killCursors # command is actually in use. if 'cursorsKilled' in reply: reply.pop('cursorsKilled') reply['cursorsUnknown'] = [42] # Found succeeded event. Pop related started event. res['started'].pop(0) elif event_type == "command_failed_event": event = res['failed'].pop(0) if len(res['failed']) else None if event is not None: # Found failed event. Pop related started event. res['started'].pop(0) else: self.fail("Unknown event type") if event is None: event_name = event_type.split('_')[1] self.fail( "Expected %s event for %s command. Actual " "results:%s" % ( event_name, expectation[event_type]['command_name'], format_actual_results(res))) for attr, expected in expectation[event_type].items(): if 'options' in expected: options = expected.pop('options') expected.update(options) actual = getattr(event, attr) if isinstance(expected, dict): for key, val in expected.items(): self.assertEqual(val, actual[key]) else: self.assertEqual(actual, expected)