Skip to content

Commit

Permalink
add a --debug-state command-line option
Browse files Browse the repository at this point in the history
This exposes set_trace functionality for debugging
state-machine transitions in the various state machines.
  • Loading branch information
meejah committed Jul 22, 2021
1 parent 679434a commit 98d769c
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 1 deletion.
52 changes: 52 additions & 0 deletions src/wormhole/cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,30 @@ def __init__(self):
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):
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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 <optional-value>" 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,
Expand Down
2 changes: 2 additions & 0 deletions src/wormhole/cli/cmd_receive.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ def go(self):
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:
Expand Down
2 changes: 2 additions & 0 deletions src/wormhole/cli/cmd_send.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ def go(self):
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
Expand Down
46 changes: 45 additions & 1 deletion src/wormhole/test/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@
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
Expand Down Expand Up @@ -1278,6 +1279,49 @@ def fake():
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")
Expand Down

0 comments on commit 98d769c

Please sign in to comment.