Merge pull request #2003 from Kriechi/coverage++

test refactoring and coverage++
This commit is contained in:
Maximilian Hils 2017-02-09 17:45:50 +01:00 committed by GitHub
commit 380ff50e57
25 changed files with 287 additions and 216 deletions

View File

@ -238,7 +238,7 @@ class Dumper:
def websocket_message(self, f):
if self.match(f):
message = f.messages[-1]
self.echo(message.info)
self.echo(f.message_info(message))
if self.flow_detail >= 3:
self._echo_message(message)

View File

@ -8,7 +8,7 @@ import types
from mitmproxy import exceptions
from mitmproxy import ctx
from mitmproxy import events
from mitmproxy import eventsequence
import watchdog.events
@ -141,7 +141,7 @@ class Script:
self.last_options = None
self.should_reload = threading.Event()
for i in events.Events:
for i in eventsequence.Events:
if not hasattr(self, i):
def mkprox():
evt = i
@ -211,7 +211,7 @@ class ScriptLoader:
raise ValueError(str(e))
sc.load_script()
for f in flows:
for evt, o in events.event_sequence(f):
for evt, o in eventsequence.iterate(f):
sc.run(evt, o)
sc.done()
return sc

View File

@ -1,6 +1,5 @@
import time
import copy
import os
from mitmproxy import stateobject
@ -82,9 +81,6 @@ class ClientConnection(tcp.BaseHandler, stateobject.StateObject):
tls_version=str,
)
def copy(self):
return copy.copy(self)
def send(self, message):
if isinstance(message, list):
message = b''.join(message)
@ -222,9 +218,6 @@ class ServerConnection(tcp.TCPClient, stateobject.StateObject):
via=None
))
def copy(self):
return copy.copy(self)
def connect(self):
self.timestamp_start = time.time()
tcp.TCPClient.connect(self)

View File

@ -37,7 +37,7 @@ Events = frozenset([
])
def event_sequence(f):
def iterate(f):
if isinstance(f, http.HTTPFlow):
if f.request:
yield "requestheaders", f
@ -70,4 +70,4 @@ def event_sequence(f):
yield "tcp_error", f
yield "tcp_end", f
else:
raise NotImplementedError
raise TypeError()

View File

@ -1,5 +1,4 @@
import time
import copy
import uuid
from mitmproxy import controller # noqa
@ -7,7 +6,7 @@ from mitmproxy import stateobject
from mitmproxy import connections
from mitmproxy import version
import typing # noqa
import typing # noqa
class Error(stateobject.StateObject):
@ -53,10 +52,6 @@ class Error(stateobject.StateObject):
f.set_state(state)
return f
def copy(self):
c = copy.copy(self)
return c
class Flow(stateobject.StateObject):
@ -116,16 +111,9 @@ class Flow(stateobject.StateObject):
return f
def copy(self):
f = copy.copy(self)
f = super().copy()
f.id = str(uuid.uuid4())
f.live = False
f.client_conn = self.client_conn.copy()
f.server_conn = self.server_conn.copy()
f.metadata = self.metadata.copy()
if self.error:
f.error = self.error.copy()
return f
def modified(self):

View File

@ -7,7 +7,7 @@ import sys
from mitmproxy import addonmanager
from mitmproxy import options
from mitmproxy import controller
from mitmproxy import events
from mitmproxy import eventsequence
from mitmproxy import exceptions
from mitmproxy import connections
from mitmproxy import http
@ -91,7 +91,7 @@ class Master:
changed = False
try:
mtype, obj = self.event_queue.get(timeout=timeout)
if mtype not in events.Events:
if mtype not in eventsequence.Events:
raise exceptions.ControlException(
"Unknown event %s" % repr(mtype)
)
@ -153,7 +153,7 @@ class Master:
f.request.port = self.server.config.upstream_server.address.port
f.request.scheme = self.server.config.upstream_server.scheme
f.reply = controller.DummyReply()
for e, o in events.event_sequence(f):
for e, o in eventsequence.iterate(f):
getattr(self, e)(o)
def load_flows(self, fr: io.FlowReader) -> int:

