Update the test matrix to reflect what's supported in 2019 (#2066)

Previously, we were running the test suite for several combinations of MongoDB,
Python, and PyMongo:
- PyPy, MongoDB v2.6, PyMongo v3.x (which really means v3.6.1 at the moment)
- Python v2.7, MongoDB v2.6, PyMongo v3.x
- Python v3.5, MongoDB v2.6, PyMongo v3.x
- Python v3.6, MongoDB v2.6, PyMongo v3.x
- Python v2.7, MongoDB v3.0, PyMongo v3.5.0
- Python v3.6, MongoDB v3.0, PyMongo v3.5.0
- Python v3.5, MongoDB v3.2, PyMongo v3.x
- Python v3.6, MongoDB v3.2, PyMongo v3.x
- Python v3.6, MongoDB v3.4, PyMongo v3.x
- Python v3.6, MongoDB v3.6, PyMongo v3.x

There were a couple issues with this setup:
1. MongoDB v2.6 – v3.2 have reached their End of Life already (v2.6 almost 3
   years ago!). See the "MongoDB Server" section on
   https://www.mongodb.com/support-policy.
2. We were only testing two recent-ish PyMongo versions (v3.5.0 & v3.6.1).
   We were not testing the oldest actively supported MongoDB/PyMongo/Python
   setup.

This PR updates the test matrix so that these problems are solved. For the
sake of simplicity, it does not yet attempt to cover MongoDB v4.0:
- PyPy, MongoDB v3.4, PyMongo v3.x (aka v3.6.1 at the moment)
- Python v2.7, MongoDB v3.4, PyMongo v3.x
- Python v3.5, MongoDB v3.4, PyMongo v3.x
- Python v3.6, MongoDB v3.4, PyMongo v3.x
- Python v2.7, MongoDB v3.4, PyMongo v3.4
- Python v3.6, MongoDB v3.6, PyMongo v3.x
This commit is contained in:
Stefan Wójcik
2019-05-31 11:01:15 +02:00
committed by GitHub
parent da3773bfe8
commit 4334955e39
14 changed files with 105 additions and 161 deletions

View File

@@ -6,7 +6,6 @@ from mongoengine.pymongo_support import list_collection_names
from mongoengine.queryset import NULLIFY, PULL
from mongoengine.connection import get_db
from tests.utils import requires_mongodb_gte_26
__all__ = ("ClassMethodsTest", )
@@ -187,7 +186,6 @@ class ClassMethodsTest(unittest.TestCase):
self.assertEqual(BlogPostWithTags.compare_indexes(), {'missing': [], 'extra': []})
self.assertEqual(BlogPostWithCustomField.compare_indexes(), {'missing': [], 'extra': []})
@requires_mongodb_gte_26
def test_compare_indexes_for_text_indexes(self):
""" Ensure that compare_indexes behaves correctly for text indexes """

View File

