Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Fixed #19462 -- Made assertQuerysetEqual detect undefined ordering

If there are more than one values to compare against and the qs isn't
ordered then assertQuerysetEqual will raise a ValueError.
  • Loading branch information...
commit 088d3bc2f84b6b68fee7e5de053b58049bd110e7 1 parent 6ed6a18
@akaariai akaariai authored
View
6 django/test/testcases.py
@@ -767,6 +767,12 @@ def assertQuerysetEqual(self, qs, values, transform=repr, ordered=True):
items = six.moves.map(transform, qs)
if not ordered:
return self.assertEqual(set(items), set(values))
+ values = list(values)
+ # For example qs.iterator() could be passed as qs, but it does not
+ # have 'ordered' attribute.
+ if len(values) > 1 and hasattr(qs, 'ordered') and not qs.ordered:
+ raise ValueError("Trying to compare non-ordered queryset "
+ "against more than one ordered values")
return self.assertEqual(list(items), values)
def assertNumQueries(self, num, func=None, *args, **kwargs):
View
5 docs/releases/1.6.txt
@@ -23,6 +23,11 @@ Minor features
* Authentication backends can raise ``PermissionDenied`` to immediately fail
the authentication chain.
+* The ``assertQuerysetEqual()`` now checks for undefined order and raises
+ ``ValueError`` if undefined order is spotted. The order is seen as
+ undefined if the given ``QuerySet`` isn't ordered and there are more than
+ one ordered values to compare against.
+
Backwards incompatible changes in 1.6
=====================================
View
5 docs/topics/testing.txt
@@ -1770,6 +1770,11 @@ your test suite.
via an explicit ``order_by()`` call on the queryset prior to
comparison.
+ .. versionchanged:: 1.6
+ The method now checks for undefined order and raises ``ValueError``
+ if undefined order is spotted. The ordering is seen as undefined if
+ the given ``qs`` isn't ordered and the comparison is against more
+ than one ordered values.
.. method:: TestCase.assertNumQueries(num, func, *args, **kwargs)
View
4 tests/modeltests/expressions/tests.py
@@ -158,6 +158,7 @@ def test_filter(self):
"Max Mustermann",
],
lambda c: six.text_type(c.point_of_contact),
+ ordered=False
)
c = Company.objects.all()[0]
@@ -170,7 +171,8 @@ def test_filter(self):
"Foobar Ltd.",
"Test GmbH",
],
- lambda c: c.name
+ lambda c: c.name,
+ ordered=False
)
Company.objects.exclude(
View
3  tests/modeltests/field_subclassing/tests.py
@@ -77,7 +77,8 @@ def test_custom_field(self):
"12",
"23",
],
- lambda m: str(m.data)
+ lambda m: str(m.data),
+ ordered=False
)
def test_field_subclassing(self):
View
14 tests/modeltests/fixtures/tests.py
@@ -96,8 +96,8 @@ def test_loading_and_dumping(self):
management.call_command('loaddata', 'fixture6.json', verbosity=0, commit=False)
self.assertQuerysetEqual(Tag.objects.all(), [
'<Tag: <Article: Copyright is fine the way it is> tagged "copyright">',
- '<Tag: <Article: Copyright is fine the way it is> tagged "law">'
- ])
+ '<Tag: <Article: Copyright is fine the way it is> tagged "law">',
+ ], ordered=False)
# Load fixture 7, XML file with dynamic ContentType fields. Testing ManyToOne.
management.call_command('loaddata', 'fixture7.xml', verbosity=0, commit=False)
@@ -105,8 +105,8 @@ def test_loading_and_dumping(self):
'<Tag: <Article: Copyright is fine the way it is> tagged "copyright">',
'<Tag: <Article: Copyright is fine the way it is> tagged "legal">',
'<Tag: <Article: Django conquers world!> tagged "django">',
- '<Tag: <Article: Django conquers world!> tagged "world domination">'
- ])
+ '<Tag: <Article: Django conquers world!> tagged "world domination">',
+ ], ordered=False)
# Load fixture 8, JSON file with dynamic Permission fields. Testing ManyToMany.
management.call_command('loaddata', 'fixture8.json', verbosity=0, commit=False)
@@ -114,7 +114,7 @@ def test_loading_and_dumping(self):
'<Visa: Django Reinhardt Can add user, Can change user, Can delete user>',
'<Visa: Stephane Grappelli Can add user>',
'<Visa: Prince >'
- ])
+ ], ordered=False)
# Load fixture 9, XML file with dynamic Permission fields. Testing ManyToMany.
management.call_command('loaddata', 'fixture9.xml', verbosity=0, commit=False)
@@ -122,7 +122,7 @@ def test_loading_and_dumping(self):
'<Visa: Django Reinhardt Can add user, Can change user, Can delete user>',
'<Visa: Stephane Grappelli Can add user, Can delete user>',
'<Visa: Artist formerly known as "Prince" Can change user>'
- ])
+ ], ordered=False)
self.assertQuerysetEqual(Book.objects.all(), [
'<Book: Achieving self-awareness of Python programs>',
@@ -280,7 +280,7 @@ def test_output_formats(self):
self.assertQuerysetEqual(Tag.objects.all(), [
'<Tag: <Article: Time to reform copyright> tagged "copyright">',
'<Tag: <Article: Time to reform copyright> tagged "law">'
- ])
+ ], ordered=False)
# Dump the current contents of the database as a JSON fixture
self._dumpdata_assert(['fixtures'], '[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}, {"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker has no place on ESPN", "pub_date": "2006-06-16T12:00:00"}}, {"pk": 3, "model": "fixtures.article", "fields": {"headline": "Time to reform copyright", "pub_date": "2006-06-16T13:00:00"}}, {"pk": 1, "model": "fixtures.tag", "fields": {"tagged_type": ["fixtures", "article"], "name": "copyright", "tagged_id": 3}}, {"pk": 2, "model": "fixtures.tag", "fields": {"tagged_type": ["fixtures", "article"], "name": "law", "tagged_id": 3}}, {"pk": 1, "model": "fixtures.person", "fields": {"name": "Django Reinhardt"}}, {"pk": 2, "model": "fixtures.person", "fields": {"name": "Stephane Grappelli"}}, {"pk": 3, "model": "fixtures.person", "fields": {"name": "Prince"}}, {"pk": 10, "model": "fixtures.book", "fields": {"name": "Achieving self-awareness of Python programs", "authors": []}}]', natural_keys=True)
View
6 tests/modeltests/generic_relations/tests.py
@@ -169,8 +169,8 @@ def test_multiple_gfk(self):
# Filtering works
self.assertQuerysetEqual(tiger.comparisons.filter(comparative="cooler"), [
"<Comparison: tiger is cooler than cheetah>",
- "<Comparison: tiger is cooler than bear>"
- ])
+ "<Comparison: tiger is cooler than bear>",
+ ], ordered=False)
# Filtering and deleting works
subjective = ["cooler"]
@@ -178,7 +178,7 @@ def test_multiple_gfk(self):
self.assertQuerysetEqual(Comparison.objects.all(), [
"<Comparison: cheetah is faster than tiger>",
"<Comparison: tiger is stronger than cheetah>"
- ])
+ ], ordered=False)
# If we delete cheetah, Comparisons with cheetah as 'first_obj' will be
# deleted since Animal has an explicit GenericRelation to Comparison
View
27 tests/modeltests/m2m_recursive/tests.py
@@ -28,7 +28,8 @@ def test_recursive_m2m(self):
"Chuck",
"David"
],
- attrgetter("name")
+ attrgetter("name"),
+ ordered=False
)
# Who is friends with Bill?
self.assertQuerysetEqual(
@@ -43,7 +44,8 @@ def test_recursive_m2m(self):
"Anne",
"David"
],
- attrgetter("name")
+ attrgetter("name"),
+ ordered=False
)
# Who is friends with David?
self.assertQuerysetEqual(
@@ -51,7 +53,8 @@ def test_recursive_m2m(self):
"Anne",
"Chuck",
],
- attrgetter("name")
+ attrgetter("name"),
+ ordered=False
)
# Bill is already friends with Anne - add Anne again, but in the
# reverse direction
@@ -64,7 +67,8 @@ def test_recursive_m2m(self):
"Chuck",
"David",
],
- attrgetter("name")
+ attrgetter("name"),
+ ordered=False
)
# Who is friends with Bill?
self.assertQuerysetEqual(
@@ -81,7 +85,8 @@ def test_recursive_m2m(self):
"Chuck",
"David",
],
- attrgetter("name")
+ attrgetter("name"),
+ ordered=False
)
# Who is friends with Bill?
self.assertQuerysetEqual(
@@ -125,7 +130,8 @@ def test_recursive_m2m(self):
"Chuck",
"David",
],
- attrgetter("name")
+ attrgetter("name"),
+ ordered=False
)
# Who is stalking Anne?
self.assertQuerysetEqual(
@@ -172,7 +178,8 @@ def test_recursive_m2m(self):
"Anne",
"Chuck",
],
- attrgetter("name")
+ attrgetter("name"),
+ ordered=False
)
# Bill is already being stalked by Anne - add Anne again, but in the
# reverse direction
@@ -184,7 +191,8 @@ def test_recursive_m2m(self):
"Chuck",
"David",
],
- attrgetter("name")
+ attrgetter("name"),
+ ordered=False
)
# Who is stalking Anne?
self.assertQuerysetEqual(
@@ -215,7 +223,8 @@ def test_recursive_m2m(self):
"Chuck",
"David",
],
- attrgetter("name")
+ attrgetter("name"),
+ ordered=False
)
# Who is stalking Anne?
self.assertQuerysetEqual(
View
8 tests/modeltests/many_to_one/tests.py
@@ -267,7 +267,9 @@ def test_reverse_selects(self):
["<Reporter: John Smith>"])
self.assertQuerysetEqual(
Reporter.objects.filter(article__headline__startswith='T'),
- ["<Reporter: John Smith>", "<Reporter: John Smith>"])
+ ["<Reporter: John Smith>", "<Reporter: John Smith>"],
+ ordered=False
+ )
self.assertQuerysetEqual(
Reporter.objects.filter(article__headline__startswith='T').distinct(),
["<Reporter: John Smith>"])
@@ -285,7 +287,9 @@ def test_reverse_selects(self):
"<Reporter: John Smith>",
"<Reporter: John Smith>",
"<Reporter: John Smith>",
- ])
+ ],
+ ordered=False
+ )
self.assertQuerysetEqual(
Reporter.objects.filter(article__reporter__first_name__startswith='John').distinct(),
["<Reporter: John Smith>"])
View
9 tests/modeltests/model_forms/tests.py
@@ -1044,9 +1044,12 @@ def test_with_data(self):
self.assertQuerysetEqual(f.clean([c1.id]), ["Entertainment"])
self.assertQuerysetEqual(f.clean([c2.id]), ["It's a test"])
self.assertQuerysetEqual(f.clean([str(c1.id)]), ["Entertainment"])
- self.assertQuerysetEqual(f.clean([str(c1.id), str(c2.id)]), ["Entertainment", "It's a test"])
- self.assertQuerysetEqual(f.clean([c1.id, str(c2.id)]), ["Entertainment", "It's a test"])
- self.assertQuerysetEqual(f.clean((c1.id, str(c2.id))), ["Entertainment", "It's a test"])
+ self.assertQuerysetEqual(f.clean([str(c1.id), str(c2.id)]), ["Entertainment", "It's a test"],
+ ordered=False)
+ self.assertQuerysetEqual(f.clean([c1.id, str(c2.id)]), ["Entertainment", "It's a test"],
+ ordered=False)
+ self.assertQuerysetEqual(f.clean((c1.id, str(c2.id))), ["Entertainment", "It's a test"],
+ ordered=False)
with self.assertRaises(ValidationError):
f.clean(['100'])
with self.assertRaises(ValidationError):
View
37 tests/regressiontests/expressions_regress/tests.py
@@ -26,12 +26,13 @@ def test_fill_with_value_from_same_object(self):
same object.
"""
self.assertQuerysetEqual(
- Number.objects.all(),
- [
- '<Number: -1, -1.000>',
- '<Number: 42, 42.000>',
- '<Number: 1337, 1337.000>'
- ]
+ Number.objects.all(),
+ [
+ '<Number: -1, -1.000>',
+ '<Number: 42, 42.000>',
+ '<Number: 1337, 1337.000>'
+ ],
+ ordered=False
)
def test_increment_value(self):
@@ -44,12 +45,13 @@ def test_increment_value(self):
2)
self.assertQuerysetEqual(
- Number.objects.all(),
- [
- '<Number: -1, -1.000>',
- '<Number: 43, 42.000>',
- '<Number: 1338, 1337.000>'
- ]
+ Number.objects.all(),
+ [
+ '<Number: -1, -1.000>',
+ '<Number: 43, 42.000>',
+ '<Number: 1338, 1337.000>'
+ ],
+ ordered=False
)
def test_filter_not_equals_other_field(self):
@@ -62,11 +64,12 @@ def test_filter_not_equals_other_field(self):
.update(integer=F('integer') + 1),
2)
self.assertQuerysetEqual(
- Number.objects.exclude(float=F('integer')),
- [
- '<Number: 43, 42.000>',
- '<Number: 1338, 1337.000>'
- ]
+ Number.objects.exclude(float=F('integer')),
+ [
+ '<Number: 43, 42.000>',
+ '<Number: 1338, 1337.000>'
+ ],
+ ordered=False
)
def test_complex_expressions(self):
View
9 tests/regressiontests/extra_regress/tests.py
@@ -58,13 +58,15 @@ def test_regression_7314_7372(self):
('First Revision', 'First Revision'),
('Second Revision', 'First Revision'),
],
- transform=lambda r: (r.title, r.base.title)
+ transform=lambda r: (r.title, r.base.title),
+ ordered=False
)
# Following queryset should return the most recent revision:
self.assertQuerysetEqual(qs & qs2,
[('Second Revision', 'First Revision')],
- transform=lambda r: (r.title, r.base.title)
+ transform=lambda r: (r.title, r.base.title),
+ ordered=False
)
def test_extra_stay_tied(self):
@@ -342,5 +344,6 @@ def test_regression_17877(self):
TestObject.objects.extra(
where=["first = 'a' OR second = 'a'", "third = 'a'"],
),
- ['<TestObject: TestObject: a,a,a>', '<TestObject: TestObject: b,a,a>']
+ ['<TestObject: TestObject: a,a,a>', '<TestObject: TestObject: b,a,a>'],
+ ordered=False
)
View
2  tests/regressiontests/m2m_regress/tests.py
@@ -74,7 +74,7 @@ def test_add_m2m_with_base_class(self):
c1.tags = [t1, t2]
c1 = TagCollection.objects.get(name='c1')
- self.assertQuerysetEqual(c1.tags.all(), ["<Tag: t1>", "<Tag: t2>"])
+ self.assertQuerysetEqual(c1.tags.all(), ["<Tag: t1>", "<Tag: t2>"], ordered=False)
self.assertQuerysetEqual(t1.tag_collections.all(), ["<TagCollection: c1>"])
def test_manager_class_caching(self):
View
9 tests/regressiontests/m2m_through_regress/tests.py
@@ -28,7 +28,8 @@ def test_everything(self):
bob.group_set.all(), [
"<Group: Rock>",
"<Group: Roll>",
- ]
+ ],
+ ordered=False
)
self.assertQuerysetEqual(
@@ -51,7 +52,8 @@ def test_everything(self):
frank.group_set.all(), [
"<Group: Rock>",
"<Group: Roll>",
- ]
+ ],
+ ordered=False
)
self.assertQuerysetEqual(
@@ -190,7 +192,8 @@ def test_add_reverse(self):
self.driver.car_set._add_items('driver', 'car', car2)
self.assertQuerysetEqual(
self.driver.car_set.all(),
- ["<Car: Toyota>", "<Car: Honda>"]
+ ["<Car: Toyota>", "<Car: Honda>"],
+ ordered=False
)
def test_add_null_reverse(self):
View
3  tests/regressiontests/managers_regress/tests.py
@@ -61,7 +61,8 @@ def test_managers(self):
self.assertQuerysetEqual(Child4.manager1.all(), [
"<Child4: d1>",
"<Child4: f1>"
- ]
+ ],
+ ordered=False
)
self.assertQuerysetEqual(Child5._default_manager.all(), ["<Child5: fred>"])
self.assertQuerysetEqual(Child6._default_manager.all(), ["<Child6: f1>"])
View
9 tests/regressiontests/model_regress/tests.py
@@ -71,7 +71,8 @@ def test_date_lookup(self):
datetime.date(1999, 12, 31),
datetime.date(1998, 12, 31),
],
- attrgetter("when")
+ attrgetter("when"),
+ ordered=False
)
self.assertQuerysetEqual(
Party.objects.filter(when__year=1998), [
@@ -85,14 +86,16 @@ def test_date_lookup(self):
datetime.date(1999, 12, 31),
datetime.date(1998, 12, 31),
],
- attrgetter("when")
+ attrgetter("when"),
+ ordered=False
)
self.assertQuerysetEqual(
Party.objects.filter(when__month="12"), [
datetime.date(1999, 12, 31),
datetime.date(1998, 12, 31),
],
- attrgetter("when")
+ attrgetter("when"),
+ ordered=False
)
self.assertQuerysetEqual(
Party.objects.filter(when__year="1998"), [
View
37 tests/regressiontests/queries/tests.py
@@ -841,11 +841,14 @@ def test_ticket17429(self):
"""
original_ordering = Tag._meta.ordering
Tag._meta.ordering = None
- self.assertQuerysetEqual(
- Tag.objects.all(),
- ['<Tag: t1>', '<Tag: t2>', '<Tag: t3>', '<Tag: t4>', '<Tag: t5>'],
- )
- Tag._meta.ordering = original_ordering
+ try:
+ self.assertQuerysetEqual(
+ Tag.objects.all(),
+ ['<Tag: t1>', '<Tag: t2>', '<Tag: t3>', '<Tag: t4>', '<Tag: t5>'],
+ ordered=False
+ )
+ finally:
+ Tag._meta.ordering = original_ordering
def test_exclude(self):
self.assertQuerysetEqual(
@@ -925,15 +928,18 @@ def test_ticket12239(self):
self.assertQuerysetEqual(Number.objects.filter(num__gt=12.1), [])
self.assertQuerysetEqual(
Number.objects.filter(num__lt=12),
- ['<Number: 4>', '<Number: 8>']
+ ['<Number: 4>', '<Number: 8>'],
+ ordered=False
)
self.assertQuerysetEqual(
Number.objects.filter(num__lt=12.0),
- ['<Number: 4>', '<Number: 8>']
+ ['<Number: 4>', '<Number: 8>'],
+ ordered=False
)
self.assertQuerysetEqual(
Number.objects.filter(num__lt=12.1),
- ['<Number: 4>', '<Number: 8>', '<Number: 12>']
+ ['<Number: 4>', '<Number: 8>', '<Number: 12>'],
+ ordered=False
)
self.assertQuerysetEqual(
Number.objects.filter(num__gte=11.9),
@@ -951,23 +957,28 @@ def test_ticket12239(self):
self.assertQuerysetEqual(Number.objects.filter(num__gte=12.9), [])
self.assertQuerysetEqual(
Number.objects.filter(num__lte=11.9),
- ['<Number: 4>', '<Number: 8>']
+ ['<Number: 4>', '<Number: 8>'],
+ ordered=False
)
self.assertQuerysetEqual(
Number.objects.filter(num__lte=12),
- ['<Number: 4>', '<Number: 8>', '<Number: 12>']
+ ['<Number: 4>', '<Number: 8>', '<Number: 12>'],
+ ordered=False
)
self.assertQuerysetEqual(
Number.objects.filter(num__lte=12.0),
- ['<Number: 4>', '<Number: 8>', '<Number: 12>']
+ ['<Number: 4>', '<Number: 8>', '<Number: 12>'],
+ ordered=False
)
self.assertQuerysetEqual(
Number.objects.filter(num__lte=12.1),
- ['<Number: 4>', '<Number: 8>', '<Number: 12>']
+ ['<Number: 4>', '<Number: 8>', '<Number: 12>'],
+ ordered=False
)
self.assertQuerysetEqual(
Number.objects.filter(num__lte=12.9),
- ['<Number: 4>', '<Number: 8>', '<Number: 12>']
+ ['<Number: 4>', '<Number: 8>', '<Number: 12>'],
+ ordered=False
)
def test_ticket7411(self):
View
6 tests/regressiontests/test_utils/models.py
@@ -1,5 +1,9 @@
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
-
+@python_2_unicode_compatible
class Person(models.Model):
name = models.CharField(max_length=100)
+
+ def __str__(self):
+ return self.name
View
40 tests/regressiontests/test_utils/tests.py
@@ -54,6 +54,46 @@ def test_func():
self.assertNumQueries(2, test_func)
+class AssertQuerysetEqualTests(TestCase):
+ def setUp(self):
+ self.p1 = Person.objects.create(name='p1')
+ self.p2 = Person.objects.create(name='p2')
+
+ def test_ordered(self):
+ self.assertQuerysetEqual(
+ Person.objects.all().order_by('name'),
+ [repr(self.p1), repr(self.p2)]
+ )
+
+ def test_unordered(self):
+ self.assertQuerysetEqual(
+ Person.objects.all().order_by('name'),
+ [repr(self.p2), repr(self.p1)],
+ ordered=False
+ )
+
+ def test_transform(self):
+ self.assertQuerysetEqual(
+ Person.objects.all().order_by('name'),
+ [self.p1.pk, self.p2.pk],
+ transform=lambda x: x.pk
+ )
+
+ def test_undefined_order(self):
+ # Using an unordered queryset with more than one ordered value
+ # is an error.
+ with self.assertRaises(ValueError):
+ self.assertQuerysetEqual(
+ Person.objects.all(),
+ [repr(self.p1), repr(self.p2)]
+ )
+ # No error for one value.
+ self.assertQuerysetEqual(
+ Person.objects.filter(name='p1'),
+ [repr(self.p1)]
+ )
+
+
class AssertNumQueriesContextManagerTests(TestCase):
urls = 'regressiontests.test_utils.urls'
Please sign in to comment.
Something went wrong with that request. Please try again.