Code

Ticket #2879: django_live_server.diff

File django_live_server.diff, 8.0 KB (added by devin, 6 years ago)

fixes way server handles error on startup

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