@@ -9,8 +9,7 @@ from six import iteritems
from mongoengine import *
from mongoengine.connection import get_db
from mongoengine.mongodb_support import get_mongodb_version, MONGODB_32, MONGODB_3
from tests.utils import requires_mongodb_gte_26, requires_mongodb_lte_32, requires_mongodb_gte_34
from mongoengine.mongodb_support import get_mongodb_version
__all__ = ("IndexesTest", )
@@ -478,8 +477,6 @@ class IndexesTest(unittest.TestCase):
def test_covered_index(self):
"""Ensure that covered indexes can be used
"""
IS_MONGODB_3 = get_mongodb_version() >= MONGODB_3
class Test(Document):
a = IntField()
b = IntField()
@@ -497,33 +494,38 @@ class IndexesTest(unittest.TestCase):
# Need to be explicit about covered indexes as mongoDB doesn't know if
# the documents returned might have more keys in that here.
query_plan = Test.objects(id=obj.id).exclude('a').explain()
if not IS_MONGODB_3:
self.assertFalse(query_plan['indexOnly'])
else:
self.assertEqual(query_plan.get('queryPlanner').get('winningPlan').get('inputStage').get('stage'), 'IDHACK')
self.assertEqual(
query_plan.get('queryPlanner').get('winningPlan').get('inputStage').get('stage'),
'IDHACK'
)
query_plan = Test.objects(id=obj.id).only('id').explain()
if not IS_MONGODB_3:
self.assertTrue(query_plan['indexOnly'])
else:
self.assertEqual(query_plan.get('queryPlanner').get('winningPlan').get('inputStage').get('stage'), 'IDHACK')
self.assertEqual(
query_plan.get('queryPlanner').get('winningPlan').get('inputStage').get('stage'),
'IDHACK'
)
query_plan = Test.objects(a=1).only('a').exclude('id').explain()
if not IS_MONGODB_3:
self.assertTrue(query_plan['indexOnly'])
else:
self.assertEqual(query_plan.get('queryPlanner').get('winningPlan').get('inputStage').get('stage'), 'IXSCAN')
self.assertEqual(query_plan.get('queryPlanner').get('winningPlan').get('stage'), 'PROJECTION')
self.assertEqual(
query_plan.get('queryPlanner').get('winningPlan').get('inputStage').get('stage'),
'IXSCAN'
)
self.assertEqual(
query_plan.get('queryPlanner').get('winningPlan').get('stage'),
'PROJECTION'
)
query_plan = Test.objects(a=1).explain()
if not IS_MONGODB_3:
self.assertFalse(query_plan['indexOnly'])
else:
self.assertEqual(query_plan.get('queryPlanner').get('winningPlan').get('inputStage').get('stage'), 'IXSCAN')
self.assertEqual(query_plan.get('queryPlanner').get('winningPlan').get('stage'), 'FETCH')
self.assertEqual(
query_plan.get('queryPlanner').get('winningPlan').get('inputStage').get('stage'),
'IXSCAN'
)
self.assertEqual(
query_plan.get('queryPlanner').get('winningPlan').get('stage'),
'FETCH'
)
def test_index_on_id(self):
class BlogPost(Document):
meta = {
'indexes': [
@@ -565,13 +567,10 @@ class IndexesTest(unittest.TestCase):
self.assertEqual(BlogPost.objects.count(), 10)
self.assertEqual(BlogPost.objects.hint().count(), 10)
if MONGO_VER >= MONGODB_32:
# Mongo32 throws an error if an index exists (i.e `tags` in our case)
# and you use hint on an index name that does not exist
with self.assertRaises(OperationFailure):
BlogPost.objects.hint([('ZZ', 1)]).count()
else:
self.assertEqual(BlogPost.objects.hint([('ZZ', 1)]).count(), 10)
# MongoDB v3.2+ throws an error if an index exists (i.e `tags` in our
# case) and you use hint on an index name that does not exist.
with self.assertRaises(OperationFailure):
BlogPost.objects.hint([('ZZ', 1)]).count()
self.assertEqual(BlogPost.objects.hint(TAGS_INDEX_NAME).count(), 10)
@@ -598,9 +597,8 @@ class IndexesTest(unittest.TestCase):
# Ensure backwards compatibility for errors
self.assertRaises(OperationError, post2.save)
@requires_mongodb_gte_34
def test_primary_key_unique_not_working_under_mongo_34(self):
"""Relates to #1445"""
def test_primary_key_unique_not_working(self):
"""Relates to #1445"""
class Blog(Document):
id = StringField(primary_key=True, unique=True)
@@ -608,21 +606,17 @@ class IndexesTest(unittest.TestCase):
with self.assertRaises(OperationFailure) as ctx_err:
Blog(id='garbage').save()
try:
self.assertIn("The field 'unique' is not valid for an _id index specification", str(ctx_err.exception))
except AssertionError:
# error is slightly different on python 3.6
self.assertIn("The field 'background' is not valid for an _id index specification", str(ctx_err.exception))
@requires_mongodb_lte_32
def test_primary_key_unique_working_under_mongo_32(self):
"""Relates to #1445"""
class Blog(Document):
id = StringField(primary_key=True, unique=True)
Blog.drop_collection()
Blog(id='garbage').save()
# One of the errors below should happen. Which one depends on the
# PyMongo version and dict order.
err_msg = str(ctx_err.exception)
self.assertTrue(
any([
"The field 'unique' is not valid for an _id index specification" in err_msg,
"The field 'background' is not valid for an _id index specification" in err_msg,
"The field 'sparse' is not valid for an _id index specification" in err_msg,
])
)
def test_unique_with(self):
"""Ensure that unique_with constraints are applied to fields.
@@ -984,7 +978,6 @@ class IndexesTest(unittest.TestCase):
info['provider_ids.foo_1_provider_ids.bar_1']['key'])
self.assertTrue(info['provider_ids.foo_1_provider_ids.bar_1']['sparse'])
@requires_mongodb_gte_26
def test_text_indexes(self):
class Book(Document):
title = DictField()

View File

@@ -28,8 +28,6 @@ from mongoengine.queryset import NULLIFY, Q
from mongoengine.context_managers import switch_db, query_counter
from mongoengine import signals
from tests.utils import requires_mongodb_gte_26
TEST_IMAGE_PATH = os.path.join(os.path.dirname(__file__),
'../fields/mongoengine.png')
@@ -850,7 +848,6 @@ class InstanceTest(MongoDBTestCase):
self.assertDbEqual([dict(other_doc.to_mongo()), dict(doc.to_mongo())])
@requires_mongodb_gte_26
def test_modify_with_positional_push(self):
class Content(EmbeddedDocument):
keywords = ListField(StringField())
@@ -3368,7 +3365,6 @@ class InstanceTest(MongoDBTestCase):
person.update(set__height=2.0)
@requires_mongodb_gte_26
def test_push_with_position(self):
"""Ensure that push with position works properly for an instance."""
class BlogPost(Document):

View File

@@ -3,7 +3,7 @@ import unittest
from mongoengine import *
from tests.utils import MongoDBTestCase, requires_mongodb_gte_3
from tests.utils import MongoDBTestCase
__all__ = ("GeoQueriesTest",)
@@ -70,9 +70,6 @@ class GeoQueriesTest(MongoDBTestCase):
self.assertEqual(events.count(), 1)
self.assertEqual(events[0], event2)
# $minDistance was added in MongoDB v2.6, but continued being buggy
# until v3.0; skip for older versions
@requires_mongodb_gte_3
def test_near_and_min_distance(self):
"""Ensure the "min_distance" operator works alongside the "near"
operator.
@@ -243,9 +240,6 @@ class GeoQueriesTest(MongoDBTestCase):
events = self.Event.objects(location__geo_within_polygon=polygon2)
self.assertEqual(events.count(), 0)
# $minDistance was added in MongoDB v2.6, but continued being buggy
# until v3.0; skip for older versions
@requires_mongodb_gte_3
def test_2dsphere_near_and_min_max_distance(self):
"""Ensure "min_distace" and "max_distance" operators work well
together with the "near" operator in a 2dsphere index.
@@ -328,8 +322,6 @@ class GeoQueriesTest(MongoDBTestCase):
"""Make sure PointField works properly in an embedded document."""
self._test_embedded(point_field_class=PointField)
# Needs MongoDB > 2.6.4 https://jira.mongodb.org/browse/SERVER-14039
@requires_mongodb_gte_3
def test_spherical_geospatial_operators(self):
"""Ensure that spherical geospatial queries are working."""
class Point(Document):

View File

@@ -2,8 +2,6 @@ import unittest
from mongoengine import connect, Document, IntField, StringField, ListField
from tests.utils import requires_mongodb_gte_26
__all__ = ("FindAndModifyTest",)
@@ -96,7 +94,6 @@ class FindAndModifyTest(unittest.TestCase):
self.assertEqual(old_doc.to_mongo(), {"_id": 1})
self.assertDbEqual([{"_id": 0, "value": 0}, {"_id": 1, "value": -1}])
@requires_mongodb_gte_26
def test_modify_with_push(self):
class BlogPost(Document):
tags = ListField(StringField())

View File

@@ -17,10 +17,9 @@ from mongoengine import *
from mongoengine.connection import get_connection, get_db
from mongoengine.context_managers import query_counter, switch_db
from mongoengine.errors import InvalidQueryError
from mongoengine.mongodb_support import get_mongodb_version, MONGODB_32, MONGODB_36
from mongoengine.mongodb_support import get_mongodb_version, MONGODB_36
from mongoengine.queryset import (DoesNotExist, MultipleObjectsReturned,
QuerySet, QuerySetManager, queryset_manager)
from tests.utils import requires_mongodb_gte_26
class db_ops_tracker(query_counter):
@@ -32,7 +31,7 @@ class db_ops_tracker(query_counter):
def get_key_compat(mongo_ver):
ORDER_BY_KEY = 'sort' if mongo_ver >= MONGODB_32 else '$orderby'
ORDER_BY_KEY = 'sort'
CMD_QUERY_KEY = 'command' if mongo_ver >= MONGODB_36 else 'query'
return ORDER_BY_KEY, CMD_QUERY_KEY
@@ -598,7 +597,6 @@ class QuerySetTest(unittest.TestCase):
self.assertEqual(post.comments[0].by, 'joe')
self.assertEqual(post.comments[0].votes.score, 4)
@requires_mongodb_gte_26
def test_update_min_max(self):
class Scores(Document):
high_score = IntField()
@@ -616,7 +614,6 @@ class QuerySetTest(unittest.TestCase):
Scores.objects(id=scores.id).update(max__high_score=500)
self.assertEqual(Scores.objects.get(id=scores.id).high_score, 1000)
@requires_mongodb_gte_26
def test_update_multiple(self):
class Product(Document):
item = StringField()
@@ -868,11 +865,7 @@ class QuerySetTest(unittest.TestCase):
with query_counter() as q:
self.assertEqual(q, 0)
Blog.objects.insert(blogs, load_bulk=False)
if MONGO_VER >= MONGODB_32:
self.assertEqual(q, 1) # 1 entry containing the list of inserts
else:
self.assertEqual(q, len(blogs)) # 1 entry per doc inserted
self.assertEqual(q, 1) # 1 entry containing the list of inserts
self.assertEqual(Blog.objects.count(), len(blogs))
@@ -885,11 +878,7 @@ class QuerySetTest(unittest.TestCase):
with query_counter() as q:
self.assertEqual(q, 0)
Blog.objects.insert(blogs)
if MONGO_VER >= MONGODB_32:
self.assertEqual(q, 2) # 1 for insert 1 for fetch
else:
self.assertEqual(q, len(blogs)+1) # + 1 to fetch all docs
self.assertEqual(q, 2) # 1 for insert 1 for fetch
Blog.drop_collection()
@@ -2030,7 +2019,6 @@ class QuerySetTest(unittest.TestCase):
pymongo_doc = BlogPost.objects.as_pymongo().first()
self.assertNotIn('title', pymongo_doc)
@requires_mongodb_gte_26
def test_update_push_with_position(self):
"""Ensure that the 'push' update with position works properly.
"""
@@ -2555,8 +2543,8 @@ class QuerySetTest(unittest.TestCase):
"""Make sure adding a comment to the query gets added to the query"""
MONGO_VER = self.mongodb_version
_, CMD_QUERY_KEY = get_key_compat(MONGO_VER)
QUERY_KEY = 'filter' if MONGO_VER >= MONGODB_32 else '$query'
COMMENT_KEY = 'comment' if MONGO_VER >= MONGODB_32 else '$comment'
QUERY_KEY = 'filter'
COMMENT_KEY = 'comment'
class User(Document):
age = IntField()
@@ -3370,7 +3358,6 @@ class QuerySetTest(unittest.TestCase):
self.assertEqual(Foo.objects.distinct("bar"), [bar])
@requires_mongodb_gte_26
def test_text_indexes(self):
class News(Document):
title = StringField()
@@ -3454,7 +3441,6 @@ class QuerySetTest(unittest.TestCase):
'brasil').order_by('$text_score').first()
self.assertEqual(item.get_text_score(), max_text_score)
@requires_mongodb_gte_26
def test_distinct_handles_references_to_alias(self):
register_connection('testdb', 'mongoenginetest2')
@@ -4586,7 +4572,6 @@ class QuerySetTest(unittest.TestCase):
self.assertEqual(bars._cursor._Cursor__read_preference,
ReadPreference.SECONDARY_PREFERRED)
@requires_mongodb_gte_26
def test_read_preference_aggregation_framework(self):
class Bar(Document):
txt = StringField()
@@ -5354,7 +5339,6 @@ class QuerySetTest(unittest.TestCase):
self.assertTrue(Person.objects._has_data(),
'Cursor has data and returned False')
@requires_mongodb_gte_26
def test_queryset_aggregation_framework(self):
class Person(Document):
name = StringField()
@@ -5396,7 +5380,6 @@ class QuerySetTest(unittest.TestCase):
{'_id': None, 'avg': 29, 'total': 2}
])
@requires_mongodb_gte_26
def test_queryset_aggregation_with_skip(self):
class Person(Document):
name = StringField()
@@ -5418,7 +5401,6 @@ class QuerySetTest(unittest.TestCase):
{'_id': p3.pk, 'name': "SANDRA MARA"}
])
@requires_mongodb_gte_26
def test_queryset_aggregation_with_limit(self):
class Person(Document):
name = StringField()
@@ -5439,7 +5421,6 @@ class QuerySetTest(unittest.TestCase):
{'_id': p1.pk, 'name': "ISABELLA LUANNA"}
])
@requires_mongodb_gte_26
def test_queryset_aggregation_with_sort(self):
class Person(Document):
name = StringField()
@@ -5462,7 +5443,6 @@ class QuerySetTest(unittest.TestCase):
{'_id': p2.pk, 'name': "WILSON JUNIOR"}
])
@requires_mongodb_gte_26
def test_queryset_aggregation_with_skip_with_limit(self):
class Person(Document):
name = StringField()
@@ -5492,7 +5472,6 @@ class QuerySetTest(unittest.TestCase):
self.assertEqual(data, list(data2))
@requires_mongodb_gte_26
def test_queryset_aggregation_with_sort_with_limit(self):
class Person(Document):
name = StringField()
@@ -5534,7 +5513,6 @@ class QuerySetTest(unittest.TestCase):
{'_id': p3.pk, 'name': "SANDRA MARA"},
])
@requires_mongodb_gte_26
def test_queryset_aggregation_with_sort_with_skip(self):
class Person(Document):
name = StringField()
@@ -5555,7 +5533,6 @@ class QuerySetTest(unittest.TestCase):
{'_id': p2.pk, 'name': "WILSON JUNIOR"}
])
@requires_mongodb_gte_26
def test_queryset_aggregation_with_sort_with_skip_with_limit(self):
class Person(Document):
name = StringField()

View File

@@ -5,7 +5,7 @@ from nose.plugins.skip import SkipTest
from mongoengine import connect
from mongoengine.connection import get_db, disconnect_all
from mongoengine.mongodb_support import get_mongodb_version, MONGODB_26, MONGODB_3, MONGODB_32, MONGODB_34
from mongoengine.mongodb_support import get_mongodb_version
MONGO_TEST_DB = 'mongoenginetest' # standard name for the test database
@@ -35,8 +35,20 @@ def get_as_pymongo(doc):
def _decorated_with_ver_requirement(func, mongo_version_req, oper):
"""Return a given function decorated with the version requirement
for a particular MongoDB version tuple.
"""Return a MongoDB version requirement decorator.
The resulting decorator will raise a SkipTest exception if the current
MongoDB version doesn't match the provided version/operator.
For example, if you define a decorator like so:
def requires_mongodb_gte_36(func):
return _decorated_with_ver_requirement(
func, (3.6), oper=operator.ge
)
Then tests decorated with @requires_mongodb_gte_36 will be skipped if
ran against MongoDB < v3.6.
:param mongo_version_req: The mongodb version requirement (tuple(int, int))
:param oper: The operator to apply (e.g: operator.ge)
@@ -51,31 +63,3 @@ def _decorated_with_ver_requirement(func, mongo_version_req, oper):
_inner.__name__ = func.__name__
_inner.__doc__ = func.__doc__
return _inner
def requires_mongodb_gte_34(func):
"""Raise a SkipTest exception if we're working with MongoDB version
lower than v3.4
"""
return _decorated_with_ver_requirement(func, MONGODB_34, oper=operator.ge)
def requires_mongodb_lte_32(func):
"""Raise a SkipTest exception if we're working with MongoDB version
greater than v3.2.
"""
return _decorated_with_ver_requirement(func, MONGODB_32, oper=operator.le)
def requires_mongodb_gte_26(func):
"""Raise a SkipTest exception if we're working with MongoDB version
lower than v2.6.
"""
return _decorated_with_ver_requirement(func, MONGODB_26, oper=operator.ge)
def requires_mongodb_gte_3(func):
"""Raise a SkipTest exception if we're working with MongoDB version
lower than v3.0.
"""
return _decorated_with_ver_requirement(func, MONGODB_3, oper=operator.ge)