summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGael Pasgrimaud <gael@gawel.org>2012-12-06 23:00:37 +0100
committerGael Pasgrimaud <gael@gawel.org>2012-12-06 23:00:37 +0100
commit2ea66aa423e06a383d236a61ba55d9a258009004 (patch)
tree9dc29b531af0e0c9d2a503622a7e9cb96480ac57
parentbb2dbe2815b3792c2d104635254a7e4624c8c8bf (diff)
downloadwebtest-2ea66aa423e06a383d236a61ba55d9a258009004.tar.gz
use waitress
-rw-r--r--setup.py1
-rw-r--r--webtest/ext.py76
-rw-r--r--webtest/http.py88
-rw-r--r--webtest/sel.py118
4 files changed, 120 insertions, 163 deletions
diff --git a/setup.py b/setup.py
index 662e7bc..0f25deb 100644
--- a/setup.py
+++ b/setup.py
@@ -51,6 +51,7 @@ setup(name='WebTest',
install_requires=[
'six',
'WebOb',
+ 'waitress',
],
test_suite='nose.collector',
tests_require=tests_require,
diff --git a/webtest/ext.py b/webtest/ext.py
index 73bd7ef..e75e747 100644
--- a/webtest/ext.py
+++ b/webtest/ext.py
@@ -2,18 +2,13 @@
from __future__ import unicode_literals
__doc__ = '''Allow to run an external process to test your application'''
from webtest import app as testapp
-from webtest.sel import _free_port
-from webtest.sel import WSGIApplication
-from webtest.sel import WSGIServer
-from webtest.sel import WSGIRequestHandler
-from six.moves import http_client
+from webtest.http import StopableWSGIServer
from contextlib import contextmanager
-from wsgiref import simple_server
from six import binary_type
import subprocess
-import threading
import logging
-import socket
+import tempfile
+import shutil
import time
import sys
import re
@@ -29,40 +24,15 @@ class TestApp(testapp.TestApp):
def __init__(self, app=None, url=None, timeout=30000,
extra_environ=None, relative_to=None, **kwargs):
- if app:
- super(TestApp, self).__init__(app, relative_to=relative_to)
- self._run_server(self.app)
- self.application_url = self.app.url
- os.environ['APPLICATION_URL'] = self.application_url
+ super(TestApp, self).__init__(app, relative_to=relative_to)
+ self.server = StopableWSGIServer.create(app)
+ self.server.wait()
+ self.application_url = self.server.application_url
+ os.environ['APPLICATION_URL'] = self.application_url
self.extra_environ = extra_environ or {}
self.timeout = timeout
self.test_app = self
- def _run_server(self, app):
- """Run a wsgi server in a separate thread"""
- ip, port = _free_port()
- self.app = app = WSGIApplication(app, (ip, port))
-
- def run():
- httpd = simple_server.make_server(
- ip, port, app,
- server_class=WSGIServer,
- handler_class=WSGIRequestHandler)
- httpd.serve_forever()
-
- app.thread = threading.Thread(target=run)
- app.thread.start()
- conn = http_client.HTTPConnection(ip, port)
- time.sleep(.5)
- for i in range(100):
- try:
- conn.request('GET', '/__application__')
- conn.getresponse()
- except (socket.error, http_client.CannotSendRequest):
- time.sleep(.3)
- else:
- break
-
def get_binary(self, name):
if os.path.isfile(name):
return name
@@ -74,17 +44,11 @@ class TestApp(testapp.TestApp):
def close(self):
"""Close WSGI server if needed"""
- if self.app:
- conn = http_client.HTTPConnection(*self.app.bind)
- for i in range(100):
- try:
- conn.request('GET', '/__kill_application__')
- conn.getresponse()
- except socket.error:
- conn.close()
- break
- else:
- time.sleep(.3)
+ if self.server:
+ self.server.shutdown()
+
+_re_result = re.compile(
+ r'.*([0-9]+ tests executed, [0-9]+ passed, ([0-9]+) failed).*')
@contextmanager
@@ -92,17 +56,17 @@ def casperjs(test_app, timeout=60):
"""A context manager to run a test with a :class:`webtest.ext.TestApp`"""
app = TestApp(test_app.app)
binary = app.get_binary('casperjs')
-
- _re_result = re.compile(
- r'.*([0-9]+ tests executed, [0-9]+ passed, ([0-9]+) failed).*')
+ tempdir = tempfile.mkdtemp(prefix='casperjs')
def run(script, *args):
dirname = os.path.dirname(sys._getframe(1).f_code.co_filename)
+ log = os.path.join(tempdir, script + '.log')
script = os.path.join(dirname, script)
if binary:
+ stdout = open(log, 'ab+')
cmd = [binary, 'test'] + list(args) + [script]
p = subprocess.Popen(cmd,
- stdout=subprocess.PIPE,
+ stdout=stdout,
stderr=subprocess.PIPE)
end = time.time() + timeout
while time.time() < end:
@@ -117,7 +81,10 @@ def casperjs(test_app, timeout=60):
except OSError:
pass
- output = p.stdout.read()
+ if os.path.isfile(log):
+ with open(log) as fd:
+ output = fd.read()
+
if isinstance(output, binary_type):
output = output.decode('utf8', 'replace')
@@ -139,4 +106,5 @@ def casperjs(test_app, timeout=60):
try:
yield run
finally:
+ shutil.rmtree(tempdir)
app.close()
diff --git a/webtest/http.py b/webtest/http.py
new file mode 100644
index 0000000..de39e07
--- /dev/null
+++ b/webtest/http.py
@@ -0,0 +1,88 @@
+# -*- coding: utf-8 -*-
+from waitress.server import WSGIServer
+from six.moves import http_client
+import threading
+import logging
+import socket
+import webob
+import time
+import os
+
+
+def _free_port():
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ s.bind(('', 0))
+ ip, port = s.getsockname()
+ s.close()
+ ip = os.environ.get('WEBTEST_SERVER_BIND', '127.0.0.1')
+ return ip, port
+
+
+class StopableWSGIServer(WSGIServer):
+
+ def __init__(self, application, *args, **kwargs):
+ super(StopableWSGIServer, self).__init__(self.wrapper, *args, **kwargs)
+ self.main_thread = None
+ self.test_app = application
+ self.application_url = 'http://%s:%s/' % (self.adj.host, self.adj.port)
+
+ def wrapper(self, environ, start_response):
+ if '__file__' in environ['PATH_INFO']:
+ req = webob.Request(environ)
+ resp = webob.Response()
+ resp.content_type = 'text/html; charset=UTF-8'
+ filename = req.params.get('__file__')
+ body = open(filename, 'rb').read()
+ body.replace('http://localhost/',
+ 'http://%s/' % req.host)
+ resp.body = body
+ return resp(environ, start_response)
+ elif '__application__' in environ['PATH_INFO']:
+ return webob.Response('server started')(environ, start_response)
+ return self.test_app(environ, start_response)
+
+ def run(self):
+ try:
+ self.asyncore.loop(.5, map=self._map)
+ except (SystemExit, KeyboardInterrupt):
+ self.task_dispatcher.shutdown()
+
+ def shutdown(self):
+ # avoid showing traceback related to asyncore
+ self.logger.setLevel(logging.FATAL)
+ while self._map:
+ triggers = list(self._map.values())
+ for trigger in triggers:
+ trigger.handle_close()
+ self.maintenance(0)
+ while not self.task_dispatcher.shutdown():
+ pass
+
+ @classmethod
+ def create(cls, application, **kwargs):
+ host, port = _free_port()
+ kwargs['port'] = port
+ if 'host' not in kwargs:
+ kwargs['host'] = host
+ server = cls(application, **kwargs)
+ thread = threading.Thread(target=server.run)
+ server.main_thread = thread
+ thread.start()
+ return server
+
+ def wait(self):
+ conn = http_client.HTTPConnection(self.adj.host, self.adj.port)
+ time.sleep(.5)
+ for i in range(100):
+ try:
+ conn.request('GET', '/__application__')
+ conn.getresponse()
+ except (socket.error, http_client.CannotSendRequest):
+ time.sleep(.3)
+ else:
+ return True
+ try:
+ self.shutdown()
+ except:
+ pass
+ return False
diff --git a/webtest/sel.py b/webtest/sel.py
index 1671715..d809017 100644
--- a/webtest/sel.py
+++ b/webtest/sel.py
@@ -23,11 +23,12 @@ import threading
import subprocess
from functools import wraps
from webtest import app as testapp
-from wsgiref import simple_server
from contextlib import contextmanager
from six.moves import http_client
from six.moves import BaseHTTPServer
from six.moves import SimpleHTTPServer
+from webtest.http import StopableWSGIServer
+from webtest.http import _free_port
from six import binary_type
from six import PY3
from webtest.compat import urlencode
@@ -220,8 +221,9 @@ class SeleniumApp(testapp.TestApp):
self.app = None
if app:
super(SeleniumApp, self).__init__(app, relative_to=relative_to)
- self._run_server(self.app)
- url = self.app.url
+ self.server = StopableWSGIServer.create(app)
+ self.server.wait()
+ url = self.server.application_url
assert is_available()
self.session_id = None
self._browser = Selenium()
@@ -296,44 +298,10 @@ class SeleniumApp(testapp.TestApp):
else:
raise LookupError('No response found')
- def _run_server(self, app):
- """Run a wsgi server in a separate thread"""
- ip, port = _free_port()
- self.app = app = WSGIApplication(app, (ip, port))
-
- def run():
- httpd = simple_server.make_server(
- ip, port, app,
- server_class=WSGIServer,
- handler_class=WSGIRequestHandler)
- httpd.serve_forever()
-
- app.thread = threading.Thread(target=run)
- app.thread.start()
- conn = http_client.HTTPConnection(ip, port)
- time.sleep(.5)
- for i in range(100):
- try:
- conn.request('GET', '/__application__')
- conn.getresponse()
- except (socket.error, http_client.CannotSendRequest):
- time.sleep(.3)
- else:
- break
-
def close(self):
"""Close selenium and the WSGI server if needed"""
- if self.app:
- conn = http_client.HTTPConnection(*self.app.bind)
- for i in range(100):
- try:
- conn.request('GET', '/__kill_application__')
- conn.getresponse()
- except socket.error:
- conn.close()
- break
- else:
- time.sleep(.3)
+ if self.server:
+ self.server.shutdown()
if 'SELENIUM_KEEP_OPEN' not in os.environ:
self.browser.stop()
if 'SELENIUM_PID' in os.environ:
@@ -479,7 +447,7 @@ class Element(object):
return self.getValue()
def value__set(self, value):
- value = _get_value(value)
+ value = json.dumps(value)
script = """(function() {
s.doFireEvent(l, "focus");
s.doType(l, %s);
@@ -575,7 +543,7 @@ class Document(object):
def __contains__(self, s):
if isinstance(s, Element):
return s.exist()
- return self.browser.isTextPresent(_get_value(s))
+ return self.browser.isTextPresent(json.dumps(s))
def __call__(self, locator):
return Element(locator)
@@ -818,61 +786,6 @@ class Form(testapp.Form, Element):
###############
-class WSGIApplication(object):
- """A WSGI middleware to handle special calls used to run a test app"""
-
- def __init__(self, app, bind):
- self.app = app
- self.serve_forever = True
- self.bind = bind
- self.url = 'http://%s:%s/' % bind
- self.thread = None
-
- def __call__(self, environ, start_response):
- if '__kill_application__' in environ['PATH_INFO']:
- self.serve_forever = False
- resp = webob.Response()
- return resp(environ, start_response)
- elif '__file__' in environ['PATH_INFO']:
- req = webob.Request(environ)
- resp = webob.Response()
- resp.content_type = 'text/html; charset=UTF-8'
- filename = req.params.get('__file__')
- body = open(filename).read()
- body.replace('http://localhost/',
- 'http://%s/' % req.host)
- if PY3:
- resp.text = body
- else:
- resp.body = body
- return resp(environ, start_response)
- elif '__application__' in environ['PATH_INFO']:
- resp = webob.Response()
- return resp(environ, start_response)
- return self.app(environ, start_response)
-
- def __repr__(self):
- return '<WSGIApplication %r at %s>' % (self.app, self.url)
-
-
-class WSGIRequestHandler(simple_server.WSGIRequestHandler):
- """A WSGIRequestHandler who log to a logger"""
-
- def log_message(self, format, *args):
- log.debug("%s - - [%s] %s" %
- (self.address_string(),
- self.log_date_time_string(),
- format % args))
-
-
-class WSGIServer(simple_server.WSGIServer):
- """A WSGIServer"""
-
- def serve_forever(self):
- while self.application.serve_forever:
- self.handle_request()
-
-
class FileHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
"""Handle a simple file"""
@@ -890,10 +803,6 @@ class FileHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
###############
-def _get_value(s):
- return json.dumps(s)
-
-
def _get_command(cmd):
if '_' in cmd:
cmd = cmd.split('_')
@@ -919,15 +828,6 @@ def _eval_xpath(tag, locator=None, index=None, **kwargs):
return locator
-def _free_port():
- s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- s.bind(('', 0))
- ip, port = s.getsockname()
- s.close()
- ip = os.environ.get('SELENIUM_BIND', '127.0.0.1')
- return ip, port
-
-
def is_available():
"""return True if the selenium module is available and a RC server is
running"""