Repository: mitmproxy/mitmproxy

Issue:
mitmdump -r shows all flows as [replay]
##### Steps to reproduce the problem:
1. Save some flows to file
2. Run mitmdump -nr file
3. 
##### What went wrong?

Flows are not replayed and shouldn't be marked as such

---

> mitmdump --sysinfo
> Mitmproxy version: 0.18
> Python version: 2.7.11
> Platform: Darwin-15.5.0-x86_64-i386-64bit
> SSL version: OpenSSL 1.0.2h  3 May 2016
> Mac version: 10.11.5 ('', '', '') x86_64



Discussion:

Resolving patch:
diff --git a/mitmproxy/builtins/dumper.py b/mitmproxy/builtins/dumper.py
new file mode 100644
--- /dev/null
+++ b/mitmproxy/builtins/dumper.py
@@ -0,0 +1,252 @@
+from __future__ import absolute_import, print_function, division
+
+import itertools
+import traceback
+
+import click
+
+from mitmproxy import contentviews
+from mitmproxy import ctx
+from mitmproxy import exceptions
+from mitmproxy import filt
+from netlib import human
+from netlib import strutils
+
+
+def indent(n, text):
+    l = str(text).strip().splitlines()
+    pad = " " * n
+    return "\n".join(pad + i for i in l)
+
+
+class Dumper():
+    def __init__(self):
+        self.filter = None
+        self.flow_detail = None
+        self.outfp = None
+        self.showhost = None
+
+    def echo(self, text, ident=None, **style):
+        if ident:
+            text = indent(ident, text)
+        click.secho(text, file=self.outfp, **style)
+        if self.outfp:
+            self.outfp.flush()
+
+    def _echo_message(self, message):
+        if self.flow_detail >= 2 and hasattr(message, "headers"):
+            headers = "\r\n".join(
+                "{}: {}".format(
+                    click.style(
+                        strutils.bytes_to_escaped_str(k), fg="blue", bold=True
+                    ),
+                    click.style(
+                        strutils.bytes_to_escaped_str(v), fg="blue"
+                    )
+                )
+                for k, v in message.headers.fields
+            )
+            self.echo(headers, ident=4)
+        if self.flow_detail >= 3:
+            try:
+                content = message.content
+            except ValueError:
+                content = message.get_content(strict=False)
+
+            if content is None:
+                self.echo("(content missing)", ident=4)
+            elif content:
+                self.echo("")
+
+                try:
+                    type, lines = contentviews.get_content_view(
+                        contentviews.get("Auto"),
+                        content,
+                        headers=getattr(message, "headers", None)
+                    )
+                except exceptions.ContentViewException:
+                    s = "Content viewer failed: \n" + traceback.format_exc()
+                    ctx.log.debug(s)
+                    type, lines = contentviews.get_content_view(
+                        contentviews.get("Raw"),
+                        content,
+                        headers=getattr(message, "headers", None)
+                    )
+
+                styles = dict(
+                    highlight=dict(bold=True),
+                    offset=dict(fg="blue"),
+                    header=dict(fg="green", bold=True),
+                    text=dict(fg="green")
+                )
+
+                def colorful(line):
+                    yield u"    "  # we can already indent here
+                    for (style, text) in line:
+                        yield click.style(text, **styles.get(style, {}))
+
+                if self.flow_detail == 3:
+                    lines_to_echo = itertools.islice(lines, 70)
+                else:
+                    lines_to_echo = lines
+
+                lines_to_echo = list(lines_to_echo)
+
+                content = u"\r\n".join(
+                    u"".join(colorful(line)) for line in lines_to_echo
+                )
+
+                self.echo(content)
+                if next(lines, None):
+                    self.echo("(cut off)", ident=4, dim=True)
+
+        if self.flow_detail >= 2:
+            self.echo("")
+
+    def _echo_request_line(self, flow):
+        if flow.request.stickycookie:
+            stickycookie = click.style(
+                "[stickycookie] ", fg="yellow", bold=True
+            )
+        else:
+            stickycookie = ""
+
+        if flow.client_conn:
+            client = click.style(
+                strutils.escape_control_characters(
+                    flow.client_conn.address.host
+                ),
+                bold=True
+            )
+        elif flow.request.is_replay:
+            client = click.style("[replay]", fg="yellow", bold=True)
+        else:
+            client = ""
+
+        method = flow.request.method
+        method_color = dict(
+            GET="green",
+            DELETE="red"
+        ).get(method.upper(), "magenta")
+        method = click.style(
+            strutils.escape_control_characters(method),
+            fg=method_color,
+            bold=True
+        )
+        if self.showhost:
+            url = flow.request.pretty_url
+        else:
+            url = flow.request.url
+        url = click.style(strutils.escape_control_characters(url), bold=True)
+
+        httpversion = ""
+        if flow.request.http_version not in ("HTTP/1.1", "HTTP/1.0"):
+            # We hide "normal" HTTP 1.
+            httpversion = " " + flow.request.http_version
+
+        line = "{stickycookie}{client} {method} {url}{httpversion}".format(
+            stickycookie=stickycookie,
+            client=client,
+            method=method,
+            url=url,
+            httpversion=httpversion
+        )
+        self.echo(line)
+
+    def _echo_response_line(self, flow):
+        if flow.response.is_replay:
+            replay = click.style("[replay] ", fg="yellow", bold=True)
+        else:
+            replay = ""
+
+        code = flow.response.status_code
+        code_color = None
+        if 200 <= code < 300:
+            code_color = "green"
+        elif 300 <= code < 400:
+            code_color = "magenta"
+        elif 400 <= code < 600:
+            code_color = "red"
+        code = click.style(
+            str(code),
+            fg=code_color,
+            bold=True,
+            blink=(code == 418)
+        )
+        reason = click.style(
+            strutils.escape_control_characters(flow.response.reason),
+            fg=code_color,
+            bold=True
+        )
+
+        if flow.response.raw_content is None:
+            size = "(content missing)"
+        else:
+            size = human.pretty_size(len(flow.response.raw_content))
+        size = click.style(size, bold=True)
+
+        arrows = click.style("    <<", bold=True)
+
+        line = "{replay} {arrows} {code} {reason} {size}".format(
+            replay=replay,
+            arrows=arrows,
+            code=code,
+            reason=reason,
+            size=size
+        )
+        self.echo(line)
+
+    def echo_flow(self, f):
+        if f.request:
+            self._echo_request_line(f)
+            self._echo_message(f.request)
+
+        if f.response:
+            self._echo_response_line(f)
+            self._echo_message(f.response)
+
+        if f.error:
+            self.echo(" << {}".format(f.error.msg), bold=True, fg="red")
+
+    def match(self, f):
+        if self.flow_detail == 0:
+            return False
+        if not self.filt:
+            return True
+        elif f.match(self.filt):
+            return True
+        return False
+
+    def configure(self, options):
+        if options.filtstr:
+            self.filt = filt.parse(options.filtstr)
+            if not self.filt:
+                raise exceptions.OptionsError(
+                    "Invalid filter expression: %s" % options.filtstr
+                )
+        else:
+            self.filt = None
+        self.flow_detail = options.flow_detail
+        self.outfp = options.tfile
+        self.showhost = options.showhost
+
+    def response(self, f):
+        if self.match(f):
+            self.echo_flow(f)
+
+    def error(self, f):
+        if self.match(f):
+            self.echo_flow(f)
+
+    def tcp_message(self, f):
+        # FIXME: Filter should be applied here
+        if self.options.flow_detail == 0:
+            return
+        message = f.messages[-1]
+        direction = "->" if message.from_client else "<-"
+        self.echo("{client} {direction} tcp {direction} {server}".format(
+            client=repr(f.client_conn.address),
+            server=repr(f.server_conn.address),
+            direction=direction,
+        ))
+        self._echo_message(message)
diff --git a/mitmproxy/controller.py b/mitmproxy/controller.py
--- a/mitmproxy/controller.py
+++ b/mitmproxy/controller.py
@@ -220,6 +220,12 @@ def wrapper(master, message):
 
         if handling and not message.reply.acked and not message.reply.taken:
             message.reply.ack()
