addon loader: add boot_into, which replaces returning from start()

While we're here, expand test coverage for addonmanager to 100%, and promote to
individual coverage.
This commit is contained in:
Aldo Cortesi 2017-03-24 11:29:36 +13:00
parent d69a411303
commit 65f0885bd6
23 changed files with 158 additions and 48 deletions

View File

@ -54,5 +54,5 @@ class Rerouter:
flow.request.port = port
def load(opts):
return Rerouter()
def load(l):
l.boot_into(Rerouter())

View File

@ -25,7 +25,7 @@ HAR = {}
SERVERS_SEEN = set()
def load(opts):
def load(l):
"""
Called once on script startup before any other events.
"""

View File

@ -14,6 +14,6 @@ Usage:
"""
def load(opts):
def load(l):
import pydevd
pydevd.settrace("localhost", port=5678, stdoutToServer=True, stderrToServer=True)

View File

@ -112,7 +112,7 @@ class TlsFeedback(TlsLayer):
tls_strategy = None
def load(opts):
def load(l):
global tls_strategy
if len(sys.argv) == 2:
tls_strategy = ProbabilisticStrategy(float(sys.argv[1]))

View File

@ -3,5 +3,5 @@ class AddHeader:
flow.response.headers["newheader"] = "foo"
def load(opts):
return AddHeader()
def load(l):
return l.boot_into(AddHeader())

View File

@ -20,7 +20,7 @@ class ViewSwapCase(contentviews.View):
view = ViewSwapCase()
def load(opts):
def load(l):
contentviews.add(view)

View File

@ -1,9 +1,9 @@
from mitmproxy import ctx
def load(options):
def load(l):
ctx.log.info("Registering option 'custom'")
options.add_option("custom", bool, False, "A custom option")
l.add_option("custom", bool, False, "A custom option")
def configure(options, updated):

View File

@ -17,7 +17,7 @@ class Filter:
print(flow)
def load(opts):
def load(l):
if len(sys.argv) != 2:
raise ValueError("Usage: -s 'filt.py FILTER'")
return Filter(sys.argv[1])
l.boot_into(Filter(sys.argv[1]))

View File

@ -23,7 +23,7 @@ class Writer:
self.w.add(flow)
def load(opts):
def load(l):
if len(sys.argv) != 2:
raise ValueError('Usage: -s "flowriter.py filename"')
return Writer(sys.argv[1])
l.boot_into(Writer(sys.argv[1]))

View File

@ -7,6 +7,6 @@ If you want to help us out: https://github.com/mitmproxy/mitmproxy/issues/1530 :
from mitmproxy import ctx
def load(opts):
def load(l):
ctx.log.info("This is some informative text.")
ctx.log.error("This is an error.")

View File

@ -23,7 +23,7 @@ class Injector:
flow.response.content = str(html).encode("utf8")
def load(opts):
def load(l):
if len(sys.argv) != 2:
raise ValueError('Usage: -s "iframe_injector.py url"')
return Injector(sys.argv[1])
return l.boot_into(Injector(sys.argv[1]))

View File

@ -9,9 +9,9 @@ class Replacer:
flow.response.replace(self.src, self.dst)
def load(opts):
def load(l):
parser = argparse.ArgumentParser()
parser.add_argument("src", type=str)
parser.add_argument("dst", type=str)
args = parser.parse_args()
return Replacer(args.src, args.dst)
l.boot_into(Replacer(args.src, args.dst))

View File

@ -14,7 +14,7 @@ def hello_world():
return 'Hello World!'
def load(opts):
def load(l):
# Host app at the magic domain "proxapp" on port 80. Requests to this
# domain and port combination will now be routed to the WSGI app instance.
return wsgiapp.WSGIApp(app, "proxapp", 80)

View File

@ -17,6 +17,7 @@ class Loader:
"""
def __init__(self, master):
self.master = master
self.boot_into_addon = None
def add_option(
self,
@ -34,6 +35,12 @@ class Loader:
choices
)
def boot_into(self, addon):
self.boot_into_addon = addon
func = getattr(addon, "load", None)
if func:
func(self)
class AddonManager:
def __init__(self, master):
@ -65,11 +72,14 @@ class AddonManager:
"""
Add addons to the end of the chain, and run their startup events.
"""
self.chain.extend(addons)
with self.master.handlecontext():
l = Loader(self.master)
for i in addons:
l = Loader(self.master)
self.invoke_addon(i, "load", l)
if l.boot_into_addon:
self.chain.append(l.boot_into_addon)
else:
self.chain.append(i)
def remove(self, addon):
"""

View File

@ -186,23 +186,21 @@ class Script:
def load_script(self):
self.ns = load_script(self.path, self.args)
l = addonmanager.Loader(ctx.master)
ret = self.run("load", l)
if ret:
self.ns = ret
self.run("load", l)
self.run("load", l)
if l.boot_into_addon:
self.ns = l.boot_into_addon
def tick(self):
if self.should_reload.is_set():
self.should_reload.clear()
ctx.log.info("Reloading script: %s" % self.name)
self.ns = load_script(self.path, self.args)
self.load(self.last_options)
self.configure(self.last_options, self.last_options.keys())
else:
self.run("tick")
def load(self, opts):
self.last_options = opts
def load(self, l):
self.last_options = ctx.master.options
self.load_script()
def configure(self, options, updated):
@ -289,7 +287,8 @@ class ScriptLoader:
ctx.master.addons.chain = ochain[:pos + 1] + ordered + ochain[pos + 1:]
for s in newscripts:
ctx.master.addons.invoke_addon(s, "load", options)
l = addonmanager.Loader(ctx.master)
ctx.master.addons.invoke_addon(s, "load", l)
if self.is_running:
# If we're already running, we configure and tell the addon
# we're up and running.

View File

@ -16,11 +16,11 @@ class ErrorCheck:
class DumpMaster(master.Master):
def __init__(
self,
options: options.Options,
server,
with_termlog=True,
with_dumper=True,
self,
options: options.Options,
server,
with_termlog=True,
with_dumper=True,
) -> None:
master.Master.__init__(self, options, server)
self.errorcheck = ErrorCheck()

View File

@ -46,7 +46,6 @@ exclude =
[tool:individual_coverage]
exclude =
mitmproxy/addonmanager.py
mitmproxy/addons/onboardingapp/app.py
mitmproxy/addons/termlog.py
mitmproxy/contentviews/base.py

View File

@ -9,6 +9,7 @@ from unittest import mock
from mitmproxy.test import tflow
from mitmproxy.test import tutils
from mitmproxy.test import taddons
from mitmproxy import addonmanager
from mitmproxy import exceptions
from mitmproxy import options
from mitmproxy import proxy
@ -116,7 +117,7 @@ def test_script_print_stdout():
"mitmproxy/data/addonscripts/print.py"
), []
)
ns.start(tctx.options)
ns.load(addonmanager.Loader(tctx.master))
mock_warn.assert_called_once_with("stdoutprint")
@ -157,7 +158,8 @@ class TestScript:
sc = script.Script(
tutils.test_data.path("mitmproxy/data/addonscripts/error.py")
)
sc.load(tctx.options)
l = addonmanager.Loader(tctx.master)
sc.load(l)
f = tflow.tflow(resp=True)
sc.request(f)
assert tctx.master.logs[0].level == "error"
@ -173,7 +175,8 @@ class TestScript:
"mitmproxy/data/addonscripts/addon.py"
)
)
sc.load(tctx.options)
l = addonmanager.Loader(tctx.master)
sc.load(l)
tctx.configure(sc)
assert sc.ns.event_log == [
'scriptload', 'addonload', 'addonconfigure'

View File

@ -17,6 +17,6 @@ def configure(options, updated):
event_log.append("addonconfigure")
def load(opts):
def load(l):
event_log.append("scriptload")
return Addon()
l.boot_into(Addon())

View File

@ -9,5 +9,5 @@ class ConcurrentClass:
time.sleep(0.1)
def load(opts):
return ConcurrentClass()
def load(l):
l.boot_into(ConcurrentClass())

View File

@ -22,5 +22,5 @@ class CallLogger:
raise AttributeError
def load(opts):
return CallLogger(*sys.argv[1:])
def load(l):
l.boot_into(CallLogger(*sys.argv[1:]))

View File

@ -2,6 +2,7 @@ from mitmproxy.test import tflow
from mitmproxy.test import tutils
from mitmproxy.test import taddons
from mitmproxy import addonmanager
from mitmproxy import controller
from mitmproxy.addons import script
@ -24,7 +25,8 @@ class TestConcurrent(tservers.MasterTest):
"mitmproxy/data/addonscripts/concurrent_decorator.py"
)
)
sc.load(tctx.options)
l = addonmanager.Loader(tctx.master)
sc.load(l)
f1, f2 = tflow.tflow(), tflow.tflow()
tctx.cycle(sc, f1)
@ -42,7 +44,8 @@ class TestConcurrent(tservers.MasterTest):
"mitmproxy/data/addonscripts/concurrent_decorator_err.py"
)
)
sc.load(tctx.options)
l = addonmanager.Loader(tctx.master)
sc.load(l)
assert tctx.master.has_log("decorator not supported")
def test_concurrent_class(self):
@ -52,7 +55,8 @@ class TestConcurrent(tservers.MasterTest):
"mitmproxy/data/addonscripts/concurrent_decorator_class.py"
)
)
sc.load(tctx.options)
l = addonmanager.Loader(tctx.master)
sc.load(l)
f1, f2 = tflow.tflow(), tflow.tflow()
tctx.cycle(sc, f1)

View File

@ -5,6 +5,7 @@ from mitmproxy import exceptions
from mitmproxy import options
from mitmproxy import master
from mitmproxy import proxy
from mitmproxy.test import tflow
class TAddon:
@ -23,6 +24,58 @@ class TAddon:
self.custom_called = True
class THalt:
def event_custom(self):
raise exceptions.AddonHalt
class AOption:
def load(self, l):
l.add_option("custom_option", bool, False, "help")
class AChain:
def __init__(self, name, next):
self.name = name
self.next = next
def load(self, l):
if self.next:
l.boot_into(self.next)
def __repr__(self):
return "<%s>" % self.name
def test_halt():
o = options.Options()
m = master.Master(o, proxy.DummyServer(o))
a = addonmanager.AddonManager(m)
halt = THalt()
end = TAddon("end")
a.add(halt)
a.add(end)
a.trigger("custom")
assert not end.custom_called
a.remove(halt)
a.trigger("custom")
assert end.custom_called
def test_lifecycle():
o = options.Options()
m = master.Master(o, proxy.DummyServer(o))
a = addonmanager.AddonManager(m)
a.add(TAddon("one"))
f = tflow.tflow()
a.handle_lifecycle("request", f)
a.configure_all(o, o.keys())
def test_simple():
o = options.Options()
m = master.Master(o, proxy.DummyServer(o))
@ -30,10 +83,13 @@ def test_simple():
with pytest.raises(exceptions.AddonError):
a.invoke_addon(TAddon("one"), "done")
assert len(a) == 0
a.add(TAddon("one"))
assert a.get("one")
assert not a.get("two")
assert len(a) == 1
a.clear()
assert len(a) == 0
assert not a.chain
a.add(TAddon("one"))
@ -41,7 +97,46 @@ def test_simple():
with pytest.raises(exceptions.AddonError):
a.trigger("tick")
a.remove(a.get("one"))
assert not a.get("one")
ta = TAddon("one")
a.add(ta)
a.trigger("custom")
assert ta.custom_called
def test_load_option():
o = options.Options()
m = master.Master(o, proxy.DummyServer(o))
a = addonmanager.AddonManager(m)
a.add(AOption())
assert "custom_option" in m.options._options
def test_loadchain():
o = options.Options()
m = master.Master(o, proxy.DummyServer(o))
a = addonmanager.AddonManager(m)
a.add(AChain("one", None))
assert a.get("one")
a.clear()
a.add(AChain("one", AChain("two", None)))
assert not a.get("one")
assert a.get("two")
a.clear()
a.add(AChain("one", AChain("two", AChain("three", None))))
assert not a.get("one")
assert not a.get("two")
assert a.get("three")
a.clear()
a.add(AChain("one", AChain("two", AChain("three", AChain("four", None)))))
assert not a.get("one")
assert not a.get("two")
assert not a.get("three")
assert a.get("four")
a.clear()