Skip to content

Commit 20baefa

Browse files
committed
PYTHON-1180 - Easier debugging of command monitoring tests
1 parent 91f2f97 commit 20baefa

File tree

1 file changed

+126
-91
lines changed

1 file changed

+126
-91
lines changed

test/test_command_monitoring_spec.py

Lines changed: 126 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -62,93 +62,91 @@ def tearDown(self):
6262
self.listener.results.clear()
6363

6464

65-
def create_test(scenario_def):
65+
def format_actual_results(results):
66+
started = results['started']
67+
succeeded = results['succeeded']
68+
failed = results['failed']
69+
msg = "\nStarted: %r" % (started[0].command if len(started) else None,)
70+
msg += "\nSucceeded: %r" % (succeeded[0].reply if len(succeeded) else None,)
71+
msg += "\nFailed: %r" % (failed[0].failure if len(failed) else None,)
72+
return msg
73+
74+
75+
def create_test(scenario_def, test):
6676
def run_scenario(self):
67-
self.assertTrue(scenario_def['tests'], "tests cannot be empty")
6877
dbname = scenario_def['database_name']
6978
collname = scenario_def['collection_name']
7079

71-
for test in scenario_def['tests']:
72-
ver = client_context.version[:2]
73-
if "ignore_if_server_version_greater_than" in test:
74-
version = test["ignore_if_server_version_greater_than"]
75-
if ver > tuple(map(int, version.split("."))):
76-
continue
77-
if "ignore_if_server_version_less_than" in test:
78-
version = test["ignore_if_server_version_less_than"]
79-
if ver < tuple(map(int, version.split("."))):
80-
continue
81-
if "ignore_if_topology_type" in test:
82-
types = set(test["ignore_if_topology_type"])
83-
if "sharded" in types and client_context.is_mongos:
84-
continue
85-
86-
coll = self.client[dbname][collname]
87-
coll.drop()
88-
coll.insert_many(scenario_def['data'])
89-
self.listener.results.clear()
90-
name = camel_to_snake(test['operation']['name'])
91-
# Don't send $readPreference to mongos before 2.4.
92-
if (client_context.version.at_least(2, 4, 0)
93-
and 'read_preference' in test['operation']):
94-
mode = read_pref_mode_from_name(
95-
test['operation']['read_preference']['mode'])
96-
coll = coll.with_options(
97-
read_preference=make_read_preference(mode, None))
98-
99-
test_args = test['operation']['arguments']
100-
if 'writeConcern' in test_args:
101-
concern = test_args.pop('writeConcern')
102-
coll = coll.with_options(
103-
write_concern=WriteConcern(**concern))
104-
args = {}
105-
for arg in test_args:
106-
args[camel_to_snake(arg)] = test_args[arg]
107-
108-
if name == 'bulk_write':
109-
bulk_args = []
110-
for request in args['requests']:
111-
opname = next(iter(request))
112-
klass = opname[0:1].upper() + opname[1:]
113-
arg = getattr(pymongo, klass)(**request[opname])
114-
bulk_args.append(arg)
115-
try:
116-
coll.bulk_write(bulk_args, args.get('ordered', True))
117-
except OperationFailure:
118-
pass
119-
elif name == 'find':
120-
if 'sort' in args:
121-
args['sort'] = list(args['sort'].items())
122-
try:
123-
# Iterate the cursor.
124-
tuple(coll.find(**args))
125-
except OperationFailure:
126-
pass
127-
# Wait for the killCursors thread to run if necessary.
128-
if 'limit' in args and client_context.version[:2] < (3, 1):
129-
self.client._kill_cursors_executor.wake()
130-
started = self.listener.results['started']
131-
wait_until(
132-
lambda: started[-1].command_name == 'killCursors',
133-
"publish a start event for killCursors.")
134-
else:
135-
try:
136-
getattr(coll, name)(**args)
137-
except OperationFailure:
138-
pass
139-
140-
for expectation in test['expectations']:
141-
event_type = next(iter(expectation))
142-
if event_type == "command_started_event":
143-
event = self.listener.results['started'].pop(0)
80+
coll = self.client[dbname][collname]
81+
coll.drop()
82+
coll.insert_many(scenario_def['data'])
83+
self.listener.results.clear()
84+
name = camel_to_snake(test['operation']['name'])
85+
# Don't send $readPreference to mongos before 2.4.
86+
if (client_context.version.at_least(2, 4, 0)
87+
and 'read_preference' in test['operation']):
88+
mode = read_pref_mode_from_name(
89+
test['operation']['read_preference']['mode'])
90+
coll = coll.with_options(
91+
read_preference=make_read_preference(mode, None))
92+
93+
test_args = test['operation']['arguments']
94+
if 'writeConcern' in test_args:
95+
concern = test_args.pop('writeConcern')
96+
coll = coll.with_options(
97+
write_concern=WriteConcern(**concern))
98+
args = {}
99+
for arg in test_args:
100+
args[camel_to_snake(arg)] = test_args[arg]
101+
102+
if name == 'bulk_write':
103+
bulk_args = []
104+
for request in args['requests']:
105+
opname = next(iter(request))
106+
klass = opname[0:1].upper() + opname[1:]
107+
arg = getattr(pymongo, klass)(**request[opname])
108+
bulk_args.append(arg)
109+
try:
110+
coll.bulk_write(bulk_args, args.get('ordered', True))
111+
except OperationFailure:
112+
pass
113+
elif name == 'find':
114+
if 'sort' in args:
115+
args['sort'] = list(args['sort'].items())
116+
try:
117+
# Iterate the cursor.
118+
tuple(coll.find(**args))
119+
except OperationFailure:
120+
pass
121+
# Wait for the killCursors thread to run if necessary.
122+
if 'limit' in args and client_context.version[:2] < (3, 1):
123+
self.client._kill_cursors_executor.wake()
124+
started = self.listener.results['started']
125+
wait_until(
126+
lambda: started[-1].command_name == 'killCursors',
127+
"publish a start event for killCursors.")
128+
else:
129+
try:
130+
getattr(coll, name)(**args)
131+
except OperationFailure:
132+
pass
133+
134+
res = self.listener.results
135+
for expectation in test['expectations']:
136+
event_type = next(iter(expectation))
137+
if event_type == "command_started_event":
138+
event = res['started'][0] if len(res['started']) else None
139+
if event is not None:
144140
# The tests substitute 42 for any number other than 0.
145141
if (event.command_name == 'getMore'
146142
and event.command['getMore']):
147143
event.command['getMore'] = 42
148144
elif event.command_name == 'killCursors':
149145
event.command['cursors'] = [42]
150-
elif event_type == "command_succeeded_event":
151-
event = self.listener.results['succeeded'].pop(0)
146+
elif event_type == "command_succeeded_event":
147+
event = (
148+
res['succeeded'].pop(0) if len(res['succeeded']) else None)
149+
if event is not None:
152150
reply = event.reply
153151
# The tests substitute 42 for any number other than 0,
154152
# and "" for any error message.
@@ -165,32 +163,69 @@ def run_scenario(self):
165163
if 'cursorsKilled' in reply:
166164
reply.pop('cursorsKilled')
167165
reply['cursorsUnknown'] = [42]
168-
elif event_type == "command_failed_event":
169-
event = self.listener.results['failed'].pop(0)
166+
# Found succeeded event. Pop related started event.
167+
res['started'].pop(0)
168+
elif event_type == "command_failed_event":
169+
event = res['failed'].pop(0) if len(res['failed']) else None
170+
if event is not None:
171+
# Found failed event. Pop related started event.
172+
res['started'].pop(0)
173+
else:
174+
self.fail("Unknown event type")
175+
176+
if event is None:
177+
event_name = event_type.split('_')[1]
178+
self.fail(
179+
"Expected %s event for %s command. Actual "
180+
"results:%s" % (
181+
event_name,
182+
expectation[event_type]['command_name'],
183+
format_actual_results(res)))
184+
185+
for attr, expected in expectation[event_type].items():
186+
actual = getattr(event, attr)
187+
if isinstance(expected, dict):
188+
for key, val in expected.items():
189+
self.assertEqual(val, actual[key])
170190
else:
171-
self.fail("Unknown event type")
172-
for attr, expected in expectation[event_type].items():
173-
actual = getattr(event, attr)
174-
if isinstance(expected, dict):
175-
for key, val in expected.items():
176-
self.assertEqual(val, actual[key])
177-
else:
178-
self.assertEqual(actual, expected)
191+
self.assertEqual(actual, expected)
179192

