diff options
| author | Andrew Godwin <andrew@aeracode.org> | 2013-07-22 19:04:25 +0100 |
|---|---|---|
| committer | Andrew Godwin <andrew@aeracode.org> | 2013-07-22 19:04:25 +0100 |
| commit | 03ec3219a04093da6c2e82436cee5c451df4f8aa (patch) | |
| tree | 69e3cc2857b7bc41f269363b0056b361129da118 /tests | |
| parent | 3a6580e485c11fc5502ffd1bb53347459db43421 (diff) | |
| parent | 29a09b3638e625458b5e07ed0956451432421acf (diff) | |
Merge branch 'master' into schema-alteration
Conflicts:
django/db/backends/mysql/introspection.py
django/db/backends/oracle/creation.py
django/db/backends/postgresql_psycopg2/creation.py
django/db/models/base.py
django/db/models/loading.py
Diffstat (limited to 'tests')
48 files changed, 909 insertions, 346 deletions
diff --git a/tests/admin_inlines/admin.py b/tests/admin_inlines/admin.py index 2f88248ca4..62f9e04e5b 100644 --- a/tests/admin_inlines/admin.py +++ b/tests/admin_inlines/admin.py @@ -12,8 +12,26 @@ class BookInline(admin.TabularInline): model = Author.books.through +class NonAutoPKBookTabularInline(admin.TabularInline): + model = NonAutoPKBook + + +class NonAutoPKBookStackedInline(admin.StackedInline): + model = NonAutoPKBook + + +class EditablePKBookTabularInline(admin.TabularInline): + model = EditablePKBook + + +class EditablePKBookStackedInline(admin.StackedInline): + model = EditablePKBook + + class AuthorAdmin(admin.ModelAdmin): - inlines = [BookInline] + inlines = [BookInline, + NonAutoPKBookTabularInline, NonAutoPKBookStackedInline, + EditablePKBookTabularInline, EditablePKBookStackedInline] class InnerInline(admin.StackedInline): diff --git a/tests/admin_inlines/models.py b/tests/admin_inlines/models.py index d4ba0ab6bc..dde5cf79e3 100644 --- a/tests/admin_inlines/models.py +++ b/tests/admin_inlines/models.py @@ -3,6 +3,7 @@ Testing of admin inline formsets. """ from __future__ import unicode_literals +import random from django.db import models from django.contrib.contenttypes.models import ContentType @@ -48,6 +49,25 @@ class Author(models.Model): books = models.ManyToManyField(Book) +class NonAutoPKBook(models.Model): + rand_pk = models.IntegerField(primary_key=True, editable=False) + author = models.ForeignKey(Author) + title = models.CharField(max_length=50) + + def save(self, *args, **kwargs): + while not self.rand_pk: + test_pk = random.randint(1, 99999) + if not NonAutoPKBook.objects.filter(rand_pk=test_pk).exists(): + self.rand_pk = test_pk + super(NonAutoPKBook, self).save(*args, **kwargs) + + +class EditablePKBook(models.Model): + manual_pk = models.IntegerField(primary_key=True) + author = models.ForeignKey(Author) + title = models.CharField(max_length=50) + + class Holder(models.Model): dummy = models.IntegerField() diff --git a/tests/admin_inlines/tests.py b/tests/admin_inlines/tests.py index 41dc0e32de..465b224d4f 100644 --- a/tests/admin_inlines/tests.py +++ b/tests/admin_inlines/tests.py @@ -211,6 +211,24 @@ class TestInline(TestCase): self.assertContains(response, max_forms_input % 2) self.assertContains(response, total_forms_hidden) + def test_inline_nonauto_noneditable_pk(self): + response = self.client.get('/admin/admin_inlines/author/add/') + self.assertContains(response, + '<input id="id_nonautopkbook_set-0-rand_pk" name="nonautopkbook_set-0-rand_pk" type="hidden" />', + html=True) + self.assertContains(response, + '<input id="id_nonautopkbook_set-2-0-rand_pk" name="nonautopkbook_set-2-0-rand_pk" type="hidden" />', + html=True) + + def test_inline_editable_pk(self): + response = self.client.get('/admin/admin_inlines/author/add/') + self.assertContains(response, + '<input class="vIntegerField" id="id_editablepkbook_set-0-manual_pk" name="editablepkbook_set-0-manual_pk" type="text" />', + html=True, count=1) + self.assertContains(response, + '<input class="vIntegerField" id="id_editablepkbook_set-2-0-manual_pk" name="editablepkbook_set-2-0-manual_pk" type="text" />', + html=True, count=1) + @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) class TestInlineMedia(TestCase): diff --git a/tests/admin_views/admin.py b/tests/admin_views/admin.py index a6ad7cc0bc..039abb819b 100644 --- a/tests/admin_views/admin.py +++ b/tests/admin_views/admin.py @@ -777,3 +777,8 @@ from django.contrib.auth.models import User, Group from django.contrib.auth.admin import UserAdmin, GroupAdmin site.register(User, UserAdmin) site.register(Group, GroupAdmin) + +# Used to test URL namespaces +site2 = admin.AdminSite(name="namespaced_admin") +site2.register(User, UserAdmin) +site2.register(Group, GroupAdmin) diff --git a/tests/admin_views/tests.py b/tests/admin_views/tests.py index 101fcd90f6..7decf6f471 100644 --- a/tests/admin_views/tests.py +++ b/tests/admin_views/tests.py @@ -26,7 +26,6 @@ from django.contrib.admin.tests import AdminSeleniumWebDriverTestCase from django.contrib.auth import REDIRECT_FIELD_NAME from django.contrib.auth.models import Group, User, Permission from django.contrib.contenttypes.models import ContentType -from django.core.urlresolvers import reverse from django.db import connection from django.forms.util import ErrorList from django.template.response import TemplateResponse @@ -53,6 +52,7 @@ from .models import (Article, BarAccount, CustomArticle, EmptyModel, FooAccount, AdminOrderedModelMethod, AdminOrderedAdminMethod, AdminOrderedCallable, Report, MainPrepopulated, RelatedPrepopulated, UnorderedObject, Simple, UndeletableObject, Choice, ShortMessage, Telegram) +from .admin import site, site2 ERROR_MESSAGE = "Please enter the correct username and password \ @@ -3824,6 +3824,59 @@ class CSSTest(TestCase): self.assertContains(response, '<tr class="model-actor">') self.assertContains(response, '<tr class="model-album">') + def testAppModelInFormBodyClass(self): + """ + Ensure app and model tag are correcly read by change_form template + """ + response = self.client.get('/test_admin/admin/admin_views/section/add/') + self.assertEqual(response.status_code, 200) + self.assertContains(response, + '<body class="app-admin_views model-section ') + + def testAppModelInListBodyClass(self): + """ + Ensure app and model tag are correcly read by change_list template + """ + response = self.client.get('/test_admin/admin/admin_views/section/') + self.assertEqual(response.status_code, 200) + self.assertContains(response, + '<body class="app-admin_views model-section ') + + def testAppModelInDeleteConfirmationBodyClass(self): + """ + Ensure app and model tag are correcly read by delete_confirmation + template + """ + response = self.client.get( + '/test_admin/admin/admin_views/section/1/delete/') + self.assertEqual(response.status_code, 200) + self.assertContains(response, + '<body class="app-admin_views model-section ') + + def testAppModelInAppIndexBodyClass(self): + """ + Ensure app and model tag are correcly read by app_index template + """ + response = self.client.get('/test_admin/admin/admin_views/') + self.assertEqual(response.status_code, 200) + self.assertContains(response, '<body class="app-admin_views ') + + def testAppModelInDeleteSelectedConfirmationBodyClass(self): + """ + Ensure app and model tag are correcly read by + delete_selected_confirmation template + """ + action_data = { + ACTION_CHECKBOX_NAME: [1], + 'action': 'delete_selected', + 'index': 0, + } + response = self.client.post('/test_admin/admin/admin_views/section/', + action_data) + self.assertEqual(response.status_code, 200) + self.assertContains(response, + '<body class="app-admin_views model-section ') + try: import docutils except ImportError: @@ -4186,6 +4239,7 @@ class AdminUserMessageTest(TestCase): class AdminKeepChangeListFiltersTests(TestCase): urls = "admin_views.urls" fixtures = ['admin-views-users.xml'] + admin_site = site def setUp(self): self.client.login(username='super', password='secret') @@ -4209,13 +4263,15 @@ class AdminKeepChangeListFiltersTests(TestCase): def get_changelist_url(self): return '%s?%s' % ( - reverse('admin:auth_user_changelist'), + reverse('admin:auth_user_changelist', + current_app=self.admin_site.name), self.get_changelist_filters_querystring(), ) def get_add_url(self): return '%s?%s' % ( - reverse('admin:auth_user_add'), + reverse('admin:auth_user_add', + current_app=self.admin_site.name), self.get_preserved_filters_querystring(), ) @@ -4223,7 +4279,8 @@ class AdminKeepChangeListFiltersTests(TestCase): if user_id is None: user_id = self.get_sample_user_id() return "%s?%s" % ( - reverse('admin:auth_user_change', args=(user_id,)), + reverse('admin:auth_user_change', args=(user_id,), + current_app=self.admin_site.name), self.get_preserved_filters_querystring(), ) @@ -4231,7 +4288,8 @@ class AdminKeepChangeListFiltersTests(TestCase): if user_id is None: user_id = self.get_sample_user_id() return "%s?%s" % ( - reverse('admin:auth_user_history', args=(user_id,)), + reverse('admin:auth_user_history', args=(user_id,), + current_app=self.admin_site.name), self.get_preserved_filters_querystring(), ) @@ -4239,7 +4297,8 @@ class AdminKeepChangeListFiltersTests(TestCase): if user_id is None: user_id = self.get_sample_user_id() return "%s?%s" % ( - reverse('admin:auth_user_delete', args=(user_id,)), + reverse('admin:auth_user_delete', args=(user_id,), + current_app=self.admin_site.name), self.get_preserved_filters_querystring(), ) @@ -4333,3 +4392,6 @@ class AdminKeepChangeListFiltersTests(TestCase): # Test redirect on "Delete". response = self.client.post(self.get_delete_url(), {'post': 'yes'}) self.assertRedirects(response, self.get_changelist_url()) + +class NamespacedAdminKeepChangeListFiltersTests(AdminKeepChangeListFiltersTests): + admin_site = site2 diff --git a/tests/admin_views/urls.py b/tests/admin_views/urls.py index 441834cd2a..763c83a450 100644 --- a/tests/admin_views/urls.py +++ b/tests/admin_views/urls.py @@ -12,4 +12,5 @@ urlpatterns = patterns('', (r'^test_admin/admin2/', include(customadmin.site.urls)), (r'^test_admin/admin3/', include(admin.site.urls), dict(form_url='pony')), (r'^test_admin/admin4/', include(customadmin.simple_site.urls)), + (r'^test_admin/admin5/', include(admin.site2.urls)), ) diff --git a/tests/admin_widgets/tests.py b/tests/admin_widgets/tests.py index 8edb2f5904..870aa6d455 100644 --- a/tests/admin_widgets/tests.py +++ b/tests/admin_widgets/tests.py @@ -1,7 +1,7 @@ # encoding: utf-8 from __future__ import absolute_import, unicode_literals -from datetime import datetime +from datetime import datetime, timedelta from unittest import TestCase from django import forms @@ -526,6 +526,64 @@ class DateTimePickerSeleniumIETests(DateTimePickerSeleniumFirefoxTests): webdriver_class = 'selenium.webdriver.ie.webdriver.WebDriver' +@override_settings(TIME_ZONE='Asia/Singapore') +@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) +class DateTimePickerShortcutsSeleniumFirefoxTests(AdminSeleniumWebDriverTestCase): + available_apps = ['admin_widgets'] + AdminSeleniumWebDriverTestCase.available_apps + fixtures = ['admin-widgets-users.xml'] + urls = "admin_widgets.urls" + webdriver_class = 'selenium.webdriver.firefox.webdriver.WebDriver' + + def test_date_time_picker_shortcuts(self): + """ + Ensure that date/time/datetime picker shortcuts work in the current time zone. + Refs #20663. + + This test case is fairly tricky, it relies on selenium still running the browser + in the default time zone "America/Chicago" despite `override_settings` changing + the time zone to "Asia/Singapore". + """ + self.admin_login(username='super', password='secret', login_url='/') + + now = datetime.now() + error_margin = timedelta(seconds=10) + + self.selenium.get('%s%s' % (self.live_server_url, + '/admin_widgets/member/add/')) + + self.selenium.find_element_by_id('id_name').send_keys('test') + + # Click on the "today" and "now" shortcuts. + shortcuts = self.selenium.find_elements_by_css_selector( + '.field-birthdate .datetimeshortcuts') + + for shortcut in shortcuts: + shortcut.find_element_by_tag_name('a').click() + + # Check that there is a time zone mismatch warning. + # Warning: This would effectively fail if the TIME_ZONE defined in the + # settings has the same UTC offset as "Asia/Singapore" because the + # mismatch warning would be rightfully missing from the page. + self.selenium.find_elements_by_css_selector( + '.field-birthdate .timezonewarning') + + # Submit the form. + self.selenium.find_element_by_tag_name('form').submit() + self.wait_page_loaded() + + # Make sure that "now" in javascript is within 10 seconds + # from "now" on the server side. + member = models.Member.objects.get(name='test') + self.assertGreater(member.birthdate, now - error_margin) + self.assertLess(member.birthdate, now + error_margin) + +class DateTimePickerShortcutsSeleniumChromeTests(DateTimePickerShortcutsSeleniumFirefoxTests): + webdriver_class = 'selenium.webdriver.chrome.webdriver.WebDriver' + +class DateTimePickerShortcutsSeleniumIETests(DateTimePickerShortcutsSeleniumFirefoxTests): + webdriver_class = 'selenium.webdriver.ie.webdriver.WebDriver' + + @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) class HorizontalVerticalFilterSeleniumFirefoxTests(AdminSeleniumWebDriverTestCase): diff --git a/tests/backends/tests.py b/tests/backends/tests.py index b1bfa4cb21..ec592f0188 100644 --- a/tests/backends/tests.py +++ b/tests/backends/tests.py @@ -641,8 +641,7 @@ class FkConstraintsTests(TransactionTestCase): """ When constraint checks are disabled, should be able to write bad data without IntegrityErrors. """ - transaction.set_autocommit(False) - try: + with transaction.atomic(): # Create an Article. models.Article.objects.create(headline="Test article", pub_date=datetime.datetime(2010, 9, 4), reporter=self.r) # Retrive it from the DB @@ -654,17 +653,13 @@ class FkConstraintsTests(TransactionTestCase): connection.enable_constraint_checking() except IntegrityError: self.fail("IntegrityError should not have occurred.") - finally: - transaction.rollback() - finally: - transaction.set_autocommit(True) + transaction.set_rollback(True) def test_disable_constraint_checks_context_manager(self): """ When constraint checks are disabled (using context manager), should be able to write bad data without IntegrityErrors. """ - transaction.set_autocommit(False) - try: + with transaction.atomic(): # Create an Article. models.Article.objects.create(headline="Test article", pub_date=datetime.datetime(2010, 9, 4), reporter=self.r) # Retrive it from the DB @@ -675,31 +670,23 @@ class FkConstraintsTests(TransactionTestCase): a.save() except IntegrityError: self.fail("IntegrityError should not have occurred.") - finally: - transaction.rollback() - finally: - transaction.set_autocommit(True) + transaction.set_rollback(True) def test_check_constraints(self): """ Constraint checks should raise an IntegrityError when bad data is in the DB. """ - try: - transaction.set_autocommit(False) + with transaction.atomic(): # Create an Article. models.Article.objects.create(headline="Test article", pub_date=datetime.datetime(2010, 9, 4), reporter=self.r) # Retrive it from the DB a = models.Article.objects.get(headline="Test article") a.reporter_id = 30 - try: - with connection.constraint_checks_disabled(): - a.save() - with self.assertRaises(IntegrityError): - connection.check_constraints() - finally: - transaction.rollback() - finally: - transaction.set_autocommit(True) + with connection.constraint_checks_disabled(): + a.save() + with self.assertRaises(IntegrityError): + connection.check_constraints() + transaction.set_rollback(True) class ThreadTests(TestCase): diff --git a/tests/basic/tests.py b/tests/basic/tests.py index 4b79c47be5..fb21b11279 100644 --- a/tests/basic/tests.py +++ b/tests/basic/tests.py @@ -6,7 +6,7 @@ import threading from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned from django.db import connections, DEFAULT_DB_ALIAS from django.db.models.fields import Field, FieldDoesNotExist -from django.db.models.query import QuerySet, EmptyQuerySet, ValuesListQuerySet +from django.db.models.query import QuerySet, EmptyQuerySet, ValuesListQuerySet, MAX_GET_RESULTS from django.test import TestCase, TransactionTestCase, skipIfDBFeature, skipUnlessDBFeature from django.utils import six from django.utils.translation import ugettext_lazy @@ -155,6 +155,28 @@ class ModelTest(TestCase): pub_date__month=7, ) + def test_multiple_objects_max_num_fetched(self): + """ + #6785 - get() should fetch a limited number of results. + """ + Article.objects.bulk_create( + Article(headline='Area %s' % i, pub_date=datetime(2005, 7, 28)) + for i in range(MAX_GET_RESULTS) + ) + six.assertRaisesRegex(self, + MultipleObjectsReturned, + "get\(\) returned more than one Article -- it returned %d!" % MAX_GET_RESULTS, + Article.objects.get, + headline__startswith='Area', + ) + Article.objects.create(headline='Area %s' % MAX_GET_RESULTS, pub_date=datetime(2005, 7, 28)) + six.assertRaisesRegex(self, + MultipleObjectsReturned, + "get\(\) returned more than one Article -- it returned more than %d!" % MAX_GET_RESULTS, + Article.objects.get, + headline__startswith='Area', + ) + def test_object_creation(self): # Create an Article. a = Article( diff --git a/tests/db_backends/__init__.py b/tests/db_backends/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/db_backends/__init__.py diff --git a/tests/db_backends/tests.py b/tests/db_backends/tests.py new file mode 100644 index 0000000000..052ee87254 --- /dev/null +++ b/tests/db_backends/tests.py @@ -0,0 +1,36 @@ +from django.test import TestCase +from django.db.backends import BaseDatabaseWrapper + + +class DummyDatabaseWrapper(BaseDatabaseWrapper): + pass + + +class DummyObject(object): + alias = None + + +class DbBackendTests(TestCase): + def test_compare_db_wrapper_with_another_object(self): + wrapper = BaseDatabaseWrapper({}) + self.assertFalse(wrapper == 'not-a-db-wrapper') + + def test_compare_db_wrapper_with_another_object_with_alias(self): + wrapper = BaseDatabaseWrapper({}) + obj = DummyObject() + obj.alias = wrapper.alias = 'foobar' + self.assertFalse(wrapper == obj) + + def test_negate_compare_db_wrapper_with_another_object(self): + wrapper = BaseDatabaseWrapper({}) + self.assertTrue(wrapper != 'not-a-db-wrapper') + + def test_compare_db_wrappers(self): + wrapper1 = DummyDatabaseWrapper({}) + wrapper2 = BaseDatabaseWrapper({}) + + wrapper1.alias = wrapper2.alias = 'foo' + self.assertTrue(wrapper1 == wrapper2) + + wrapper1.alias = 'bar' + self.assertFalse(wrapper1 == wrapper2) diff --git a/tests/file_uploads/tests.py b/tests/file_uploads/tests.py index 66bc4465a2..f5a9c10be4 100644 --- a/tests/file_uploads/tests.py +++ b/tests/file_uploads/tests.py @@ -187,6 +187,27 @@ class FileUploadTests(TestCase): got = json.loads(self.client.request(**r).content.decode('utf-8')) self.assertTrue(len(got['file']) < 256, "Got a long file name (%s characters)." % len(got['file'])) + def test_content_type_extra(self): + """Uploaded files may have content type parameters available.""" + tdir = tempfile.gettempdir() + + no_content_type = tempfile.NamedTemporaryFile(suffix=".ctype_extra", dir=tdir) + no_content_type.write(b'something') + no_content_type.seek(0) + + simple_file = tempfile.NamedTemporaryFile(suffix=".ctype_extra", dir=tdir) + simple_file.write(b'something') + simple_file.seek(0) + simple_file.content_type = 'text/plain; test-key=test_value' + + response = self.client.post('/file_uploads/echo_content_type_extra/', { + 'no_content_type': no_content_type, + 'simple_file': simple_file, + }) + received = json.loads(response.content.decode('utf-8')) + self.assertEqual(received['no_content_type'], {}) + self.assertEqual(received['simple_file'], {'test-key': 'test_value'}) + def test_truncated_multipart_handled_gracefully(self): """ If passed an incomplete multipart message, MultiPartParser does not diff --git a/tests/file_uploads/urls.py b/tests/file_uploads/urls.py index fc5576828f..96efaaa5d8 100644 --- a/tests/file_uploads/urls.py +++ b/tests/file_uploads/urls.py @@ -10,6 +10,7 @@ urlpatterns = patterns('', (r'^verify/$', views.file_upload_view_verify), (r'^unicode_name/$', views.file_upload_unicode_name), (r'^echo/$', views.file_upload_echo), + (r'^echo_content_type_extra/$', views.file_upload_content_type_extra), (r'^echo_content/$', views.file_upload_echo_content), (r'^quota/$', views.file_upload_quota), (r'^quota/broken/$', views.file_upload_quota_broken), diff --git a/tests/file_uploads/views.py b/tests/file_uploads/views.py index eb7b654c09..d1bd2cf44e 100644 --- a/tests/file_uploads/views.py +++ b/tests/file_uploads/views.py @@ -7,7 +7,7 @@ import os from django.core.files.uploadedfile import UploadedFile from django.http import HttpResponse, HttpResponseServerError from django.utils import six -from django.utils.encoding import force_bytes +from django.utils.encoding import force_bytes, smart_str from .models import FileModel from .tests import UNICODE_FILENAME, UPLOAD_TO @@ -136,3 +136,14 @@ def file_upload_filename_case_view(request): obj = FileModel() obj.testfile.save(file.name, file) return HttpResponse('%d' % obj.pk) + +def file_upload_content_type_extra(request): + """ + Simple view to echo back extra content-type parameters. + """ + params = {} + for file_name, uploadedfile in request.FILES.items(): + params[file_name] = dict([ + (k, smart_str(v)) for k, v in uploadedfile.content_type_extra.items() + ]) + return HttpResponse(json.dumps(params)) diff --git a/tests/fixtures_model_package/tests.py b/tests/fixtures_model_package/tests.py index 4b00e6dfb9..ad82267da3 100644 --- a/tests/fixtures_model_package/tests.py +++ b/tests/fixtures_model_package/tests.py @@ -30,8 +30,7 @@ class TestNoInitialDataLoading(TransactionTestCase): available_apps = ['fixtures_model_package'] def test_syncdb(self): - transaction.set_autocommit(False) - try: + with transaction.atomic(): Book.objects.all().delete() management.call_command( @@ -40,9 +39,6 @@ class TestNoInitialDataLoading(TransactionTestCase): load_initial_data=False ) self.assertQuerysetEqual(Book.objects.all(), []) - transaction.rollback() - finally: - transaction.set_autocommit(True) def test_flush(self): @@ -54,8 +50,7 @@ class TestNoInitialDataLoading(TransactionTestCase): lambda a: a.name ) - transaction.set_autocommit(False) - try: + with transaction.atomic(): management.call_command( 'flush', verbosity=0, @@ -63,9 +58,6 @@ class TestNoInitialDataLoading(TransactionTestCase): load_initial_data=False ) self.assertQuerysetEqual(Book.objects.all(), []) - transaction.rollback() - finally: - transaction.set_autocommit(True) class FixtureTestCase(TestCase): diff --git a/tests/fixtures_regress/tests.py b/tests/fixtures_regress/tests.py index a9d67ec9a2..a6bad5716d 100644 --- a/tests/fixtures_regress/tests.py +++ b/tests/fixtures_regress/tests.py @@ -660,22 +660,15 @@ class TestTicket11101(TransactionTestCase): 'django.contrib.contenttypes', ] - def ticket_11101(self): - management.call_command( - 'loaddata', - 'thingy.json', - verbosity=0, - ) - self.assertEqual(Thingy.objects.count(), 1) - transaction.rollback() - self.assertEqual(Thingy.objects.count(), 0) - transaction.commit() - @skipUnlessDBFeature('supports_transactions') def test_ticket_11101(self): """Test that fixtures can be rolled back (ticket #11101).""" - transaction.set_autocommit(False) - try: - self.ticket_11101() - finally: - transaction.set_autocommit(True) + with transaction.atomic(): + management.call_command( + 'loaddata', + 'thingy.json', + verbosity=0, + ) + self.assertEqual(Thingy.objects.count(), 1) + transaction.set_rollback(True) + self.assertEqual(Thingy.objects.count(), 0) diff --git a/tests/forms_tests/tests/test_extra.py b/tests/forms_tests/tests/test_extra.py index ea0f063c30..d439e2223c 100644 --- a/tests/forms_tests/tests/test_extra.py +++ b/tests/forms_tests/tests/test_extra.py @@ -391,7 +391,7 @@ class FormsExtraTestCase(TestCase, AssertFormErrorsMixin): def decompress(self, value): if value: data = value.split(',') - return [data[0], data[1], datetime.datetime.strptime(data[2], "%Y-%m-%d %H:%M:%S")] + return [data[0], list(data[1]), datetime.datetime.strptime(data[2], "%Y-%m-%d %H:%M:%S")] return [None, None, None] def format_output(self, rendered_widgets): diff --git a/tests/forms_tests/tests/test_fields.py b/tests/forms_tests/tests/test_fields.py index f7d83503f4..f02593e488 100644 --- a/tests/forms_tests/tests/test_fields.py +++ b/tests/forms_tests/tests/test_fields.py @@ -374,6 +374,17 @@ class FieldsTests(SimpleTestCase): self.assertEqual(f.clean('.01'), Decimal(".01")) self.assertRaisesMessage(ValidationError, "'Ensure that there are no more than 0 digits before the decimal point.'", f.clean, '1.1') + def test_decimalfield_widget_attrs(self): + f = DecimalField(max_digits=6, decimal_places=2) + self.assertEqual(f.widget_attrs(Widget()), {}) + self.assertEqual(f.widget_attrs(NumberInput()), {'step': '0.01'}) + f = DecimalField(max_digits=10, decimal_places=0) + self.assertEqual(f.widget_attrs(NumberInput()), {'step': '1'}) + f = DecimalField(max_digits=19, decimal_places=19) + self.assertEqual(f.widget_attrs(NumberInput()), {'step': '1e-19'}) + f = DecimalField(max_digits=20) + self.assertEqual(f.widget_attrs(NumberInput()), {'step': 'any'}) + def test_decimalfield_localized(self): """ Make sure localized DecimalField's widget renders to a text input with @@ -1236,6 +1247,7 @@ class FieldsTests(SimpleTestCase): def test_splitdatetimefield_changed(self): f = SplitDateTimeField(input_date_formats=['%d/%m/%Y']) + self.assertFalse(f._has_changed(['11/01/2012', '09:18:15'], ['11/01/2012', '09:18:15'])) self.assertTrue(f._has_changed(datetime.datetime(2008, 5, 6, 12, 40, 00), ['2008-05-06', '12:40:00'])) self.assertFalse(f._has_changed(datetime.datetime(2008, 5, 6, 12, 40, 00), ['06/05/2008', '12:40'])) self.assertTrue(f._has_changed(datetime.datetime(2008, 5, 6, 12, 40, 00), ['06/05/2008', '12:41'])) diff --git a/tests/forms_tests/views.py b/tests/forms_tests/views.py index 4bf384363c..2081e0af14 100644 --- a/tests/forms_tests/views.py +++ b/tests/forms_tests/views.py @@ -6,3 +6,4 @@ from .models import Article class ArticleFormView(UpdateView): model = Article success_url = '/' + fields = '__all__' diff --git a/tests/get_or_create/tests.py b/tests/get_or_create/tests.py index 847a6dec01..0f766ab128 100644 --- a/tests/get_or_create/tests.py +++ b/tests/get_or_create/tests.py @@ -131,3 +131,68 @@ class GetOrCreateThroughManyToMany(TestCase): Tag.objects.create(text='foo') a_thing = Thing.objects.create(name='a') self.assertRaises(IntegrityError, a_thing.tags.get_or_create, text='foo') + + +class UpdateOrCreateTests(TestCase): + + def test_update(self): + Person.objects.create( + first_name='John', last_name='Lennon', birthday=date(1940, 10, 9) + ) + p, created = Person.objects.update_or_create( + first_name='John', last_name='Lennon', defaults={ + 'birthday': date(1940, 10, 10) + } + ) + self.assertFalse(created) + self.assertEqual(p.first_name, 'John') + self.assertEqual(p.last_name, 'Lennon') + self.assertEqual(p.birthday, date(1940, 10, 10)) + + def test_create(self): + p, created = Person.objects.update_or_create( + first_name='John', last_name='Lennon', defaults={ + 'birthday': date(1940, 10, 10) + } + ) + self.assertTrue(created) + self.assertEqual(p.first_name, 'John') + self.assertEqual(p.last_name, 'Lennon') + self.assertEqual(p.birthday, date(1940, 10, 10)) + + def test_create_twice(self): + params = { + 'first_name': 'John', + 'last_name': 'Lennon', + 'birthday': date(1940, 10, 10), + } + Person.objects.update_or_create(**params) + # If we execute the exact same statement, it won't create a Person. + p, created = Person.objects.update_or_create(**params) + self.assertFalse(created) + + def test_integrity(self): + # If you don't specify a value or default value for all required + # fields, you will get an error. + self.assertRaises(IntegrityError, + Person.objects.update_or_create, first_name="Tom", last_name="Smith") + + def test_mananual_primary_key_test(self): + # If you specify an existing primary key, but different other fields, + # then you will get an error and data will not be updated. + ManualPrimaryKeyTest.objects.create(id=1, data="Original") + self.assertRaises(IntegrityError, + ManualPrimaryKeyTest.objects.update_or_create, id=1, data="Different" + ) + self.assertEqual(ManualPrimaryKeyTest.objects.get(id=1).data, "Original") + + def test_error_contains_full_traceback(self): + # update_or_create should raise IntegrityErrors with the full traceback. + # This is tested by checking that a known method call is in the traceback. + # We cannot use assertRaises/assertRaises here because we need to inspect + # the actual traceback. Refs #16340. + try: + ManualPrimaryKeyTest.objects.update_or_create(id=1, data="Different") + except IntegrityError as e: + formatted_traceback = traceback.format_exc() + self.assertIn('obj.save', formatted_traceback) diff --git a/tests/logging_tests/tests.py b/tests/logging_tests/tests.py index cae2860306..8ba4702aba 100644 --- a/tests/logging_tests/tests.py +++ b/tests/logging_tests/tests.py @@ -19,7 +19,6 @@ from admin_scripts.tests import AdminScriptTestCase from .logconfig import MyEmailBackend -PYVERS = sys.version_info[:2] # logging config prior to using filter with mail_admins OLD_LOGGING = { @@ -87,7 +86,6 @@ class DefaultLoggingTest(TestCase): self.logger.error("Hey, this is an error.") self.assertEqual(output.getvalue(), 'Hey, this is an error.\n') -@skipUnless(PYVERS > (2,6), "warnings captured only in Python >= 2.7") class WarningLoggerTests(TestCase): """ Tests that warnings output for DeprecationWarnings is enabled diff --git a/tests/mail/tests.py b/tests/mail/tests.py index c90dc7e22a..822c572b0f 100644 --- a/tests/mail/tests.py +++ b/tests/mail/tests.py @@ -9,6 +9,8 @@ import smtpd import sys import tempfile import threading +from smtplib import SMTPException +from ssl import SSLError from django.core import mail from django.core.mail import (EmailMessage, mail_admins, mail_managers, @@ -621,11 +623,23 @@ class ConsoleBackendTests(BaseEmailBackendTests, TestCase): self.assertTrue(s.getvalue().startswith('Content-Type: text/plain; charset="utf-8"\nMIME-Version: 1.0\nContent-Transfer-Encoding: 7bit\nSubject: Subject\nFrom: from@example.com\nTo: to@example.com\nDate: ')) +class FakeSMTPChannel(smtpd.SMTPChannel): + + def collect_incoming_data(self, data): + try: + super(FakeSMTPChannel, self).collect_incoming_data(data) + except UnicodeDecodeError: + # ignore decode error in SSL/TLS connection tests as we only care + # whether the connection attempt was made + pass + + class FakeSMTPServer(smtpd.SMTPServer, threading.Thread): """ Asyncore SMTP server wrapped into a thread. Based on DummyFTPServer from: http://svn.python.org/view/python/branches/py3k/Lib/test/test_ftplib.py?revision=86061&view=markup """ + channel_class = FakeSMTPChannel def __init__(self, *args, **kwargs): threading.Thread.__init__(self) @@ -738,3 +752,44 @@ class SMTPBackendTests(BaseEmailBackendTests, TestCase): backend.close() except Exception as e: self.fail("close() unexpectedly raised an exception: %s" % e) + + @override_settings(EMAIL_USE_TLS=True) + def test_email_tls_use_settings(self): + backend = smtp.EmailBackend() + self.assertTrue(backend.use_tls) + + @override_settings(EMAIL_USE_TLS=True) + def test_email_tls_override_settings(self): + backend = smtp.EmailBackend(use_tls=False) + self.assertFalse(backend.use_tls) + + def test_email_tls_default_disabled(self): + backend = smtp.EmailBackend() + self.assertFalse(backend.use_tls) + + @override_settings(EMAIL_USE_SSL=True) + def test_email_ssl_use_settings(self): + backend = smtp.EmailBackend() + self.assertTrue(backend.use_ssl) + + @override_settings(EMAIL_USE_SSL=True) + def test_email_ssl_override_settings(self): + backend = smtp.EmailBackend(use_ssl=False) + self.assertFalse(backend.use_ssl) + + def test_email_ssl_default_disabled(self): + backend = smtp.EmailBackend() + self.assertFalse(backend.use_ssl) + + @override_settings(EMAIL_USE_TLS=True) + def test_email_tls_attempts_starttls(self): + backend = smtp.EmailBackend() + self.assertTrue(backend.use_tls) + self.assertRaisesMessage(SMTPException, + 'STARTTLS extension not supported by server.', backend.open) + + @override_settings(EMAIL_USE_SSL=True) + def test_email_ssl_attempts_ssl_connection(self): + backend = smtp.EmailBackend() + self.assertTrue(backend.use_ssl) + self.assertRaises(SSLError, backend.open) diff --git a/tests/model_fields/tests.py b/tests/model_fields/tests.py index 6546c49ec7..1ce5eba3e9 100644 --- a/tests/model_fields/tests.py +++ b/tests/model_fields/tests.py @@ -468,3 +468,16 @@ class BinaryFieldTests(test.TestCase): def test_max_length(self): dm = DataModel(short_data=self.binary_data*4) self.assertRaises(ValidationError, dm.full_clean) + +class GenericIPAddressFieldTests(test.TestCase): + def test_genericipaddressfield_formfield_protocol(self): + """ + Test that GenericIPAddressField with a specified protocol does not + generate a formfield with no specified protocol. See #20740. + """ + model_field = models.GenericIPAddressField(protocol='IPv4') + form_field = model_field.formfield() + self.assertRaises(ValidationError, form_field.clean, '::1') + model_field = models.GenericIPAddressField(protocol='IPv6') + form_field = model_field.formfield() + self.assertRaises(ValidationError, form_field.clean, '127.0.0.1') diff --git a/tests/model_formsets/tests.py b/tests/model_formsets/tests.py index 09a3ba1778..4411bbf59a 100644 --- a/tests/model_formsets/tests.py +++ b/tests/model_formsets/tests.py @@ -576,7 +576,7 @@ class ModelFormsetTest(TestCase): formset = AuthorBooksFormSet2(instance=author) self.assertEqual(len(formset.forms), 1) self.assertHTMLEqual(formset.forms[0].as_p(), - '<p><label for="id_bookwithcustompk_set-0-my_pk">My pk:</label> <input id="id_bookwithcustompk_set-0-my_pk" type="number" name="bookwithcustompk_set-0-my_pk" /></p>\n' + '<p><label for="id_bookwithcustompk_set-0-my_pk">My pk:</label> <input id="id_bookwithcustompk_set-0-my_pk" type="number" name="bookwithcustompk_set-0-my_pk" step="1" /></p>\n' '<p><label for="id_bookwithcustompk_set-0-title">Title:</label> <input id="id_bookwithcustompk_set-0-title" type="text" name="bookwithcustompk_set-0-title" maxlength="100" /><input type="hidden" name="bookwithcustompk_set-0-author" value="1" id="id_bookwithcustompk_set-0-author" /></p>') data = { diff --git a/tests/model_package/models/article.py b/tests/model_package/models/article.py index f70c971c7e..4dfcdc1056 100644 --- a/tests/model_package/models/article.py +++ b/tests/model_package/models/article.py @@ -6,6 +6,3 @@ class Article(models.Model): sites = models.ManyToManyField(Site) headline = models.CharField(max_length=100) publications = models.ManyToManyField("model_package.Publication", null=True, blank=True,) - - class Meta: - app_label = 'model_package' diff --git a/tests/model_package/models/publication.py b/tests/model_package/models/publication.py index 29828b9295..b0a49a94a1 100644 --- a/tests/model_package/models/publication.py +++ b/tests/model_package/models/publication.py @@ -3,6 +3,3 @@ from django.db import models class Publication(models.Model): title = models.CharField(max_length=30) - - class Meta: - app_label = 'model_package' diff --git a/tests/model_package/tests.py b/tests/model_package/tests.py index 5cf464858b..5d856a9608 100644 --- a/tests/model_package/tests.py +++ b/tests/model_package/tests.py @@ -14,9 +14,6 @@ class Advertisment(models.Model): "model_package.Publication", null=True, blank=True ) - class Meta: - app_label = 'model_package' - class ModelPackageTests(TestCase): def test_model_packages(self): diff --git a/tests/queries/tests.py b/tests/queries/tests.py index 6520eb0176..87d54b637e 100644 --- a/tests/queries/tests.py +++ b/tests/queries/tests.py @@ -2148,13 +2148,6 @@ class ConditionalTests(BaseQuerysetTest): t4 = Tag.objects.create(name='t4', parent=t3) t5 = Tag.objects.create(name='t5', parent=t3) - - # In Python 2.6 beta releases, exceptions raised in __len__ are swallowed - # (Python issue 1242657), so these cases return an empty list, rather than - # raising an exception. Not a lot we can do about that, unfortunately, due to - # the way Python handles list() calls internally. Thus, we skip the tests for - # Python 2.6. - @unittest.skipIf(sys.version_info[:2] == (2, 6), "Python version is 2.6") def test_infinite_loop(self): # If you're not careful, it's possible to introduce infinite loops via # default ordering on foreign keys in a cycle. We detect that. diff --git a/tests/requests/tests.py b/tests/requests/tests.py index b00eec87a9..8c56e48f58 100644 --- a/tests/requests/tests.py +++ b/tests/requests/tests.py @@ -3,6 +3,7 @@ from __future__ import unicode_literals from datetime import datetime, timedelta from io import BytesIO +from itertools import chain import time from unittest import skipIf import warnings @@ -122,221 +123,6 @@ class RequestsTests(SimpleTestCase): self.assertEqual(request.build_absolute_uri(location="/path/with:colons"), 'http://www.example.com/path/with:colons') - @override_settings( - USE_X_FORWARDED_HOST=False, - ALLOWED_HOSTS=[ - 'forward.com', 'example.com', 'internal.com', '12.34.56.78', - '[2001:19f0:feee::dead:beef:cafe]', 'xn--4ca9at.com', - '.multitenant.com', 'INSENSITIVE.com', - ]) - def test_http_get_host(self): - # Check if X_FORWARDED_HOST is provided. - request = HttpRequest() - request.META = { - 'HTTP_X_FORWARDED_HOST': 'forward.com', - 'HTTP_HOST': 'example.com', - 'SERVER_NAME': 'internal.com', - 'SERVER_PORT': 80, - } - # X_FORWARDED_HOST is ignored. - self.assertEqual(request.get_host(), 'example.com') - - # Check if X_FORWARDED_HOST isn't provided. - request = HttpRequest() - request.META = { - 'HTTP_HOST': 'example.com', - 'SERVER_NAME': 'internal.com', - 'SERVER_PORT': 80, - } - self.assertEqual(request.get_host(), 'example.com') - - # Check if HTTP_HOST isn't provided. - request = HttpRequest() - request.META = { - 'SERVER_NAME': 'internal.com', - 'SERVER_PORT': 80, - } - self.assertEqual(request.get_host(), 'internal.com') - - # Check if HTTP_HOST isn't provided, and we're on a nonstandard port - request = HttpRequest() - request.META = { - 'SERVER_NAME': 'internal.com', - 'SERVER_PORT': 8042, - } - self.assertEqual(request.get_host(), 'internal.com:8042') - - # Poisoned host headers are rejected as suspicious - legit_hosts = [ - 'example.com', - 'example.com:80', - '12.34.56.78', - '12.34.56.78:443', - '[2001:19f0:feee::dead:beef:cafe]', - '[2001:19f0:feee::dead:beef:cafe]:8080', - 'xn--4ca9at.com', # Punnycode for öäü.com - 'anything.multitenant.com', - 'multitenant.com', - 'insensitive.com', - ] - - poisoned_hosts = [ - 'example.com@evil.tld', - 'example.com:dr.frankenstein@evil.tld', - 'example.com:dr.frankenstein@evil.tld:80', - 'example.com:80/badpath', - 'example.com: recovermypassword.com', - 'other.com', # not in ALLOWED_HOSTS - ] - - for host in legit_hosts: - request = HttpRequest() - request.META = { - 'HTTP_HOST': host, - } - request.get_host() - - for host in poisoned_hosts: - with self.assertRaises(SuspiciousOperation): - request = HttpRequest() - request.META = { - 'HTTP_HOST': host, - } - request.get_host() - - @override_settings(USE_X_FORWARDED_HOST=True, ALLOWED_HOSTS=['*']) - def test_http_get_host_with_x_forwarded_host(self): - # Check if X_FORWARDED_HOST is provided. - request = HttpRequest() - request.META = { - 'HTTP_X_FORWARDED_HOST': 'forward.com', - 'HTTP_HOST': 'example.com', - 'SERVER_NAME': 'internal.com', - 'SERVER_PORT': 80, - } - # X_FORWARDED_HOST is obeyed. - self.assertEqual(request.get_host(), 'forward.com') - - # Check if X_FORWARDED_HOST isn't provided. - request = HttpRequest() - request.META = { - 'HTTP_HOST': 'example.com', - 'SERVER_NAME': 'internal.com', - 'SERVER_PORT': 80, - } - self.assertEqual(request.get_host(), 'example.com') - - # Check if HTTP_HOST isn't provided. - request = HttpRequest() - request.META = { - 'SERVER_NAME': 'internal.com', - 'SERVER_PORT': 80, - } - self.assertEqual(request.get_host(), 'internal.com') - - # Check if HTTP_HOST isn't provided, and we're on a nonstandard port - request = HttpRequest() - request.META = { - 'SERVER_NAME': 'internal.com', - 'SERVER_PORT': 8042, - } - self.assertEqual(request.get_host(), 'internal.com:8042') - - # Poisoned host headers are rejected as suspicious - legit_hosts = [ - 'example.com', - 'example.com:80', - '12.34.56.78', - '12.34.56.78:443', - '[2001:19f0:feee::dead:beef:cafe]', - '[2001:19f0:feee::dead:beef:cafe]:8080', - 'xn--4ca9at.com', # Punnycode for öäü.com - ] - - poisoned_hosts = [ - 'example.com@evil.tld', - 'example.com:dr.frankenstein@evil.tld', - 'example.com:dr.frankenstein@evil.tld:80', - 'example.com:80/badpath', - 'example.com: recovermypassword.com', - ] - - for host in legit_hosts: - request = HttpRequest() - request.META = { - 'HTTP_HOST': host, - } - request.get_host() - - for host in poisoned_hosts: - with self.assertRaises(SuspiciousOperation): - request = HttpRequest() - request.META = { - 'HTTP_HOST': host, - } - request.get_host() - - - @override_settings(DEBUG=True, ALLOWED_HOSTS=[]) - def test_host_validation_disabled_in_debug_mode(self): - """If ALLOWED_HOSTS is empty and DEBUG is True, all hosts pass.""" - request = HttpRequest() - request.META = { - 'HTTP_HOST': 'example.com', - } - self.assertEqual(request.get_host(), 'example.com') - - - @override_settings(ALLOWED_HOSTS=[]) - def test_get_host_suggestion_of_allowed_host(self): - """get_host() makes helpful suggestions if a valid-looking host is not in ALLOWED_HOSTS.""" - msg_invalid_host = "Invalid HTTP_HOST header: %r." - msg_suggestion = msg_invalid_host + "You may need to add %r to ALLOWED_HOSTS." - - for host in [ # Valid-looking hosts - 'example.com', - '12.34.56.78', - '[2001:19f0:feee::dead:beef:cafe]', - 'xn--4ca9at.com', # Punnycode for öäü.com - ]: - request = HttpRequest() - request.META = {'HTTP_HOST': host} - self.assertRaisesMessage( - SuspiciousOperation, - msg_suggestion % (host, host), - request.get_host - ) - - for domain, port in [ # Valid-looking hosts with a port number - ('example.com', 80), - ('12.34.56.78', 443), - ('[2001:19f0:feee::dead:beef:cafe]', 8080), - ]: - host = '%s:%s' % (domain, port) - request = HttpRequest() - request.META = {'HTTP_HOST': host} - self.assertRaisesMessage( - SuspiciousOperation, - msg_suggestion % (host, domain), - request.get_host - ) - - for host in [ # Invalid hosts - 'example.com@evil.tld', - 'example.com:dr.frankenstein@evil.tld', - 'example.com:dr.frankenstein@evil.tld:80', - 'example.com:80/badpath', - 'example.com: recovermypassword.com', - ]: - request = HttpRequest() - request.META = {'HTTP_HOST': host} - self.assertRaisesMessage( - SuspiciousOperation, - msg_invalid_host % host, - request.get_host - ) - - def test_near_expiration(self): "Cookie will expire when an near expiration time is provided" response = HttpResponse() @@ -678,8 +464,209 @@ class RequestsTests(SimpleTestCase): request.FILES +class HostValidationTests(SimpleTestCase): + poisoned_hosts = [ + 'example.com@evil.tld', + 'example.com:dr.frankenstein@evil.tld', + 'example.com:dr.frankenstein@evil.tld:80', + 'example.com:80/badpath', + 'example.com: recovermypassword.com', + ] + + @override_settings( + USE_X_FORWARDED_HOST=False, + ALLOWED_HOSTS=[ + 'forward.com', 'example.com', 'internal.com', '12.34.56.78', + '[2001:19f0:feee::dead:beef:cafe]', 'xn--4ca9at.com', + '.multitenant.com', 'INSENSITIVE.com', + ]) + def test_http_get_host(self): + # Check if X_FORWARDED_HOST is provided. + request = HttpRequest() + request.META = { + 'HTTP_X_FORWARDED_HOST': 'forward.com', + 'HTTP_HOST': 'example.com', + 'SERVER_NAME': 'internal.com', + 'SERVER_PORT': 80, + } + # X_FORWARDED_HOST is ignored. + self.assertEqual(request.get_host(), 'example.com') + + # Check if X_FORWARDED_HOST isn't provided. + request = HttpRequest() + request.META = { + 'HTTP_HOST': 'example.com', + 'SERVER_NAME': 'internal.com', + 'SERVER_PORT': 80, + } + self.assertEqual(request.get_host(), 'example.com') + + # Check if HTTP_HOST isn't provided. + request = HttpRequest() + request.META = { + 'SERVER_NAME': 'internal.com', + 'SERVER_PORT': 80, + } + self.assertEqual(request.get_host(), 'internal.com') + + # Check if HTTP_HOST isn't provided, and we're on a nonstandard port + request = HttpRequest() + request.META = { + 'SERVER_NAME': 'internal.com', + 'SERVER_PORT': 8042, + } + self.assertEqual(request.get_host(), 'internal.com:8042') + + legit_hosts = [ + 'example.com', + 'example.com:80', + '12.34.56.78', + '12.34.56.78:443', + '[2001:19f0:feee::dead:beef:cafe]', + '[2001:19f0:feee::dead:beef:cafe]:8080', + 'xn--4ca9at.com', # Punnycode for öäü.com + 'anything.multitenant.com', + 'multitenant.com', + 'insensitive.com', + ] + + for host in legit_hosts: + request = HttpRequest() + request.META = { + 'HTTP_HOST': host, + } + request.get_host() + + # Poisoned host headers are rejected as suspicious + for host in chain(self.poisoned_hosts, ['other.com']): + with self.assertRaises(SuspiciousOperation): + request = HttpRequest() + request.META = { + 'HTTP_HOST': host, + } + request.get_host() + + @override_settings(USE_X_FORWARDED_HOST=True, ALLOWED_HOSTS=['*']) + def test_http_get_host_with_x_forwarded_host(self): + # Check if X_FORWARDED_HOST is provided. + request = HttpRequest() + request.META = { + 'HTTP_X_FORWARDED_HOST': 'forward.com', + 'HTTP_HOST': 'example.com', + 'SERVER_NAME': 'internal.com', + 'SERVER_PORT': 80, + } + # X_FORWARDED_HOST is obeyed. + self.assertEqual(request.get_host(), 'forward.com') + + # Check if X_FORWARDED_HOST isn't provided. + request = HttpRequest() + request.META = { + 'HTTP_HOST': 'example.com', + 'SERVER_NAME': 'internal.com', + 'SERVER_PORT': 80, + } + self.assertEqual(request.get_host(), 'example.com') + + # Check if HTTP_HOST isn't provided. + request = HttpRequest() + request.META = { + 'SERVER_NAME': 'internal.com', + 'SERVER_PORT': 80, + } + self.assertEqual(request.get_host(), 'internal.com') + + # Check if HTTP_HOST isn't provided, and we're on a nonstandard port + request = HttpRequest() + request.META = { + 'SERVER_NAME': 'internal.com', + 'SERVER_PORT': 8042, + } + self.assertEqual(request.get_host(), 'internal.com:8042') + + # Poisoned host headers are rejected as suspicious + legit_hosts = [ + 'example.com', + 'example.com:80', + '12.34.56.78', + '12.34.56.78:443', + '[2001:19f0:feee::dead:beef:cafe]', + '[2001:19f0:feee::dead:beef:cafe]:8080', + 'xn--4ca9at.com', # Punnycode for öäü.com + ] + + for host in legit_hosts: + request = HttpRequest() + request.META = { + 'HTTP_HOST': host, + } + request.get_host() + + for host in self.poisoned_hosts: + with self.assertRaises(SuspiciousOperation): + request = HttpRequest() + request.META = { + 'HTTP_HOST': host, + } + request.get_host() + + + @override_settings(DEBUG=True, ALLOWED_HOSTS=[]) + def test_host_validation_disabled_in_debug_mode(self): + """If ALLOWED_HOSTS is empty and DEBUG is True, all hosts pass.""" + request = HttpRequest() + request.META = { + 'HTTP_HOST': 'example.com', + } + self.assertEqual(request.get_host(), 'example.com') + + + @override_settings(ALLOWED_HOSTS=[]) + def test_get_host_suggestion_of_allowed_host(self): + """get_host() makes helpful suggestions if a valid-looking host is not in ALLOWED_HOSTS.""" + msg_invalid_host = "Invalid HTTP_HOST header: %r." + msg_suggestion = msg_invalid_host + "You may need to add %r to ALLOWED_HOSTS." + + for host in [ # Valid-looking hosts + 'example.com', + '12.34.56.78', + '[2001:19f0:feee::dead:beef:cafe]', + 'xn--4ca9at.com', # Punnycode for öäü.com + ]: + request = HttpRequest() + request.META = {'HTTP_HOST': host} + self.assertRaisesMessage( + SuspiciousOperation, + msg_suggestion % (host, host), + request.get_host + ) + + for domain, port in [ # Valid-looking hosts with a port number + ('example.com', 80), + ('12.34.56.78', 443), + ('[2001:19f0:feee::dead:beef:cafe]', 8080), + ]: + host = '%s:%s' % (domain, port) + request = HttpRequest() + request.META = {'HTTP_HOST': host} + self.assertRaisesMessage( + SuspiciousOperation, + msg_suggestion % (host, domain), + request.get_host + ) + + for host in self.poisoned_hosts: + request = HttpRequest() + request.META = {'HTTP_HOST': host} + self.assertRaisesMessage( + SuspiciousOperation, + msg_invalid_host % host, + request.get_host + ) + + @skipIf(connection.vendor == 'sqlite' - and connection.settings_dict['NAME'] in ('', ':memory:'), + and connection.settings_dict['TEST_NAME'] in (None, '', ':memory:'), "Cannot establish two connections to an in-memory SQLite database.") class DatabaseConnectionHandlingTests(TransactionTestCase): diff --git a/tests/requirements/base.txt b/tests/requirements/base.txt new file mode 100644 index 0000000000..89bd8230b2 --- /dev/null +++ b/tests/requirements/base.txt @@ -0,0 +1,6 @@ +bcrypt +docutils +Pillow +PyYAML +pytz +selenium diff --git a/tests/requirements/mysql.txt b/tests/requirements/mysql.txt new file mode 100644 index 0000000000..c7a2347407 --- /dev/null +++ b/tests/requirements/mysql.txt @@ -0,0 +1 @@ +MySQL-python diff --git a/tests/requirements/oracle.txt b/tests/requirements/oracle.txt new file mode 100644 index 0000000000..ae5b7349cd --- /dev/null +++ b/tests/requirements/oracle.txt @@ -0,0 +1 @@ +cx_oracle diff --git a/tests/requirements/postgres.txt b/tests/requirements/postgres.txt new file mode 100644 index 0000000000..658130bb2c --- /dev/null +++ b/tests/requirements/postgres.txt @@ -0,0 +1 @@ +psycopg2 diff --git a/tests/requirements/py2.txt b/tests/requirements/py2.txt new file mode 100644 index 0000000000..a9f6804039 --- /dev/null +++ b/tests/requirements/py2.txt @@ -0,0 +1,2 @@ +-r base.txt +python-memcached diff --git a/tests/requirements/py3.txt b/tests/requirements/py3.txt new file mode 100644 index 0000000000..ced3eed101 --- /dev/null +++ b/tests/requirements/py3.txt @@ -0,0 +1,2 @@ +-r base.txt +python3-memcached diff --git a/tests/runtests.py b/tests/runtests.py index b604155190..8d5b375fb3 100755 --- a/tests/runtests.py +++ b/tests/runtests.py @@ -22,6 +22,8 @@ TEMP_DIR = tempfile.mkdtemp(prefix='django_') os.environ['DJANGO_TEST_TEMP_DIR'] = TEMP_DIR SUBDIRS_TO_SKIP = [ + 'data', + 'requirements', 'templates', 'test_discovery_sample', 'test_discovery_sample2', @@ -49,10 +51,18 @@ ALWAYS_INSTALLED_APPS = [ def get_test_modules(): + from django.contrib.gis.tests.utils import HAS_SPATIAL_DB modules = [] - for modpath, dirpath in ( + discovery_paths = [ (None, RUNTESTS_DIR), - (CONTRIB_MODULE_PATH, CONTRIB_DIR)): + (CONTRIB_MODULE_PATH, CONTRIB_DIR) + ] + if HAS_SPATIAL_DB: + discovery_paths.append( + ('django.contrib.gis.tests', os.path.join(CONTRIB_DIR, 'gis', 'tests')) + ) + + for modpath, dirpath in discovery_paths: for f in os.listdir(dirpath): if ('.' in f or # Python 3 byte code dirs (PEP 3147) @@ -64,6 +74,7 @@ def get_test_modules(): modules.append((modpath, f)) return modules + def get_installed(): from django.db.models.loading import get_apps return [app.__name__.rsplit('.', 1)[0] for app in get_apps()] @@ -125,14 +136,6 @@ def setup(verbosity, test_labels): bits = bits[:1] test_labels_set.add('.'.join(bits)) - # If GeoDjango, then we'll want to add in the test applications - # that are a part of its test suite. - from django.contrib.gis.tests.utils import HAS_SPATIAL_DB - if HAS_SPATIAL_DB: - from django.contrib.gis.tests import geo_apps - test_modules.extend(geo_apps()) - settings.INSTALLED_APPS.extend(['django.contrib.gis', 'django.contrib.sitemaps']) - for modpath, module_name in test_modules: if modpath: module_label = '.'.join([modpath, module_name]) diff --git a/tests/select_for_update/tests.py b/tests/select_for_update/tests.py index b8a8ad19bf..3204d74224 100644 --- a/tests/select_for_update/tests.py +++ b/tests/select_for_update/tests.py @@ -99,15 +99,8 @@ class SelectForUpdateTests(TransactionTestCase): list(Person.objects.all().select_for_update(nowait=True)) self.assertTrue(self.has_for_update_sql(connection, nowait=True)) - # In Python 2.6 beta and some final releases, exceptions raised in __len__ - # are swallowed (Python issue 1242657), so these cases return an empty - # list, rather than raising an exception. Not a lot we can do about that, - # unfortunately, due to the way Python handles list() calls internally. - # Python 2.6.1 is the "in the wild" version affected by this, so we skip - # the test for that version. @requires_threading @skipUnlessDBFeature('has_select_for_update_nowait') - @unittest.skipIf(sys.version_info[:3] == (2, 6, 1), "Python version is 2.6.1") def test_nowait_raises_error_on_block(self): """ If nowait is specified, we expect an error to be raised rather @@ -128,15 +121,8 @@ class SelectForUpdateTests(TransactionTestCase): self.end_blocking_transaction() self.assertIsInstance(status[-1], DatabaseError) - # In Python 2.6 beta and some final releases, exceptions raised in __len__ - # are swallowed (Python issue 1242657), so these cases return an empty - # list, rather than raising an exception. Not a lot we can do about that, - # unfortunately, due to the way Python handles list() calls internally. - # Python 2.6.1 is the "in the wild" version affected by this, so we skip - # the test for that version. @skipIfDBFeature('has_select_for_update_nowait') @skipUnlessDBFeature('has_select_for_update') - @unittest.skipIf(sys.version_info[:3] == (2, 6, 1), "Python version is 2.6.1") def test_unsupported_nowait_raises_error(self): """ If a SELECT...FOR UPDATE NOWAIT is run on a database backend diff --git a/tests/settings_tests/tests.py b/tests/settings_tests/tests.py index bfd8d607a9..87ed4d4f5e 100644 --- a/tests/settings_tests/tests.py +++ b/tests/settings_tests/tests.py @@ -203,6 +203,29 @@ class SettingsTests(TestCase): 'ALLOWED_INCLUDE_ROOTS', '/var/www/ssi/') +class TestComplexSettingOverride(TestCase): + def setUp(self): + self.old_warn_override_settings = signals.COMPLEX_OVERRIDE_SETTINGS.copy() + signals.COMPLEX_OVERRIDE_SETTINGS.add('TEST_WARN') + + def tearDown(self): + signals.COMPLEX_OVERRIDE_SETTINGS = self.old_warn_override_settings + self.assertFalse('TEST_WARN' in signals.COMPLEX_OVERRIDE_SETTINGS) + + def test_complex_override_warning(self): + """Regression test for #19031""" + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + + override = override_settings(TEST_WARN='override') + override.enable() + self.assertEqual('override', settings.TEST_WARN) + override.disable() + + self.assertEqual(len(w), 1) + self.assertEqual('Overriding setting TEST_WARN can lead to unexpected behaviour.', str(w[-1].message)) + + class TrailingSlashURLTests(TestCase): """ Tests for the MEDIA_URL and STATIC_URL settings. diff --git a/tests/syndication/feeds.py b/tests/syndication/feeds.py index 0956820bf0..1cd5c3d988 100644 --- a/tests/syndication/feeds.py +++ b/tests/syndication/feeds.py @@ -33,7 +33,10 @@ class TestRss2Feed(views.Feed): return "Overridden description: %s" % item def item_pubdate(self, item): - return item.date + return item.published + + def item_updateddate(self, item): + return item.updated item_author_name = 'Sally Smith' item_author_email = 'test@example.com' @@ -72,6 +75,17 @@ class TestAtomFeed(TestRss2Feed): subtitle = TestRss2Feed.description +class TestLatestFeed(TestRss2Feed): + """ + A feed where the latest entry date is an `updated` element. + """ + feed_type = feedgenerator.Atom1Feed + subtitle = TestRss2Feed.description + + def items(self): + return Entry.objects.exclude(pk=5) + + class ArticlesFeed(TestRss2Feed): """ A feed to test no link being defined. Articles have no get_absolute_url() @@ -115,7 +129,7 @@ class NaiveDatesFeed(TestAtomFeed): A feed with naive (non-timezone-aware) dates. """ def item_pubdate(self, item): - return item.date + return item.published class TZAwareDatesFeed(TestAtomFeed): @@ -126,7 +140,7 @@ class TZAwareDatesFeed(TestAtomFeed): # Provide a weird offset so that the test can know it's getting this # specific offset and not accidentally getting on from # settings.TIME_ZONE. - return item.date.replace(tzinfo=tzinfo.FixedOffset(42)) + return item.published.replace(tzinfo=tzinfo.FixedOffset(42)) class TestFeedUrlFeed(TestAtomFeed): diff --git a/tests/syndication/fixtures/feeddata.json b/tests/syndication/fixtures/feeddata.json index 167115c925..52e5028fcd 100644 --- a/tests/syndication/fixtures/feeddata.json +++ b/tests/syndication/fixtures/feeddata.json @@ -4,7 +4,8 @@ "pk": 1, "fields": { "title": "My first entry", - "date": "1850-01-01 12:30:00" + "updated": "1850-01-01 12:30:00", + "published": "1066-09-25 20:15:00" } }, { @@ -12,7 +13,8 @@ "pk": 2, "fields": { "title": "My second entry", - "date": "2008-01-02 12:30:00" + "updated": "2008-01-02 12:30:00", + "published": "2006-03-17 18:00:00" } }, { @@ -20,7 +22,8 @@ "pk": 3, "fields": { "title": "My third entry", - "date": "2008-01-02 13:30:00" + "updated": "2008-01-02 13:30:00", + "published": "2005-06-14 10:45:00" } }, { @@ -28,7 +31,17 @@ "pk": 4, "fields": { "title": "A & B < C > D", - "date": "2008-01-03 13:30:00" + "updated": "2008-01-03 13:30:00", + "published": "2005-11-25 12:11:23" + } + }, + { + "model": "syndication.entry", + "pk": 5, + "fields": { + "title": "My last entry", + "updated": "2013-01-20 00:00:00", + "published": "2013-03-25 20:00:00" } }, { diff --git a/tests/syndication/models.py b/tests/syndication/models.py index 10b3fe3a0c..166acd69ca 100644 --- a/tests/syndication/models.py +++ b/tests/syndication/models.py @@ -5,10 +5,11 @@ from django.utils.encoding import python_2_unicode_compatible @python_2_unicode_compatible class Entry(models.Model): title = models.CharField(max_length=200) - date = models.DateTimeField() + updated = models.DateTimeField() + published = models.DateTimeField() class Meta: - ordering = ('date',) + ordering = ('updated',) def __str__(self): return self.title @@ -24,4 +25,3 @@ class Article(models.Model): def __str__(self): return self.title - diff --git a/tests/syndication/tests.py b/tests/syndication/tests.py index 1627f7182b..d3b0058d53 100644 --- a/tests/syndication/tests.py +++ b/tests/syndication/tests.py @@ -58,7 +58,7 @@ class SyndicationFeedTest(FeedTestCase): chan = chan_elem[0] # Find the last build date - d = Entry.objects.latest('date').date + d = Entry.objects.latest('published').published ltz = tzinfo.LocalTimezone(d) last_build_date = rfc2822_date(d.replace(tzinfo=ltz)) @@ -88,7 +88,7 @@ class SyndicationFeedTest(FeedTestCase): ) # Find the pubdate of the first feed item - d = Entry.objects.get(pk=1).date + d = Entry.objects.get(pk=1).published ltz = tzinfo.LocalTimezone(d) pub_date = rfc2822_date(d.replace(tzinfo=ltz)) @@ -203,10 +203,61 @@ class SyndicationFeedTest(FeedTestCase): entries = feed.getElementsByTagName('entry') self.assertEqual(len(entries), Entry.objects.count()) for entry in entries: - self.assertChildNodes(entry, ['title', 'link', 'id', 'summary', 'category', 'updated', 'rights', 'author']) + self.assertChildNodes(entry, [ + 'title', + 'link', + 'id', + 'summary', + 'category', + 'updated', + 'published', + 'rights', + 'author', + ]) summary = entry.getElementsByTagName('summary')[0] self.assertEqual(summary.getAttribute('type'), 'html') + def test_atom_feed_published_and_updated_elements(self): + """ + Test that the published and updated elements are not + the same and now adhere to RFC 4287. + """ + response = self.client.get('/syndication/atom/') + feed = minidom.parseString(response.content).firstChild + entries = feed.getElementsByTagName('entry') + + published = entries[0].getElementsByTagName('published')[0].firstChild.wholeText + updated = entries[0].getElementsByTagName('updated')[0].firstChild.wholeText + + self.assertNotEqual(published, updated) + + def test_latest_post_date(self): + """ + Test that both the published and updated dates are + considered when determining the latest post date. + """ + # this feed has a `published` element with the latest date + response = self.client.get('/syndication/atom/') + feed = minidom.parseString(response.content).firstChild + updated = feed.getElementsByTagName('updated')[0].firstChild.wholeText + + d = Entry.objects.latest('published').published + ltz = tzinfo.LocalTimezone(d) + latest_published = rfc3339_date(d.replace(tzinfo=ltz)) + + self.assertEqual(updated, latest_published) + + # this feed has an `updated` element with the latest date + response = self.client.get('/syndication/latest/') + feed = minidom.parseString(response.content).firstChild + updated = feed.getElementsByTagName('updated')[0].firstChild.wholeText + + d = Entry.objects.exclude(pk=5).latest('updated').updated + ltz = tzinfo.LocalTimezone(d) + latest_updated = rfc3339_date(d.replace(tzinfo=ltz)) + + self.assertEqual(updated, latest_updated) + def test_custom_feed_generator(self): response = self.client.get('/syndication/custom/') feed = minidom.parseString(response.content).firstChild @@ -219,7 +270,18 @@ class SyndicationFeedTest(FeedTestCase): self.assertEqual(len(entries), Entry.objects.count()) for entry in entries: self.assertEqual(entry.getAttribute('bacon'), 'yum') - self.assertChildNodes(entry, ['title', 'link', 'id', 'summary', 'ministry', 'rights', 'author', 'updated', 'category']) + self.assertChildNodes(entry, [ + 'title', + 'link', + 'id', + 'summary', + 'ministry', + 'rights', + 'author', + 'updated', + 'published', + 'category', + ]) summary = entry.getElementsByTagName('summary')[0] self.assertEqual(summary.getAttribute('type'), 'html') @@ -245,7 +307,7 @@ class SyndicationFeedTest(FeedTestCase): doc = minidom.parseString(response.content) updated = doc.getElementsByTagName('updated')[0].firstChild.wholeText - d = Entry.objects.latest('date').date + d = Entry.objects.latest('published').published ltz = tzinfo.LocalTimezone(d) latest = rfc3339_date(d.replace(tzinfo=ltz)) @@ -257,12 +319,12 @@ class SyndicationFeedTest(FeedTestCase): """ response = self.client.get('/syndication/aware-dates/') doc = minidom.parseString(response.content) - updated = doc.getElementsByTagName('updated')[0].firstChild.wholeText - self.assertEqual(updated[-6:], '+00:42') + published = doc.getElementsByTagName('published')[0].firstChild.wholeText + self.assertEqual(published[-6:], '+00:42') def test_feed_last_modified_time(self): response = self.client.get('/syndication/naive-dates/') - self.assertEqual(response['Last-Modified'], 'Thu, 03 Jan 2008 19:30:00 GMT') + self.assertEqual(response['Last-Modified'], 'Tue, 26 Mar 2013 01:00:00 GMT') # No last-modified when feed has no item_pubdate response = self.client.get('/syndication/no_pubdate/') diff --git a/tests/syndication/urls.py b/tests/syndication/urls.py index 1dd7e92332..06a75a4e68 100644 --- a/tests/syndication/urls.py +++ b/tests/syndication/urls.py @@ -15,6 +15,7 @@ urlpatterns = patterns('django.contrib.syndication.views', (r'^syndication/rss091/$', feeds.TestRss091Feed()), (r'^syndication/no_pubdate/$', feeds.TestNoPubdateFeed()), (r'^syndication/atom/$', feeds.TestAtomFeed()), + (r'^syndication/latest/$', feeds.TestLatestFeed()), (r'^syndication/custom/$', feeds.TestCustomFeed()), (r'^syndication/naive-dates/$', feeds.NaiveDatesFeed()), (r'^syndication/aware-dates/$', feeds.TZAwareDatesFeed()), diff --git a/tests/template_tests/test_context.py b/tests/template_tests/test_context.py index 224b94d060..ca167a73f3 100644 --- a/tests/template_tests/test_context.py +++ b/tests/template_tests/test_context.py @@ -16,3 +16,12 @@ class ContextTests(TestCase): self.assertEqual(c.pop(), {"a": 2}) self.assertEqual(c["a"], 1) self.assertEqual(c.get("foo", 42), 42) + + with c.push(): + c['a'] = 2 + self.assertEqual(c['a'], 2) + self.assertEqual(c['a'], 1) + + with c.push(a=3): + self.assertEqual(c['a'], 3) + self.assertEqual(c['a'], 1) diff --git a/tests/test_runner/tests.py b/tests/test_runner/tests.py index 4e9e44bc12..72200e40d3 100644 --- a/tests/test_runner/tests.py +++ b/tests/test_runner/tests.py @@ -291,6 +291,65 @@ class DummyBackendTest(unittest.TestCase): db.connections = old_db_connections +class AliasedDefaultTestSetupTest(unittest.TestCase): + def test_setup_aliased_default_database(self): + """ + Test that setup_datebases() doesn't fail when 'default' is aliased + """ + runner_instance = runner.DiscoverRunner(verbosity=0) + old_db_connections = db.connections + try: + db.connections = db.ConnectionHandler({ + 'default': { + 'NAME': 'dummy' + }, + 'aliased': { + 'NAME': 'dummy' + } + }) + old_config = runner_instance.setup_databases() + runner_instance.teardown_databases(old_config) + except Exception as e: + self.fail("setup_databases/teardown_databases unexpectedly raised " + "an error: %s" % e) + finally: + db.connections = old_db_connections + + +class AliasedDatabaseTeardownTest(unittest.TestCase): + def test_setup_aliased_databases(self): + from django.db.backends.dummy.base import DatabaseCreation + + runner_instance = runner.DiscoverRunner(verbosity=0) + old_db_connections = db.connections + old_destroy_test_db = DatabaseCreation.destroy_test_db + old_create_test_db = DatabaseCreation.create_test_db + try: + destroyed_names = [] + DatabaseCreation.destroy_test_db = lambda self, old_database_name, verbosity=1: destroyed_names.append(old_database_name) + DatabaseCreation.create_test_db = lambda self, verbosity=1, autoclobber=False: self._get_test_db_name() + + db.connections = db.ConnectionHandler({ + 'default': { + 'ENGINE': 'django.db.backends.dummy', + 'NAME': 'dbname', + }, + 'other': { + 'ENGINE': 'django.db.backends.dummy', + 'NAME': 'dbname', + } + }) + + old_config = runner_instance.setup_databases() + runner_instance.teardown_databases(old_config) + + self.assertEqual(destroyed_names.count('dbname'), 1) + finally: + DatabaseCreation.create_test_db = old_create_test_db + DatabaseCreation.destroy_test_db = old_destroy_test_db + db.connections = old_db_connections + + class DeprecationDisplayTest(AdminScriptTestCase): # tests for 19546 def setUp(self): @@ -308,8 +367,6 @@ class DeprecationDisplayTest(AdminScriptTestCase): self.assertIn("DeprecationWarning: warning from test", err) self.assertIn("DeprecationWarning: module-level warning from deprecation_app", err) - @unittest.skipIf(sys.version_info[:2] == (2, 6), - "On Python 2.6, DeprecationWarnings are visible anyway") def test_runner_deprecation_verbosity_zero(self): args = ['test', '--settings=settings', '--verbosity=0'] out, err = self.run_django_admin(args) diff --git a/tests/test_utils/tests.py b/tests/test_utils/tests.py index 2c2bc24d72..24aa433dd3 100644 --- a/tests/test_utils/tests.py +++ b/tests/test_utils/tests.py @@ -2,13 +2,12 @@ from __future__ import absolute_import, unicode_literals import unittest -from unittest import skip from django.db import connection from django.forms import EmailField, IntegerField from django.http import HttpResponse from django.template.loader import render_to_string -from django.test import SimpleTestCase, TestCase, skipUnlessDBFeature +from django.test import SimpleTestCase, TestCase, skipIfDBFeature, skipUnlessDBFeature from django.test.html import HTMLParseError, parse_html from django.test.utils import CaptureQueriesContext, IgnoreAllDeprecationWarningsMixin from django.utils import six @@ -27,6 +26,29 @@ class SkippingTestCase(TestCase): self.assertRaises(ValueError, test_func) +class SkippingClassTestCase(TestCase): + def test_skip_class_unless_db_feature(self): + @skipUnlessDBFeature("__class__") + class NotSkippedTests(unittest.TestCase): + def test_dummy(self): + return + + @skipIfDBFeature("__class__") + class SkippedTests(unittest.TestCase): + def test_will_be_skipped(self): + self.fail("We should never arrive here.") + + test_suite = unittest.TestSuite() + test_suite.addTest(NotSkippedTests('test_dummy')) + try: + test_suite.addTest(SkippedTests('test_will_be_skipped')) + except unittest.SkipTest: + self.fail("SkipTest should not be raised at this stage") + result = unittest.TextTestRunner(stream=six.StringIO()).run(test_suite) + self.assertEqual(result.testsRun, 2) + self.assertEqual(len(result.skipped), 1) + + class AssertNumQueriesTests(TestCase): urls = 'test_utils.urls' @@ -561,7 +583,7 @@ class SkippingExtraTests(TestCase): with self.assertNumQueries(0): super(SkippingExtraTests, self).__call__(result) - @skip("Fixture loading should not be performed for skipped tests.") + @unittest.skip("Fixture loading should not be performed for skipped tests.") def test_fixtures_are_skipped(self): pass diff --git a/tests/transactions_regress/tests.py b/tests/transactions_regress/tests.py index bd9e78b033..01f8fc4186 100644 --- a/tests/transactions_regress/tests.py +++ b/tests/transactions_regress/tests.py @@ -191,10 +191,9 @@ class TestTransactionClosing(IgnoreDeprecationWarningsMixin, TransactionTestCase """ self.test_failing_query_transaction_closed() -@skipIf(connection.vendor == 'sqlite' and - (connection.settings_dict['NAME'] == ':memory:' or - not connection.settings_dict['NAME']), - 'Test uses multiple connections, but in-memory sqlite does not support this') +@skipIf(connection.vendor == 'sqlite' + and connection.settings_dict['TEST_NAME'] in (None, '', ':memory:'), + "Cannot establish two connections to an in-memory SQLite database.") class TestNewConnection(IgnoreDeprecationWarningsMixin, TransactionTestCase): """ Check that new connections don't have special behaviour. diff --git a/tests/validators/tests.py b/tests/validators/tests.py index ec3e2dc8c1..9be49d7726 100644 --- a/tests/validators/tests.py +++ b/tests/validators/tests.py @@ -46,6 +46,7 @@ TEST_DATA = ( (validate_email, 'example@-invalid.com', ValidationError), (validate_email, 'example@inv-.alid-.com', ValidationError), (validate_email, 'example@inv-.-alid.com', ValidationError), + (validate_email, 'test@example.com\n\n<script src="x.js">', ValidationError), # Quoted-string format (CR not allowed) (validate_email, '"\\\011"@here.com', None), (validate_email, '"\\\012"@here.com', ValidationError), |
