From 782bbee8c0a7d14be25e87d61c9c6db03b532eb7 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Tue, 29 Jan 2013 11:35:57 +1300 Subject: Unit tests for ServerConnectionPool --- test/test_proxy.py | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) (limited to 'test') diff --git a/test/test_proxy.py b/test/test_proxy.py index c73f61d8..0edb2fee 100644 --- a/test/test_proxy.py +++ b/test/test_proxy.py @@ -1,7 +1,7 @@ from libmproxy import proxy, flow import tutils from libpathod import test -from netlib import http +from netlib import http, tcp import mock @@ -58,3 +58,31 @@ class TestServerConnection: sc.connection = mock.Mock() sc.connection.close = mock.Mock(side_effect=IOError) sc.terminate() + + + +def _dummysc(config, host, port): + return mock.MagicMock(config=config, host=host, port=port) + + +def _errsc(config, host, port): + m = mock.MagicMock(config=config, host=host, port=port) + m.connect = mock.MagicMock(side_effect=tcp.NetLibError()) + return m + + +class TestServerConnectionPool: + @mock.patch("libmproxy.proxy.ServerConnection", _dummysc) + def test_pooling(self): + p = proxy.ServerConnectionPool(proxy.ProxyConfig()) + c = p.get_connection("http", "localhost", 80) + c2 = p.get_connection("http", "localhost", 80) + assert c is c2 + c3 = p.get_connection("http", "foo", 80) + assert not c is c3 + + @mock.patch("libmproxy.proxy.ServerConnection", _errsc) + def test_connection_error(self): + p = proxy.ServerConnectionPool(proxy.ProxyConfig()) + tutils.raises("502", p.get_connection, "http", "localhost", 80) + -- cgit v1.2.3 From 1ccb2c5dea9530682aae83d489f1738d9286fa4e Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sat, 16 Feb 2013 16:46:16 +1300 Subject: Test WSGI app calling. - Factor out test servers into a separate file - Adjust docs to note new Flask dependency --- test/test_proxy.py | 2 +- test/test_server.py | 24 +++++-- test/tservers.py | 183 ++++++++++++++++++++++++++++++++++++++++++++++++++++ test/tutils.py | 167 +---------------------------------------------- 4 files changed, 204 insertions(+), 172 deletions(-) create mode 100644 test/tservers.py (limited to 'test') diff --git a/test/test_proxy.py b/test/test_proxy.py index 0edb2fee..bdac8697 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 0a2f142e..58dfee58 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 00000000..2966a436 --- /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 9868c778..d5497bae 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__) -- cgit v1.2.3 From aaf892e3afc682b2dc2a166a96872420e50092cd Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sun, 17 Feb 2013 12:42:48 +1300 Subject: Significantly refactor the master/slave message passing interface. --- test/test_dump.py | 6 +++++- test/test_flow.py | 32 +++++++++++++++++++------------- test/tservers.py | 2 +- test/tutils.py | 15 +++++++++++---- 4 files changed, 36 insertions(+), 19 deletions(-) (limited to 'test') diff --git a/test/test_dump.py b/test/test_dump.py index e1241e29..5d3f9133 100644 --- a/test/test_dump.py +++ b/test/test_dump.py @@ -3,6 +3,7 @@ from cStringIO import StringIO import libpry from libmproxy import dump, flow, proxy import tutils +import mock def test_strfuncs(): t = tutils.tresp() @@ -21,6 +22,7 @@ class TestDumpMaster: req = tutils.treq() req.content = content l = proxy.Log("connect") + l.reply = mock.MagicMock() m.handle_log(l) cc = req.client_conn cc.connection_error = "error" @@ -29,7 +31,9 @@ class TestDumpMaster: m.handle_clientconnect(cc) m.handle_request(req) f = m.handle_response(resp) - m.handle_clientdisconnect(flow.ClientDisconnect(cc)) + cd = flow.ClientDisconnect(cc) + cd.reply = mock.MagicMock() + m.handle_clientdisconnect(cd) return f def _dummy_cycle(self, n, filt, content, **options): diff --git a/test/test_flow.py b/test/test_flow.py index da5b095e..6aa898ad 100644 --- a/test/test_flow.py +++ b/test/test_flow.py @@ -223,16 +223,16 @@ class TestFlow: f = tutils.tflow() f.request = tutils.treq() f.intercept() - assert not f.request.acked + assert not f.request.reply.acked f.kill(fm) - assert f.request.acked + assert f.request.reply.acked f.intercept() f.response = tutils.tresp() f.request = f.response.request - f.request._ack() - assert not f.response.acked + f.request.reply() + assert not f.response.reply.acked f.kill(fm) - assert f.response.acked + assert f.response.reply.acked def test_killall(self): s = flow.State() @@ -245,25 +245,25 @@ class TestFlow: fm.handle_request(r) for i in s.view: - assert not i.request.acked + assert not i.request.reply.acked s.killall(fm) for i in s.view: - assert i.request.acked + assert i.request.reply.acked def test_accept_intercept(self): f = tutils.tflow() f.request = tutils.treq() f.intercept() - assert not f.request.acked + assert not f.request.reply.acked f.accept_intercept() - assert f.request.acked + assert f.request.reply.acked f.response = tutils.tresp() f.request = f.response.request f.intercept() - f.request._ack() - assert not f.response.acked + f.request.reply() + assert not f.response.reply.acked f.accept_intercept() - assert f.response.acked + assert f.response.reply.acked def test_serialization(self): f = flow.Flow(None) @@ -562,9 +562,11 @@ class TestFlowMaster: fm.handle_response(resp) assert fm.script.ns["log"][-1] == "response" dc = flow.ClientDisconnect(req.client_conn) + dc.reply = controller.DummyReply() fm.handle_clientdisconnect(dc) assert fm.script.ns["log"][-1] == "clientdisconnect" err = flow.Error(f.request, "msg") + err.reply = controller.DummyReply() fm.handle_error(err) assert fm.script.ns["log"][-1] == "error" @@ -598,10 +600,12 @@ class TestFlowMaster: assert not fm.handle_response(rx) dc = flow.ClientDisconnect(req.client_conn) + dc.reply = controller.DummyReply() req.client_conn.requestcount = 1 fm.handle_clientdisconnect(dc) err = flow.Error(f.request, "msg") + err.reply = controller.DummyReply() fm.handle_error(err) fm.load_script(tutils.test_data.path("scripts/a.py")) @@ -621,7 +625,9 @@ class TestFlowMaster: fm.tick(q) assert fm.state.flow_count() - fm.handle_error(flow.Error(f.request, "error")) + err = flow.Error(f.request, "error") + err.reply = controller.DummyReply() + fm.handle_error(err) def test_server_playback(self): controller.should_exit = False diff --git a/test/tservers.py b/test/tservers.py index 2966a436..4cbdc942 100644 --- a/test/tservers.py +++ b/test/tservers.py @@ -31,7 +31,7 @@ class TestMaster(flow.FlowMaster): def handle(self, m): flow.FlowMaster.handle(self, m) - m._ack() + m.reply() class ProxyThread(threading.Thread): diff --git a/test/tutils.py b/test/tutils.py index d5497bae..1a1c8724 100644 --- a/test/tutils.py +++ b/test/tutils.py @@ -1,15 +1,18 @@ import os, shutil, tempfile from contextlib import contextmanager -from libmproxy import flow, utils +from libmproxy import flow, utils, controller from netlib import certutils - +import mock def treq(conn=None): if not conn: conn = flow.ClientConnect(("address", 22)) + conn.reply = controller.DummyReply() headers = flow.ODictCaseless() headers["header"] = ["qvalue"] - return flow.Request(conn, (1, 1), "host", 80, "http", "GET", "/path", headers, "content") + r = flow.Request(conn, (1, 1), "host", 80, "http", "GET", "/path", headers, "content") + r.reply = controller.DummyReply() + return r def tresp(req=None): @@ -18,7 +21,9 @@ def tresp(req=None): headers = flow.ODictCaseless() headers["header_response"] = ["svalue"] cert = certutils.SSLCert.from_der(file(test_data.path("data/dercert")).read()) - return flow.Response(req, (1, 1), 200, "message", headers, "content_response", cert) + resp = flow.Response(req, (1, 1), 200, "message", headers, "content_response", cert) + resp.reply = controller.DummyReply() + return resp def tflow(): @@ -37,9 +42,11 @@ def tflow_err(): r = treq() f = flow.Flow(r) f.error = flow.Error(r, "error") + f.error.reply = controller.DummyReply() return f + @contextmanager def tmpdir(*args, **kwargs): orig_workdir = os.getcwd() -- cgit v1.2.3 From 269780c57780d155c4d8bd95fcc2af2104effa5b Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sat, 23 Feb 2013 16:34:59 +1300 Subject: Unit test dummy response functions. --- test/test_server.py | 16 ++++++++++++++++ test/tservers.py | 18 ++++++++++++------ 2 files changed, 28 insertions(+), 6 deletions(-) (limited to 'test') diff --git a/test/test_server.py b/test/test_server.py index 58dfee58..5cba891c 100644 --- a/test/test_server.py +++ b/test/test_server.py @@ -2,6 +2,7 @@ import socket, time from netlib import tcp from libpathod import pathoc import tutils, tservers +from libmproxy import flow """ Note that the choice of response code in these tests matters more than you @@ -144,3 +145,18 @@ class TestProxy(tservers.HTTPProxTest): request = self.master.state.view[1].request assert request.timestamp_end - request.timestamp_start <= 0.1 + + +class MasterFakeResponse(tservers.TestMaster): + def handle_request(self, m): + resp = tutils.tresp() + m.reply(resp) + + +class TestFakeResponse(tservers.HTTPProxTest): + masterclass = MasterFakeResponse + def test_kill(self): + p = self.pathoc() + f = self.pathod("200") + assert "header_response" in f.headers.keys() + diff --git a/test/tservers.py b/test/tservers.py index 4cbdc942..3fdb8d13 100644 --- a/test/tservers.py +++ b/test/tservers.py @@ -29,16 +29,20 @@ class TestMaster(flow.FlowMaster): flow.FlowMaster.__init__(self, s, state) self.testq = testq - def handle(self, m): - flow.FlowMaster.handle(self, m) + def handle_request(self, m): + flow.FlowMaster.handle_request(self, m) + m.reply() + + def handle_response(self, m): + flow.FlowMaster.handle_response(self, m) m.reply() class ProxyThread(threading.Thread): - def __init__(self, testq, config): - self.tmaster = TestMaster(testq, config) - controller.should_exit = False + def __init__(self, tmaster): threading.Thread.__init__(self) + self.tmaster = tmaster + controller.should_exit = False @property def port(self): @@ -52,6 +56,7 @@ class ProxyThread(threading.Thread): class ProxTestBase: + masterclass = TestMaster @classmethod def setupAll(cls): cls.tqueue = Queue.Queue() @@ -61,7 +66,8 @@ class ProxTestBase: certfile=tutils.test_data.path("data/testkey.pem"), **pconf ) - cls.proxy = ProxyThread(cls.tqueue, config) + tmaster = cls.masterclass(cls.tqueue, config) + cls.proxy = ProxyThread(tmaster) cls.proxy.start() @property -- cgit v1.2.3 From 05e4d4468ec372adb73649e6980c525a185e9c07 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sat, 23 Feb 2013 21:59:25 +1300 Subject: Test request and response kill functionality. --- test/.gitignore | 1 - test/.pry | 6 ------ test/test_server.py | 32 +++++++++++++++++++++++++++++++- 3 files changed, 31 insertions(+), 8 deletions(-) delete mode 100644 test/.gitignore delete mode 100644 test/.pry (limited to 'test') diff --git a/test/.gitignore b/test/.gitignore deleted file mode 100644 index 6350e986..00000000 --- a/test/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.coverage diff --git a/test/.pry b/test/.pry deleted file mode 100644 index f6f18e7b..00000000 --- a/test/.pry +++ /dev/null @@ -1,6 +0,0 @@ -base = .. -coverage = ../libmproxy -exclude = . - ../libmproxy/contrib - ../libmproxy/tnetstring.py - diff --git a/test/test_server.py b/test/test_server.py index 5cba891c..8aefa4b8 100644 --- a/test/test_server.py +++ b/test/test_server.py @@ -2,7 +2,7 @@ import socket, time from netlib import tcp from libpathod import pathoc import tutils, tservers -from libmproxy import flow +from libmproxy import flow, proxy """ Note that the choice of response code in these tests matters more than you @@ -147,6 +147,7 @@ class TestProxy(tservers.HTTPProxTest): assert request.timestamp_end - request.timestamp_start <= 0.1 + class MasterFakeResponse(tservers.TestMaster): def handle_request(self, m): resp = tutils.tresp() @@ -160,3 +161,32 @@ class TestFakeResponse(tservers.HTTPProxTest): f = self.pathod("200") assert "header_response" in f.headers.keys() + + +class MasterKillRequest(tservers.TestMaster): + def handle_request(self, m): + m.reply(proxy.KILL) + + +class TestKillRequest(tservers.HTTPProxTest): + masterclass = MasterKillRequest + def test_kill(self): + p = self.pathoc() + tutils.raises("empty reply", self.pathod, "200") + # Nothing should have hit the server + assert not self.last_log() + + +class MasterKillResponse(tservers.TestMaster): + def handle_response(self, m): + m.reply(proxy.KILL) + + +class TestKillResponse(tservers.HTTPProxTest): + masterclass = MasterKillResponse + def test_kill(self): + p = self.pathoc() + tutils.raises("empty reply", self.pathod, "200") + # The server should have seen a request + assert self.last_log() + -- cgit v1.2.3 From 51de9f9fdf6ec4cd345e0b2c8607453cc22c5045 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sun, 24 Feb 2013 10:51:14 +1300 Subject: Test client connection close conditions. --- test/test_server.py | 16 ++++++++++++++++ test/tservers.py | 3 +++ 2 files changed, 19 insertions(+) (limited to 'test') diff --git a/test/test_server.py b/test/test_server.py index 8aefa4b8..a2c65275 100644 --- a/test/test_server.py +++ b/test/test_server.py @@ -81,6 +81,22 @@ class TestHTTP(tservers.HTTPProxTest, SanityMixin): assert "host" in l.request.headers assert l.response.code == 304 + def test_connection_close(self): + # Add a body, so we have a content-length header, which combined with + # HTTP1.1 means the connection is kept alive. + response = '%s/p/200:b@1'%self.urlbase + + # Lets sanity check that the connection does indeed stay open by + # issuing two requests over the same connection + p = self.pathoc() + assert p.request("get:'%s'"%response) + assert p.request("get:'%s'"%response) + + # Now check that the connection is closed as the client specifies + p = self.pathoc() + assert p.request("get:'%s':h'Connection'='close'"%response) + tutils.raises("disconnect", p.request, "get:'%s'"%response) + class TestHTTPS(tservers.HTTPProxTest, SanityMixin): ssl = True diff --git a/test/tservers.py b/test/tservers.py index 3fdb8d13..ae0bacf5 100644 --- a/test/tservers.py +++ b/test/tservers.py @@ -117,6 +117,9 @@ class HTTPProxTest(ProxTestBase): return d def pathoc(self, connect_to = None): + """ + Returns a connected Pathoc instance. + """ p = libpathod.pathoc.Pathoc("localhost", self.proxy.port) p.connect(connect_to) return p -- cgit v1.2.3 From 64285140f959eaa939c4cf35585cfe21cbf1a449 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sun, 24 Feb 2013 11:34:01 +1300 Subject: Test a difficult-to-trigger IOError, fix cert generation in test suite. --- test/test_server.py | 9 +++++++++ test/tservers.py | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) (limited to 'test') diff --git a/test/test_server.py b/test/test_server.py index a2c65275..9df88400 100644 --- a/test/test_server.py +++ b/test/test_server.py @@ -1,4 +1,5 @@ import socket, time +import mock from netlib import tcp from libpathod import pathoc import tutils, tservers @@ -97,6 +98,14 @@ class TestHTTP(tservers.HTTPProxTest, SanityMixin): assert p.request("get:'%s':h'Connection'='close'"%response) tutils.raises("disconnect", p.request, "get:'%s'"%response) + def test_proxy_ioerror(self): + # Tests a difficult-to-trigger condition, where an IOError is raised + # within our read loop. + with mock.patch("libmproxy.proxy.ProxyHandler.read_request") as m: + m.side_effect = IOError("error!") + tutils.raises("empty reply", self.pathod, "304") + + class TestHTTPS(tservers.HTTPProxTest, SanityMixin): ssl = True diff --git a/test/tservers.py b/test/tservers.py index ae0bacf5..262536a7 100644 --- a/test/tservers.py +++ b/test/tservers.py @@ -63,7 +63,7 @@ class ProxTestBase: cls.server = libpathod.test.Daemon(ssl=cls.ssl) pconf = cls.get_proxy_config() config = proxy.ProxyConfig( - certfile=tutils.test_data.path("data/testkey.pem"), + cacert = tutils.test_data.path("data/serverkey.pem"), **pconf ) tmaster = cls.masterclass(cls.tqueue, config) -- cgit v1.2.3 From d0639e8925541bd6f6f386386c982d23b3828d3d Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sun, 24 Feb 2013 14:04:56 +1300 Subject: Handle server disconnects better. Server connections can be closed for legitimate reasons, like timeouts. If we've already pumped data over a server connection, we reconnect on error. If not, we treat it as a legitimate error and pass it on to the client. Fixes #85 --- test/test_server.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) (limited to 'test') diff --git a/test/test_server.py b/test/test_server.py index 9df88400..924b63b7 100644 --- a/test/test_server.py +++ b/test/test_server.py @@ -98,6 +98,19 @@ class TestHTTP(tservers.HTTPProxTest, SanityMixin): assert p.request("get:'%s':h'Connection'='close'"%response) tutils.raises("disconnect", p.request, "get:'%s'"%response) + def test_reconnect(self): + req = "get:'%s/p/200:b@1:da'"%self.urlbase + p = self.pathoc() + assert p.request(req) + # Server has disconnected. Mitmproxy should detect this, and reconnect. + assert p.request(req) + assert p.request(req) + + # However, if the server disconnects on our first try, it's an error. + req = "get:'%s/p/200:b@1:d0'"%self.urlbase + p = self.pathoc() + tutils.raises("server disconnect", p.request, req) + def test_proxy_ioerror(self): # Tests a difficult-to-trigger condition, where an IOError is raised # within our read loop. @@ -106,7 +119,6 @@ class TestHTTP(tservers.HTTPProxTest, SanityMixin): tutils.raises("empty reply", self.pathod, "304") - class TestHTTPS(tservers.HTTPProxTest, SanityMixin): ssl = True clientcerts = True -- cgit v1.2.3 From 705559d65e5dc5883395efb85bacbf1459eb243c Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sun, 24 Feb 2013 17:35:24 +1300 Subject: Refactor to prepare for SNI fixes. --- test/test_proxy.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'test') diff --git a/test/test_proxy.py b/test/test_proxy.py index bdac8697..b575a1d0 100644 --- a/test/test_proxy.py +++ b/test/test_proxy.py @@ -40,7 +40,7 @@ class TestServerConnection: def test_simple(self): sc = proxy.ServerConnection(proxy.ProxyConfig(), self.d.IFACE, self.d.port) - sc.connect("http") + sc.connect("http", "host.com") r = tutils.treq() r.path = "/p/200:da" sc.send(r) @@ -54,7 +54,7 @@ class TestServerConnection: def test_terminate_error(self): sc = proxy.ServerConnection(proxy.ProxyConfig(), self.d.IFACE, self.d.port) - sc.connect("http") + sc.connect("http", "host.com") sc.connection = mock.Mock() sc.connection.close = mock.Mock(side_effect=IOError) sc.terminate() @@ -75,14 +75,14 @@ class TestServerConnectionPool: @mock.patch("libmproxy.proxy.ServerConnection", _dummysc) def test_pooling(self): p = proxy.ServerConnectionPool(proxy.ProxyConfig()) - c = p.get_connection("http", "localhost", 80) - c2 = p.get_connection("http", "localhost", 80) + c = p.get_connection("http", "localhost", 80, "localhost") + c2 = p.get_connection("http", "localhost", 80, "localhost") assert c is c2 - c3 = p.get_connection("http", "foo", 80) + c3 = p.get_connection("http", "foo", 80, "localhost") assert not c is c3 @mock.patch("libmproxy.proxy.ServerConnection", _errsc) def test_connection_error(self): p = proxy.ServerConnectionPool(proxy.ProxyConfig()) - tutils.raises("502", p.get_connection, "http", "localhost", 80) + tutils.raises("502", p.get_connection, "http", "localhost", 80, "localhost") -- cgit v1.2.3 From 02578151410fff4b3c018303290e2f843e244a89 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sun, 24 Feb 2013 22:24:21 +1300 Subject: Significantly simplify server connection handling, and test. --- test/test_proxy.py | 35 ++++------------------------------- test/test_server.py | 28 ++++++++++++++++++++++------ test/tservers.py | 23 ++++++++++++----------- 3 files changed, 38 insertions(+), 48 deletions(-) (limited to 'test') diff --git a/test/test_proxy.py b/test/test_proxy.py index b575a1d0..3995b393 100644 --- a/test/test_proxy.py +++ b/test/test_proxy.py @@ -39,8 +39,8 @@ class TestServerConnection: self.d.shutdown() def test_simple(self): - sc = proxy.ServerConnection(proxy.ProxyConfig(), self.d.IFACE, self.d.port) - sc.connect("http", "host.com") + sc = proxy.ServerConnection(proxy.ProxyConfig(), "http", self.d.IFACE, self.d.port, "host.com") + sc.connect() r = tutils.treq() r.path = "/p/200:da" sc.send(r) @@ -53,36 +53,9 @@ class TestServerConnection: sc.terminate() def test_terminate_error(self): - sc = proxy.ServerConnection(proxy.ProxyConfig(), self.d.IFACE, self.d.port) - sc.connect("http", "host.com") + sc = proxy.ServerConnection(proxy.ProxyConfig(), "http", self.d.IFACE, self.d.port, "host.com") + sc.connect() sc.connection = mock.Mock() sc.connection.close = mock.Mock(side_effect=IOError) sc.terminate() - - -def _dummysc(config, host, port): - return mock.MagicMock(config=config, host=host, port=port) - - -def _errsc(config, host, port): - m = mock.MagicMock(config=config, host=host, port=port) - m.connect = mock.MagicMock(side_effect=tcp.NetLibError()) - return m - - -class TestServerConnectionPool: - @mock.patch("libmproxy.proxy.ServerConnection", _dummysc) - def test_pooling(self): - p = proxy.ServerConnectionPool(proxy.ProxyConfig()) - c = p.get_connection("http", "localhost", 80, "localhost") - c2 = p.get_connection("http", "localhost", 80, "localhost") - assert c is c2 - c3 = p.get_connection("http", "foo", 80, "localhost") - assert not c is c3 - - @mock.patch("libmproxy.proxy.ServerConnection", _errsc) - def test_connection_error(self): - p = proxy.ServerConnectionPool(proxy.ProxyConfig()) - tutils.raises("502", p.get_connection, "http", "localhost", 80, "localhost") - diff --git a/test/test_server.py b/test/test_server.py index 924b63b7..f93ddbb3 100644 --- a/test/test_server.py +++ b/test/test_server.py @@ -85,7 +85,7 @@ class TestHTTP(tservers.HTTPProxTest, SanityMixin): def test_connection_close(self): # Add a body, so we have a content-length header, which combined with # HTTP1.1 means the connection is kept alive. - response = '%s/p/200:b@1'%self.urlbase + response = '%s/p/200:b@1'%self.server.urlbase # Lets sanity check that the connection does indeed stay open by # issuing two requests over the same connection @@ -99,7 +99,7 @@ class TestHTTP(tservers.HTTPProxTest, SanityMixin): tutils.raises("disconnect", p.request, "get:'%s'"%response) def test_reconnect(self): - req = "get:'%s/p/200:b@1:da'"%self.urlbase + req = "get:'%s/p/200:b@1:da'"%self.server.urlbase p = self.pathoc() assert p.request(req) # Server has disconnected. Mitmproxy should detect this, and reconnect. @@ -107,7 +107,7 @@ class TestHTTP(tservers.HTTPProxTest, SanityMixin): assert p.request(req) # However, if the server disconnects on our first try, it's an error. - req = "get:'%s/p/200:b@1:d0'"%self.urlbase + req = "get:'%s/p/200:b@1:d0'"%self.server.urlbase p = self.pathoc() tutils.raises("server disconnect", p.request, req) @@ -118,13 +118,29 @@ class TestHTTP(tservers.HTTPProxTest, SanityMixin): m.side_effect = IOError("error!") tutils.raises("empty reply", self.pathod, "304") + def test_get_connection_switching(self): + def switched(l): + for i in l: + if "switching" in i: + return True + req = "get:'%s/p/200:b@1'" + p = self.pathoc() + assert p.request(req%self.server.urlbase) + assert p.request(req%self.server2.urlbase) + assert switched(self.proxy.log) + + def test_get_connection_err(self): + p = self.pathoc() + ret = p.request("get:'http://localhost:0'") + assert ret[1] == 502 + class TestHTTPS(tservers.HTTPProxTest, SanityMixin): ssl = True clientcerts = True def test_clientcert(self): f = self.pathod("304") - assert self.last_log()["request"]["clientcert"]["keyinfo"] + assert self.server.last_log()["request"]["clientcert"]["keyinfo"] class TestReverse(tservers.ReverseProxTest, SanityMixin): @@ -211,7 +227,7 @@ class TestKillRequest(tservers.HTTPProxTest): p = self.pathoc() tutils.raises("empty reply", self.pathod, "200") # Nothing should have hit the server - assert not self.last_log() + assert not self.server.last_log() class MasterKillResponse(tservers.TestMaster): @@ -225,5 +241,5 @@ class TestKillResponse(tservers.HTTPProxTest): p = self.pathoc() tutils.raises("empty reply", self.pathod, "200") # The server should have seen a request - assert self.last_log() + assert self.server.last_log() diff --git a/test/tservers.py b/test/tservers.py index 262536a7..9597dab4 100644 --- a/test/tservers.py +++ b/test/tservers.py @@ -28,6 +28,7 @@ class TestMaster(flow.FlowMaster): state = flow.State() flow.FlowMaster.__init__(self, s, state) self.testq = testq + self.log = [] def handle_request(self, m): flow.FlowMaster.handle_request(self, m) @@ -37,6 +38,10 @@ class TestMaster(flow.FlowMaster): flow.FlowMaster.handle_response(self, m) m.reply() + def handle_log(self, l): + self.log.append(l.msg) + l.reply() + class ProxyThread(threading.Thread): def __init__(self, tmaster): @@ -48,6 +53,10 @@ class ProxyThread(threading.Thread): def port(self): return self.tmaster.server.port + @property + def log(self): + return self.tmaster.log + def run(self): self.tmaster.run() @@ -61,6 +70,7 @@ class ProxTestBase: def setupAll(cls): cls.tqueue = Queue.Queue() cls.server = libpathod.test.Daemon(ssl=cls.ssl) + cls.server2 = libpathod.test.Daemon(ssl=cls.ssl) pconf = cls.get_proxy_config() config = proxy.ProxyConfig( cacert = tutils.test_data.path("data/serverkey.pem"), @@ -78,6 +88,7 @@ class ProxTestBase: def teardownAll(cls): cls.proxy.shutdown() cls.server.shutdown() + cls.server2.shutdown() def setUp(self): self.master.state.clear() @@ -95,16 +106,6 @@ class ProxTestBase: (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 @@ -129,7 +130,7 @@ class HTTPProxTest(ProxTestBase): Constructs a pathod request, with the appropriate base and proxy. """ return hurl.get( - self.urlbase + "/p/" + spec, + self.server.urlbase + "/p/" + spec, proxy=self.proxies, validate_cert=False, #debug=hurl.utils.stdout_debug -- cgit v1.2.3 From b077189dd5230b6c440a200d867c70c6ce031b66 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sun, 24 Feb 2013 22:52:59 +1300 Subject: Test cert file specification, spruce up server testing truss a bit. --- test/test_server.py | 7 +++++++ test/tservers.py | 31 ++++++++++++++++++------------- 2 files changed, 25 insertions(+), 13 deletions(-) (limited to 'test') diff --git a/test/test_server.py b/test/test_server.py index f93ddbb3..034fab41 100644 --- a/test/test_server.py +++ b/test/test_server.py @@ -143,6 +143,13 @@ class TestHTTPS(tservers.HTTPProxTest, SanityMixin): assert self.server.last_log()["request"]["clientcert"]["keyinfo"] +class TestHTTPSCertfile(tservers.HTTPProxTest, SanityMixin): + ssl = True + certfile = True + def test_certfile(self): + assert self.pathod("304") + + class TestReverse(tservers.ReverseProxTest, SanityMixin): reverse = True diff --git a/test/tservers.py b/test/tservers.py index 9597dab4..998ad6c6 100644 --- a/test/tservers.py +++ b/test/tservers.py @@ -65,6 +65,11 @@ class ProxyThread(threading.Thread): class ProxTestBase: + # Test Configuration + ssl = None + clientcerts = False + certfile = None + masterclass = TestMaster @classmethod def setupAll(cls): @@ -106,17 +111,17 @@ class ProxTestBase: (self.scheme, ("127.0.0.1", self.proxy.port)) ) - -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") + if cls.certfile: + d["certfile"] =tutils.test_data.path("data/testkey.pem") return d + +class HTTPProxTest(ProxTestBase): def pathoc(self, connect_to = None): """ Returns a connected Pathoc instance. @@ -149,12 +154,12 @@ class TransparentProxTest(ProxTestBase): ssl = None @classmethod def get_proxy_config(cls): - return dict( - transparent_proxy = dict( - resolver = TResolver(cls.server.port), - sslports = [] - ) - ) + d = ProxTestBase.get_proxy_config() + d["transparent_proxy"] = dict( + resolver = TResolver(cls.server.port), + sslports = [] + ) + return d def pathod(self, spec): """ @@ -172,13 +177,13 @@ class ReverseProxTest(ProxTestBase): ssl = None @classmethod def get_proxy_config(cls): - return dict( - reverse_proxy = ( + d = ProxTestBase.get_proxy_config() + d["reverse_proxy"] = ( "https" if cls.ssl else "http", "127.0.0.1", cls.server.port ) - ) + return d def pathod(self, spec): """ -- cgit v1.2.3