View File

@ -158,8 +158,9 @@ def connection_close(http_version, headers):
"""
Checks the message to see if the client connection should be closed
according to RFC 2616 Section 8.1.
If we don't have a Connection header, HTTP 1.1 connections are assumed
to be persistent.
"""
# At first, check if we have an explicit Connection header.
if "connection" in headers:
tokens = get_header_tokens(headers, "connection")
if "close" in tokens:
@ -167,9 +168,7 @@ def connection_close(http_version, headers):
elif "keep-alive" in tokens:
return False
# If we don't have a Connection header, HTTP 1.1 connections are assumed to
# be persistent
return http_version != "HTTP/1.1" and http_version != b"HTTP/1.1" # FIXME: Remove one case.
return http_version != "HTTP/1.1" and http_version != b"HTTP/1.1"
def expected_http_body_size(request, response=None):

View File

@ -21,7 +21,6 @@ def parse_headers(headers):
first_line_format = "relative"
else:
first_line_format = "absolute"
# FIXME: verify if path or :host contains what we need
scheme, host, port, _ = url.parse(path)
if authority:

View File

@ -8,7 +8,7 @@ from mitmproxy import flow
from mitmproxy.proxy.protocol import base
from mitmproxy.net import tcp
from mitmproxy.net import websockets
from mitmproxy.websocket import WebSocketFlow, WebSocketBinaryMessage, WebSocketTextMessage
from mitmproxy.websocket import WebSocketFlow, WebSocketMessage
class WebSocketLayer(base.Layer):
@ -65,12 +65,7 @@ class WebSocketLayer(base.Layer):
compressed_message = fb[0].header.rsv1
fb.clear()
if message_type == websockets.OPCODE.TEXT:
t = WebSocketTextMessage
else:
t = WebSocketBinaryMessage
websocket_message = t(self.flow, not is_server, payload)
websocket_message = WebSocketMessage(message_type, not is_server, payload)
length = len(websocket_message.content)
self.flow.messages.append(websocket_message)
self.channel.ask("websocket_message", self.flow)

View File

@ -3,7 +3,7 @@ This module provides a @concurrent decorator primitive to
offload computations from mitmproxy's main master thread.
"""
from mitmproxy import events
from mitmproxy import eventsequence
from mitmproxy.types import basethread
@ -12,7 +12,7 @@ class ScriptThread(basethread.BaseThread):
def concurrent(fn):
if fn.__name__ not in events.Events - {"start", "configure", "tick"}:
if fn.__name__ not in eventsequence.Events - {"start", "configure", "tick"}:
raise NotImplementedError(
"Concurrent decorator not supported for '%s' method." % fn.__name__
)

View File

@ -9,8 +9,8 @@ from mitmproxy.types import serializable
class TCPMessage(serializable.Serializable):
def __init__(self, from_client, content, timestamp=None):
self.content = content
self.from_client = from_client
self.content = content
self.timestamp = timestamp or time.time()
@classmethod
@ -21,9 +21,7 @@ class TCPMessage(serializable.Serializable):
return self.from_client, self.content, self.timestamp
def set_state(self, state):
self.from_client = state.pop("from_client")
self.content = state.pop("content")
self.timestamp = state.pop("timestamp")
self.from_client, self.content, self.timestamp = state
def __repr__(self):
return "{direction} {content}".format(

View File

@ -3,7 +3,7 @@ import contextlib
import mitmproxy.master
import mitmproxy.options
from mitmproxy import proxy
from mitmproxy import events
from mitmproxy import eventsequence
from mitmproxy import exceptions
@ -57,7 +57,7 @@ class context:
is taken (as in flow interception).
"""
f.reply._state = "handled"
for evt, arg in events.event_sequence(f):
for evt, arg in eventsequence.iterate(f):
h = getattr(addon, evt, None)
if h:
h(arg)

