Code

Ticket #2879: django_live_server_r7936.diff

File django_live_server_r7936.diff, 8.7 KB (added by devin, 6 years ago)

refactor and cleanup

Line 
1Index: django/test/testcases.py
2===================================================================
3--- django/test/testcases.py    (revision 7936)
4+++ django/test/testcases.py    (working copy)
5@@ -1,14 +1,16 @@
6-import re
7-import unittest
8+import re, socket, threading, unittest
9 from urlparse import urlsplit, urlunsplit
10 
11 from django.http import QueryDict
12 from django.db import transaction
13 from django.conf import settings
14 from django.core import mail
15+from django.core.handlers.wsgi import WSGIHandler
16 from django.core.management import call_command
17+from django.core.servers import basehttp
18 from django.test import _doctest as doctest
19 from django.test.client import Client
20+from django.test.utils import create_test_db
21 from django.core.urlresolvers import clear_url_caches
22 
23 normalize_long_ints = lambda s: re.sub(r'(?<![\w])(\d+)L(?![\w])', '\\1', s)
24@@ -49,6 +51,50 @@
25         # side effects on other tests.
26         transaction.rollback_unless_managed()
27 
28+class TestServerThread(threading.Thread):
29+    """Thread for running a http server while tests are running."""
30+
31+    def __init__(self, address, port):
32+        self.address = address
33+        self.port = port
34+        self._stopevent = threading.Event()
35+        self.started = threading.Event()
36+        self.error = None
37+        super(TestServerThread, self).__init__()
38+
39+    def run(self):
40+        """Sets up test server and database and loops over handling http requests."""
41+        try:
42+            handler = basehttp.AdminMediaHandler(WSGIHandler())
43+            server_address = (self.address, self.port)
44+            httpd = basehttp.StoppableWSGIServer(server_address, basehttp.WSGIRequestHandler)
45+            httpd.set_app(handler)
46+            self.started.set()
47+        except basehttp.WSGIServerException, e:
48+            self.error = e
49+            self.started.set()
50+            return
51+
52+        # Must do database stuff in this new thread if database in memory.
53+        from django.conf import settings
54+        if settings.DATABASE_ENGINE == 'sqlite3' \
55+            and (not settings.TEST_DATABASE_NAME or settings.TEST_DATABASE_NAME == ':memory:'):
56+            db_name = create_test_db(0)
57+            # Import the fixture data into the test database.
58+            if hasattr(self, 'fixtures'):
59+                # We have to use this slightly awkward syntax due to the fact
60+                # that we're using *args and **kwargs together.
61+                call_command('loaddata', *self.fixtures, **{'verbosity': 0})
62+
63+        # Loop until we get a stop event.
64+        while not self._stopevent.isSet():
65+            httpd.handle_request()
66+
67+    def join(self, timeout=None):
68+        """Stop the thread and wait for it to finish."""
69+        self._stopevent.set()
70+        threading.Thread.join(self, timeout)
71+
72 class TestCase(unittest.TestCase):
73     def _pre_setup(self):
74         """Performs any pre-test setup. This includes:
75@@ -105,6 +151,18 @@
76             settings.ROOT_URLCONF = self._old_root_urlconf
77             clear_url_caches()
78 
79+    def start_test_server(self, address='localhost', port=8000):
80+        """Creates a live test server object (instance of WSGIServer)."""
81+        self.server_thread = TestServerThread(address, port)
82+        self.server_thread.start()
83+        self.server_thread.started.wait()
84+        if self.server_thread.error:
85+            raise self.server_thread.error
86+
87+    def stop_test_server(self):
88+        if self.server_thread:
89+            self.server_thread.join()
90+
91     def assertRedirects(self, response, expected_url, status_code=302,
92                         target_status_code=200, host=None):
93         """Asserts that a response redirected to a specific URL, and that the
94Index: django/core/servers/basehttp.py
95===================================================================
96--- django/core/servers/basehttp.py     (revision 7936)
97+++ django/core/servers/basehttp.py     (working copy)
98@@ -11,6 +11,7 @@
99 import mimetypes
100 import os
101 import re
102+import socket
103 import sys
104 import urllib
105 
106@@ -658,6 +659,23 @@
107         start_response(status, headers.items())
108         return output
109 
110+class StoppableWSGIServer(WSGIServer):
111+    """WSGIServer with short timeout, so that server thread can stop this server."""
112+
113+    def server_bind(self):
114+        """Sets timeout to 1 second."""
115+        WSGIServer.server_bind(self)
116+        self.socket.settimeout(1)
117+
118+    def get_request(self):
119+        """Checks for timeout when getting request."""
120+        try:
121+            sock, address = self.socket.accept()
122+            sock.settimeout(None)
123+            return (sock, address)
124+        except socket.timeout:
125+            raise
126+
127 def run(addr, port, wsgi_handler):
128     server_address = (addr, port)
129     httpd = WSGIServer(server_address, WSGIRequestHandler)
130Index: tests/regressiontests/test_client_regress/models.py
131===================================================================
132--- tests/regressiontests/test_client_regress/models.py (revision 7936)
133+++ tests/regressiontests/test_client_regress/models.py (working copy)
134@@ -7,6 +7,7 @@
135 from django.core.exceptions import SuspiciousOperation
136 import os
137 import sha
138+import urllib
139 
140 class AssertContainsTests(TestCase):
141     def test_contains(self):
142@@ -327,3 +328,22 @@
143         "URLconf is reverted to original value after modification in a TestCase"
144         url = reverse('arg_view', args=['somename'])
145         self.assertEquals(url, '/test_client_regress/arg_view/somename/')
146+
147+class TestServerTests(TestCase):
148+
149+    def setUp(self):
150+        self.start_test_server(address='localhost', port=8001)
151+
152+    def tearDown(self):
153+        self.stop_test_server()
154+
155+    def test_server_up(self):
156+        url = urllib.urlopen('http://localhost:8001')
157+        self.assertEqual(url.read(), 'Django Internal Tests: 404 Error')
158+        url.close()
159+
160+    def test_serve_page(self):
161+        url = urllib.urlopen('http://localhost:8001/accounts/login')
162+        # Just make sure this isn't a 404, and we've gotten something.
163+        self.assertNotEqual(url.read(), 'Django Internal Tests: 404 Error')
164+        url.close()
165Index: AUTHORS
166===================================================================
167--- AUTHORS     (revision 7936)
168+++ AUTHORS     (working copy)
169@@ -273,6 +273,7 @@
170     Robert Myers <myer0052@gmail.com>
171     Nebojša Dorđević
172     Doug Napoleone <doug@dougma.com>
173+    Devin Naquin <dnaquin@gmail.com>
174     Gopal Narayanan <gopastro@gmail.com>
175     Fraser Nevett <mail@nevett.org>
176     Sam Newman <http://www.magpiebrain.com/>
177Index: docs/testing.txt
178===================================================================
179--- docs/testing.txt    (revision 7936)
180+++ docs/testing.txt    (working copy)
181@@ -830,6 +830,54 @@
182 This test case will use the contents of ``myapp.test_urls`` as the
183 URLconf for the duration of the test case.
184 
185+Running tests with a live test server
186+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
187+
188+**New in Django development version**
189+
190+When running tests that use in-browser frameworks such as Twille_ and
191+Selenium_, it's necessary to have a running test server. Django's custom
192+``TestCase`` class supports starting a live server for these purposes.
193+
194+``start_test_server(address='localhost', port=8000)``
195+    Starts a test server at ``address`` on ``port``. This should be done in the
196+    ``setUp()`` method of a subclass of ``TestCase``. The server then can be
197+    accessed at http://address:port.
198+
199+``stop_test_server()``
200+    Stops the test server that was started with ``start_test_server``. This
201+    must be done before ``start_test_server`` can be called again, so this
202+    should be done in the ``tearDown()`` method of a subclass of ``TestCase``.
203+
204+This can be used to start a server that can then be accessed by Twill, Selenium
205+or another in-browser test framework. For example::
206+
207+        from django.test.testcases import TestCase
208+        from selenium import selenium
209+       
210+        class TestLogin(TestCase):
211+            fixtures = ['login_info.xml']
212+
213+            def setUp(self):
214+                # Start a test server and tell selenium where to find it.
215+                self.start_test_server('localhost', 8000)
216+                self.selenium = selenium('localhost', 4444, \
217+                        '*pifirefox', 'http://localhost:8000')
218+
219+            def tearDown(self):
220+                # Stop server and Selenium
221+                self.selenium.stop()
222+                self.stop_test_server()
223+
224+            def testLogin(self):
225+                self.selenium.open('/admin/')
226+                self.selenium.type('username', 'admin')
227+                self.selenium.type('password', 'password')
228+                self.selenium.click("//input[@value='Log in']")
229+
230+.. _Twill: http://twill.idyll.org/
231+.. _Selenium: http://www.openqa.org/selenium/
232+
233 Emptying the test outbox
234 ~~~~~~~~~~~~~~~~~~~~~~~~
235