From 1ccb2c5dea9530682aae83d489f1738d9286fa4e Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sat, 16 Feb 2013 16:46:16 +1300 Subject: [PATCH] Test WSGI app calling. - Factor out test servers into a separate file - Adjust docs to note new Flask dependency --- README.mkd | 28 +++---- test/test_proxy.py | 2 +- test/test_server.py | 24 ++++-- test/tservers.py | 183 ++++++++++++++++++++++++++++++++++++++++++++ test/tutils.py | 167 +--------------------------------------- 5 files changed, 216 insertions(+), 188 deletions(-) create mode 100644 test/tservers.py diff --git a/README.mkd b/README.mkd index b7d5f4ee1..4179ce2be 100644 --- a/README.mkd +++ b/README.mkd @@ -50,28 +50,24 @@ Requirements ------------ * [Python](http://www.python.org) 2.7.x. +* [netlib](http://pypi.python.org/pypi/netlib) 0.2.2 or newer. * [PyOpenSSL](http://pypi.python.org/pypi/pyOpenSSL) 0.13 or newer. * [pyasn1](http://pypi.python.org/pypi/pyasn1) 0.1.2 or newer. * [urwid](http://excess.org/urwid/) version 1.1 or newer. * [PIL](http://www.pythonware.com/products/pil/) version 1.1 or newer. * [lxml](http://lxml.de/) version 2.3 or newer. -* [netlib](http://pypi.python.org/pypi/netlib) 0.2.2 or newer. - -The following auxiliary components may be needed if you plan to hack on -mitmproxy: - -* The test suite uses the [nose](http://readthedocs.org/docs/nose/en/latest/) unit testing - framework and requires [human_curl](https://github.com/Lispython/human_curl) and - [pathod](http://pathod.org). -* Rendering the documentation requires [countershape](http://github.com/cortesi/countershape). __mitmproxy__ is tested and developed on OSX, Linux and OpenBSD. Windows is not -supported at the moment. +officially supported at the moment. -You should also make sure that your console environment is set up with the -following: -* EDITOR environment variable to determine the external editor. -* PAGER environment variable to determine the external pager. -* Appropriate entries in your mailcap files to determine external - viewers for request and response contents. +Hacking +------- + +The following components are needed if you plan to hack on mitmproxy: + +* The test suite uses the [nose](http://readthedocs.org/docs/nose/en/latest/) unit testing + framework and requires [human_curl](https://github.com/Lispython/human_curl), + [pathod](http://pathod.org) and [flask](http://flask.pocoo.org/). +* Rendering the documentation requires [countershape](http://github.com/cortesi/countershape). + diff --git a/test/test_proxy.py b/test/test_proxy.py index 0edb2fee4..bdac8697a 100644 --- a/test/test_proxy.py +++ b/test/test_proxy.py @@ -67,7 +67,7 @@ def _dummysc(config, host, port): def _errsc(config, host, port): m = mock.MagicMock(config=config, host=host, port=port) - m.connect = mock.MagicMock(side_effect=tcp.NetLibError()) + m.connect = mock.MagicMock(side_effect=tcp.NetLibError()) return m diff --git a/test/test_server.py b/test/test_server.py index 0a2f142ec..58dfee580 100644 --- a/test/test_server.py +++ b/test/test_server.py @@ -1,7 +1,7 @@ import socket, time from netlib import tcp from libpathod import pathoc -import tutils +import tutils, tservers """ Note that the choice of response code in these tests matters more than you @@ -39,7 +39,19 @@ class SanityMixin: assert l.error -class TestHTTP(tutils.HTTPProxTest, SanityMixin): +class TestHTTP(tservers.HTTPProxTest, SanityMixin): + def test_app(self): + p = self.pathoc() + ret = p.request("get:'http://testapp/'") + assert ret[1] == 200 + assert ret[4] == "testapp" + + def test_app_err(self): + p = self.pathoc() + ret = p.request("get:'http://errapp/'") + assert ret[1] == 500 + assert "ValueError" in ret[4] + def test_invalid_http(self): t = tcp.TCPClient("127.0.0.1", self.proxy.port) t.connect() @@ -69,7 +81,7 @@ class TestHTTP(tutils.HTTPProxTest, SanityMixin): assert l.response.code == 304 -class TestHTTPS(tutils.HTTPProxTest, SanityMixin): +class TestHTTPS(tservers.HTTPProxTest, SanityMixin): ssl = True clientcerts = True def test_clientcert(self): @@ -77,15 +89,15 @@ class TestHTTPS(tutils.HTTPProxTest, SanityMixin): assert self.last_log()["request"]["clientcert"]["keyinfo"] -class TestReverse(tutils.ReverseProxTest, SanityMixin): +class TestReverse(tservers.ReverseProxTest, SanityMixin): reverse = True -class TestTransparent(tutils.TransparentProxTest, SanityMixin): +class TestTransparent(tservers.TransparentProxTest, SanityMixin): transparent = True -class TestProxy(tutils.HTTPProxTest): +class TestProxy(tservers.HTTPProxTest): def test_http(self): f = self.pathod("304") assert f.status_code == 304 diff --git a/test/tservers.py b/test/tservers.py new file mode 100644 index 000000000..2966a4360 --- /dev/null +++ b/test/tservers.py @@ -0,0 +1,183 @@ +import threading, Queue +import flask +import human_curl as hurl +import libpathod.test, libpathod.pathoc +from libmproxy import proxy, flow, controller +import tutils + +testapp = flask.Flask(__name__) + +@testapp.route("/") +def hello(): + return "testapp" + +@testapp.route("/error") +def error(): + raise ValueError("An exception...") + + +def errapp(environ, start_response): + raise ValueError("errapp") + + +class TestMaster(flow.FlowMaster): + def __init__(self, testq, config): + s = proxy.ProxyServer(config, 0) + s.apps.add(testapp, "testapp", 80) + s.apps.add(errapp, "errapp", 80) + state = flow.State() + flow.FlowMaster.__init__(self, s, state) + self.testq = testq + + def handle(self, m): + flow.FlowMaster.handle(self, m) + m._ack() + + +class ProxyThread(threading.Thread): + def __init__(self, testq, config): + self.tmaster = TestMaster(testq, config) + controller.should_exit = False + threading.Thread.__init__(self) + + @property + def port(self): + return self.tmaster.server.port + + def run(self): + self.tmaster.run() + + def shutdown(self): + self.tmaster.shutdown() + + +class ProxTestBase: + @classmethod + def setupAll(cls): + cls.tqueue = Queue.Queue() + cls.server = libpathod.test.Daemon(ssl=cls.ssl) + pconf = cls.get_proxy_config() + config = proxy.ProxyConfig( + certfile=tutils.test_data.path("data/testkey.pem"), + **pconf + ) + cls.proxy = ProxyThread(cls.tqueue, config) + cls.proxy.start() + + @property + def master(cls): + return cls.proxy.tmaster + + @classmethod + def teardownAll(cls): + cls.proxy.shutdown() + cls.server.shutdown() + + def setUp(self): + self.master.state.clear() + + @property + def scheme(self): + return "https" if self.ssl else "http" + + @property + def proxies(self): + """ + The URL base for the server instance. + """ + return ( + (self.scheme, ("127.0.0.1", self.proxy.port)) + ) + + @property + def urlbase(self): + """ + The URL base for the server instance. + """ + return self.server.urlbase + + def last_log(self): + return self.server.last_log() + + +class HTTPProxTest(ProxTestBase): + ssl = None + clientcerts = False + @classmethod + def get_proxy_config(cls): + d = dict() + if cls.clientcerts: + d["clientcerts"] = tutils.test_data.path("data/clientcert") + return d + + def pathoc(self, connect_to = None): + p = libpathod.pathoc.Pathoc("localhost", self.proxy.port) + p.connect(connect_to) + return p + + def pathod(self, spec): + """ + Constructs a pathod request, with the appropriate base and proxy. + """ + return hurl.get( + self.urlbase + "/p/" + spec, + proxy=self.proxies, + validate_cert=False, + #debug=hurl.utils.stdout_debug + ) + + +class TResolver: + def __init__(self, port): + self.port = port + + def original_addr(self, sock): + return ("127.0.0.1", self.port) + + +class TransparentProxTest(ProxTestBase): + ssl = None + @classmethod + def get_proxy_config(cls): + return dict( + transparent_proxy = dict( + resolver = TResolver(cls.server.port), + sslports = [] + ) + ) + + def pathod(self, spec): + """ + Constructs a pathod request, with the appropriate base and proxy. + """ + r = hurl.get( + "http://127.0.0.1:%s"%self.proxy.port + "/p/" + spec, + validate_cert=False, + #debug=hurl.utils.stdout_debug + ) + return r + + +class ReverseProxTest(ProxTestBase): + ssl = None + @classmethod + def get_proxy_config(cls): + return dict( + reverse_proxy = ( + "https" if cls.ssl else "http", + "127.0.0.1", + cls.server.port + ) + ) + + def pathod(self, spec): + """ + Constructs a pathod request, with the appropriate base and proxy. + """ + r = hurl.get( + "http://127.0.0.1:%s"%self.proxy.port + "/p/" + spec, + validate_cert=False, + #debug=hurl.utils.stdout_debug + ) + return r + diff --git a/test/tutils.py b/test/tutils.py index 9868c7784..d5497bae1 100644 --- a/test/tutils.py +++ b/test/tutils.py @@ -1,10 +1,8 @@ -import threading, Queue import os, shutil, tempfile from contextlib import contextmanager -from libmproxy import proxy, flow, controller, utils +from libmproxy import flow, utils from netlib import certutils -import human_curl as hurl -import libpathod.test, libpathod.pathoc + def treq(conn=None): if not conn: @@ -42,166 +40,6 @@ def tflow_err(): return f -class TestMaster(flow.FlowMaster): - def __init__(self, testq, config): - s = proxy.ProxyServer(config, 0) - state = flow.State() - flow.FlowMaster.__init__(self, s, state) - self.testq = testq - - def handle(self, m): - flow.FlowMaster.handle(self, m) - m._ack() - - -class ProxyThread(threading.Thread): - def __init__(self, testq, config): - self.tmaster = TestMaster(testq, config) - controller.should_exit = False - threading.Thread.__init__(self) - - @property - def port(self): - return self.tmaster.server.port - - def run(self): - self.tmaster.run() - - def shutdown(self): - self.tmaster.shutdown() - - -class ProxTestBase: - @classmethod - def setupAll(cls): - cls.tqueue = Queue.Queue() - cls.server = libpathod.test.Daemon(ssl=cls.ssl) - pconf = cls.get_proxy_config() - config = proxy.ProxyConfig( - certfile=test_data.path("data/testkey.pem"), - **pconf - ) - cls.proxy = ProxyThread(cls.tqueue, config) - cls.proxy.start() - - @property - def master(cls): - return cls.proxy.tmaster - - @classmethod - def teardownAll(cls): - cls.proxy.shutdown() - cls.server.shutdown() - - def setUp(self): - self.master.state.clear() - - @property - def scheme(self): - return "https" if self.ssl else "http" - - @property - def proxies(self): - """ - The URL base for the server instance. - """ - return ( - (self.scheme, ("127.0.0.1", self.proxy.port)) - ) - - @property - def urlbase(self): - """ - The URL base for the server instance. - """ - return self.server.urlbase - - def last_log(self): - return self.server.last_log() - - -class HTTPProxTest(ProxTestBase): - ssl = None - clientcerts = False - @classmethod - def get_proxy_config(cls): - d = dict() - if cls.clientcerts: - d["clientcerts"] = test_data.path("data/clientcert") - return d - - def pathoc(self, connect_to = None): - p = libpathod.pathoc.Pathoc("localhost", self.proxy.port) - p.connect(connect_to) - return p - - def pathod(self, spec): - """ - Constructs a pathod request, with the appropriate base and proxy. - """ - return hurl.get( - self.urlbase + "/p/" + spec, - proxy=self.proxies, - validate_cert=False, - #debug=hurl.utils.stdout_debug - ) - - -class TResolver: - def __init__(self, port): - self.port = port - - def original_addr(self, sock): - return ("127.0.0.1", self.port) - - -class TransparentProxTest(ProxTestBase): - ssl = None - @classmethod - def get_proxy_config(cls): - return dict( - transparent_proxy = dict( - resolver = TResolver(cls.server.port), - sslports = [] - ) - ) - - def pathod(self, spec): - """ - Constructs a pathod request, with the appropriate base and proxy. - """ - r = hurl.get( - "http://127.0.0.1:%s"%self.proxy.port + "/p/" + spec, - validate_cert=False, - #debug=hurl.utils.stdout_debug - ) - return r - - -class ReverseProxTest(ProxTestBase): - ssl = None - @classmethod - def get_proxy_config(cls): - return dict( - reverse_proxy = ( - "https" if cls.ssl else "http", - "127.0.0.1", - cls.server.port - ) - ) - - def pathod(self, spec): - """ - Constructs a pathod request, with the appropriate base and proxy. - """ - r = hurl.get( - "http://127.0.0.1:%s"%self.proxy.port + "/p/" + spec, - validate_cert=False, - #debug=hurl.utils.stdout_debug - ) - return r - - @contextmanager def tmpdir(*args, **kwargs): orig_workdir = os.getcwd() @@ -252,5 +90,4 @@ def raises(exc, obj, *args, **kwargs): ) raise AssertionError("No exception raised.") - test_data = utils.Data(__name__)