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.
This commit is contained in:
Anssi Kääriäinen 2012-12-13 13:33:11 +02:00
parent 6ed6a18a03
commit 088d3bc2f8
19 changed files with 171 additions and 68 deletions

View File

@ -767,6 +767,12 @@ class TransactionTestCase(SimpleTestCase):
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 File

@ -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 File

@ -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 File

@ -158,6 +158,7 @@ class ExpressionsTests(TestCase):
"Max Mustermann",
],
lambda c: six.text_type(c.point_of_contact),
ordered=False
)
c = Company.objects.all()[0]
@ -170,7 +171,8 @@ class ExpressionsTests(TestCase):
"Foobar Ltd.",
"Test GmbH",
],
lambda c: c.name
lambda c: c.name,
ordered=False
)
Company.objects.exclude(

View File

@ -77,7 +77,8 @@ class CustomField(TestCase):
"12",
"23",
],
lambda m: str(m.data)
lambda m: str(m.data),
ordered=False
)
def test_field_subclassing(self):

View File

@ -96,8 +96,8 @@ class FixtureLoadingTests(TestCase):
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 @@ class FixtureLoadingTests(TestCase):
'<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 @@ class FixtureLoadingTests(TestCase):
'<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 @@ class FixtureLoadingTests(TestCase):
'<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 @@ class FixtureLoadingTests(TestCase):
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 File

@ -169,8 +169,8 @@ class GenericRelationsTests(TestCase):
# 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 @@ class GenericRelationsTests(TestCase):
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 File

@ -28,7 +28,8 @@ class RecursiveM2MTests(TestCase):
"Chuck",
"David"
],
attrgetter("name")
attrgetter("name"),
ordered=False
)
# Who is friends with Bill?
self.assertQuerysetEqual(
@ -43,7 +44,8 @@ class RecursiveM2MTests(TestCase):
"Anne",
"David"
],
attrgetter("name")
attrgetter("name"),
ordered=False
)
# Who is friends with David?
self.assertQuerysetEqual(
@ -51,7 +53,8 @@ class RecursiveM2MTests(TestCase):
"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 @@ class RecursiveM2MTests(TestCase):
"Chuck",
"David",
],
attrgetter("name")
attrgetter("name"),
ordered=False
)
# Who is friends with Bill?
self.assertQuerysetEqual(
@ -81,7 +85,8 @@ class RecursiveM2MTests(TestCase):
"Chuck",
"David",
],
attrgetter("name")
attrgetter("name"),
ordered=False
)
# Who is friends with Bill?
self.assertQuerysetEqual(
@ -125,7 +130,8 @@ class RecursiveM2MTests(TestCase):
"Chuck",
"David",
],
attrgetter("name")
attrgetter("name"),
ordered=False
)
# Who is stalking Anne?
self.assertQuerysetEqual(
@ -172,7 +178,8 @@ class RecursiveM2MTests(TestCase):
"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 @@ class RecursiveM2MTests(TestCase):
"Chuck",
"David",
],
attrgetter("name")
attrgetter("name"),
ordered=False
)
# Who is stalking Anne?
self.assertQuerysetEqual(
@ -215,7 +223,8 @@ class RecursiveM2MTests(TestCase):
"Chuck",
"David",
],
attrgetter("name")
attrgetter("name"),
ordered=False
)
# Who is stalking Anne?
self.assertQuerysetEqual(

View File

@ -267,7 +267,9 @@ class ManyToOneTests(TestCase):
["<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 @@ class ManyToOneTests(TestCase):
"<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 File

@ -1044,9 +1044,12 @@ class OldFormForXTests(TestCase):
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 File

@ -31,7 +31,8 @@ class ExpressionsRegressTests(TestCase):
'<Number: -1, -1.000>',
'<Number: 42, 42.000>',
'<Number: 1337, 1337.000>'
]
],
ordered=False
)
def test_increment_value(self):
@ -49,7 +50,8 @@ class ExpressionsRegressTests(TestCase):
'<Number: -1, -1.000>',
'<Number: 43, 42.000>',
'<Number: 1338, 1337.000>'
]
],
ordered=False
)
def test_filter_not_equals_other_field(self):
@ -66,7 +68,8 @@ class ExpressionsRegressTests(TestCase):
[
'<Number: 43, 42.000>',
'<Number: 1338, 1337.000>'
]
],
ordered=False
)
def test_complex_expressions(self):

View File

@ -58,13 +58,15 @@ class ExtraRegressTests(TestCase):
('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 @@ class ExtraRegressTests(TestCase):
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 File

@ -74,7 +74,7 @@ class M2MRegressionTests(TestCase):
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 File

@ -28,7 +28,8 @@ class M2MThroughTestCase(TestCase):
bob.group_set.all(), [
"<Group: Rock>",
"<Group: Roll>",
]
],
ordered=False
)
self.assertQuerysetEqual(
@ -51,7 +52,8 @@ class M2MThroughTestCase(TestCase):
frank.group_set.all(), [
"<Group: Rock>",
"<Group: Roll>",
]
],
ordered=False
)
self.assertQuerysetEqual(
@ -190,7 +192,8 @@ class ToFieldThroughTests(TestCase):
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 File

@ -61,7 +61,8 @@ class ManagersRegressionTests(TestCase):
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 File

@ -71,7 +71,8 @@ class ModelTests(TestCase):
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 @@ class ModelTests(TestCase):
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 File

@ -841,10 +841,13 @@ class Queries1Tests(BaseQuerysetTest):
"""
original_ordering = Tag._meta.ordering
Tag._meta.ordering = None
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):
@ -925,15 +928,18 @@ class Queries2Tests(TestCase):
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 @@ class Queries2Tests(TestCase):
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 File

@ -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 File

@ -54,6 +54,46 @@ class AssertNumQueriesTests(TestCase):
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'