diff --git a/django/test/testcases.py b/django/test/testcases.py index f673a10d08..3af6b8c346 100644 --- a/django/test/testcases.py +++ b/django/test/testcases.py @@ -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): diff --git a/docs/releases/1.6.txt b/docs/releases/1.6.txt index 49649bc6b8..1f57913397 100644 --- a/docs/releases/1.6.txt +++ b/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 ===================================== diff --git a/docs/topics/testing.txt b/docs/topics/testing.txt index a1524e4f15..a52e4fe169 100644 --- a/docs/topics/testing.txt +++ b/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) diff --git a/tests/modeltests/expressions/tests.py b/tests/modeltests/expressions/tests.py index ca47521ccd..a351496442 100644 --- a/tests/modeltests/expressions/tests.py +++ b/tests/modeltests/expressions/tests.py @@ -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( diff --git a/tests/modeltests/field_subclassing/tests.py b/tests/modeltests/field_subclassing/tests.py index 48755123f2..0ec317dea5 100644 --- a/tests/modeltests/field_subclassing/tests.py +++ b/tests/modeltests/field_subclassing/tests.py @@ -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): diff --git a/tests/modeltests/fixtures/tests.py b/tests/modeltests/fixtures/tests.py index b667c8c6d4..415ed6dcf2 100644 --- a/tests/modeltests/fixtures/tests.py +++ b/tests/modeltests/fixtures/tests.py @@ -96,8 +96,8 @@ class FixtureLoadingTests(TestCase): management.call_command('loaddata', 'fixture6.json', verbosity=0, commit=False) self.assertQuerysetEqual(Tag.objects.all(), [ ' tagged "copyright">', - ' tagged "law">' - ]) + ' 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): ' tagged "copyright">', ' tagged "legal">', ' tagged "django">', - ' tagged "world domination">' - ]) + ' 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): '', '', '' - ]) + ], 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): '', '', '' - ]) + ], ordered=False) self.assertQuerysetEqual(Book.objects.all(), [ '', @@ -280,7 +280,7 @@ class FixtureLoadingTests(TestCase): self.assertQuerysetEqual(Tag.objects.all(), [ ' tagged "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) diff --git a/tests/modeltests/generic_relations/tests.py b/tests/modeltests/generic_relations/tests.py index 14871e4e09..73b0a483a2 100644 --- a/tests/modeltests/generic_relations/tests.py +++ b/tests/modeltests/generic_relations/tests.py @@ -169,8 +169,8 @@ class GenericRelationsTests(TestCase): # Filtering works self.assertQuerysetEqual(tiger.comparisons.filter(comparative="cooler"), [ "", - "" - ]) + "", + ], ordered=False) # Filtering and deleting works subjective = ["cooler"] @@ -178,7 +178,7 @@ class GenericRelationsTests(TestCase): self.assertQuerysetEqual(Comparison.objects.all(), [ "", "" - ]) + ], ordered=False) # If we delete cheetah, Comparisons with cheetah as 'first_obj' will be # deleted since Animal has an explicit GenericRelation to Comparison diff --git a/tests/modeltests/m2m_recursive/tests.py b/tests/modeltests/m2m_recursive/tests.py index 5742836929..a3f2c670d6 100644 --- a/tests/modeltests/m2m_recursive/tests.py +++ b/tests/modeltests/m2m_recursive/tests.py @@ -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( diff --git a/tests/modeltests/many_to_one/tests.py b/tests/modeltests/many_to_one/tests.py index 4fb19dbc69..44ae689dd4 100644 --- a/tests/modeltests/many_to_one/tests.py +++ b/tests/modeltests/many_to_one/tests.py @@ -267,7 +267,9 @@ class ManyToOneTests(TestCase): [""]) self.assertQuerysetEqual( Reporter.objects.filter(article__headline__startswith='T'), - ["", ""]) + ["", ""], + ordered=False + ) self.assertQuerysetEqual( Reporter.objects.filter(article__headline__startswith='T').distinct(), [""]) @@ -285,7 +287,9 @@ class ManyToOneTests(TestCase): "", "", "", - ]) + ], + ordered=False + ) self.assertQuerysetEqual( Reporter.objects.filter(article__reporter__first_name__startswith='John').distinct(), [""]) diff --git a/tests/modeltests/model_forms/tests.py b/tests/modeltests/model_forms/tests.py index 47d72abdc2..9699b155c0 100644 --- a/tests/modeltests/model_forms/tests.py +++ b/tests/modeltests/model_forms/tests.py @@ -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): diff --git a/tests/regressiontests/expressions_regress/tests.py b/tests/regressiontests/expressions_regress/tests.py index 508a497151..ddb2c83b4f 100644 --- a/tests/regressiontests/expressions_regress/tests.py +++ b/tests/regressiontests/expressions_regress/tests.py @@ -26,12 +26,13 @@ class ExpressionsRegressTests(TestCase): same object. """ self.assertQuerysetEqual( - Number.objects.all(), - [ - '', - '', - '' - ] + Number.objects.all(), + [ + '', + '', + '' + ], + ordered=False ) def test_increment_value(self): @@ -44,12 +45,13 @@ class ExpressionsRegressTests(TestCase): 2) self.assertQuerysetEqual( - Number.objects.all(), - [ - '', - '', - '' - ] + Number.objects.all(), + [ + '', + '', + '' + ], + ordered=False ) def test_filter_not_equals_other_field(self): @@ -62,11 +64,12 @@ class ExpressionsRegressTests(TestCase): .update(integer=F('integer') + 1), 2) self.assertQuerysetEqual( - Number.objects.exclude(float=F('integer')), - [ - '', - '' - ] + Number.objects.exclude(float=F('integer')), + [ + '', + '' + ], + ordered=False ) def test_complex_expressions(self): diff --git a/tests/regressiontests/extra_regress/tests.py b/tests/regressiontests/extra_regress/tests.py index f591900afe..1bc6789edd 100644 --- a/tests/regressiontests/extra_regress/tests.py +++ b/tests/regressiontests/extra_regress/tests.py @@ -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'"], ), - ['', ''] + ['', ''], + ordered=False ) diff --git a/tests/regressiontests/m2m_regress/tests.py b/tests/regressiontests/m2m_regress/tests.py index c39d883de4..3dc1d2417c 100644 --- a/tests/regressiontests/m2m_regress/tests.py +++ b/tests/regressiontests/m2m_regress/tests.py @@ -74,7 +74,7 @@ class M2MRegressionTests(TestCase): c1.tags = [t1, t2] c1 = TagCollection.objects.get(name='c1') - self.assertQuerysetEqual(c1.tags.all(), ["", ""]) + self.assertQuerysetEqual(c1.tags.all(), ["", ""], ordered=False) self.assertQuerysetEqual(t1.tag_collections.all(), [""]) def test_manager_class_caching(self): diff --git a/tests/regressiontests/m2m_through_regress/tests.py b/tests/regressiontests/m2m_through_regress/tests.py index eba956b3ba..17a4525442 100644 --- a/tests/regressiontests/m2m_through_regress/tests.py +++ b/tests/regressiontests/m2m_through_regress/tests.py @@ -28,7 +28,8 @@ class M2MThroughTestCase(TestCase): bob.group_set.all(), [ "", "", - ] + ], + ordered=False ) self.assertQuerysetEqual( @@ -51,7 +52,8 @@ class M2MThroughTestCase(TestCase): frank.group_set.all(), [ "", "", - ] + ], + 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(), - ["", ""] + ["", ""], + ordered=False ) def test_add_null_reverse(self): diff --git a/tests/regressiontests/managers_regress/tests.py b/tests/regressiontests/managers_regress/tests.py index f3721a4c01..45059be4e5 100644 --- a/tests/regressiontests/managers_regress/tests.py +++ b/tests/regressiontests/managers_regress/tests.py @@ -61,7 +61,8 @@ class ManagersRegressionTests(TestCase): self.assertQuerysetEqual(Child4.manager1.all(), [ "", "" - ] + ], + ordered=False ) self.assertQuerysetEqual(Child5._default_manager.all(), [""]) self.assertQuerysetEqual(Child6._default_manager.all(), [""]) diff --git a/tests/regressiontests/model_regress/tests.py b/tests/regressiontests/model_regress/tests.py index 6a45a83052..c90fe98658 100644 --- a/tests/regressiontests/model_regress/tests.py +++ b/tests/regressiontests/model_regress/tests.py @@ -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"), [ diff --git a/tests/regressiontests/queries/tests.py b/tests/regressiontests/queries/tests.py index eea5fead55..e4009cdf20 100644 --- a/tests/regressiontests/queries/tests.py +++ b/tests/regressiontests/queries/tests.py @@ -841,11 +841,14 @@ class Queries1Tests(BaseQuerysetTest): """ original_ordering = Tag._meta.ordering Tag._meta.ordering = None - self.assertQuerysetEqual( - Tag.objects.all(), - ['', '', '', '', ''], - ) - Tag._meta.ordering = original_ordering + try: + self.assertQuerysetEqual( + Tag.objects.all(), + ['', '', '', '', ''], + ordered=False + ) + finally: + Tag._meta.ordering = original_ordering def test_exclude(self): self.assertQuerysetEqual( @@ -925,15 +928,18 @@ class Queries2Tests(TestCase): self.assertQuerysetEqual(Number.objects.filter(num__gt=12.1), []) self.assertQuerysetEqual( Number.objects.filter(num__lt=12), - ['', ''] + ['', ''], + ordered=False ) self.assertQuerysetEqual( Number.objects.filter(num__lt=12.0), - ['', ''] + ['', ''], + ordered=False ) self.assertQuerysetEqual( Number.objects.filter(num__lt=12.1), - ['', '', ''] + ['', '', ''], + 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), - ['', ''] + ['', ''], + ordered=False ) self.assertQuerysetEqual( Number.objects.filter(num__lte=12), - ['', '', ''] + ['', '', ''], + ordered=False ) self.assertQuerysetEqual( Number.objects.filter(num__lte=12.0), - ['', '', ''] + ['', '', ''], + ordered=False ) self.assertQuerysetEqual( Number.objects.filter(num__lte=12.1), - ['', '', ''] + ['', '', ''], + ordered=False ) self.assertQuerysetEqual( Number.objects.filter(num__lte=12.9), - ['', '', ''] + ['', '', ''], + ordered=False ) def test_ticket7411(self): diff --git a/tests/regressiontests/test_utils/models.py b/tests/regressiontests/test_utils/models.py index 4da7a07bbf..85a1031c02 100644 --- a/tests/regressiontests/test_utils/models.py +++ b/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 diff --git a/tests/regressiontests/test_utils/tests.py b/tests/regressiontests/test_utils/tests.py index 95913b5aab..d5d49b2104 100644 --- a/tests/regressiontests/test_utils/tests.py +++ b/tests/regressiontests/test_utils/tests.py @@ -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'