Merge branch 'master' into refactoring_poor_assertions_in_tests

This commit is contained in:
erdenezul 2018-09-07 14:37:28 +08:00 committed by GitHub
commit 8f76e1e344
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 143 additions and 33 deletions

View File

@ -538,7 +538,7 @@ There are a few top level defaults for all indexes that can be set::
:attr:`index_options` (Optional) :attr:`index_options` (Optional)
Set any default index options - see the `full options list <http://docs.mongodb.org/manual/reference/method/db.collection.ensureIndex/#db.collection.ensureIndex>`_ Set any default index options - see the `full options list <https://docs.mongodb.com/manual/reference/method/db.collection.createIndex/#ensureindex-options>`_
:attr:`index_background` (Optional) :attr:`index_background` (Optional)
Set the default value for if an index should be indexed in the background Set the default value for if an index should be indexed in the background

View File

@ -159,51 +159,69 @@ class no_sub_classes(object):
class query_counter(object): class query_counter(object):
"""Query_counter context manager to get the number of queries.""" """Query_counter context manager to get the number of queries.
This works by updating the `profiling_level` of the database so that all queries get logged,
resetting the db.system.profile collection at the beginnig of the context and counting the new entries.
This was designed for debugging purpose. In fact it is a global counter so queries issued by other threads/processes
can interfere with it
Be aware that:
- Iterating over large amount of documents (>101) makes pymongo issue `getmore` queries to fetch the next batch of
documents (https://docs.mongodb.com/manual/tutorial/iterate-a-cursor/#cursor-batches)
- Some queries are ignored by default by the counter (killcursors, db.system.indexes)
"""
def __init__(self): def __init__(self):
"""Construct the query_counter.""" """Construct the query_counter
self.counter = 0 """
self.db = get_db() self.db = get_db()
self.initial_profiling_level = None
self._ctx_query_counter = 0 # number of queries issued by the context
def __enter__(self): self._ignored_query = {
"""On every with block we need to drop the profile collection.""" 'ns':
{'$ne': '%s.system.indexes' % self.db.name},
'op':
{'$ne': 'killcursors'}
}
def _turn_on_profiling(self):
self.initial_profiling_level = self.db.profiling_level()
self.db.set_profiling_level(0) self.db.set_profiling_level(0)
self.db.system.profile.drop() self.db.system.profile.drop()
self.db.set_profiling_level(2) self.db.set_profiling_level(2)
def _resets_profiling(self):
self.db.set_profiling_level(self.initial_profiling_level)
def __enter__(self):
self._turn_on_profiling()
return self return self
def __exit__(self, t, value, traceback): def __exit__(self, t, value, traceback):
"""Reset the profiling level.""" self._resets_profiling()
self.db.set_profiling_level(0)
def __eq__(self, value): def __eq__(self, value):
"""== Compare querycounter."""
counter = self._get_count() counter = self._get_count()
return value == counter return value == counter
def __ne__(self, value): def __ne__(self, value):
"""!= Compare querycounter."""
return not self.__eq__(value) return not self.__eq__(value)
def __lt__(self, value): def __lt__(self, value):
"""< Compare querycounter."""
return self._get_count() < value return self._get_count() < value
def __le__(self, value): def __le__(self, value):
"""<= Compare querycounter."""
return self._get_count() <= value return self._get_count() <= value
def __gt__(self, value): def __gt__(self, value):
"""> Compare querycounter."""
return self._get_count() > value return self._get_count() > value
def __ge__(self, value): def __ge__(self, value):
""">= Compare querycounter."""
return self._get_count() >= value return self._get_count() >= value
def __int__(self): def __int__(self):
"""int representation."""
return self._get_count() return self._get_count()
def __repr__(self): def __repr__(self):
@ -211,10 +229,12 @@ class query_counter(object):
return u"%s" % self._get_count() return u"%s" % self._get_count()
def _get_count(self): def _get_count(self):
"""Get the number of queries.""" """Get the number of queries by counting the current number of entries in db.system.profile
ignore_query = {'ns': {'$ne': '%s.system.indexes' % self.db.name}} and substracting the queries issued by this context. In fact everytime this is called, 1 query is
count = self.db.system.profile.find(ignore_query).count() - self.counter issued so we need to balance that
self.counter += 1 # Account for the query we just fired """
count = self.db.system.profile.find(self._ignored_query).count() - self._ctx_query_counter
self._ctx_query_counter += 1 # Account for the query we just issued to gather the information
return count return count

