Test WSGI app calling.

- Factor out test servers into a separate file
- Adjust docs to note new Flask dependency
This commit is contained in:
Aldo Cortesi 2013-02-16 16:46:16 +13:00
parent 782bbee8c0
commit 1ccb2c5dea
5 changed files with 216 additions and 188 deletions

View File

@ -50,28 +50,24 @@ Requirements
------------ ------------
* [Python](http://www.python.org) 2.7.x. * [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. * [PyOpenSSL](http://pypi.python.org/pypi/pyOpenSSL) 0.13 or newer.
* [pyasn1](http://pypi.python.org/pypi/pyasn1) 0.1.2 or newer. * [pyasn1](http://pypi.python.org/pypi/pyasn1) 0.1.2 or newer.
* [urwid](http://excess.org/urwid/) version 1.1 or newer. * [urwid](http://excess.org/urwid/) version 1.1 or newer.
* [PIL](http://www.pythonware.com/products/pil/) 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. * [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 __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. Hacking
* PAGER environment variable to determine the external pager. -------
* Appropriate entries in your mailcap files to determine external
viewers for request and response contents. 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).

View File

@ -1,7 +1,7 @@
import socket, time import socket, time
from netlib import tcp from netlib import tcp
from libpathod import pathoc from libpathod import pathoc
import tutils import tutils, tservers
""" """
Note that the choice of response code in these tests matters more than you Note that the choice of response code in these tests matters more than you
@ -39,7 +39,19 @@ class SanityMixin:
assert l.error 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): def test_invalid_http(self):
t = tcp.TCPClient("127.0.0.1", self.proxy.port) t = tcp.TCPClient("127.0.0.1", self.proxy.port)
t.connect() t.connect()
@ -69,7 +81,7 @@ class TestHTTP(tutils.HTTPProxTest, SanityMixin):
assert l.response.code == 304 assert l.response.code == 304
class TestHTTPS(tutils.HTTPProxTest, SanityMixin): class TestHTTPS(tservers.HTTPProxTest, SanityMixin):
ssl = True ssl = True
clientcerts = True clientcerts = True
def test_clientcert(self): def test_clientcert(self):
@ -77,15 +89,15 @@ class TestHTTPS(tutils.HTTPProxTest, SanityMixin):
assert self.last_log()["request"]["clientcert"]["keyinfo"] assert self.last_log()["request"]["clientcert"]["keyinfo"]
class TestReverse(tutils.ReverseProxTest, SanityMixin): class TestReverse(tservers.ReverseProxTest, SanityMixin):
reverse = True reverse = True
class TestTransparent(tutils.TransparentProxTest, SanityMixin): class TestTransparent(tservers.TransparentProxTest, SanityMixin):
transparent = True transparent = True
class TestProxy(tutils.HTTPProxTest): class TestProxy(tservers.HTTPProxTest):
def test_http(self): def test_http(self):
f = self.pathod("304") f = self.pathod("304")
assert f.status_code == 304 assert f.status_code == 304

183
test/tservers.py Normal file
View File

@ -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

View File

@ -1,10 +1,8 @@
import threading, Queue
import os, shutil, tempfile import os, shutil, tempfile
from contextlib import contextmanager from contextlib import contextmanager
from libmproxy import proxy, flow, controller, utils from libmproxy import flow, utils
from netlib import certutils from netlib import certutils
import human_curl as hurl
import libpathod.test, libpathod.pathoc
def treq(conn=None): def treq(conn=None):
if not conn: if not conn:
@ -42,166 +40,6 @@ def tflow_err():
return f 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 @contextmanager
def tmpdir(*args, **kwargs): def tmpdir(*args, **kwargs):
orig_workdir = os.getcwd() orig_workdir = os.getcwd()
@ -252,5 +90,4 @@ def raises(exc, obj, *args, **kwargs):
) )
raise AssertionError("No exception raised.") raise AssertionError("No exception raised.")
test_data = utils.Data(__name__) test_data = utils.Data(__name__)