Skip to content

Commit 1ab61ca

Browse files
committed
add valid-fail testing
1 parent d86b72a commit 1ab61ca

File tree

3 files changed

+84
-35
lines changed

3 files changed

+84
-35
lines changed

test/test_unified_format.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,26 @@
2626
_TEST_PATH = os.path.join(
2727
os.path.dirname(os.path.realpath(__file__)), 'unified-test-format')
2828

29+
2930
globals().update(generate_test_classes(
3031
os.path.join(_TEST_PATH, 'valid-pass'),
3132
module=__name__,
32-
class_name_prefix='UnifiedTestFormat'))
33+
class_name_prefix='UnifiedTestFormat',
34+
expected_failures=[
35+
'Client side error in command starting transaction', # PYTHON-1894
36+
'InsertOne fails after multiple retryable writeConcernErrors' # PYTHON-2452
37+
]))
38+
39+
40+
globals().update(generate_test_classes(
41+
os.path.join(_TEST_PATH, 'valid-fail'),
42+
module=__name__,
43+
class_name_prefix='UnifiedTestFormat',
44+
expected_failures=[
45+
'foo',
46+
'FindOneAndReplace returnDocument invalid enum value',
47+
'FindOneAndUpdate returnDocument invalid enum value'
48+
]))
3349

3450

3551
class TestMatchEvaluatorUtil(unittest.TestCase):

test/unified_format.py

Lines changed: 65 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import functools
2323
import os
2424
import sys
25+
import traceback
2526
import types
2627

2728
from bson import json_util, SON, Int64
@@ -168,7 +169,11 @@ def __init__(self, test_class):
168169
self._test_class = test_class
169170

170171
def __getitem__(self, item):
171-
return self._entities[item]
172+
try:
173+
return self._entities[item]
174+
except KeyError:
175+
self._test_class.fail('Could not find entity named %s in map' % (
176+
item,))
172177

173178
def __setitem__(self, key, value):
174179
if not isinstance(key, text_type):
@@ -193,9 +198,9 @@ def _create_entity(self, entity_spec):
193198
# - uriOptions
194199
# - useMultipleMongoses
195200
uri_options = spec.get('uriOptions', {})
196-
observe_events = spec.get('observeEvents')
201+
observe_events = spec.get('observeEvents', [])
197202
ignore_commands = spec.get('ignoreCommandMonitoringEvents', [])
198-
if observe_events:
203+
if len(observe_events) or len(ignore_commands):
199204
listener = EventListenerUtil(observe_events, ignore_commands)
200205
client = rs_or_single_client(
201206
event_listeners=[listener], **uri_options)
@@ -234,7 +239,7 @@ def _create_entity(self, entity_spec):
234239
self._test_class.fail(
235240
'Expected entity %s to be of type MongoClient, got %s' % (
236241
spec['client'], type(client)))
237-
opts = camel_to_snake_args(spec['sessionOptions'])
242+
opts = camel_to_snake_args(spec.get('sessionOptions', {}))
238243
if 'default_transaction_options' in opts:
239244
txn_opts = parse_spec_options(
240245
opts['default_transaction_options'])
@@ -477,7 +482,7 @@ def match_event(self, expectation, actual):
477482
'Unsupported event type %s' % (event_type,))
478483

479484

480-
class UnifiedSpecTestMixin(IntegrationTest):
485+
class UnifiedSpecTestMixinV1(IntegrationTest):
481486
"""Mixin class to run test cases from test specification files.
482487
483488
Assumes that tests conform to the `unified test format
@@ -486,7 +491,7 @@ class UnifiedSpecTestMixin(IntegrationTest):
486491
Specification of the test suite being currently run is available as
487492
a class attribute ``TEST_SPEC``.
488493
"""
489-
SCHEMA_VERSION = '1.0'
494+
SCHEMA_VERSION = Version.from_string('1.0')
490495

491496
@staticmethod
492497
def should_run_on(run_on_spec):
@@ -510,19 +515,17 @@ def insert_initial_data(self, initial_data):
510515
coll.drop()
511516

512517
# documents MAY be an empty list
513-
if documents:
518+
if len(documents):
514519
coll.insert_many(documents)
515520

516521
@classmethod
517522
def setUpClass(cls):
518523
# super call creates internal client cls.client
519-
super(UnifiedSpecTestMixin, cls).setUpClass()
524+
super(UnifiedSpecTestMixinV1, cls).setUpClass()
520525

521526
# process schemaVersion
522-
version = cls.TEST_SPEC['schemaVersion']
523-
version_tuple = tuple(version.split('.', 2)[:2])
524-
max_version_tuple = tuple(cls.SCHEMA_VERSION.split('.', 2)[:2])
525-
if not version_tuple <= max_version_tuple:
527+
version = Version.from_string(cls.TEST_SPEC['schemaVersion'])
528+
if not version <= cls.SCHEMA_VERSION:
526529
raise unittest.SkipTest(
527530
'expected schemaVersion %s or lower, got %s' % (
528531
cls.SCHEMA_VERSION, version))
@@ -534,19 +537,11 @@ def setUpClass(cls):
534537

535538
@classmethod
536539
def tearDownClass(cls):
537-
super(UnifiedSpecTestMixin, cls).tearDownClass()
540+
super(UnifiedSpecTestMixinV1, cls).tearDownClass()
538541
cls.client.close()
539542

540543
def setUp(self):
541-
super(UnifiedSpecTestMixin, self).setUp()
542-
543-
# process createEntities
544-
self.entity_map = EntityMapUtil(self)
545-
self.entity_map.create_entities_from_spec(
546-
self.TEST_SPEC.get('createEntities', []))
547-
548-
# process initialData
549-
self.insert_initial_data(self.TEST_SPEC.get('initialData', []))
544+
super(UnifiedSpecTestMixinV1, self).setUp()
550545