View File

@ -4749,6 +4749,7 @@ class QuerySetTest(unittest.TestCase):
Person(name="No: %s" % i).save() Person(name="No: %s" % i).save()
with query_counter() as q: with query_counter() as q:
try:
self.assertEqual(q, 0) self.assertEqual(q, 0)
people = Person.objects.no_cache() people = Person.objects.no_cache()
@ -4760,6 +4761,14 @@ class QuerySetTest(unittest.TestCase):
people.count() people.count()
self.assertEqual(q, 3) self.assertEqual(q, 3)
except AssertionError as exc:
db = get_db()
msg = ''
for q in list(db.system.profile.find())[-50:]:
msg += str([q['ts'], q['ns'], q.get('query'), q['op']])+'\n'
msg += str(q)
raise AssertionError(str(exc) + '\n'+msg)
def test_cache_not_cloned(self): def test_cache_not_cloned(self):

View File

@ -209,18 +209,99 @@ class ContextManagersTest(unittest.TestCase):
with no_sub_classes(User): with no_sub_classes(User):
raise TypeError() raise TypeError()
def test_query_counter_does_not_swallow_exception(self):
with self.assertRaises(TypeError):
with query_counter() as q:
raise TypeError()
def test_query_counter_temporarily_modifies_profiling_level(self):
connect('mongoenginetest')
db = get_db()
initial_profiling_level = db.profiling_level()
try:
NEW_LEVEL = 1
db.set_profiling_level(NEW_LEVEL)
self.assertEqual(db.profiling_level(), NEW_LEVEL)
with query_counter() as q:
self.assertEqual(db.profiling_level(), 2)
self.assertEqual(db.profiling_level(), NEW_LEVEL)
except Exception:
db.set_profiling_level(initial_profiling_level) # Ensures it gets reseted no matter the outcome of the test
raise
def test_query_counter(self): def test_query_counter(self):
connect('mongoenginetest') connect('mongoenginetest')
db = get_db() db = get_db()
db.test.find({})
collection = db.query_counter
collection.drop()
def issue_1_count_query():
collection.find({}).count()
def issue_1_insert_query():
collection.insert_one({'test': 'garbage'})
def issue_1_find_query():
collection.find_one()
counter = 0
with query_counter() as q:
self.assertEqual(q, counter)
self.assertEqual(q, counter) # Ensures previous count query did not get counted
for _ in range(10):
issue_1_insert_query()
counter += 1
self.assertEqual(q, counter)
for _ in range(4):
issue_1_find_query()
counter += 1
self.assertEqual(q, counter)
for _ in range(3):
issue_1_count_query()
counter += 1
self.assertEqual(q, counter)
def test_query_counter_counts_getmore_queries(self):
connect('mongoenginetest')
db = get_db()
collection = db.query_counter
collection.drop()
many_docs = [{'test': 'garbage %s' % i} for i in range(150)]
collection.insert_many(many_docs) # first batch of documents contains 101 documents
with query_counter() as q: with query_counter() as q:
self.assertEqual(0, q) self.assertEqual(q, 0)
list(collection.find())
self.assertEqual(q, 2) # 1st select + 1 getmore
for i in range(1, 51): def test_query_counter_ignores_particular_queries(self):
db.test.find({}).count() connect('mongoenginetest')
db = get_db()
self.assertEqual(50, q) collection = db.query_counter
collection.insert_many([{'test': 'garbage %s' % i} for i in range(10)])
with query_counter() as q:
self.assertEqual(q, 0)
cursor = collection.find()
self.assertEqual(q, 0) # cursor wasn't opened yet
_ = next(cursor) # opens the cursor and fires the find query
self.assertEqual(q, 1)
cursor.close() # issues a `killcursors` query that is ignored by the context
self.assertEqual(q, 1)
_ = db.system.indexes.find_one() # queries on db.system.indexes are ignored as well
self.assertEqual(q, 1)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()