View File

@ -1,3 +1,4 @@
from mitmproxy.net import websockets
from mitmproxy.test import tutils
from mitmproxy import tcp
from mitmproxy import websocket
@ -70,8 +71,8 @@ def twebsocketflow(client_conn=True, server_conn=True, messages=True, err=None,
if messages is True:
messages = [
websocket.WebSocketBinaryMessage(f, True, b"hello binary"),
websocket.WebSocketTextMessage(f, False, "hello text".encode()),
websocket.WebSocketMessage(websockets.OPCODE.BINARY, True, b"hello binary"),
websocket.WebSocketMessage(websockets.OPCODE.TEXT, False, "hello text".encode()),
]
if err is True:
err = terr()

View File

@ -1,63 +1,38 @@
import time
from typing import List
from typing import List, Optional
from mitmproxy import flow
from mitmproxy.http import HTTPFlow
from mitmproxy.net import websockets
from mitmproxy.utils import strutils
from mitmproxy.types import serializable
from mitmproxy.utils import strutils
class WebSocketMessage(serializable.Serializable):
def __init__(self, flow, from_client, content, timestamp=None):
self.flow = flow
self.content = content
def __init__(self, type: int, from_client: bool, content: bytes, timestamp: Optional[int]=None):
self.type = type
self.from_client = from_client
self.timestamp = timestamp or time.time()
self.content = content
self.timestamp = timestamp or time.time() # type: int
@classmethod
def from_state(cls, state):
return cls(*state)
def get_state(self):
return self.from_client, self.content, self.timestamp
return self.type, self.from_client, self.content, self.timestamp
def set_state(self, state):
self.from_client = state.pop("from_client")
self.content = state.pop("content")
self.timestamp = state.pop("timestamp")
@property
def info(self):
return "{client} {direction} WebSocket {type} message {direction} {server}{endpoint}".format(
type=self.type,
client=repr(self.flow.client_conn.address),
server=repr(self.flow.server_conn.address),
direction="->" if self.from_client else "<-",
endpoint=self.flow.handshake_flow.request.path,
)
class WebSocketBinaryMessage(WebSocketMessage):
type = 'binary'
self.type, self.from_client, self.content, self.timestamp = state
def __repr__(self):
return "binary message: {}".format(strutils.bytes_to_escaped_str(self.content))
class WebSocketTextMessage(WebSocketMessage):
type = 'text'
def __repr__(self):
return "text message: {}".format(repr(self.content))
if self.type == websockets.OPCODE.TEXT:
return "text message: {}".format(repr(self.content))
else:
return "binary message: {}".format(strutils.bytes_to_escaped_str(self.content))
class WebSocketFlow(flow.Flow):
"""
A WebsocketFlow is a simplified representation of a Websocket session.
"""
@ -70,18 +45,55 @@ class WebSocketFlow(flow.Flow):
self.close_message = '(message missing)'
self.close_reason = 'unknown status code'
self.handshake_flow = handshake_flow
self.client_key = websockets.get_client_key(self.handshake_flow.request.headers)
self.client_protocol = websockets.get_protocol(self.handshake_flow.request.headers)
self.client_extensions = websockets.get_extensions(self.handshake_flow.request.headers)
self.server_accept = websockets.get_server_accept(self.handshake_flow.response.headers)
self.server_protocol = websockets.get_protocol(self.handshake_flow.response.headers)
self.server_extensions = websockets.get_extensions(self.handshake_flow.response.headers)
_stateobject_attributes = flow.Flow._stateobject_attributes.copy()
_stateobject_attributes.update(
messages=List[WebSocketMessage],
close_sender=str,
close_code=str,
close_message=str,
close_reason=str,
handshake_flow=HTTPFlow,
)
@classmethod
def from_state(cls, state):
f = cls(None, None, None)
f.set_state(state)
return f
def __repr__(self):
return "WebSocketFlow ({} messages)".format(len(self.messages))
return "<WebSocketFlow ({} messages)>".format(len(self.messages))
@property
def client_key(self):
return websockets.get_client_key(self.handshake_flow.request.headers)
@property
def client_protocol(self):
return websockets.get_protocol(self.handshake_flow.request.headers)
@property
def client_extensions(self):
return websockets.get_extensions(self.handshake_flow.request.headers)
@property
def server_accept(self):
return websockets.get_server_accept(self.handshake_flow.response.headers)
@property
def server_protocol(self):
return websockets.get_protocol(self.handshake_flow.response.headers)
@property
def server_extensions(self):
return websockets.get_extensions(self.handshake_flow.response.headers)
def message_info(self, message: WebSocketMessage) -> str:
return "{client} {direction} WebSocket {type} message {direction} {server}{endpoint}".format(
type=message.type,
client=repr(self.client_conn.address),
server=repr(self.server_conn.address),
direction="->" if message.from_client else "<-",
endpoint=self.handshake_flow.request.path,
)

View File

@ -103,15 +103,17 @@ def pytest_runtestloop(session):
measured_files = [os.path.normpath(os.path.relpath(f, prefix)) for f in cov.get_data().measured_files()]
measured_files = [f for f in measured_files if not any(f.startswith(excluded_f) for excluded_f in excluded_files)]
for name in pytest.config.option.full_cov:
for name in coverage_values.keys():
files = [f for f in measured_files if f.startswith(os.path.normpath(name))]
try:
with open(os.devnull, 'w') as null:
coverage_values[name] = cov.report(files, ignore_errors=True, file=null)
overall = cov.report(files, ignore_errors=True, file=null)
singles = [(s, cov.report(s, ignore_errors=True, file=null)) for s in files]
coverage_values[name] = (overall, singles)
except:
pass
if any(v < 100 for v in coverage_values.values()):
if any(v < 100 for v, _ in coverage_values.values()):
# make sure we get the EXIT_TESTSFAILED exit code
session.testsfailed += 1
else:
@ -132,12 +134,15 @@ def pytest_terminal_summary(terminalreporter, exitstatus):
msg = "FAIL: Full test coverage not reached!\n"
terminalreporter.write(msg, **markup)
for name, value in coverage_values.items():
if value < 100:
for name in sorted(coverage_values.keys()):
msg = 'Coverage for {}: {:.2f}%\n'.format(name, coverage_values[name][0])
if coverage_values[name][0] < 100:
markup = {'red': True, 'bold': True}
for s, v in sorted(coverage_values[name][1]):
if v < 100:
msg += ' {}: {:.2f}%\n'.format(s, v)
else:
markup = {'green': True}
msg = 'Coverage for {}: {:.2f}%\n'.format(name, value)
terminalreporter.write(msg, **markup)
else:
markup = {'green': True}
@ -145,6 +150,7 @@ def pytest_terminal_summary(terminalreporter, exitstatus):
msg += '{}\n\n'.format('\n'.join(pytest.config.option.full_cov))
terminalreporter.write(msg, **markup)
msg = 'Excluded files:\n'
msg += '{}\n'.format('\n'.join(pytest.config.option.no_full_cov))
msg = '\nExcluded files:\n'
for s in sorted(pytest.config.option.no_full_cov):
msg += " {}\n".format(s)
terminalreporter.write(msg)

View File

@ -39,7 +39,6 @@ class TestStickyCookie:
assert "cookie" not in f.request.headers
f = f.copy()
f.reply.acked = False
sc.request(f)
assert f.request.headers["cookie"] == "foo=bar"

View File

@ -1,5 +1,5 @@
from mitmproxy import controller
from mitmproxy import events
from mitmproxy import eventsequence
from mitmproxy import ctx
import sys
@ -11,7 +11,7 @@ class CallLogger:
self.name = name
def __getattr__(self, attr):
if attr in events.Events:
if attr in eventsequence.Events:
def prox(*args, **kwargs):
lg = (self.name, attr, args, kwargs)
if attr != "log":

View File

@ -179,16 +179,15 @@ class TestSimple(_WebSocketTest):
assert isinstance(self.master.state.flows[1], WebSocketFlow)
assert len(self.master.state.flows[1].messages) == 5
assert self.master.state.flows[1].messages[0].content == b'server-foobar'
assert self.master.state.flows[1].messages[0].type == 'text'
assert self.master.state.flows[1].messages[0].type == websockets.OPCODE.TEXT
assert self.master.state.flows[1].messages[1].content == b'client-foobar'
assert self.master.state.flows[1].messages[1].type == 'text'
assert self.master.state.flows[1].messages[1].type == websockets.OPCODE.TEXT
assert self.master.state.flows[1].messages[2].content == b'client-foobar'
assert self.master.state.flows[1].messages[2].type == 'text'
assert self.master.state.flows[1].messages[2].type == websockets.OPCODE.TEXT
assert self.master.state.flows[1].messages[3].content == b'\xde\xad\xbe\xef'
assert self.master.state.flows[1].messages[3].type == 'binary'
assert self.master.state.flows[1].messages[3].type == websockets.OPCODE.BINARY
assert self.master.state.flows[1].messages[4].content == b'\xde\xad\xbe\xef'
assert self.master.state.flows[1].messages[4].type == 'binary'
assert [m.info for m in self.master.state.flows[1].messages]
assert self.master.state.flows[1].messages[4].type == websockets.OPCODE.BINARY
class TestSimpleTLS(_WebSocketTest):

View File

@ -1,81 +1,57 @@
from mitmproxy import events
import contextlib
from . import tservers
import pytest
from mitmproxy import eventsequence
from mitmproxy.test import tflow
class Eventer:
def __init__(self, **handlers):
self.failure = None
self.called = []
self.handlers = handlers
for i in events.Events - {"tick"}:
def mkprox():
evt = i
def prox(*args, **kwargs):
self.called.append(evt)
if evt in self.handlers:
try:
handlers[evt](*args, **kwargs)
except AssertionError as e:
self.failure = e
return prox
setattr(self, i, mkprox())
def fail(self):
pass
@pytest.mark.parametrize("resp, err", [
(False, False),
(True, False),
(False, True),
(True, True),
])
def test_http_flow(resp, err):
f = tflow.tflow(resp=resp, err=err)
i = eventsequence.iterate(f)
assert next(i) == ("requestheaders", f)
assert next(i) == ("request", f)
if resp:
assert next(i) == ("responseheaders", f)
assert next(i) == ("response", f)
if err:
assert next(i) == ("error", f)
class SequenceTester:
@contextlib.contextmanager
def addon(self, addon):
self.master.addons.add(addon)
yield
self.master.addons.remove(addon)
if addon.failure:
raise addon.failure
@pytest.mark.parametrize("err", [False, True])
def test_websocket_flow(err):
f = tflow.twebsocketflow(err=err)
i = eventsequence.iterate(f)
assert next(i) == ("websocket_start", f)
assert len(f.messages) == 0
assert next(i) == ("websocket_message", f)
assert len(f.messages) == 1
assert next(i) == ("websocket_message", f)
assert len(f.messages) == 2
if err:
assert next(i) == ("websocket_error", f)
assert next(i) == ("websocket_end", f)
class TestBasic(tservers.HTTPProxyTest, SequenceTester):
ssl = True
@pytest.mark.parametrize("err", [False, True])
def test_tcp_flow(err):
f = tflow.ttcpflow(err=err)
i = eventsequence.iterate(f)
assert next(i) == ("tcp_start", f)
assert len(f.messages) == 0
assert next(i) == ("tcp_message", f)
assert len(f.messages) == 1
assert next(i) == ("tcp_message", f)
assert len(f.messages) == 2
if err:
assert next(i) == ("tcp_error", f)
assert next(i) == ("tcp_end", f)
def test_requestheaders(self):
def hdrs(f):
assert f.request.headers
assert not f.request.content
def req(f):
assert f.request.headers
assert f.request.content
with self.addon(Eventer(requestheaders=hdrs, request=req)):
p = self.pathoc()
with p.connect():
assert p.request("get:'/p/200':b@10").status_code == 200
def test_100_continue_fail(self):
e = Eventer()
with self.addon(e):
p = self.pathoc()
with p.connect():
p.request(
"""
get:'/p/200'
h'expect'='100-continue'
h'content-length'='1000'
da
"""
)
assert "requestheaders" in e.called
assert "responseheaders" not in e.called
def test_connect(self):
e = Eventer()
with self.addon(e):
p = self.pathoc()
with p.connect():
p.request("get:'/p/200:b@1'")
assert "http_connect" in e.called
assert e.called.count("requestheaders") == 1
assert e.called.count("request") == 1
def test_invalid():
with pytest.raises(TypeError):
next(eventsequence.iterate(42))

View File

@ -10,6 +10,7 @@ from mitmproxy.exceptions import FlowReadException, Kill
from mitmproxy import flow
from mitmproxy import http
from mitmproxy import connections
from mitmproxy import tcp
from mitmproxy.proxy import ProxyConfig
from mitmproxy.proxy.server import DummyServer
from mitmproxy import master
@ -156,8 +157,99 @@ class TestHTTPFlow:
assert f.response.raw_content == b"abarb"
class TestWebSocketFlow:
def test_copy(self):
f = tflow.twebsocketflow()
f.get_state()
f2 = f.copy()
a = f.get_state()
b = f2.get_state()
del a["id"]
del b["id"]
del a["handshake_flow"]["id"]
del b["handshake_flow"]["id"]
assert a == b
assert not f == f2
assert f is not f2
assert f.client_key == f2.client_key
assert f.client_protocol == f2.client_protocol
assert f.client_extensions == f2.client_extensions
assert f.server_accept == f2.server_accept
assert f.server_protocol == f2.server_protocol
assert f.server_extensions == f2.server_extensions
assert f.messages is not f2.messages
assert f.handshake_flow is not f2.handshake_flow
for m in f.messages:
m2 = m.copy()
m2.set_state(m2.get_state())
assert m is not m2
assert m.get_state() == m2.get_state()
f = tflow.twebsocketflow(err=True)
f2 = f.copy()
assert f is not f2
assert f.handshake_flow is not f2.handshake_flow
assert f.error.get_state() == f2.error.get_state()
assert f.error is not f2.error
def test_match(self):
f = tflow.twebsocketflow()
assert not flowfilter.match("~b nonexistent", f)
assert flowfilter.match(None, f)
assert not flowfilter.match("~b nonexistent", f)
f = tflow.twebsocketflow(err=True)
assert flowfilter.match("~e", f)
with pytest.raises(ValueError):
flowfilter.match("~", f)
def test_repr(self):
f = tflow.twebsocketflow()
assert 'WebSocketFlow' in repr(f)
assert 'binary message: ' in repr(f.messages[0])
assert 'text message: ' in repr(f.messages[1])
class TestTCPFlow:
def test_copy(self):
f = tflow.ttcpflow()
f.get_state()
f2 = f.copy()
a = f.get_state()
b = f2.get_state()
del a["id"]
del b["id"]
assert a == b
assert not f == f2
assert f is not f2
assert f.messages is not f2.messages
for m in f.messages:
assert m.get_state()
m2 = m.copy()
assert not m == m2
assert m is not m2
a = m.get_state()
b = m2.get_state()
assert a == b
m = tcp.TCPMessage(False, 'foo')
m.set_state(f.messages[0].get_state())
assert m.timestamp == f.messages[0].timestamp
f = tflow.ttcpflow(err=True)
f2 = f.copy()
assert f is not f2
assert f.error.get_state() == f2.error.get_state()
assert f.error is not f2.error
def test_match(self):
f = tflow.ttcpflow()
assert not flowfilter.match("~b nonexistent", f)
@ -170,6 +262,11 @@ class TestTCPFlow:
with pytest.raises(ValueError):
flowfilter.match("~", f)
def test_repr(self):
f = tflow.ttcpflow()
assert 'TCPFlow' in repr(f)
assert '-> ' in repr(f.messages[0])
class TestSerialize:
@ -308,11 +405,11 @@ class TestFlowMaster:
fm.clientconnect(f.client_conn)
f.request = http.HTTPRequest.wrap(mitmproxy.test.tutils.treq())
fm.request(f)
assert s.flow_count() == 1
assert len(s.flows) == 1
f.response = http.HTTPResponse.wrap(mitmproxy.test.tutils.tresp())
fm.response(f)
assert s.flow_count() == 1
assert len(s.flows) == 1
fm.clientdisconnect(f.client_conn)

View File

@ -984,16 +984,16 @@ class TestUpstreamProxySSL(
assert req.status_code == 418
# CONNECT from pathoc to chain[0],
assert self.proxy.tmaster.state.flow_count() == 1
assert len(self.proxy.tmaster.state.flows) == 1
assert self.proxy.tmaster.state.flows[0].server_conn.via
# request from pathoc to chain[0]
# CONNECT from proxy to chain[1],
assert self.chain[0].tmaster.state.flow_count() == 1
assert len(self.chain[0].tmaster.state.flows) == 1
assert self.chain[0].tmaster.state.flows[0].server_conn.via
# request from proxy to chain[1]
# request from chain[0] (regular proxy doesn't store CONNECTs)
assert not self.chain[1].tmaster.state.flows[0].server_conn.via
assert self.chain[1].tmaster.state.flow_count() == 1
assert len(self.chain[1].tmaster.state.flows) == 1
def test_change_upstream_proxy_connect(self):
# skip chain[0].
@ -1050,17 +1050,17 @@ class TestProxyChainingSSLReconnect(tservers.HTTPUpstreamProxyTest):
assert req.status_code == 418
# First request goes through all three proxies exactly once
assert self.proxy.tmaster.state.flow_count() == 1
assert self.chain[0].tmaster.state.flow_count() == 1
assert self.chain[1].tmaster.state.flow_count() == 1
assert len(self.proxy.tmaster.state.flows) == 1
assert len(self.chain[0].tmaster.state.flows) == 1
assert len(self.chain[1].tmaster.state.flows) == 1
req = p.request("get:'/p/418:b\"content2\"'")
assert req.status_code == 502
assert self.proxy.tmaster.state.flow_count() == 2
assert self.chain[0].tmaster.state.flow_count() == 2
assert len(self.proxy.tmaster.state.flows) == 2
assert len(self.chain[0].tmaster.state.flows) == 2
# Upstream sees two requests due to reconnection attempt
assert self.chain[1].tmaster.state.flow_count() == 3
assert len(self.chain[1].tmaster.state.flows) == 3
assert not self.chain[1].tmaster.state.flows[-1].response
assert not self.chain[1].tmaster.state.flows[-2].response

View File

@ -1,4 +1,5 @@
from typing import List
import pytest
from mitmproxy.stateobject import StateObject
@ -67,3 +68,14 @@ def test_container_list():
assert len(copy.children) == 2
assert copy.children is not a.children
assert copy.children[0] is not a.children[0]
def test_too_much_state():
a = Container()
a.child = Child(42)
s = a.get_state()
s['foo'] = 'bar'
b = Container()
with pytest.raises(RuntimeWarning):
b.set_state(s)

View File

@ -0,0 +1,10 @@
import runpy
from mitmproxy import version
def test_version(capsys):
runpy.run_module('mitmproxy.version', run_name='__main__')
stdout, stderr = capsys.readouterr()
assert len(stdout) > 0
assert stdout.strip() == version.VERSION

View File

@ -35,10 +35,6 @@ class TestState:
# if f not in self.flows:
# self.flows.append(f)
# FIXME: compat with old state - remove in favor of len(state.flows)
def flow_count(self):
return len(self.flows)
class TestMaster(master.Master):

21
tox.ini
View File

@ -12,21 +12,12 @@ setenv = HOME = {envtmpdir}
commands =
mitmdump --version
pytest --timeout 60 --cov-report='' --cov=mitmproxy --cov=pathod \
--full-cov=mitmproxy/addons/ \
--full-cov=mitmproxy/contentviews/ --no-full-cov=mitmproxy/contentviews/__init__.py --no-full-cov=mitmproxy/contentviews/protobuf.py --no-full-cov=mitmproxy/contentviews/wbxml.py --no-full-cov=mitmproxy/contentviews/xml_html.py \
--full-cov=mitmproxy/net/ --no-full-cov=mitmproxy/net/tcp.py --no-full-cov=mitmproxy/net/http/cookies.py --no-full-cov=mitmproxy/net/http/encoding.py --no-full-cov=mitmproxy/net/http/message.py --no-full-cov=mitmproxy/net/http/request.py --no-full-cov=mitmproxy/net/http/response.py --no-full-cov=mitmproxy/net/http/url.py \
--full-cov=mitmproxy/proxy/ --no-full-cov=mitmproxy/proxy/protocol/ --no-full-cov=mitmproxy/proxy/config.py --no-full-cov=mitmproxy/proxy/root_context.py --no-full-cov=mitmproxy/proxy/server.py \
--full-cov=mitmproxy/script/ \
--full-cov=mitmproxy/test/ \
--full-cov=mitmproxy/types/ \
--full-cov=mitmproxy/utils/ \
--full-cov=mitmproxy/__init__.py \
--full-cov=mitmproxy/addonmanager.py \
--full-cov=mitmproxy/ctx.py \
--full-cov=mitmproxy/exceptions.py \
--full-cov=mitmproxy/io.py \
--full-cov=mitmproxy/log.py \
--full-cov=mitmproxy/options.py \
--full-cov=mitmproxy/ \
--no-full-cov=mitmproxy/contentviews/__init__.py --no-full-cov=mitmproxy/contentviews/protobuf.py --no-full-cov=mitmproxy/contentviews/wbxml.py --no-full-cov=mitmproxy/contentviews/xml_html.py \
--no-full-cov=mitmproxy/net/tcp.py --no-full-cov=mitmproxy/net/http/cookies.py --no-full-cov=mitmproxy/net/http/encoding.py --no-full-cov=mitmproxy/net/http/message.py --no-full-cov=mitmproxy/net/http/request.py --no-full-cov=mitmproxy/net/http/response.py --no-full-cov=mitmproxy/net/http/url.py \
--no-full-cov=mitmproxy/proxy/protocol/ --no-full-cov=mitmproxy/proxy/config.py --no-full-cov=mitmproxy/proxy/root_context.py --no-full-cov=mitmproxy/proxy/server.py \
--no-full-cov=mitmproxy/tools/ \
--no-full-cov=mitmproxy/certs.py --no-full-cov=mitmproxy/connections.py --no-full-cov=mitmproxy/controller.py --no-full-cov=mitmproxy/export.py --no-full-cov=mitmproxy/flow.py --no-full-cov=mitmproxy/flowfilter.py --no-full-cov=mitmproxy/http.py --no-full-cov=mitmproxy/io_compat.py --no-full-cov=mitmproxy/master.py --no-full-cov=mitmproxy/optmanager.py \
--full-cov=pathod/ --no-full-cov=pathod/pathoc.py --no-full-cov=pathod/pathod.py --no-full-cov=pathod/test.py --no-full-cov=pathod/protocols/http2.py \
{posargs}
{env:CI_COMMANDS:python -c ""}