diff options
author | Gael Pasgrimaud <gael@gawel.org> | 2012-12-06 23:00:37 +0100 |
---|---|---|
committer | Gael Pasgrimaud <gael@gawel.org> | 2012-12-06 23:00:37 +0100 |
commit | 2ea66aa423e06a383d236a61ba55d9a258009004 (patch) | |
tree | 9dc29b531af0e0c9d2a503622a7e9cb96480ac57 | |
parent | bb2dbe2815b3792c2d104635254a7e4624c8c8bf (diff) | |
download | webtest-2ea66aa423e06a383d236a61ba55d9a258009004.tar.gz |
use waitress
-rw-r--r-- | setup.py | 1 | ||||
-rw-r--r-- | webtest/ext.py | 76 | ||||
-rw-r--r-- | webtest/http.py | 88 | ||||
-rw-r--r-- | webtest/sel.py | 118 |
4 files changed, 120 insertions, 163 deletions
@@ -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""" |