+
+        # Reset the handled flag - it's common for us to feed the same object
+        # through handlers repeatedly, so we don't want this to persist across
+        # calls.
+        if message.reply.handled:
+            message.reply.handled = False
         return ret
     # Mark this function as a handler wrapper
     wrapper.__dict__["__handler"] = True
diff --git a/mitmproxy/dump.py b/mitmproxy/dump.py
--- a/mitmproxy/dump.py
+++ b/mitmproxy/dump.py
@@ -1,24 +1,19 @@
 from __future__ import absolute_import, print_function, division
 
-import itertools
 import sys
-import traceback
-
-import click
 
 from typing import Optional  # noqa
 import typing  # noqa
 
-from mitmproxy import contentviews
+import click
+
 from mitmproxy import controller
 from mitmproxy import exceptions
-from mitmproxy import filt
 from mitmproxy import flow
 from mitmproxy import builtins
 from mitmproxy import utils
-from netlib import human
+from mitmproxy.builtins import dumper
 from netlib import tcp
-from netlib import strutils
 
 
 class DumpError(Exception):
@@ -28,9 +23,9 @@ class DumpError(Exception):
 class Options(flow.options.Options):
     def __init__(
             self,
+            keepserving=False,  # type: bool
             filtstr=None,  # type: Optional[str]
             flow_detail=1,  # type: int
-            keepserving=False,  # type: bool
             tfile=None,  # type: Optional[typing.io.TextIO]
             **kwargs
     ):
