diff options
| author | Andrew Godwin <andrew@aeracode.org> | 2019-04-12 06:15:18 -0700 |
|---|---|---|
| committer | Mariusz Felisiak <felisiak.mariusz@gmail.com> | 2019-06-20 12:29:43 +0200 |
| commit | a415ce70bef6d91036b00dd2c8544aed7aeeaaed (patch) | |
| tree | 3583cef22e9b56d2ed52456ab586d9c47620bc51 /tests | |
| parent | cce47ff65a4dd3786c049ec14ee889e128ca7de9 (diff) | |
Fixed #30451 -- Added ASGI handler and coroutine-safety.
This adds an ASGI handler, asgi.py file for the default project layout,
a few async utilities and adds async-safety to many parts of Django.
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/asgi/__init__.py | 0 | ||||
| -rw-r--r-- | tests/asgi/tests.py | 84 | ||||
| -rw-r--r-- | tests/asgi/urls.py | 15 | ||||
| -rw-r--r-- | tests/async/__init__.py | 0 | ||||
| -rw-r--r-- | tests/async/models.py | 5 | ||||
| -rw-r--r-- | tests/async/tests.py | 36 | ||||
| -rw-r--r-- | tests/i18n/tests.py | 9 | ||||
| -rw-r--r-- | tests/template_tests/syntax_tests/i18n/test_blocktrans.py | 5 | ||||
| -rw-r--r-- | tests/template_tests/syntax_tests/i18n/test_trans.py | 4 |
9 files changed, 149 insertions, 9 deletions
diff --git a/tests/asgi/__init__.py b/tests/asgi/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/asgi/__init__.py diff --git a/tests/asgi/tests.py b/tests/asgi/tests.py new file mode 100644 index 0000000000..243e77defb --- /dev/null +++ b/tests/asgi/tests.py @@ -0,0 +1,84 @@ +import sys + +from asgiref.sync import async_to_sync +from asgiref.testing import ApplicationCommunicator + +from django.core.asgi import get_asgi_application +from django.core.signals import request_started +from django.db import close_old_connections +from django.test import SimpleTestCase, override_settings + +from .urls import test_filename + + +@override_settings(ROOT_URLCONF='asgi.urls') +class ASGITest(SimpleTestCase): + + def setUp(self): + request_started.disconnect(close_old_connections) + + def _get_scope(self, **kwargs): + return { + 'type': 'http', + 'asgi': {'version': '3.0', 'spec_version': '2.1'}, + 'http_version': '1.1', + 'method': 'GET', + 'query_string': b'', + 'server': ('testserver', 80), + **kwargs, + } + + def tearDown(self): + request_started.connect(close_old_connections) + + @async_to_sync + async def test_get_asgi_application(self): + """ + get_asgi_application() returns a functioning ASGI callable. + """ + application = get_asgi_application() + # Construct HTTP request. + communicator = ApplicationCommunicator(application, self._get_scope(path='/')) + await communicator.send_input({'type': 'http.request'}) + # Read the response. + response_start = await communicator.receive_output() + self.assertEqual(response_start['type'], 'http.response.start') + self.assertEqual(response_start['status'], 200) + self.assertEqual( + set(response_start['headers']), + { + (b'Content-Length', b'12'), + (b'Content-Type', b'text/html; charset=utf-8'), + }, + ) + response_body = await communicator.receive_output() + self.assertEqual(response_body['type'], 'http.response.body') + self.assertEqual(response_body['body'], b'Hello World!') + + @async_to_sync + async def test_file_response(self): + """ + Makes sure that FileResponse works over ASGI. + """ + application = get_asgi_application() + # Construct HTTP request. + communicator = ApplicationCommunicator(application, self._get_scope(path='/file/')) + await communicator.send_input({'type': 'http.request'}) + # Get the file content. + with open(test_filename, 'rb') as test_file: + test_file_contents = test_file.read() + # Read the response. + response_start = await communicator.receive_output() + self.assertEqual(response_start['type'], 'http.response.start') + self.assertEqual(response_start['status'], 200) + self.assertEqual( + set(response_start['headers']), + { + (b'Content-Length', str(len(test_file_contents)).encode('ascii')), + (b'Content-Type', b'text/plain' if sys.platform.startswith('win') else b'text/x-python'), + (b'Content-Disposition', b'inline; filename="urls.py"'), + }, + ) + response_body = await communicator.receive_output() + self.assertEqual(response_body['type'], 'http.response.body') + self.assertEqual(response_body['body'], test_file_contents) diff --git a/tests/asgi/urls.py b/tests/asgi/urls.py new file mode 100644 index 0000000000..4177ec8c9a --- /dev/null +++ b/tests/asgi/urls.py @@ -0,0 +1,15 @@ +from django.http import FileResponse, HttpResponse +from django.urls import path + + +def helloworld(request): + return HttpResponse('Hello World!') + + +test_filename = __file__ + + +urlpatterns = [ + path('', helloworld), + path('file/', lambda x: FileResponse(open(test_filename, 'rb'))), +] diff --git a/tests/async/__init__.py b/tests/async/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/async/__init__.py diff --git a/tests/async/models.py b/tests/async/models.py new file mode 100644 index 0000000000..0fd606b07e --- /dev/null +++ b/tests/async/models.py @@ -0,0 +1,5 @@ +from django.db import models + + +class SimpleModel(models.Model): + field = models.IntegerField() diff --git a/tests/async/tests.py b/tests/async/tests.py new file mode 100644 index 0000000000..1e1cabc1c6 --- /dev/null +++ b/tests/async/tests.py @@ -0,0 +1,36 @@ +from asgiref.sync import async_to_sync + +from django.core.exceptions import SynchronousOnlyOperation +from django.test import SimpleTestCase +from django.utils.asyncio import async_unsafe + +from .models import SimpleModel + + +class DatabaseConnectionTest(SimpleTestCase): + """A database connection cannot be used in an async context.""" + @async_to_sync + async def test_get_async_connection(self): + with self.assertRaises(SynchronousOnlyOperation): + list(SimpleModel.objects.all()) + + +class AsyncUnsafeTest(SimpleTestCase): + """ + async_unsafe decorator should work correctly and returns the correct + message. + """ + @async_unsafe + def dangerous_method(self): + return True + + @async_to_sync + async def test_async_unsafe(self): + # async_unsafe decorator catches bad access and returns the right + # message. + msg = ( + 'You cannot call this from an async context - use a thread or ' + 'sync_to_async.' + ) + with self.assertRaisesMessage(SynchronousOnlyOperation, msg): + self.dangerous_method() diff --git a/tests/i18n/tests.py b/tests/i18n/tests.py index 8bb284e0c3..300af388e6 100644 --- a/tests/i18n/tests.py +++ b/tests/i18n/tests.py @@ -8,10 +8,9 @@ import tempfile from contextlib import contextmanager from importlib import import_module from pathlib import Path -from threading import local from unittest import mock -import _thread +from asgiref.local import Local from django import forms from django.apps import AppConfig @@ -289,7 +288,7 @@ class TranslationTests(SimpleTestCase): @override_settings(LOCALE_PATHS=extended_locale_paths) def test_pgettext(self): - trans_real._active = local() + trans_real._active = Local() trans_real._translations = {} with translation.override('de'): self.assertEqual(pgettext("unexisting", "May"), "May") @@ -310,7 +309,7 @@ class TranslationTests(SimpleTestCase): Translating a string requiring no auto-escaping with gettext or pgettext shouldn't change the "safe" status. """ - trans_real._active = local() + trans_real._active = Local() trans_real._translations = {} s1 = mark_safe('Password') s2 = mark_safe('May') @@ -1882,7 +1881,7 @@ class TranslationFileChangedTests(SimpleTestCase): self.assertEqual(gettext_module._translations, {}) self.assertEqual(trans_real._translations, {}) self.assertIsNone(trans_real._default) - self.assertIsInstance(trans_real._active, _thread._local) + self.assertIsInstance(trans_real._active, Local) class UtilsTests(SimpleTestCase): diff --git a/tests/template_tests/syntax_tests/i18n/test_blocktrans.py b/tests/template_tests/syntax_tests/i18n/test_blocktrans.py index ac8fc16da8..744b410ea6 100644 --- a/tests/template_tests/syntax_tests/i18n/test_blocktrans.py +++ b/tests/template_tests/syntax_tests/i18n/test_blocktrans.py @@ -1,5 +1,6 @@ import os -from threading import local + +from asgiref.local import Local from django.template import Context, Template, TemplateSyntaxError from django.test import SimpleTestCase, override_settings @@ -278,7 +279,7 @@ class TranslationBlockTransTagTests(SimpleTestCase): @override_settings(LOCALE_PATHS=extended_locale_paths) def test_template_tags_pgettext(self): """{% blocktrans %} takes message contexts into account (#14806).""" - trans_real._active = local() + trans_real._active = Local() trans_real._translations = {} with translation.override('de'): # Nonexistent context diff --git a/tests/template_tests/syntax_tests/i18n/test_trans.py b/tests/template_tests/syntax_tests/i18n/test_trans.py index ba5021a5d5..47a79ff74d 100644 --- a/tests/template_tests/syntax_tests/i18n/test_trans.py +++ b/tests/template_tests/syntax_tests/i18n/test_trans.py @@ -1,4 +1,4 @@ -from threading import local +from asgiref.local import Local from django.template import Context, Template, TemplateSyntaxError from django.templatetags.l10n import LocalizeNode @@ -136,7 +136,7 @@ class TranslationTransTagTests(SimpleTestCase): @override_settings(LOCALE_PATHS=extended_locale_paths) def test_template_tags_pgettext(self): """{% trans %} takes message contexts into account (#14806).""" - trans_real._active = local() + trans_real._active = Local() trans_real._translations = {} with translation.override('de'): # Nonexistent context... |