551546
# initialize internals
552547
self.match_evaluator = MatchEvaluatorUtil(self)
@@ -855,6 +850,14 @@ def verify_outcome(self, spec):
855850
actual_documents)
856851

857852
def run_scenario(self, spec):
853+
# process createEntities
854+
self.entity_map = EntityMapUtil(self)
855+
self.entity_map.create_entities_from_spec(
856+
self.TEST_SPEC.get('createEntities', []))
857+
858+
# process initialData
859+
self.insert_initial_data(self.TEST_SPEC.get('initialData', []))
860+
858861
# process test-level runOnRequirements
859862
run_on_spec = spec.get('runOnRequirements', [])
860863
if not self.should_run_on(run_on_spec):
@@ -887,14 +890,29 @@ def test_case(self):
887890

888891
for test_spec in cls.TEST_SPEC['tests']:
889892
description = test_spec['description']
890-
test_name = 'test_%s' % (
891-
description.strip('. ').replace(' ', '_').replace('.', '_'),)
893+
test_name = 'test_%s' % (description.strip('. ').
894+
replace(' ', '_').replace('.', '_'),)
892895
test_method = create_test(copy.deepcopy(test_spec))
893896
test_method.__name__ = test_name
897+
898+
if description in cls.EXPECTED_FAILURES:
899+
test_method = unittest.expectedFailure(test_method)
900+
894901
setattr(cls, test_name, test_method)
895902

896903

897-
def generate_test_classes(test_path, module=__name__, class_name_prefix=''):
904+
_ALL_MIXIN_CLASSES = [
905+
UnifiedSpecTestMixinV1,
906+
# add mixin classes for new schema major versions here
907+
]
908+
909+
910+
_SCHEMA_VERSION_MAJOR_TO_MIXIN_CLASS = {
911+
KLASS.SCHEMA_VERSION[0]: KLASS for KLASS in _ALL_MIXIN_CLASSES}
912+
913+
914+
def generate_test_classes(test_path, module=__name__, class_name_prefix='',
915+
expected_failures=[]):
898916
"""Method for generating test classes. Returns a dictionary where keys are
899917
the names of test classes and values are the test class objects."""
900918
test_klasses = {}
@@ -905,29 +923,43 @@ def test_base_class_factory(test_spec):
905923
the metaclass __init__ is invoked."""
906924
class SpecTestBase(with_metaclass(UnifiedSpecTestMeta)):
907925
TEST_SPEC = test_spec
926+
EXPECTED_FAILURES = expected_failures
908927
return SpecTestBase
909928

910929
for dirpath, _, filenames in os.walk(test_path):
911930
dirname = os.path.split(dirpath)[-1]
912931

913932
for filename in filenames:
914-
with open(os.path.join(dirpath, filename)) as scenario_stream:
933+
fpath = os.path.join(dirpath, filename)
934+
with open(fpath) as scenario_stream:
915935
# Use tz_aware=False to match how CodecOptions decodes
916936
# dates.
917937
opts = json_util.JSONOptions(tz_aware=False)
918-
scenario_def = ScenarioDict(
919-
json_util.loads(scenario_stream.read(),
920-
json_options=opts))
938+
scenario_def = json_util.loads(
939+
scenario_stream.read(), json_options=opts)
921940

922941
test_type = os.path.splitext(filename)[0]
923942
snake_class_name = 'Test%s_%s_%s' % (
924943
class_name_prefix, dirname.replace('-', '_'),
925944
test_type.replace('-', '_').replace('.', '_'))
926945
class_name = snake_to_camel(snake_class_name)
927946

928-
test_klasses[class_name] = type(
929-
class_name,
930-
(UnifiedSpecTestMixin, test_base_class_factory(scenario_def),),
931-
{'__module__': module})
947+
try:
948+
schema_version = Version.from_string(
949+
scenario_def['schemaVersion'])
950+
mixin_class = _SCHEMA_VERSION_MAJOR_TO_MIXIN_CLASS.get(
951+
schema_version[0])
952+
if mixin_class is None:
953+
print('Ignoring test file %s with '
954+
'unsupported schemaVersion %s' %
955+
(fpath, schema_version))
956+
test_klasses[class_name] = type(
957+
class_name,
958+
(mixin_class, test_base_class_factory(scenario_def),),
959+
{'__module__': module})
960+
except (AttributeError, KeyError, TypeError):
961+
print("Ignoring invalid test file '%s'\n"
962+
"Original exception: %s" %
963+
(fpath, traceback.format_exc()))
932964

933965
return test_klasses

test/utils.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737

3838
from pymongo import (MongoClient,
3939
monitoring, operations, read_preferences)
40+
from pymongo.collection import ReturnDocument
4041
from pymongo.errors import ConfigurationError, OperationFailure
4142
from pymongo.monitoring import _SENSITIVE_COMMANDS, ConnectionPoolListener
4243
from pymongo.pool import (_CancellationContext,
@@ -1020,7 +1021,7 @@ def prepare_spec_arguments(spec, arguments, opname, entity_map,
10201021
continue
10211022
# Requires boolean returnDocument.
10221023
elif arg_name == "returnDocument":
1023-
arguments[c2s] = arguments.pop(arg_name) == "After"
1024+
arguments[c2s] = getattr(ReturnDocument, arguments.pop(arg_name).upper())
10241025
elif c2s == "requests":
10251026
# Parse each request into a bulk write model.
10261027
requests = []

0 commit comments

Comments
 (0)