180193
return run_scenario
181194

182195

183196
def create_tests():
184197
for dirpath, _, filenames in os.walk(_TEST_PATH):
198+
dirname = os.path.split(dirpath)[-1]
185199
for filename in filenames:
186200
with open(os.path.join(dirpath, filename)) as scenario_stream:
187201
scenario_def = json.load(
188202
scenario_stream, object_hook=object_hook)
203+
assert bool(scenario_def.get('tests')), "tests cannot be empty"
189204
# Construct test from scenario.
190-
new_test = create_test(scenario_def)
191-
test_name = 'test_%s' % (os.path.splitext(filename)[0],)
192-
new_test.__name__ = test_name
193-
setattr(TestAllScenarios, new_test.__name__, new_test)
205+
for test in scenario_def['tests']:
206+
new_test = create_test(scenario_def, test)
207+
if "ignore_if_server_version_greater_than" in test:
208+
version = test["ignore_if_server_version_greater_than"]
209+
ver = tuple(int(elt) for elt in version.split('.'))
210+
new_test = client_context.require_version_max(*ver)(
211+
new_test)
212+
if "ignore_if_server_version_less_than" in test:
213+
version = test["ignore_if_server_version_less_than"]
214+
ver = tuple(int(elt) for elt in version.split('.'))
215+
new_test = client_context.require_version_min(*ver)(
216+
new_test)
217+
if "ignore_if_topology_type" in test:
218+
types = set(test["ignore_if_topology_type"])
219+
if "sharded" in types:
220+
new_test = client_context.require_no_mongos(None)(
221+
new_test)
222+
223+
test_name = 'test_%s.%s.%s' % (
224+
dirname,
225+
os.path.splitext(filename)[0],
226+
str(test['description'].replace(" ", "_")))
227+
new_test.__name__ = test_name
228+
setattr(TestAllScenarios, new_test.__name__, new_test)
194229

195230

196231
create_tests()

0 commit comments

Comments
 (0)