@@ -47,10 +42,9 @@ def __init__(self, server, options):
         flow.FlowMaster.__init__(self, options, server, flow.State())
         self.has_errored = False
         self.addons.add(*builtins.default_addons())
+        self.addons.add(dumper.Dumper())
         # This line is just for type hinting
         self.options = self.options  # type: Options
-        self.o = options
-        self.showhost = options.showhost
         self.replay_ignore_params = options.replay_ignore_params
         self.replay_ignore_content = options.replay_ignore_content
         self.replay_ignore_host = options.replay_ignore_host
@@ -64,11 +58,6 @@ def __init__(self, server, options):
                   "HTTP/2 is disabled. Use --no-http2 to silence this warning.",
                   file=sys.stderr)
 
-        if options.filtstr:
-            self.filt = filt.parse(options.filtstr)
-        else:
-            self.filt = None
-
         if options.setheaders:
             for i in options.setheaders:
                 self.setheaders.add(*i)
@@ -115,221 +104,21 @@ def add_log(self, e, level="info"):
         if level == "error":
             self.has_errored = True
         if self.options.verbosity >= utils.log_tier(level):
-            self.echo(
+            click.secho(
                 e,
+                file=self.options.tfile,
                 fg="red" if level == "error" else None,
                 dim=(level == "debug"),
                 err=(level == "error")
             )
 
-    @staticmethod
-    def indent(n, text):
-        l = str(text).strip().splitlines()
-        pad = " " * n
-        return "\n".join(pad + i for i in l)
-
-    def echo(self, text, indent=None, **style):
-        if indent:
-            text = self.indent(indent, text)
-        click.secho(text, file=self.options.tfile, **style)
-
-    def _echo_message(self, message):
-        if self.options.flow_detail >= 2 and hasattr(message, "headers"):
-            headers = "\r\n".join(
-                "{}: {}".format(
-                    click.style(strutils.bytes_to_escaped_str(k), fg="blue", bold=True),
-                    click.style(strutils.bytes_to_escaped_str(v), fg="blue"))
-                for k, v in message.headers.fields
-            )
-            self.echo(headers, indent=4)
-        if self.options.flow_detail >= 3:
-            try:
-                content = message.content
-            except ValueError:
-                content = message.get_content(strict=False)
-
-            if content is None:
-                self.echo("(content missing)", indent=4)
-            elif content:
-                self.echo("")
-
-                try:
-                    type, lines = contentviews.get_content_view(
-                        contentviews.get("Auto"),
-                        content,
-                        headers=getattr(message, "headers", None)
-                    )
-                except exceptions.ContentViewException:
-                    s = "Content viewer failed: \n" + traceback.format_exc()
-                    self.add_log(s, "debug")
-                    type, lines = contentviews.get_content_view(
-                        contentviews.get("Raw"),
-                        content,
-                        headers=getattr(message, "headers", None)
-                    )
-
-                styles = dict(
-                    highlight=dict(bold=True),
-                    offset=dict(fg="blue"),
-                    header=dict(fg="green", bold=True),
-                    text=dict(fg="green")
-                )
-
-                def colorful(line):
-                    yield u"    "  # we can already indent here
-                    for (style, text) in line:
-                        yield click.style(text, **styles.get(style, {}))
-
-                if self.options.flow_detail == 3:
-                    lines_to_echo = itertools.islice(lines, 70)
-                else:
-                    lines_to_echo = lines
-
-                lines_to_echo = list(lines_to_echo)
-
-                content = u"\r\n".join(
-                    u"".join(colorful(line)) for line in lines_to_echo
-                )
-
-                self.echo(content)
-                if next(lines, None):
-                    self.echo("(cut off)", indent=4, dim=True)
-
-        if self.options.flow_detail >= 2:
-            self.echo("")
-
-    def _echo_request_line(self, flow):
-        if flow.request.stickycookie:
-            stickycookie = click.style(
-                "[stickycookie] ", fg="yellow", bold=True
-            )
-        else:
-            stickycookie = ""
-
-        if flow.client_conn:
-            client = click.style(strutils.escape_control_characters(flow.client_conn.address.host), bold=True)
-        else:
-            client = click.style("[replay]", fg="yellow", bold=True)
-
-        method = flow.request.method
-        method_color = dict(
-            GET="green",
-            DELETE="red"
-        ).get(method.upper(), "magenta")
-        method = click.style(strutils.escape_control_characters(method), fg=method_color, bold=True)
-        if self.showhost:
-            url = flow.request.pretty_url
-        else:
-            url = flow.request.url
-        url = click.style(strutils.escape_control_characters(url), bold=True)
-
-        httpversion = ""
-        if flow.request.http_version not in ("HTTP/1.1", "HTTP/1.0"):
-            httpversion = " " + flow.request.http_version  # We hide "normal" HTTP 1.
-
-        line = "{stickycookie}{client} {method} {url}{httpversion}".format(
-            stickycookie=stickycookie,
-            client=client,
-            method=method,
-            url=url,
-            httpversion=httpversion
-        )
-        self.echo(line)
-
-    def _echo_response_line(self, flow):
-        if flow.response.is_replay:
-            replay = click.style("[replay] ", fg="yellow", bold=True)
-        else:
-            replay = ""
-
-        code = flow.response.status_code
-        code_color = None
-        if 200 <= code < 300:
-            code_color = "green"
-        elif 300 <= code < 400:
-            code_color = "magenta"
-        elif 400 <= code < 600:
-            code_color = "red"
-        code = click.style(str(code), fg=code_color, bold=True, blink=(code == 418))
-        reason = click.style(strutils.escape_control_characters(flow.response.reason), fg=code_color, bold=True)
-
-        if flow.response.raw_content is None:
-            size = "(content missing)"
-        else:
-            size = human.pretty_size(len(flow.response.raw_content))
-        size = click.style(size, bold=True)
-
-        arrows = click.style("<<", bold=True)
-
-        line = "{replay} {arrows} {code} {reason} {size}".format(
-            replay=replay,
-            arrows=arrows,
-            code=code,
-            reason=reason,
-            size=size
-        )
-        self.echo(line)
-
-    def echo_flow(self, f):
-        if self.options.flow_detail == 0:
-            return
-
-        if f.request:
-            self._echo_request_line(f)
-            self._echo_message(f.request)
-
-        if f.response:
-            self._echo_response_line(f)
-            self._echo_message(f.response)
-
-        if f.error:
-            self.echo(" << {}".format(f.error.msg), bold=True, fg="red")
-
-        if self.options.tfile:
-            self.options.tfile.flush()
-
-    def _process_flow(self, f):
-        if self.filt and not f.match(self.filt):
-            return
-
-        self.echo_flow(f)
-
     @controller.handler
     def request(self, f):
-        f = flow.FlowMaster.request(self, f)
+        f = super(DumpMaster, self).request(f)
         if f:
             self.state.delete_flow(f)
         return f
 
-    @controller.handler
-    def response(self, f):
-        f = flow.FlowMaster.response(self, f)
-        if f:
-            self._process_flow(f)
-        return f
-
-    @controller.handler
-    def error(self, f):
-        flow.FlowMaster.error(self, f)
-        if f:
-            self._process_flow(f)
-        return f
-
-    @controller.handler
-    def tcp_message(self, f):
-        super(DumpMaster, self).tcp_message(f)
-
-        if self.options.flow_detail == 0:
-            return
-        message = f.messages[-1]
-        direction = "->" if message.from_client else "<-"
-        self.echo("{client} {direction} tcp {direction} {server}".format(
-            client=repr(f.client_conn.address),
-            server=repr(f.server_conn.address),
-            direction=direction,
-        ))
-        self._echo_message(message)
-
     def run(self):  # pragma: no cover
         if self.options.rfile and not self.options.keepserving:
             return


Test patch:
diff --git a/test/mitmproxy/builtins/test_dumper.py b/test/mitmproxy/builtins/test_dumper.py
new file mode 100644
--- /dev/null
+++ b/test/mitmproxy/builtins/test_dumper.py
@@ -0,0 +1,86 @@
+from .. import tutils, mastertest
+from six.moves import cStringIO as StringIO
+
+from mitmproxy.builtins import dumper
+from mitmproxy.flow import state
+from mitmproxy import exceptions
+from mitmproxy import dump
+from mitmproxy import models
+import netlib.tutils
+import mock
+
+
+class TestDumper(mastertest.MasterTest):
+    def test_simple(self):
+        d = dumper.Dumper()
+        sio = StringIO()
+
+        d.configure(dump.Options(tfile = sio, flow_detail = 0))
+        d.response(tutils.tflow())
+        assert not sio.getvalue()
+
+        d.configure(dump.Options(tfile = sio, flow_detail = 4))
+        d.response(tutils.tflow())
+        assert sio.getvalue()
+
+        sio = StringIO()
+        d.configure(dump.Options(tfile = sio, flow_detail = 4))
+        d.response(tutils.tflow(resp=True))
+        assert "<<" in sio.getvalue()
+
+        sio = StringIO()
+        d.configure(dump.Options(tfile = sio, flow_detail = 4))
+        d.response(tutils.tflow(err=True))
+        assert "<<" in sio.getvalue()
+
+        sio = StringIO()
+        d.configure(dump.Options(tfile = sio, flow_detail = 4))
+        flow = tutils.tflow()
+        flow.request = netlib.tutils.treq()
+        flow.request.stickycookie = True
+        flow.client_conn = mock.MagicMock()
+        flow.client_conn.address.host = "foo"
+        flow.response = netlib.tutils.tresp(content=None)
+        flow.response.is_replay = True
+        flow.response.status_code = 300
+        d.response(flow)
+        assert sio.getvalue()
+
+        sio = StringIO()
+        d.configure(dump.Options(tfile = sio, flow_detail = 4))
+        flow = tutils.tflow(resp=netlib.tutils.tresp(content=b"{"))
+        flow.response.headers["content-type"] = "application/json"
+        flow.response.status_code = 400
+        d.response(flow)
+        assert sio.getvalue()
+
+        sio = StringIO()
+        d.configure(dump.Options(tfile = sio))
+        flow = tutils.tflow()
+        flow.request.content = None
+        flow.response = models.HTTPResponse.wrap(netlib.tutils.tresp())
+        flow.response.content = None
+        d.response(flow)
+        assert "content missing" in sio.getvalue()
+
+
+class TestContentView(mastertest.MasterTest):
+    @mock.patch("mitmproxy.contentviews.get_content_view")
+    def test_contentview(self, get_content_view):
+        se = exceptions.ContentViewException(""), ("x", iter([]))
+        get_content_view.side_effect = se
+
+        s = state.State()
+        sio = StringIO()
+        m = mastertest.RecordingMaster(
+            dump.Options(
+                flow_detail=4,
+                verbosity=3,
+                tfile=sio,
+            ),
+            None, s
+        )
+        d = dumper.Dumper()
+        m.addons.add(d)
+        self.invoke(m, "response", tutils.tflow())
+        assert "Content viewer failed" in m.event_log[0][1]
diff --git a/test/mitmproxy/mastertest.py b/test/mitmproxy/mastertest.py
--- a/test/mitmproxy/mastertest.py
+++ b/test/mitmproxy/mastertest.py
@@ -8,11 +8,12 @@
 
 
 class MasterTest:
-    def invoke(self, master, handler, message):
+    def invoke(self, master, handler, *message):
         with master.handlecontext():
             func = getattr(master, handler)
-            func(message)
-        message.reply = controller.DummyReply()
+            func(*message)
+        if message:
+            message[0].reply = controller.DummyReply()
 
     def cycle(self, master, content):
         f = tutils.tflow(req=netlib.tutils.treq(content=content))
diff --git a/test/mitmproxy/test_dump.py b/test/mitmproxy/test_dump.py
--- a/test/mitmproxy/test_dump.py
+++ b/test/mitmproxy/test_dump.py
@@ -1,67 +1,11 @@
 import os
 from six.moves import cStringIO as StringIO
-from mitmproxy.exceptions import ContentViewException
 
-import netlib.tutils
-
-from mitmproxy import dump, flow, models, exceptions
+from mitmproxy import dump, flow, exceptions
 from . import tutils, mastertest
 import mock
 
 
-def test_strfuncs():
-    o = dump.Options(
-        tfile = StringIO(),
-        flow_detail = 0,
-    )
-    m = dump.DumpMaster(None, o)
-
-    m.o.flow_detail = 0
-    m.echo_flow(tutils.tflow())
-    assert not o.tfile.getvalue()
-
-    m.o.flow_detail = 4
-    m.echo_flow(tutils.tflow())
-    assert o.tfile.getvalue()
-
-    o.tfile = StringIO()
-    m.echo_flow(tutils.tflow(resp=True))
-    assert "<<" in o.tfile.getvalue()
-
-    o.tfile = StringIO()
-    m.echo_flow(tutils.tflow(err=True))
-    assert "<<" in o.tfile.getvalue()
-
-    flow = tutils.tflow()
-    flow.request = netlib.tutils.treq()
-    flow.request.stickycookie = True
-    flow.client_conn = mock.MagicMock()
-    flow.client_conn.address.host = "foo"
-    flow.response = netlib.tutils.tresp(content=None)
-    flow.response.is_replay = True
-    flow.response.status_code = 300
-    m.echo_flow(flow)
-
-    flow = tutils.tflow(resp=netlib.tutils.tresp(content=b"{"))
-    flow.response.headers["content-type"] = "application/json"
-    flow.response.status_code = 400
-    m.echo_flow(flow)
-
-
-@mock.patch("mitmproxy.contentviews.get_content_view")
-def test_contentview(get_content_view):
-    get_content_view.side_effect = ContentViewException(""), ("x", iter([]))
-
-    o = dump.Options(
-        flow_detail=4,
-        verbosity=3,
-        tfile=StringIO(),
-    )
-    m = dump.DumpMaster(None, o)
-    m.echo_flow(tutils.tflow())
-    assert "Content viewer failed" in m.options.tfile.getvalue()
-
-
 class TestDumpMaster(mastertest.MasterTest):
     def dummy_cycle(self, master, n, content):
         mastertest.MasterTest.dummy_cycle(self, master, n, content)
@@ -72,11 +16,7 @@ def mkmaster(self, filt, **options):
             options["verbosity"] = 0
         if "flow_detail" not in options:
             options["flow_detail"] = 0
-        o = dump.Options(
-            filtstr=filt,
-            tfile=StringIO(),
-            **options
-        )
+        o = dump.Options(filtstr=filt, tfile=StringIO(), **options)
         return dump.DumpMaster(None, o)
 
     def test_basic(self):
@@ -104,24 +44,10 @@ def test_error(self):
         )
         m = dump.DumpMaster(None, o)
         f = tutils.tflow(err=True)
-        m.request(f)
+        m.error(f)
         assert m.error(f)
         assert "error" in o.tfile.getvalue()
 
-    def test_missing_content(self):
-        o = dump.Options(
-            flow_detail=3,
-            tfile=StringIO(),
-        )
-        m = dump.DumpMaster(None, o)
-        f = tutils.tflow()
-        f.request.content = None
-        m.request(f)
-        f.response = models.HTTPResponse.wrap(netlib.tutils.tresp())
-        f.response.content = None
-        m.response(f)
-        assert "content missing" in o.tfile.getvalue()
-
     def test_replay(self):
         o = dump.Options(server_replay=["nonexistent"], kill=True)
         tutils.raises(dump.DumpError, dump.DumpMaster, None, o)
@@ -155,9 +81,8 @@ def test_read(self):
             self.flowfile(p)
             assert "GET" in self.dummy_cycle(
                 self.mkmaster(None, flow_detail=1, rfile=p),
-                0, b"",
+                1, b"",
             )
-
             tutils.raises(
                 dump.DumpError,
                 self.mkmaster, None, verbosity=1, rfile="/nonexistent"