From 98d769c9835e15f5013bbcad5a01803017fa1d8e Mon Sep 17 00:00:00 2001 From: meejah Date: Tue, 20 Apr 2021 14:45:22 -0600 Subject: [PATCH] add a --debug-state command-line option This exposes set_trace functionality for debugging state-machine transitions in the various state machines. --- src/wormhole/cli/cli.py | 52 +++++++++++++++++++++++++++++++++ src/wormhole/cli/cmd_receive.py | 2 ++ src/wormhole/cli/cmd_send.py | 2 ++ src/wormhole/test/test_cli.py | 46 ++++++++++++++++++++++++++++- 4 files changed, 101 insertions(+), 1 deletion(-) diff --git a/src/wormhole/cli/cli.py b/src/wormhole/cli/cli.py index 6051a12..f638d9f 100644 --- a/src/wormhole/cli/cli.py +++ b/src/wormhole/cli/cli.py @@ -38,6 +38,30 @@ class Config(object): self.stdout = stdout self.stderr = stderr self.tor = False # XXX? + self._debug_state = None + + @property + def debug_state(self): + return self._debug_state + + @debug_state.setter + def debug_state(self, debug_state): + if not debug_state: + return + valid_machines = [ + 'B', 'N', 'M', 'S', 'O', 'K', 'SK', 'R', 'RC', 'L', 'C', 'T' + ] + debug_state = debug_state.split(",") + invalid_machines = [ + machine + for machine in debug_state + if machine not in valid_machines + ] + if invalid_machines: + raise click.UsageError( + "Cannot debug unknown machines: {}".format(" ".join(invalid_machines)) + ) + self._debug_state = debug_state def _compose(*decorators): @@ -236,6 +260,19 @@ def help(context, **kwargs): default=False, is_flag=True, help="Don't raise an error if a file can't be read.") +@click.option( + "--debug-state", + is_flag=False, + flag_value="B,N,M,S,O,K,SK,R,RC,L,C,T", + default=None, + metavar="MACHINES", + help=( + "Debug state-machine transitions. " + "Possible machines to debug are accepted as a comma-separated list " + "and the default is all of them. Valid machines are " + "any of: B,N,M,S,O,K,SK,R,RC,L,C,T" + ) +) @click.argument("what", required=False, type=click.Path(path_type=type(u""))) @click.pass_obj def send(cfg, **kwargs): @@ -277,6 +314,21 @@ def go(f, cfg): help=("The file or directory to create, overriding the name suggested" " by the sender."), ) +# --debug-state might be better at the top-level but Click can't parse +# an option like "--debug-state " if there's a subcommand name next +@click.option( + "--debug-state", + is_flag=False, + flag_value="B,N,M,S,O,K,SK,R,RC,L,C,T", + default=None, + metavar="MACHINES", + help=( + "Debug state-machine transitions. " + "Possible machines to debug are accepted as a comma-separated list " + "and the default is all of them. Valid machines are " + "any of: B,N,M,S,O,K,SK,R,RC,L,C,T" + ) +) @click.argument( "code", nargs=-1, diff --git a/src/wormhole/cli/cmd_receive.py b/src/wormhole/cli/cmd_receive.py index 2f232aa..92bab99 100644 --- a/src/wormhole/cli/cmd_receive.py +++ b/src/wormhole/cli/cmd_receive.py @@ -84,6 +84,8 @@ class Receiver: self._reactor, tor=self._tor, timing=self.args.timing) + if self.args.debug_state: + w.debug_set_trace("recv", which=" ".join(self.args.debug_state), file=self.args.stdout) self._w = w # so tests can wait on events too # I wanted to do this instead: diff --git a/src/wormhole/cli/cmd_send.py b/src/wormhole/cli/cmd_send.py index 5594f9b..b9977db 100644 --- a/src/wormhole/cli/cmd_send.py +++ b/src/wormhole/cli/cmd_send.py @@ -69,6 +69,8 @@ class Sender: self._reactor, tor=self._tor, timing=self._timing) + if self._args.debug_state: + w.debug_set_trace("send", which=" ".join(self._args.debug_state), file=self._args.stdout) d = self._go(w) # if we succeed, we should close and return the w.close results diff --git a/src/wormhole/test/test_cli.py b/src/wormhole/test/test_cli.py index ec0dfd0..4f48595 100644 --- a/src/wormhole/test/test_cli.py +++ b/src/wormhole/test/test_cli.py @@ -9,10 +9,11 @@ import zipfile from textwrap import dedent, fill import six +from click import UsageError from click.testing import CliRunner from humanize import naturalsize from twisted.internet import endpoints, reactor -from twisted.internet.defer import gatherResults, inlineCallbacks, returnValue +from twisted.internet.defer import gatherResults, inlineCallbacks, returnValue, CancelledError from twisted.internet.error import ConnectionRefusedError from twisted.internet.utils import getProcessOutputAndValue from twisted.python import log, procutils @@ -1278,6 +1279,49 @@ class Dispatch(unittest.TestCase): self.assertEqual(cfg.timing.mock_calls[-1], mock.call.write("filename", cfg.stderr)) + def test_debug_state_invalid_machine(self): + cfg = cli.Config() + with self.assertRaises(UsageError): + cfg.debug_state = "ZZZ" + + @inlineCallbacks + def test_debug_state_send(self): + args = config("send") + args.debug_state = "B,N,M,S,O,K,SK,R,RC,L,C,T" + args.stdout = io.StringIO() + s = cmd_send.Sender(args, reactor) + d = s.go() + d.cancel() + try: + yield d + except CancelledError: + pass + # just check for at least one state-transition we expected to + # get logged due to the --debug-state option + self.assertIn( + "send.B[S0_empty].close", + args.stdout.getvalue(), + ) + + @inlineCallbacks + def test_debug_state_receive(self): + args = config("receive") + args.debug_state = "B,N,M,S,O,K,SK,R,RC,L,C,T" + args.stdout = io.StringIO() + s = cmd_receive.Receiver(args, reactor) + d = s.go() + d.cancel() + try: + yield d + except CancelledError: + pass + # just check for at least one state-transition we expected to + # get logged due to the --debug-state option + self.assertIn( + "recv.B[S0_empty].close", + args.stdout.getvalue(), + ) + @inlineCallbacks def test_wrong_password_error(self): cfg = config("send")