diff options
Diffstat (limited to 'test')
24 files changed, 263 insertions, 206 deletions
diff --git a/test/bench/.gitignore b/test/bench/.gitignore new file mode 100644 index 00000000..1a06816d --- /dev/null +++ b/test/bench/.gitignore @@ -0,0 +1 @@ +results diff --git a/test/bench/README.md b/test/bench/README.md new file mode 100644 index 00000000..05741c07 --- /dev/null +++ b/test/bench/README.md @@ -0,0 +1,56 @@ + +This directory contains a set of tools for benchmarking and profiling mitmproxy. +At the moment, this is simply to give developers a quick way to see the impact +of their work. Eventually, this might grow into a performance dashboard with +historical data, so we can track performance over time. + + +# Setup + +Install the following tools: + + go get -u github.com/rakyll/hey + go get github.com/cortesi/devd/cmd/devd + +You may also want to install snakeviz to make viewing profiles easier: + + pip install snakeviz + +In one window, run the devd server: + + ./backend + + +# Running tests + +Each run consists of two files - a mitproxy invocation, and a traffic generator. +Make sure the backend is started, then run the proxy: + + ./simple.mitmproxy + +Now run the traffic generator: + + ./simple.traffic + +After the run is done, quit the proxy with ctrl-c. + + +# Reading results + +Results are placed in the ./results directory. You should see two files - a +performance log from **hey**, and a profile. You can view the profile like so: + + snakeviz ./results/simple.prof + + + + + + + + + + + + + diff --git a/test/bench/backend b/test/bench/backend new file mode 100755 index 00000000..12a05d70 --- /dev/null +++ b/test/bench/backend @@ -0,0 +1,3 @@ +#!/bin/sh + +devd -p 10001 .
\ No newline at end of file diff --git a/test/bench/profiler.py b/test/bench/profiler.py new file mode 100644 index 00000000..9072e17d --- /dev/null +++ b/test/bench/profiler.py @@ -0,0 +1,25 @@ +import cProfile +from mitmproxy import ctx + + +class Profile: + """ + A simple profiler addon. + """ + def __init__(self): + self.pr = cProfile.Profile() + + def load(self, loader): + loader.add_option( + "profile_path", + str, + "/tmp/profile", + "Destination for the run profile, saved at exit" + ) + self.pr.enable() + + def done(self): + self.pr.dump_stats(ctx.options.profile_path) + + +addons = [Profile()]
\ No newline at end of file diff --git a/test/bench/simple.mitmproxy b/test/bench/simple.mitmproxy new file mode 100755 index 00000000..9de32981 --- /dev/null +++ b/test/bench/simple.mitmproxy @@ -0,0 +1,5 @@ +#!/bin/sh + +mkdir -p results +mitmdump -p 10002 --mode reverse:http://devd.io:10001 \ + -s ./profiler.py --set profile_path=./results/simple.prof diff --git a/test/bench/simple.traffic b/test/bench/simple.traffic new file mode 100755 index 00000000..08200e05 --- /dev/null +++ b/test/bench/simple.traffic @@ -0,0 +1,3 @@ +#!/bin/sh + +hey -disable-keepalive http://localhost:10002/profiler.py | tee ./results/simple.perf
\ No newline at end of file diff --git a/test/filename_matching.py b/test/filename_matching.py index e74848d4..5f49725e 100644..100755 --- a/test/filename_matching.py +++ b/test/filename_matching.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python3 + import os import re import glob diff --git a/test/individual_coverage.py b/test/individual_coverage.py index c975b4c8..097b290f 100644..100755 --- a/test/individual_coverage.py +++ b/test/individual_coverage.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python3 + import io import contextlib import os diff --git a/test/mitmproxy/addons/test_onboarding.py b/test/mitmproxy/addons/test_onboarding.py index 810ddef1..0d99b1ff 100644 --- a/test/mitmproxy/addons/test_onboarding.py +++ b/test/mitmproxy/addons/test_onboarding.py @@ -4,6 +4,10 @@ from mitmproxy.addons import onboarding from mitmproxy.test import taddons from .. import tservers +import asyncio +import tornado.platform.asyncio +asyncio.set_event_loop_policy(tornado.platform.asyncio.AnyThreadEventLoopPolicy()) + class TestApp(tservers.HTTPProxyTest): def addons(self): diff --git a/test/mitmproxy/data/addonscripts/shutdown.py b/test/mitmproxy/data/addonscripts/shutdown.py index 51a99b5c..3da4d03e 100644 --- a/test/mitmproxy/data/addonscripts/shutdown.py +++ b/test/mitmproxy/data/addonscripts/shutdown.py @@ -1,5 +1,5 @@ from mitmproxy import ctx -def running(): +def tick(): ctx.master.shutdown() diff --git a/test/mitmproxy/net/http/test_request.py b/test/mitmproxy/net/http/test_request.py index ce49002c..ef581a91 100644 --- a/test/mitmproxy/net/http/test_request.py +++ b/test/mitmproxy/net/http/test_request.py @@ -351,10 +351,10 @@ class TestRequestUtils: request.headers["Content-Type"] = "application/x-www-form-urlencoded" assert list(request.urlencoded_form.items()) == [("foobar", "baz")] request.raw_content = b"\xFF" - assert len(request.urlencoded_form) == 0 + assert len(request.urlencoded_form) == 1 def test_set_urlencoded_form(self): - request = treq() + request = treq(content=b"\xec\xed") request.urlencoded_form = [('foo', 'bar'), ('rab', 'oof')] assert request.headers["Content-Type"] == "application/x-www-form-urlencoded" assert request.content diff --git a/test/mitmproxy/proxy/protocol/test_http2.py b/test/mitmproxy/proxy/protocol/test_http2.py index d9aa03b4..13f28728 100644 --- a/test/mitmproxy/proxy/protocol/test_http2.py +++ b/test/mitmproxy/proxy/protocol/test_http2.py @@ -10,7 +10,6 @@ import h2 from mitmproxy import options import mitmproxy.net -from mitmproxy.addons import core from ...net import tservers as net_tservers from mitmproxy import exceptions from mitmproxy.net.http import http1, http2 @@ -90,9 +89,7 @@ class _Http2TestBase: @classmethod def setup_class(cls): cls.options = cls.get_options() - tmaster = tservers.TestMaster(cls.options) - tmaster.addons.add(core.Core()) - cls.proxy = tservers.ProxyThread(tmaster) + cls.proxy = tservers.ProxyThread(tservers.TestMaster, cls.options) cls.proxy.start() @classmethod @@ -120,6 +117,7 @@ class _Http2TestBase: def teardown(self): if self.client: self.client.close() + self.server.server.wait_for_silence() def setup_connection(self): self.client = mitmproxy.net.tcp.TCPClient(("127.0.0.1", self.proxy.port)) diff --git a/test/mitmproxy/proxy/protocol/test_websocket.py b/test/mitmproxy/proxy/protocol/test_websocket.py index 661605b7..e5ed8e9d 100644 --- a/test/mitmproxy/proxy/protocol/test_websocket.py +++ b/test/mitmproxy/proxy/protocol/test_websocket.py @@ -3,10 +3,10 @@ import os import struct import tempfile import traceback +import time from mitmproxy import options from mitmproxy import exceptions -from mitmproxy.addons import core from mitmproxy.http import HTTPFlow from mitmproxy.websocket import WebSocketFlow @@ -52,9 +52,7 @@ class _WebSocketTestBase: @classmethod def setup_class(cls): cls.options = cls.get_options() - tmaster = tservers.TestMaster(cls.options) - tmaster.addons.add(core.Core()) - cls.proxy = tservers.ProxyThread(tmaster) + cls.proxy = tservers.ProxyThread(tservers.TestMaster, cls.options) cls.proxy.start() @classmethod @@ -163,7 +161,7 @@ class TestSimple(_WebSocketTest): def websocket_start(self, f): f.stream = streaming - self.master.addons.add(Stream()) + self.proxy.set_addons(Stream()) self.setup_connection() frame = websockets.Frame.from_file(self.client.rfile) @@ -204,7 +202,7 @@ class TestSimple(_WebSocketTest): def websocket_message(self, f): f.messages[-1].content = "foo" - self.master.addons.add(Addon()) + self.proxy.set_addons(Addon()) self.setup_connection() frame = websockets.Frame.from_file(self.client.rfile) @@ -235,7 +233,7 @@ class TestKillFlow(_WebSocketTest): def websocket_message(self, f): f.kill() - self.master.addons.add(KillFlow()) + self.proxy.set_addons(KillFlow()) self.setup_connection() with pytest.raises(exceptions.TcpDisconnect): @@ -329,7 +327,12 @@ class TestPong(_WebSocketTest): assert frame.header.opcode == websockets.OPCODE.PONG assert frame.payload == b'foobar' - assert self.master.has_log("Pong Received from server", "info") + for i in range(20): + if self.master.has_log("Pong Received from server", "info"): + break + time.sleep(0.01) + else: + raise AssertionError("No pong seen") class TestClose(_WebSocketTest): @@ -405,7 +408,7 @@ class TestStreaming(_WebSocketTest): def websocket_start(self, f): f.stream = streaming - self.master.addons.add(Stream()) + self.proxy.set_addons(Stream()) self.setup_connection() frame = None diff --git a/test/mitmproxy/proxy/test_server.py b/test/mitmproxy/proxy/test_server.py index 986dfb39..f594fb40 100644 --- a/test/mitmproxy/proxy/test_server.py +++ b/test/mitmproxy/proxy/test_server.py @@ -21,14 +21,6 @@ from pathod import pathod from .. import tservers from ...conftest import skip_appveyor -""" - Note that the choice of response code in these tests matters more than you - might think. libcurl treats a 304 response code differently from, say, a - 200 response code - it will correctly terminate a 304 response with no - content-length header, whereas it will block forever waiting for content - for a 200 response. -""" - class CommonMixin: @@ -237,28 +229,14 @@ class TestHTTP(tservers.HTTPProxyTest, CommonMixin): p.request("get:'%s'" % response) def test_reconnect(self): - req = "get:'%s/p/200:b@1'" % self.server.urlbase + req = "get:'%s/p/200:b@1:da'" % self.server.urlbase p = self.pathoc() - class MockOnce: - call = 0 - - def mock_once(self, http1obj, req): - self.call += 1 - if self.call == 1: - raise exceptions.TcpDisconnect - else: - headers = http1.assemble_request_head(req) - http1obj.server_conn.wfile.write(headers) - http1obj.server_conn.wfile.flush() - with p.connect(): - with mock.patch("mitmproxy.proxy.protocol.http1.Http1Layer.send_request_headers", - side_effect=MockOnce().mock_once, autospec=True): - # Server disconnects while sending headers but mitmproxy reconnects - resp = p.request(req) - assert resp - assert resp.status_code == 200 + assert p.request(req) + # Server has disconnected. Mitmproxy should detect this, and reconnect. + assert p.request(req) + assert p.request(req) def test_get_connection_switching(self): req = "get:'%s/p/200:b@1'" @@ -284,10 +262,9 @@ class TestHTTP(tservers.HTTPProxyTest, CommonMixin): s = script.Script( tutils.test_data.path("mitmproxy/data/addonscripts/stream_modify.py") ) - self.master.addons.add(s) + self.set_addons(s) d = self.pathod('200:b"foo"') assert d.content == b"bar" - self.master.addons.remove(s) def test_first_line_rewrite(self): """ @@ -591,12 +568,11 @@ class TestTransparent(tservers.TransparentProxyTest, CommonMixin, TcpMixin): s = script.Script( tutils.test_data.path("mitmproxy/data/addonscripts/tcp_stream_modify.py") ) - self.master.addons.add(s) + self.set_addons(s) self._tcpproxy_on() d = self.pathod('200:b"foo"') self._tcpproxy_off() assert d.content == b"bar" - self.master.addons.remove(s) class TestTransparentSSL(tservers.TransparentProxyTest, CommonMixin, TcpMixin): @@ -739,7 +715,7 @@ class TestRedirectRequest(tservers.HTTPProxyTest): This test verifies that the original destination is restored for the third request. """ - self.proxy.tmaster.addons.add(ARedirectRequest(self.server2.port)) + self.set_addons(ARedirectRequest(self.server2.port)) p = self.pathoc() with p.connect(): @@ -778,7 +754,7 @@ class AStreamRequest: class TestStreamRequest(tservers.HTTPProxyTest): def test_stream_simple(self): - self.proxy.tmaster.addons.add(AStreamRequest()) + self.set_addons(AStreamRequest()) p = self.pathoc() with p.connect(): # a request with 100k of data but without content-length @@ -787,7 +763,7 @@ class TestStreamRequest(tservers.HTTPProxyTest): assert len(r1.content) > 100000 def test_stream_multiple(self): - self.proxy.tmaster.addons.add(AStreamRequest()) + self.set_addons(AStreamRequest()) p = self.pathoc() with p.connect(): # simple request with streaming turned on @@ -799,7 +775,7 @@ class TestStreamRequest(tservers.HTTPProxyTest): assert r1.status_code == 201 def test_stream_chunked(self): - self.proxy.tmaster.addons.add(AStreamRequest()) + self.set_addons(AStreamRequest()) connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM) connection.connect(("127.0.0.1", self.proxy.port)) fconn = connection.makefile("rb") @@ -828,7 +804,7 @@ class AFakeResponse: class TestFakeResponse(tservers.HTTPProxyTest): def test_fake(self): - self.proxy.tmaster.addons.add(AFakeResponse()) + self.set_addons(AFakeResponse()) f = self.pathod("200") assert "header-response" in f.headers @@ -844,7 +820,7 @@ class TestServerConnect(tservers.HTTPProxyTest): def test_unnecessary_serverconnect(self): """A replayed/fake response with no upstream_cert should not connect to an upstream server""" - self.proxy.tmaster.addons.add(AFakeResponse()) + self.set_addons(AFakeResponse()) assert self.pathod("200").status_code == 200 assert not self.proxy.tmaster.has_log("serverconnect") @@ -857,7 +833,7 @@ class AKillRequest: class TestKillRequest(tservers.HTTPProxyTest): def test_kill(self): - self.proxy.tmaster.addons.add(AKillRequest()) + self.set_addons(AKillRequest()) with pytest.raises(exceptions.HttpReadDisconnect): self.pathod("200") # Nothing should have hit the server @@ -871,7 +847,7 @@ class AKillResponse: class TestKillResponse(tservers.HTTPProxyTest): def test_kill(self): - self.proxy.tmaster.addons.add(AKillResponse()) + self.set_addons(AKillResponse()) with pytest.raises(exceptions.HttpReadDisconnect): self.pathod("200") # The server should have seen a request @@ -894,7 +870,7 @@ class AIncomplete: class TestIncompleteResponse(tservers.HTTPProxyTest): def test_incomplete(self): - self.proxy.tmaster.addons.add(AIncomplete()) + self.set_addons(AIncomplete()) assert self.pathod("200").status_code == 502 @@ -977,7 +953,7 @@ class TestUpstreamProxySSL( def test_change_upstream_proxy_connect(self): # skip chain[0]. - self.proxy.tmaster.addons.add( + self.set_addons( UpstreamProxyChanger( ("127.0.0.1", self.chain[1].port) ) @@ -996,8 +972,8 @@ class TestUpstreamProxySSL( Client <- HTTPS -> Proxy <- HTTP -> Proxy <- HTTPS -> Server """ - self.proxy.tmaster.addons.add(RewriteToHttp()) - self.chain[1].tmaster.addons.add(RewriteToHttps()) + self.set_addons(RewriteToHttp()) + self.chain[1].set_addons(RewriteToHttps()) p = self.pathoc() with p.connect(): resp = p.request("get:'/p/418'") @@ -1055,24 +1031,8 @@ class TestProxyChainingSSLReconnect(tservers.HTTPUpstreamProxyTest): request again. """ - class MockOnce: - call = 0 - - def mock_once(self, http1obj, req): - self.call += 1 - - if self.call == 2: - headers = http1.assemble_request_head(req) - http1obj.server_conn.wfile.write(headers) - http1obj.server_conn.wfile.flush() - raise exceptions.TcpDisconnect - else: - headers = http1.assemble_request_head(req) - http1obj.server_conn.wfile.write(headers) - http1obj.server_conn.wfile.flush() - - self.chain[0].tmaster.addons.add(RequestKiller([1, 2])) - self.chain[1].tmaster.addons.add(RequestKiller([1])) + self.chain[0].set_addons(RequestKiller([1, 2])) + self.chain[1].set_addons(RequestKiller([1])) p = self.pathoc() with p.connect(): @@ -1085,9 +1045,7 @@ class TestProxyChainingSSLReconnect(tservers.HTTPUpstreamProxyTest): assert len(self.chain[0].tmaster.state.flows) == 1 assert len(self.chain[1].tmaster.state.flows) == 1 - with mock.patch("mitmproxy.proxy.protocol.http1.Http1Layer.send_request_headers", - side_effect=MockOnce().mock_once, autospec=True): - req = p.request("get:'/p/418:b\"content2\"'") + req = p.request("get:'/p/418:b\"content2\"'") assert req.status_code == 502 diff --git a/test/mitmproxy/test_command.py b/test/mitmproxy/test_command.py index e2b80753..3d0a43f8 100644 --- a/test/mitmproxy/test_command.py +++ b/test/mitmproxy/test_command.py @@ -309,6 +309,31 @@ class TDec: pass +class TAttr: + def __getattr__(self, item): + raise IOError + + +class TCmds(TAttr): + def __init__(self): + self.TAttr = TAttr() + + @command.command("empty") + def empty(self) -> None: + pass + + +def test_collect_commands(): + """ + This tests for the error thrown by hasattr() + """ + with taddons.context() as tctx: + c = command.CommandManager(tctx.master) + a = TCmds() + c.collect_commands(a) + assert "empty" in c.commands + + def test_decorator(): with taddons.context() as tctx: c = command.CommandManager(tctx.master) diff --git a/test/mitmproxy/test_connections.py b/test/mitmproxy/test_connections.py index 00cdbc87..845a9043 100644 --- a/test/mitmproxy/test_connections.py +++ b/test/mitmproxy/test_connections.py @@ -38,6 +38,9 @@ class TestClientConnection: assert 'ALPN' not in repr(c) assert 'TLS' in repr(c) + c.address = None + assert repr(c) + def test_tls_established_property(self): c = tflow.tclient_conn() c.tls_established = True @@ -110,6 +113,9 @@ class TestServerConnection: c.tls_established = False assert 'TLS' not in repr(c) + c.address = None + assert repr(c) + def test_tls_established_property(self): c = tflow.tserver_conn() c.tls_established = True diff --git a/test/mitmproxy/test_controller.py b/test/mitmproxy/test_controller.py index e840380a..f7c64ed9 100644 --- a/test/mitmproxy/test_controller.py +++ b/test/mitmproxy/test_controller.py @@ -1,82 +1,31 @@ -from threading import Thread, Event -from unittest.mock import Mock +import asyncio import queue import pytest from mitmproxy.exceptions import Kill, ControlException from mitmproxy import controller -from mitmproxy import master -from mitmproxy import proxy from mitmproxy.test import taddons -class TMsg: - pass +@pytest.mark.asyncio +async def test_master(): + class TMsg: + pass + class tAddon: + def log(self, _): + ctx.master.should_exit.set() -class TestMaster: - def test_simple(self): - class tAddon: - def log(self, _): - ctx.master.should_exit.set() + with taddons.context(tAddon()) as ctx: + assert not ctx.master.should_exit.is_set() - with taddons.context() as ctx: - ctx.master.addons.add(tAddon()) - assert not ctx.master.should_exit.is_set() + async def test(): msg = TMsg() msg.reply = controller.DummyReply() - ctx.master.event_queue.put(("log", msg)) - ctx.master.run() - assert ctx.master.should_exit.is_set() - - def test_server_simple(self): - m = master.Master(None) - m.server = proxy.DummyServer() - m.start() - m.shutdown() - m.start() - m.shutdown() + await ctx.master.channel.tell("log", msg) - -class TestServerThread: - def test_simple(self): - m = Mock() - t = master.ServerThread(m) - t.run() - assert m.serve_forever.called - - -class TestChannel: - def test_tell(self): - q = queue.Queue() - channel = controller.Channel(q, Event()) - m = Mock(name="test_tell") - channel.tell("test", m) - assert q.get() == ("test", m) - assert m.reply - - def test_ask_simple(self): - q = queue.Queue() - - def reply(): - m, obj = q.get() - assert m == "test" - obj.reply.send(42) - obj.reply.take() - obj.reply.commit() - - Thread(target=reply).start() - - channel = controller.Channel(q, Event()) - assert channel.ask("test", Mock(name="test_ask_simple")) == 42 - - def test_ask_shutdown(self): - q = queue.Queue() - done = Event() - done.set() - channel = controller.Channel(q, done) - with pytest.raises(Kill): - channel.ask("test", Mock(name="test_ask_shutdown")) + asyncio.ensure_future(test()) + assert not ctx.master.should_exit.is_set() class TestReply: diff --git a/test/mitmproxy/test_flow.py b/test/mitmproxy/test_flow.py index 8cc11a16..4042de5b 100644 --- a/test/mitmproxy/test_flow.py +++ b/test/mitmproxy/test_flow.py @@ -7,7 +7,7 @@ import mitmproxy.io from mitmproxy import flowfilter from mitmproxy import options from mitmproxy.io import tnetstring -from mitmproxy.exceptions import FlowReadException, ReplayException, ControlException +from mitmproxy.exceptions import FlowReadException, ReplayException from mitmproxy import flow from mitmproxy import http from mitmproxy.net import http as net_http @@ -169,9 +169,10 @@ class TestFlowMaster: f.error = flow.Error("msg") fm.addons.handle_lifecycle("error", f) - fm.tell("foo", f) - with pytest.raises(ControlException): - fm.tick(timeout=1) + # FIXME: This no longer works, because we consume on the main loop. + # fm.tell("foo", f) + # with pytest.raises(ControlException): + # fm.addons.trigger("unknown") fm.shutdown() diff --git a/test/mitmproxy/test_fuzzing.py b/test/mitmproxy/test_fuzzing.py index 905ba1cd..57d0ca55 100644 --- a/test/mitmproxy/test_fuzzing.py +++ b/test/mitmproxy/test_fuzzing.py @@ -25,14 +25,4 @@ class TestFuzzy(tservers.HTTPProxyTest): p = self.pathoc() with p.connect(): resp = p.request(req % self.server.port) - assert resp.status_code == 400 - - # def test_invalid_upstream(self): - # req = r"get:'http://localhost:%s/p/200:i10,\x27+\x27'" - # p = self.pathoc() - # assert p.request(req % self.server.port).status_code == 502 - - # def test_upstream_disconnect(self): - # req = r'200:d0' - # p = self.pathod(req) - # assert p.status_code == 502 + assert resp.status_code == 400
\ No newline at end of file diff --git a/test/mitmproxy/tools/console/test_statusbar.py b/test/mitmproxy/tools/console/test_statusbar.py index db8a63a7..108f238e 100644 --- a/test/mitmproxy/tools/console/test_statusbar.py +++ b/test/mitmproxy/tools/console/test_statusbar.py @@ -1,3 +1,5 @@ +import pytest + from mitmproxy import options from mitmproxy.tools.console import statusbar, master @@ -31,3 +33,29 @@ def test_statusbar(monkeypatch): bar = statusbar.StatusBar(m) # this already causes a redraw assert bar.ib._w + + +@pytest.mark.parametrize("message,ready_message", [ + ("", [(None, ""), ("warn", "")]), + (("info", "Line fits into statusbar"), [("info", "Line fits into statusbar"), + ("warn", "")]), + ("Line doesn't fit into statusbar", [(None, "Line doesn'\u2026"), + ("warn", "(more in eventlog)")]), + (("alert", "Two lines.\nFirst fits"), [("alert", "Two lines."), + ("warn", "(more in eventlog)")]), + ("Two long lines\nFirst doesn't fit", [(None, "Two long li\u2026"), + ("warn", "(more in eventlog)")]) +]) +def test_shorten_message(message, ready_message): + o = options.Options() + m = master.ConsoleMaster(o) + ab = statusbar.ActionBar(m) + assert ab.shorten_message(message, max_width=30) == ready_message + + +def test_shorten_message_narrow(): + o = options.Options() + m = master.ConsoleMaster(o) + ab = statusbar.ActionBar(m) + shorten_msg = ab.shorten_message("error", max_width=4) + assert shorten_msg == [(None, "\u2026"), ("warn", "(more in eventlog)")] diff --git a/test/mitmproxy/tools/test_main.py b/test/mitmproxy/tools/test_main.py index 88e2fe86..a514df74 100644 --- a/test/mitmproxy/tools/test_main.py +++ b/test/mitmproxy/tools/test_main.py @@ -1,19 +1,25 @@ +import pytest + from mitmproxy.test import tutils from mitmproxy.tools import main shutdown_script = tutils.test_data.path("mitmproxy/data/addonscripts/shutdown.py") -def test_mitmweb(): +@pytest.mark.asyncio +async def test_mitmweb(): main.mitmweb([ "--no-web-open-browser", "-q", + "-p", "0", "-s", shutdown_script ]) -def test_mitmdump(): +@pytest.mark.asyncio +async def test_mitmdump(): main.mitmdump([ "-q", + "-p", "0", "-s", shutdown_script ]) diff --git a/test/mitmproxy/tservers.py b/test/mitmproxy/tservers.py index 0040b023..2d102a5d 100644 --- a/test/mitmproxy/tservers.py +++ b/test/mitmproxy/tservers.py @@ -2,7 +2,9 @@ import os.path import threading import tempfile import sys +import time from unittest import mock +import asyncio import mitmproxy.platform from mitmproxy.addons import core @@ -12,6 +14,7 @@ from mitmproxy import controller from mitmproxy import options from mitmproxy import exceptions from mitmproxy import io +from mitmproxy.utils import human import pathod.test import pathod.pathoc @@ -62,11 +65,6 @@ class TestState: if f not in self.flows: self.flows.append(f) - # TODO: add TCP support? - # def tcp_start(self, f): - # if f not in self.flows: - # self.flows.append(f) - class TestMaster(taddons.RecordingMaster): @@ -90,13 +88,12 @@ class TestMaster(taddons.RecordingMaster): class ProxyThread(threading.Thread): - def __init__(self, tmaster): + def __init__(self, masterclass, options): threading.Thread.__init__(self) - self.tmaster = tmaster - self.name = "ProxyThread (%s:%s)" % ( - tmaster.server.address[0], - tmaster.server.address[1], - ) + self.masterclass = masterclass + self.options = options + self.tmaster = None + self.event_loop = None controller.should_exit = False @property @@ -107,11 +104,27 @@ class ProxyThread(threading.Thread): def tlog(self): return self.tmaster.logs + def shutdown(self): + self.tmaster.shutdown() + def run(self): + self.event_loop = asyncio.new_event_loop() + asyncio.set_event_loop(self.event_loop) + self.tmaster = self.masterclass(self.options) + self.tmaster.addons.add(core.Core()) + self.name = "ProxyThread (%s)" % human.format_address(self.tmaster.server.address) self.tmaster.run() - def shutdown(self): - self.tmaster.shutdown() + def set_addons(self, *addons): + self.tmaster.reset(addons) + self.tmaster.addons.trigger("tick") + + def start(self): + super().start() + while True: + if self.tmaster: + break + time.sleep(0.01) class ProxyTestBase: @@ -132,9 +145,7 @@ class ProxyTestBase: ssloptions=cls.ssloptions) cls.options = cls.get_options() - tmaster = cls.masterclass(cls.options) - tmaster.addons.add(core.Core()) - cls.proxy = ProxyThread(tmaster) + cls.proxy = ProxyThread(cls.masterclass, cls.options) cls.proxy.start() @classmethod @@ -173,6 +184,9 @@ class ProxyTestBase: ssl_insecure=True, ) + def set_addons(self, *addons): + self.proxy.set_addons(*addons) + def addons(self): """ Can be over-ridden to add a standard set of addons to tests. @@ -327,8 +341,7 @@ class SocksModeTest(HTTPProxyTest): return opts -class ChainProxyTest(ProxyTestBase): - +class HTTPUpstreamProxyTest(HTTPProxyTest): """ Chain three instances of mitmproxy in a row to test upstream mode. Proxy order is cls.proxy -> cls.chain[0] -> cls.chain[1] @@ -344,11 +357,12 @@ class ChainProxyTest(ProxyTestBase): cls.chain = [] for _ in range(cls.n): opts = cls.get_options() - tmaster = cls.masterclass(opts) - tmaster.addons.add(core.Core()) - proxy = ProxyThread(tmaster) + proxy = ProxyThread(cls.masterclass, opts) proxy.start() cls.chain.insert(0, proxy) + while True: + if proxy.event_loop and proxy.event_loop.is_running(): + break super().setup_class() @@ -372,7 +386,3 @@ class ChainProxyTest(ProxyTestBase): mode="upstream:" + s, ) return opts - - -class HTTPUpstreamProxyTest(ChainProxyTest, HTTPProxyTest): - pass diff --git a/test/mitmproxy/utils/test_human.py b/test/mitmproxy/utils/test_human.py index 947cfa4a..faf35f72 100644 --- a/test/mitmproxy/utils/test_human.py +++ b/test/mitmproxy/utils/test_human.py @@ -56,3 +56,4 @@ def test_format_address(): assert human.format_address(("example.com", "54010")) == "example.com:54010" assert human.format_address(("::", "8080")) == "*:8080" assert human.format_address(("0.0.0.0", "8080")) == "*:8080" + assert human.format_address(None) == "<no address>" diff --git a/test/mitmproxy/utils/test_typecheck.py b/test/mitmproxy/utils/test_typecheck.py index 9cb4334e..85713e14 100644 --- a/test/mitmproxy/utils/test_typecheck.py +++ b/test/mitmproxy/utils/test_typecheck.py @@ -1,6 +1,5 @@ import io import typing -from unittest import mock import pytest from mitmproxy.utils import typecheck @@ -32,12 +31,6 @@ def test_check_union(): with pytest.raises(TypeError): typecheck.check_option_type("foo", [], typing.Union[int, str]) - # Python 3.5 only defines __union_params__ - m = mock.Mock() - m.__str__ = lambda self: "typing.Union" - m.__union_params__ = (int,) - typecheck.check_option_type("foo", 42, m) - def test_check_tuple(): typecheck.check_option_type("foo", (42, "42"), typing.Tuple[int, str]) @@ -50,12 +43,6 @@ def test_check_tuple(): with pytest.raises(TypeError): typecheck.check_option_type("foo", ("42", 42), typing.Tuple[int, str]) - # Python 3.5 only defines __tuple_params__ - m = mock.Mock() - m.__str__ = lambda self: "typing.Tuple" - m.__tuple_params__ = (int, str) - typecheck.check_option_type("foo", (42, "42"), m) - def test_check_sequence(): typecheck.check_option_type("foo", [10], typing.Sequence[int]) @@ -68,12 +55,6 @@ def test_check_sequence(): with pytest.raises(TypeError): typecheck.check_option_type("foo", "foo", typing.Sequence[str]) - # Python 3.5 only defines __parameters__ - m = mock.Mock() - m.__str__ = lambda self: "typing.Sequence" - m.__parameters__ = (int,) - typecheck.check_option_type("foo", [10], m) - def test_check_io(): typecheck.check_option_type("foo", io.StringIO(), typing.IO[str]) |