magic-wormhole/src/wormhole/test/test_cli.py

1368 lines
51 KiB
Python
Raw Normal View History

from __future__ import print_function
2018-04-21 07:30:08 +00:00
import io
import os
import re
import stat
import sys
import zipfile
from textwrap import dedent, fill
import six
2017-07-10 18:19:56 +00:00
from click.testing import CliRunner
2018-04-21 07:30:08 +00:00
from humanize import naturalsize
from twisted.internet import endpoints, reactor
from twisted.internet.defer import gatherResults, inlineCallbacks, returnValue
from twisted.internet.error import ConnectionRefusedError
2018-04-21 07:30:08 +00:00
from twisted.internet.utils import getProcessOutputAndValue
from twisted.python import log, procutils
from twisted.trial import unittest
from zope.interface import implementer
import mock
2016-04-21 01:54:10 +00:00
from .. import __version__
from .._interfaces import ITorManager
2018-04-21 07:30:08 +00:00
from ..cli import cli, cmd_receive, cmd_send, welcome
from ..errors import (ServerConnectionError, TransferError,
UnsendableFileError, WelcomeError, WrongPasswordError)
from .common import ServerBase, config
2016-06-03 22:17:47 +00:00
2016-02-17 18:35:59 +00:00
def build_offer(args):
s = cmd_send.Sender(args, None)
return s._build_offer()
2016-06-03 22:17:47 +00:00
class OfferData(unittest.TestCase):
def setUp(self):
self._things_to_delete = []
self.cfg = cfg = config("send")
2016-06-03 22:17:47 +00:00
cfg.stdout = io.StringIO()
cfg.stderr = io.StringIO()
def tearDown(self):
for fn in self._things_to_delete:
if os.path.exists(fn):
os.unlink(fn)
2016-06-03 22:17:47 +00:00
del self.cfg
2016-02-17 18:35:59 +00:00
def test_text(self):
2016-06-03 22:17:47 +00:00
self.cfg.text = message = "blah blah blah ponies"
d, fd_to_send = build_offer(self.cfg)
2016-02-17 18:35:59 +00:00
self.assertIn("message", d)
self.assertNotIn("file", d)
self.assertNotIn("directory", d)
self.assertEqual(d["message"], message)
self.assertEqual(fd_to_send, None)
def test_file(self):
2016-06-03 22:17:47 +00:00
self.cfg.what = filename = "my file"
2016-02-17 18:35:59 +00:00
message = b"yay ponies\n"
send_dir = self.mktemp()
os.mkdir(send_dir)
abs_filename = os.path.join(send_dir, filename)
with open(abs_filename, "wb") as f:
f.write(message)
2016-06-03 22:17:47 +00:00
self.cfg.cwd = send_dir
d, fd_to_send = build_offer(self.cfg)
2016-02-17 18:35:59 +00:00
self.assertNotIn("message", d)
self.assertIn("file", d)
self.assertNotIn("directory", d)
self.assertEqual(d["file"]["filesize"], len(message))
self.assertEqual(d["file"]["filename"], filename)
self.assertEqual(fd_to_send.tell(), 0)
self.assertEqual(fd_to_send.read(), message)
2017-05-24 01:51:40 +00:00
def _create_broken_symlink(self):
2017-05-24 01:01:40 +00:00
if not hasattr(os, 'symlink'):
raise unittest.SkipTest("host OS does not support symlinks")
parent_dir = self.mktemp()
os.mkdir(parent_dir)
send_dir = "dirname"
os.mkdir(os.path.join(parent_dir, send_dir))
os.symlink('/non/existent/file',
os.path.join(parent_dir, send_dir, 'linky'))
send_dir_arg = send_dir
self.cfg.what = send_dir_arg
self.cfg.cwd = parent_dir
2017-05-24 01:51:40 +00:00
def test_broken_symlink_raises_err(self):
self._create_broken_symlink()
self.cfg.ignore_unsendable_files = False
2017-05-24 01:01:40 +00:00
e = self.assertRaises(UnsendableFileError, build_offer, self.cfg)
# On english distributions of Linux, this will be
# "linky: No such file or directory", but the error may be
# different on Windows and other locales and/or Unix variants, so
# we'll just assert the part we know about.
self.assertIn("linky: ", str(e))
2017-05-24 01:01:40 +00:00
2017-05-24 01:51:40 +00:00
def test_broken_symlink_is_ignored(self):
self._create_broken_symlink()
self.cfg.ignore_unsendable_files = True
d, fd_to_send = build_offer(self.cfg)
self.assertIn('(ignoring error)', self.cfg.stderr.getvalue())
self.assertEqual(d['directory']['numfiles'], 0)
self.assertEqual(d['directory']['numbytes'], 0)
2016-02-17 18:35:59 +00:00
def test_missing_file(self):
2016-06-03 22:17:47 +00:00
self.cfg.what = filename = "missing"
2016-02-17 18:35:59 +00:00
send_dir = self.mktemp()
os.mkdir(send_dir)
2016-06-03 22:17:47 +00:00
self.cfg.cwd = send_dir
2016-02-17 18:35:59 +00:00
2016-06-03 22:17:47 +00:00
e = self.assertRaises(TransferError, build_offer, self.cfg)
2018-04-21 07:30:08 +00:00
self.assertEqual(
str(e), "Cannot send: no file/directory named '%s'" % filename)
2016-02-17 18:35:59 +00:00
def _do_test_directory(self, addslash):
2016-02-17 18:35:59 +00:00
parent_dir = self.mktemp()
os.mkdir(parent_dir)
send_dir = "dirname"
os.mkdir(os.path.join(parent_dir, send_dir))
ponies = [str(i) for i in range(5)]
for p in ponies:
with open(os.path.join(parent_dir, send_dir, p), "wb") as f:
f.write(("%s ponies\n" % p).encode("ascii"))
send_dir_arg = send_dir
if addslash:
send_dir_arg += os.sep
2016-06-03 22:17:47 +00:00
self.cfg.what = send_dir_arg
self.cfg.cwd = parent_dir
2016-02-17 18:35:59 +00:00
2016-06-03 22:17:47 +00:00
d, fd_to_send = build_offer(self.cfg)
2016-02-17 18:35:59 +00:00
self.assertNotIn("message", d)
self.assertNotIn("file", d)
self.assertIn("directory", d)
self.assertEqual(d["directory"]["dirname"], send_dir)
self.assertEqual(d["directory"]["mode"], "zipfile/deflated")
self.assertEqual(d["directory"]["numfiles"], 5)
self.assertIn("numbytes", d["directory"])
self.assertIsInstance(d["directory"]["numbytes"], six.integer_types)
2016-02-17 18:35:59 +00:00
self.assertEqual(fd_to_send.tell(), 0)
zdata = fd_to_send.read()
self.assertEqual(len(zdata), d["directory"]["zipsize"])
fd_to_send.seek(0, 0)
with zipfile.ZipFile(fd_to_send, "r", zipfile.ZIP_DEFLATED) as zf:
zipnames = zf.namelist()
self.assertEqual(list(sorted(ponies)), list(sorted(zipnames)))
for name in zipnames:
contents = zf.open(name, "r").read()
self.assertEqual(("%s ponies\n" % name).encode("ascii"),
contents)
def test_directory(self):
return self._do_test_directory(addslash=False)
def test_directory_addslash(self):
return self._do_test_directory(addslash=True)
2016-02-17 18:35:59 +00:00
def test_unknown(self):
2016-06-03 22:17:47 +00:00
self.cfg.what = filename = "unknown"
2016-02-17 18:35:59 +00:00
send_dir = self.mktemp()
os.mkdir(send_dir)
abs_filename = os.path.abspath(os.path.join(send_dir, filename))
2016-06-03 22:17:47 +00:00
self.cfg.cwd = send_dir
2016-02-17 18:35:59 +00:00
try:
os.mkfifo(abs_filename)
except AttributeError:
2016-02-17 18:35:59 +00:00
raise unittest.SkipTest("is mkfifo supported on this platform?")
# Delete the named pipe for the sake of users who might run "pip
# wheel ." in this directory later. That command wants to copy
# everything into a tempdir before building a wheel, and the
# shutil.copy_tree() is uses can't handle the named pipe.
self._things_to_delete.append(abs_filename)
2016-02-17 18:35:59 +00:00
self.assertFalse(os.path.isfile(abs_filename))
self.assertFalse(os.path.isdir(abs_filename))
2016-06-03 22:17:47 +00:00
e = self.assertRaises(TypeError, build_offer, self.cfg)
2018-04-21 07:30:08 +00:00
self.assertEqual(
str(e), "'%s' is neither file nor directory" % filename)
2016-02-17 18:35:59 +00:00
def test_symlink(self):
if not hasattr(os, 'symlink'):
raise unittest.SkipTest("host OS does not support symlinks")
# build A/B1 -> B2 (==A/B2), and A/B2/C.txt
parent_dir = self.mktemp()
os.mkdir(parent_dir)
os.mkdir(os.path.join(parent_dir, "B2"))
with open(os.path.join(parent_dir, "B2", "C.txt"), "wb") as f:
f.write(b"success")
os.symlink("B2", os.path.join(parent_dir, "B1"))
# now send "B1/C.txt" from A, and it should get the right file
self.cfg.cwd = parent_dir
self.cfg.what = os.path.join("B1", "C.txt")
d, fd_to_send = build_offer(self.cfg)
self.assertEqual(d["file"]["filename"], "C.txt")
self.assertEqual(fd_to_send.read(), b"success")
def test_symlink_collapse(self):
if not hasattr(os, 'symlink'):
raise unittest.SkipTest("host OS does not support symlinks")
# build A/B1, A/B1/D.txt
# A/B2/C2, A/B2/D.txt
# symlink A/B1/C1 -> A/B2/C2
parent_dir = self.mktemp()
os.mkdir(parent_dir)
os.mkdir(os.path.join(parent_dir, "B1"))
with open(os.path.join(parent_dir, "B1", "D.txt"), "wb") as f:
f.write(b"fail")
os.mkdir(os.path.join(parent_dir, "B2"))
os.mkdir(os.path.join(parent_dir, "B2", "C2"))
with open(os.path.join(parent_dir, "B2", "D.txt"), "wb") as f:
f.write(b"success")
2018-04-21 07:30:08 +00:00
os.symlink(
os.path.abspath(os.path.join(parent_dir, "B2", "C2")),
os.path.join(parent_dir, "B1", "C1"))
# Now send "B1/C1/../D.txt" from A. The correct traversal will be:
# * start: A
# * B1: A/B1
# * C1: follow symlink to A/B2/C2
# * ..: climb to A/B2
# * D.txt: open A/B2/D.txt, which contains "success"
# If the code mistakenly uses normpath(), it would do:
# * normpath turns B1/C1/../D.txt into B1/D.txt
# * start: A
# * B1: A/B1
# * D.txt: open A/B1/D.txt , which contains "fail"
self.cfg.cwd = parent_dir
self.cfg.what = os.path.join("B1", "C1", os.pardir, "D.txt")
d, fd_to_send = build_offer(self.cfg)
self.assertEqual(d["file"]["filename"], "D.txt")
self.assertEqual(fd_to_send.read(), b"success")
2018-04-21 07:30:08 +00:00
if os.name == "nt":
test_symlink_collapse.todo = "host OS has broken os.path.realpath()"
# ntpath.py's realpath() is built out of normpath(), and does not
# follow symlinks properly, so this test always fails. "wormhole send
# PATH" on windows will do the wrong thing. See
# https://bugs.python.org/issue9949" for details. I'm making this a
# TODO instead of a SKIP because 1: this causes an observable
# misbehavior (albeit in rare circumstances), 2: it probably used to
# work (sometimes, but not in #251). See cmd_send.py for more notes.
2018-04-21 07:30:08 +00:00
class LocaleFinder:
def __init__(self):
self._run_once = False
@inlineCallbacks
def find_utf8_locale(self):
if sys.platform == "win32":
returnValue("en_US.UTF-8")
if self._run_once:
returnValue(self._best_locale)
self._best_locale = yield self._find_utf8_locale()
self._run_once = True
returnValue(self._best_locale)
@inlineCallbacks
def _find_utf8_locale(self):
# Click really wants to be running under a unicode-capable locale,
# especially on python3. macOS has en-US.UTF-8 but not C.UTF-8, and
# most linux boxes have C.UTF-8 but not en-US.UTF-8 . For tests,
# figure out which one is present and use that. For runtime, it's a
# mess, as really the user must take responsibility for setting their
# locale properly. I'm thinking of abandoning Click and going back to
# twisted.python.usage to avoid this problem in the future.
(out, err, rc) = yield getProcessOutputAndValue("locale", ["-a"])
if rc != 0:
2018-04-21 07:30:08 +00:00
log.msg("error running 'locale -a', rc=%s" % (rc, ))
log.msg("stderr: %s" % (err, ))
returnValue(None)
2018-04-21 07:30:08 +00:00
out = out.decode("utf-8") # make sure we get a string
utf8_locales = {}
for locale in out.splitlines():
locale = locale.strip()
if locale.lower().endswith((".utf-8", ".utf8")):
utf8_locales[locale.lower()] = locale
for wanted in ["C.utf8", "C.UTF-8", "en_US.utf8", "en_US.UTF-8"]:
if wanted.lower() in utf8_locales:
returnValue(utf8_locales[wanted.lower()])
if utf8_locales:
returnValue(list(utf8_locales.values())[0])
returnValue(None)
2018-04-21 07:30:08 +00:00
locale_finder = LocaleFinder()
2015-09-27 01:00:09 +00:00
2018-04-21 07:30:08 +00:00
class ScriptsBase:
2015-09-27 01:00:09 +00:00
def find_executable(self):
# to make sure we're running the right executable (in a virtualenv),
# we require that our "wormhole" lives in the same directory as our
# "python"
2015-09-27 01:00:09 +00:00
locations = procutils.which("wormhole")
if not locations:
raise unittest.SkipTest("unable to find 'wormhole' in $PATH")
wormhole = locations[0]
2018-04-21 07:30:08 +00:00
if (os.path.dirname(os.path.abspath(wormhole)) != os.path.dirname(
sys.executable)):
log.msg("locations: %s" % (locations, ))
log.msg("sys.executable: %s" % (sys.executable, ))
raise unittest.SkipTest(
"found the wrong 'wormhole' in $PATH: %s %s" %
(wormhole, sys.executable))
2015-09-27 01:00:09 +00:00
return wormhole
@inlineCallbacks
def is_runnable(self):
# One property of Versioneer is that many changes to the source tree
# (making a commit, dirtying a previously-clean tree) will change the
# version string. Entrypoint scripts frequently insist upon importing
# a library version that matches the script version (whatever was
# reported when 'pip install' was run), and throw a
# DistributionNotFound error when they don't match. This is really
# annoying in a workspace created with "pip install -e .", as you
# must re-run pip after each commit.
# So let's report just one error in this case (from test_version),
# and skip the other tests that we know will fail.
# Setting LANG/LC_ALL to a unicode-capable locale is necessary to
# convince Click to not complain about a forced-ascii locale. My
# apologies to folks who want to run tests on a machine that doesn't
# have the C.UTF-8 locale installed.
locale = yield locale_finder.find_utf8_locale()
if not locale:
raise unittest.SkipTest("unable to find UTF-8 locale")
locale_env = dict(LC_ALL=locale, LANG=locale)
wormhole = self.find_executable()
2018-04-21 07:30:08 +00:00
res = yield getProcessOutputAndValue(
wormhole, ["--version"], env=locale_env)
out, err, rc = res
if rc != 0:
log.msg("wormhole not runnable in this tree:")
log.msg("out", out)
log.msg("err", err)
log.msg("rc", rc)
raise unittest.SkipTest("wormhole is not runnable in this tree")
returnValue(locale_env)
2018-04-21 07:30:08 +00:00
class ScriptVersion(ServerBase, ScriptsBase, unittest.TestCase):
# we need Twisted to run the server, but we run the sender and receiver
# with deferToThread()
2016-06-03 22:17:47 +00:00
@inlineCallbacks
2015-09-27 01:00:09 +00:00
def test_version(self):
# "wormhole" must be on the path, so e.g. "pip install -e ." in a
# virtualenv. This guards against an environment where the tests
# below might run the wrong executable.
2016-06-03 22:17:47 +00:00
self.maxDiff = None
2015-09-27 01:00:09 +00:00
wormhole = self.find_executable()
2016-06-03 22:17:47 +00:00
# we must pass on the environment so that "something" doesn't
# get sad about UTF8 vs. ascii encodings
2018-04-21 07:30:08 +00:00
out, err, rc = yield getProcessOutputAndValue(
wormhole, ["--version"], env=os.environ)
2016-06-03 22:17:47 +00:00
err = err.decode("utf-8")
if "DistributionNotFound" in err:
log.msg("stderr was %s" % err)
last = err.strip().split("\n")[-1]
self.fail("wormhole not runnable: %s" % last)
ver = out.decode("utf-8") or err
2018-04-21 07:30:08 +00:00
self.failUnlessEqual(ver.strip(),
"magic-wormhole {}".format(__version__))
2016-06-03 22:17:47 +00:00
self.failUnlessEqual(rc, 0)
2015-09-27 01:00:09 +00:00
2018-04-21 07:30:08 +00:00
@implementer(ITorManager)
class FakeTor:
# use normal endpoints, but record the fact that we were asked
def __init__(self):
self.endpoints = []
2018-04-21 07:30:08 +00:00
def stream_via(self, host, port):
self.endpoints.append((host, port))
return endpoints.HostnameEndpoint(reactor, host, port)
2018-04-21 07:30:08 +00:00
class PregeneratedCode(ServerBase, ScriptsBase, unittest.TestCase):
# we need Twisted to run the server, but we run the sender and receiver
# with deferToThread()
@inlineCallbacks
def setUp(self):
self._env = yield self.is_runnable()
yield ServerBase.setUp(self)
@inlineCallbacks
2018-04-21 07:30:08 +00:00
def _do_test(self,
as_subprocess=False,
mode="text",
addslash=False,
override_filename=False,
fake_tor=False,
overwrite=False,
mock_accept=False,
verify=False):
2018-04-21 07:30:08 +00:00
assert mode in ("text", "file", "empty-file", "directory", "slow-text",
"slow-sender-text")
if fake_tor:
assert not as_subprocess
2016-08-16 09:40:47 +00:00
send_cfg = config("send")
recv_cfg = config("receive")
2016-06-03 22:17:47 +00:00
message = "blah blah blah ponies"
2016-06-03 22:17:47 +00:00
for cfg in [send_cfg, recv_cfg]:
cfg.hide_progress = True
cfg.relay_url = self.relayurl
cfg.transit_helper = ""
cfg.listen = True
cfg.code = u"1-abc"
2016-06-03 22:17:47 +00:00
cfg.stdout = io.StringIO()
cfg.stderr = io.StringIO()
cfg.verify = verify
2015-09-27 01:00:09 +00:00
send_dir = self.mktemp()
os.mkdir(send_dir)
receive_dir = self.mktemp()
os.mkdir(receive_dir)
if mode in ("text", "slow-text", "slow-sender-text"):
2016-06-03 22:17:47 +00:00
send_cfg.text = message
elif mode in ("file", "empty-file"):
if mode == "empty-file":
message = ""
2018-04-21 07:30:08 +00:00
send_filename = u"testfil\u00EB" # e-with-diaeresis
with open(os.path.join(send_dir, send_filename), "w") as f:
f.write(message)
2016-06-03 22:17:47 +00:00
send_cfg.what = send_filename
receive_filename = send_filename
2017-02-05 23:42:49 +00:00
recv_cfg.accept_file = False if mock_accept else True
if override_filename:
recv_cfg.output_file = receive_filename = u"outfile"
if overwrite:
recv_cfg.output_file = receive_filename
existing_file = os.path.join(receive_dir, receive_filename)
with open(existing_file, 'w') as f:
f.write('pls overwrite me')
elif mode == "directory":
# $send_dir/
# $send_dir/middle/
# $send_dir/middle/$dirname/
# $send_dir/middle/$dirname/[12345]
# cd $send_dir && wormhole send middle/$dirname
# cd $receive_dir && wormhole receive
# expect: $receive_dir/$dirname/[12345]
send_dirname = u"testdir"
2018-04-21 07:30:08 +00:00
def message(i):
return "test message %d\n" % i
2018-04-21 07:30:08 +00:00
os.mkdir(os.path.join(send_dir, u"middle"))
source_dir = os.path.join(send_dir, u"middle", send_dirname)
os.mkdir(source_dir)
modes = {}
for i in range(5):
path = os.path.join(source_dir, str(i))
with open(path, "w") as f:
f.write(message(i))
if i == 3:
os.chmod(path, 0o755)
modes[i] = stat.S_IMODE(os.stat(path).st_mode)
send_dirname_arg = os.path.join(u"middle", send_dirname)
if addslash:
send_dirname_arg += os.sep
2016-06-03 22:17:47 +00:00
send_cfg.what = send_dirname_arg
receive_dirname = send_dirname
2017-02-05 23:42:49 +00:00
recv_cfg.accept_file = False if mock_accept else True
if override_filename:
recv_cfg.output_file = receive_dirname = u"outdir"
if overwrite:
recv_cfg.output_file = receive_dirname
os.mkdir(os.path.join(receive_dir, receive_dirname))
if as_subprocess:
wormhole_bin = self.find_executable()
2016-06-03 22:17:47 +00:00
if send_cfg.text:
content_args = ['--text', send_cfg.text]
elif send_cfg.what:
content_args = [send_cfg.what]
# raise the rx KEY_TIMER to some large number here, to avoid
# spurious test failures on hosts that are slow enough to trigger
# the "Waiting for sender..." pacifier message. We can do in
# not-as_subprocess, because we can directly patch the value before
# running the receiver. But we can't patch across the subprocess
# boundary, so we use an environment variable.
env = self._env.copy()
env["_MAGIC_WORMHOLE_TEST_KEY_TIMER"] = "999999"
env["_MAGIC_WORMHOLE_TEST_VERIFY_TIMER"] = "999999"
2016-06-03 22:17:47 +00:00
send_args = [
2018-04-21 07:30:08 +00:00
'--relay-url',
self.relayurl,
'--transit-helper',
'',
'send',
'--hide-progress',
'--code',
send_cfg.code,
] + content_args
2016-06-03 22:17:47 +00:00
send_d = getProcessOutputAndValue(
2018-04-21 07:30:08 +00:00
wormhole_bin,
send_args,
2016-06-03 22:17:47 +00:00
path=send_dir,
env=env,
2016-06-03 22:17:47 +00:00
)
recv_args = [
2018-04-21 07:30:08 +00:00
'--relay-url',
self.relayurl,
'--transit-helper',
'',
2016-06-03 22:17:47 +00:00
'receive',
'--hide-progress',
2016-06-03 22:17:47 +00:00
'--accept-file',
recv_cfg.code,
]
if override_filename:
recv_args.extend(['-o', receive_filename])
receive_d = getProcessOutputAndValue(
2018-04-21 07:30:08 +00:00
wormhole_bin,
recv_args,
2016-06-03 22:17:47 +00:00
path=receive_dir,
env=env,
2016-06-03 22:17:47 +00:00
)
(send_res, receive_res) = yield gatherResults([send_d, receive_d],
True)
send_stdout = send_res[0].decode("utf-8")
send_stderr = send_res[1].decode("utf-8")
send_rc = send_res[2]
receive_stdout = receive_res[0].decode("utf-8")
receive_stderr = receive_res[1].decode("utf-8")
receive_rc = receive_res[2]
NL = os.linesep
self.assertEqual((send_rc, receive_rc), (0, 0),
(send_res, receive_res))
else:
2016-06-03 22:17:47 +00:00
send_cfg.cwd = send_dir
recv_cfg.cwd = receive_dir
if fake_tor:
send_cfg.tor = True
send_cfg.transit_helper = self.transit
tx_tm = FakeTor()
2018-04-21 07:30:08 +00:00
with mock.patch(
"wormhole.tor_manager.get_tor",
return_value=tx_tm,
) as mtx_tm:
send_d = cmd_send.send(send_cfg)
recv_cfg.tor = True
recv_cfg.transit_helper = self.transit
rx_tm = FakeTor()
2018-04-21 07:30:08 +00:00
with mock.patch(
"wormhole.tor_manager.get_tor",
return_value=rx_tm,
) as mrx_tm:
receive_d = cmd_receive.receive(recv_cfg)
else:
KEY_TIMER = 0 if mode == "slow-sender-text" else 99999
rxw = []
with mock.patch.object(cmd_receive, "KEY_TIMER", KEY_TIMER):
send_d = cmd_send.send(send_cfg)
2018-04-21 07:30:08 +00:00
receive_d = cmd_receive.receive(
recv_cfg, _debug_stash_wormhole=rxw)
# we need to keep KEY_TIMER patched until the receiver
# gets far enough to start the timer, which happens after
# the code is set
if mode == "slow-sender-text":
yield rxw[0].get_unverified_key()
# The sender might fail, leaving the receiver hanging, or vice
# versa. Make sure we don't wait on one side exclusively
VERIFY_TIMER = 0 if mode == "slow-text" else 99999
with mock.patch.object(cmd_receive, "VERIFY_TIMER", VERIFY_TIMER):
with mock.patch.object(cmd_send, "VERIFY_TIMER", VERIFY_TIMER):
if mock_accept or verify:
2018-04-21 07:30:08 +00:00
with mock.patch.object(
cmd_receive.six.moves, 'input',
return_value='yes') as i:
yield gatherResults([send_d, receive_d], True)
if verify:
s = i.mock_calls[0][1][0]
mo = re.search(r'^Verifier (\w+)\. ok\?', s)
self.assertTrue(mo, s)
sender_verifier = mo.group(1)
else:
yield gatherResults([send_d, receive_d], True)
if fake_tor:
2018-02-21 08:38:19 +00:00
expected_endpoints = [("127.0.0.1", self.rdv_ws_port)]
if mode in ("file", "directory"):
expected_endpoints.append(("127.0.0.1", self.transitport))
tx_timing = mtx_tm.call_args[1]["timing"]
self.assertEqual(tx_tm.endpoints, expected_endpoints)
2018-04-21 07:30:08 +00:00
self.assertEqual(
mtx_tm.mock_calls,
[mock.call(reactor, False, None, timing=tx_timing)])
rx_timing = mrx_tm.call_args[1]["timing"]
self.assertEqual(rx_tm.endpoints, expected_endpoints)
2018-04-21 07:30:08 +00:00
self.assertEqual(
mrx_tm.mock_calls,
[mock.call(reactor, False, None, timing=rx_timing)])
2016-06-03 22:17:47 +00:00
send_stdout = send_cfg.stdout.getvalue()
send_stderr = send_cfg.stderr.getvalue()
receive_stdout = recv_cfg.stdout.getvalue()
receive_stderr = recv_cfg.stderr.getvalue()
# all output here comes from a StringIO, which uses \n for
# newlines, even if we're on windows
NL = "\n"
2018-04-21 07:30:08 +00:00
self.maxDiff = None # show full output for assertion failures
key_established = ""
if mode == "slow-text":
key_established = "Key established, waiting for confirmation...\n"
self.assertEqual(send_stdout, "")
# check sender
if mode == "text" or mode == "slow-text":
expected = ("Sending text message ({bytes:d} Bytes){NL}"
"Wormhole code is: {code}{NL}"
"On the other computer, please run:{NL}{NL}"
"wormhole receive {verify}{code}{NL}{NL}"
"{KE}"
2018-04-21 07:30:08 +00:00
"text message sent{NL}").format(
bytes=len(message),
verify="--verify " if verify else "",
2018-04-21 07:30:08 +00:00
code=send_cfg.code,
NL=NL,
KE=key_established)
self.failUnlessEqual(send_stderr, expected)
elif mode == "file":
self.failUnlessIn(u"Sending {size:s} file named '{name}'{NL}"
2018-04-21 07:30:08 +00:00
.format(
size=naturalsize(len(message)),
name=send_filename,
NL=NL), send_stderr)
self.failUnlessIn(u"Wormhole code is: {code}{NL}"
"On the other computer, please run:{NL}{NL}"
2018-04-21 07:30:08 +00:00
"wormhole receive {code}{NL}{NL}".format(
code=send_cfg.code, NL=NL), send_stderr)
self.failUnlessIn(
u"File sent.. waiting for confirmation{NL}"
"Confirmation received. Transfer complete.{NL}".format(NL=NL),
send_stderr)
elif mode == "directory":
self.failUnlessIn(u"Sending directory", send_stderr)
self.failUnlessIn(u"named 'testdir'", send_stderr)
self.failUnlessIn(u"Wormhole code is: {code}{NL}"
"On the other computer, please run:{NL}{NL}"
2018-04-21 07:30:08 +00:00
"wormhole receive {code}{NL}{NL}".format(
code=send_cfg.code, NL=NL), send_stderr)
self.failUnlessIn(
u"File sent.. waiting for confirmation{NL}"
"Confirmation received. Transfer complete.{NL}".format(NL=NL),
send_stderr)
# check receiver
if mode in ("text", "slow-text", "slow-sender-text"):
2018-04-21 07:30:08 +00:00
self.assertEqual(receive_stdout, message + NL)
if mode == "text":
if verify:
mo = re.search(r'^Verifier (\w+)\.\s*$', receive_stderr)
self.assertTrue(mo, receive_stderr)
receiver_verifier = mo.group(1)
self.assertEqual(sender_verifier, receiver_verifier)
else:
self.assertEqual(receive_stderr, "")
elif mode == "slow-text":
self.assertEqual(receive_stderr, key_established)
elif mode == "slow-sender-text":
self.assertEqual(receive_stderr, "Waiting for sender...\n")
elif mode == "file":
self.failUnlessEqual(receive_stdout, "")
2018-04-21 07:30:08 +00:00
self.failUnlessIn(u"Receiving file ({size:s}) into: {name}".format(
size=naturalsize(len(message)), name=receive_filename),
2018-06-16 23:17:26 +00:00
receive_stderr)
self.failUnlessIn(u"Received file written to ", receive_stderr)
fn = os.path.join(receive_dir, receive_filename)
self.failUnless(os.path.exists(fn))
with open(fn, "r") as f:
self.failUnlessEqual(f.read(), message)
elif mode == "directory":
self.failUnlessEqual(receive_stdout, "")
want = (r"Receiving directory \(\d+ \w+\) into: {name}/"
.format(name=receive_dirname))
2018-04-21 07:30:08 +00:00
self.failUnless(
re.search(want, receive_stderr), (want, receive_stderr))
self.failUnlessIn(
u"Received files written to {name}"
.format(name=receive_dirname),
receive_stderr)
fn = os.path.join(receive_dir, receive_dirname)
2016-02-17 21:19:48 +00:00
self.failUnless(os.path.exists(fn), fn)
2015-11-29 07:33:15 +00:00
for i in range(5):
fn = os.path.join(receive_dir, receive_dirname, str(i))
2015-11-29 07:33:15 +00:00
with open(fn, "r") as f:
self.failUnlessEqual(f.read(), message(i))
2018-04-21 07:30:08 +00:00
self.failUnlessEqual(modes[i], stat.S_IMODE(
os.stat(fn).st_mode))
def test_text(self):
return self._do_test()
2018-04-21 07:30:08 +00:00
def test_text_subprocess(self):
return self._do_test(as_subprocess=True)
2018-04-21 07:30:08 +00:00
def test_text_tor(self):
return self._do_test(fake_tor=True)
def test_text_verify(self):
return self._do_test(verify=True)
def test_file(self):
return self._do_test(mode="file")
2018-04-21 07:30:08 +00:00
def test_file_override(self):
return self._do_test(mode="file", override_filename=True)
2018-04-21 07:30:08 +00:00
def test_file_overwrite(self):
return self._do_test(mode="file", overwrite=True)
2018-04-21 07:30:08 +00:00
2017-02-05 23:42:49 +00:00
def test_file_overwrite_mock_accept(self):
return self._do_test(mode="file", overwrite=True, mock_accept=True)
2018-04-21 07:30:08 +00:00
def test_file_tor(self):
return self._do_test(mode="file", fake_tor=True)
2018-04-21 07:30:08 +00:00
def test_empty_file(self):
return self._do_test(mode="empty-file")
def test_directory(self):
return self._do_test(mode="directory")
2018-04-21 07:30:08 +00:00
def test_directory_addslash(self):
return self._do_test(mode="directory", addslash=True)
2018-04-21 07:30:08 +00:00
def test_directory_override(self):
return self._do_test(mode="directory", override_filename=True)
2018-04-21 07:30:08 +00:00
def test_directory_overwrite(self):
return self._do_test(mode="directory", overwrite=True)
2018-04-21 07:30:08 +00:00
2017-02-05 23:42:49 +00:00
def test_directory_overwrite_mock_accept(self):
2018-04-21 07:30:08 +00:00
return self._do_test(
mode="directory", overwrite=True, mock_accept=True)
def test_slow_text(self):
return self._do_test(mode="slow-text")
2018-04-21 07:30:08 +00:00
def test_slow_sender_text(self):
return self._do_test(mode="slow-sender-text")
@inlineCallbacks
def _do_test_fail(self, mode, failmode):
assert mode in ("file", "directory")
assert failmode in ("noclobber", "toobig")
send_cfg = config("send")
recv_cfg = config("receive")
2016-06-03 22:17:47 +00:00
for cfg in [send_cfg, recv_cfg]:
cfg.hide_progress = True
cfg.relay_url = self.relayurl
cfg.transit_helper = ""
cfg.listen = False
cfg.code = u"1-abc"
2016-06-03 22:17:47 +00:00
cfg.stdout = io.StringIO()
cfg.stderr = io.StringIO()
send_dir = self.mktemp()
os.mkdir(send_dir)
receive_dir = self.mktemp()
os.mkdir(receive_dir)
2018-04-21 07:30:08 +00:00
recv_cfg.accept_file = True # don't ask for permission
if mode == "file":
message = "test message\n"
send_cfg.what = receive_name = send_filename = "testfile"
2016-12-11 00:14:54 +00:00
fn = os.path.join(send_dir, send_filename)
with open(fn, "w") as f:
f.write(message)
2016-12-11 00:14:54 +00:00
size = os.stat(fn).st_size
elif mode == "directory":
# $send_dir/
# $send_dir/$dirname/
# $send_dir/$dirname/[12345]
# cd $send_dir && wormhole send $dirname
# cd $receive_dir && wormhole receive
# expect: $receive_dir/$dirname/[12345]
2016-12-11 00:14:54 +00:00
size = 0
send_cfg.what = receive_name = send_dirname = "testdir"
os.mkdir(os.path.join(send_dir, send_dirname))
for i in range(5):
path = os.path.join(send_dir, send_dirname, str(i))
with open(path, "w") as f:
f.write("test message %d\n" % i)
2016-12-11 00:14:54 +00:00
size += os.stat(path).st_size
if failmode == "noclobber":
PRESERVE = "don't clobber me\n"
clobberable = os.path.join(receive_dir, receive_name)
with open(clobberable, "w") as f:
f.write(PRESERVE)
2016-06-03 22:17:47 +00:00
send_cfg.cwd = send_dir
send_d = cmd_send.send(send_cfg)
recv_cfg.cwd = receive_dir
receive_d = cmd_receive.receive(recv_cfg)
# both sides will fail
if failmode == "noclobber":
free_space = 10000000
else:
free_space = 0
2018-04-21 07:30:08 +00:00
with mock.patch(
"wormhole.cli.cmd_receive.estimate_free_space",
return_value=free_space):
f = yield self.assertFailure(send_d, TransferError)
2018-04-21 07:30:08 +00:00
self.assertEqual(
str(f), "remote error, transfer abandoned: transfer rejected")
f = yield self.assertFailure(receive_d, TransferError)
self.assertEqual(str(f), "transfer rejected")
2016-06-03 22:17:47 +00:00
send_stdout = send_cfg.stdout.getvalue()
send_stderr = send_cfg.stderr.getvalue()
receive_stdout = recv_cfg.stdout.getvalue()
receive_stderr = recv_cfg.stderr.getvalue()
# all output here comes from a StringIO, which uses \n for
# newlines, even if we're on windows
NL = "\n"
2018-04-21 07:30:08 +00:00
self.maxDiff = None # show full output for assertion failures
self.assertEqual(send_stdout, "")
self.assertEqual(receive_stdout, "")
# check sender
if mode == "file":
self.failUnlessIn("Sending {size:s} file named '{name}'{NL}"
2018-04-21 07:30:08 +00:00
.format(
size=naturalsize(size),
name=send_filename,
NL=NL), send_stderr)
self.failUnlessIn("Wormhole code is: {code}{NL}"
"On the other computer, please run:{NL}{NL}"
2018-04-21 07:30:08 +00:00
"wormhole receive {code}{NL}".format(
code=send_cfg.code, NL=NL), send_stderr)
self.failIfIn(
"File sent.. waiting for confirmation{NL}"
"Confirmation received. Transfer complete.{NL}".format(NL=NL),
send_stderr)
elif mode == "directory":
self.failUnlessIn("Sending directory", send_stderr)
self.failUnlessIn("named 'testdir'", send_stderr)
self.failUnlessIn("Wormhole code is: {code}{NL}"
"On the other computer, please run:{NL}{NL}"
2018-04-21 07:30:08 +00:00
"wormhole receive {code}{NL}".format(
code=send_cfg.code, NL=NL), send_stderr)
self.failIfIn(
"File sent.. waiting for confirmation{NL}"
"Confirmation received. Transfer complete.{NL}".format(NL=NL),
send_stderr)
# check receiver
if mode == "file":
self.failIfIn("Received file written to ", receive_stderr)
if failmode == "noclobber":
2018-04-21 07:30:08 +00:00
self.failUnlessIn(
"Error: "
"refusing to overwrite existing 'testfile'{NL}"
.format(NL=NL),
receive_stderr)
else:
2018-04-21 07:30:08 +00:00
self.failUnlessIn(
"Error: "
"insufficient free space (0B) for file ({size:d}B){NL}"
.format(NL=NL, size=size), receive_stderr)
elif mode == "directory":
2018-04-21 07:30:08 +00:00
self.failIfIn(
"Received files written to {name}".format(name=receive_name),
receive_stderr)
# want = (r"Receiving directory \(\d+ \w+\) into: {name}/"
# .format(name=receive_name))
2018-04-21 07:30:08 +00:00
# self.failUnless(re.search(want, receive_stderr),
# (want, receive_stderr))
if failmode == "noclobber":
2018-04-21 07:30:08 +00:00
self.failUnlessIn(
"Error: "
"refusing to overwrite existing 'testdir'{NL}"
.format(NL=NL),
receive_stderr)
else:
2018-04-21 07:30:08 +00:00
self.failUnlessIn(("Error: "
"insufficient free space (0B) for directory"
" ({size:d}B){NL}").format(
NL=NL, size=size), receive_stderr)
if failmode == "noclobber":
fn = os.path.join(receive_dir, receive_name)
self.failUnless(os.path.exists(fn))
with open(fn, "r") as f:
self.failUnlessEqual(f.read(), PRESERVE)
def test_fail_file_noclobber(self):
return self._do_test_fail("file", "noclobber")
2018-04-21 07:30:08 +00:00
def test_fail_directory_noclobber(self):
return self._do_test_fail("directory", "noclobber")
2018-04-21 07:30:08 +00:00
def test_fail_file_toobig(self):
return self._do_test_fail("file", "toobig")
2018-04-21 07:30:08 +00:00
def test_fail_directory_toobig(self):
return self._do_test_fail("directory", "toobig")
2018-04-21 07:30:08 +00:00
2017-04-16 21:07:59 +00:00
class ZeroMode(ServerBase, unittest.TestCase):
@inlineCallbacks
def test_text(self):
send_cfg = config("send")
recv_cfg = config("receive")
message = "textponies"
for cfg in [send_cfg, recv_cfg]:
cfg.hide_progress = True
cfg.relay_url = self.relayurl
cfg.transit_helper = ""
cfg.listen = True
cfg.zeromode = True
cfg.stdout = io.StringIO()
cfg.stderr = io.StringIO()
send_cfg.text = message
2018-04-21 07:30:08 +00:00
# send_cfg.cwd = send_dir
# recv_cfg.cwd = receive_dir
2017-04-16 21:07:59 +00:00
send_d = cmd_send.send(send_cfg)
receive_d = cmd_receive.receive(recv_cfg)
yield gatherResults([send_d, receive_d], True)
send_stdout = send_cfg.stdout.getvalue()
send_stderr = send_cfg.stderr.getvalue()
receive_stdout = recv_cfg.stdout.getvalue()
receive_stderr = recv_cfg.stderr.getvalue()
# all output here comes from a StringIO, which uses \n for
# newlines, even if we're on windows
NL = "\n"
2018-04-21 07:30:08 +00:00
self.maxDiff = None # show full output for assertion failures
2017-04-16 21:07:59 +00:00
self.assertEqual(send_stdout, "")
# check sender
expected = ("Sending text message ({bytes:d} Bytes){NL}"
2017-12-19 20:54:16 +00:00
"On the other computer, please run:{NL}"
"{NL}"
"wormhole receive -0{NL}"
"{NL}"
2018-04-21 07:30:08 +00:00
"text message sent{NL}").format(
bytes=len(message), code=send_cfg.code, NL=NL)
2017-04-16 21:07:59 +00:00
self.failUnlessEqual(send_stderr, expected)
# check receiver
2018-04-21 07:30:08 +00:00
self.assertEqual(receive_stdout, message + NL)
2017-04-16 21:07:59 +00:00
self.assertEqual(receive_stderr, "")
2018-04-21 07:30:08 +00:00
class NotWelcome(ServerBase, unittest.TestCase):
2018-02-21 08:38:19 +00:00
@inlineCallbacks
def setUp(self):
2018-02-21 08:38:19 +00:00
yield self._setup_relay(error="please upgrade XYZ")
self.cfg = cfg = config("send")
2016-06-03 22:17:47 +00:00
cfg.hide_progress = True
cfg.listen = False
cfg.relay_url = self.relayurl
cfg.transit_helper = ""
cfg.stdout = io.StringIO()
cfg.stderr = io.StringIO()
@inlineCallbacks
def test_sender(self):
2016-06-03 22:17:47 +00:00
self.cfg.text = "hi"
self.cfg.code = u"1-abc"
2016-06-03 22:17:47 +00:00
send_d = cmd_send.send(self.cfg)
f = yield self.assertFailure(send_d, WelcomeError)
self.assertEqual(str(f), "please upgrade XYZ")
@inlineCallbacks
def test_receiver(self):
self.cfg.code = u"1-abc"
2016-06-03 22:17:47 +00:00
receive_d = cmd_receive.receive(self.cfg)
f = yield self.assertFailure(receive_d, WelcomeError)
self.assertEqual(str(f), "please upgrade XYZ")
2016-06-03 22:17:47 +00:00
2018-04-21 07:30:08 +00:00
class NoServer(ServerBase, unittest.TestCase):
@inlineCallbacks
def setUp(self):
2018-02-21 08:38:19 +00:00
yield self._setup_relay(None)
yield self._relay_server.disownServiceParent()
@inlineCallbacks
def test_sender(self):
cfg = config("send")
cfg.hide_progress = True
cfg.listen = False
cfg.relay_url = self.relayurl
cfg.transit_helper = ""
cfg.stdout = io.StringIO()
cfg.stderr = io.StringIO()
cfg.text = "hi"
cfg.code = u"1-abc"
send_d = cmd_send.send(cfg)
e = yield self.assertFailure(send_d, ServerConnectionError)
self.assertIsInstance(e.reason, ConnectionRefusedError)
@inlineCallbacks
def test_sender_allocation(self):
cfg = config("send")
cfg.hide_progress = True
cfg.listen = False
cfg.relay_url = self.relayurl
cfg.transit_helper = ""
cfg.stdout = io.StringIO()
cfg.stderr = io.StringIO()
cfg.text = "hi"
send_d = cmd_send.send(cfg)
e = yield self.assertFailure(send_d, ServerConnectionError)
self.assertIsInstance(e.reason, ConnectionRefusedError)
@inlineCallbacks
def test_receiver(self):
cfg = config("receive")
cfg.hide_progress = True
cfg.listen = False
cfg.relay_url = self.relayurl
cfg.transit_helper = ""
cfg.stdout = io.StringIO()
cfg.stderr = io.StringIO()
cfg.code = u"1-abc"
receive_d = cmd_receive.receive(cfg)
e = yield self.assertFailure(receive_d, ServerConnectionError)
self.assertIsInstance(e.reason, ConnectionRefusedError)
2016-06-03 22:17:47 +00:00
2018-04-21 07:30:08 +00:00
class Cleanup(ServerBase, unittest.TestCase):
def make_config(self):
cfg = config("send")
2016-06-03 22:17:47 +00:00
# common options for all tests in this suite
cfg.hide_progress = True
cfg.relay_url = self.relayurl
cfg.transit_helper = ""
cfg.stdout = io.StringIO()
cfg.stderr = io.StringIO()
return cfg
2016-06-03 22:17:47 +00:00
@inlineCallbacks
2016-06-03 22:17:47 +00:00
@mock.patch('sys.stdout')
def test_text(self, stdout):
# the rendezvous channel should be deleted after success
cfg = self.make_config()
cfg.text = "hello"
cfg.code = u"1-abc"
send_d = cmd_send.send(cfg)
receive_d = cmd_receive.receive(cfg)
2016-06-03 22:17:47 +00:00
yield send_d
yield receive_d
cids = self._rendezvous.get_app(cmd_send.APPID).get_nameplate_ids()
self.assertEqual(len(cids), 0)
@inlineCallbacks
def test_text_wrong_password(self):
# if the password was wrong, the rendezvous channel should still be
# deleted
send_cfg = self.make_config()
send_cfg.text = "secret message"
send_cfg.code = u"1-abc"
send_d = cmd_send.send(send_cfg)
2016-06-03 22:17:47 +00:00
rx_cfg = self.make_config()
rx_cfg.code = u"1-WRONG"
receive_d = cmd_receive.receive(rx_cfg)
# both sides should be capable of detecting the mismatch
yield self.assertFailure(send_d, WrongPasswordError)
yield self.assertFailure(receive_d, WrongPasswordError)
cids = self._rendezvous.get_app(cmd_send.APPID).get_nameplate_ids()
self.assertEqual(len(cids), 0)
2018-04-21 07:30:08 +00:00
2016-06-03 22:38:49 +00:00
class ExtractFile(unittest.TestCase):
def test_filenames(self):
args = mock.Mock()
args.relay_url = u""
ef = cmd_receive.Receiver(args)._extract_file
2016-06-03 22:38:49 +00:00
extract_dir = os.path.abspath(self.mktemp())
zf = mock.Mock()
zi = mock.Mock()
zi.filename = "ok"
zi.external_attr = 5 << 16
expected = os.path.join(extract_dir, "ok")
with mock.patch.object(cmd_receive.os, "chmod") as chmod:
ef(zf, zi, extract_dir)
self.assertEqual(zf.extract.mock_calls,
[mock.call(zi.filename, path=extract_dir)])
self.assertEqual(chmod.mock_calls, [mock.call(expected, 5)])
zf = mock.Mock()
zi = mock.Mock()
zi.filename = "../haha"
e = self.assertRaises(ValueError, ef, zf, zi, extract_dir)
self.assertIn("malicious zipfile", str(e))
zf = mock.Mock()
zi = mock.Mock()
2018-04-21 07:30:08 +00:00
zi.filename = "haha//root" # abspath squashes this, hopefully zipfile
# does too
2016-06-03 22:38:49 +00:00
zi.external_attr = 5 << 16
2016-06-03 23:43:22 +00:00
expected = os.path.join(extract_dir, "haha", "root")
2016-06-03 22:38:49 +00:00
with mock.patch.object(cmd_receive.os, "chmod") as chmod:
ef(zf, zi, extract_dir)
self.assertEqual(zf.extract.mock_calls,
[mock.call(zi.filename, path=extract_dir)])
self.assertEqual(chmod.mock_calls, [mock.call(expected, 5)])
zf = mock.Mock()
zi = mock.Mock()
zi.filename = "/etc/passwd"
e = self.assertRaises(ValueError, ef, zf, zi, extract_dir)
self.assertIn("malicious zipfile", str(e))
2018-04-21 07:30:08 +00:00
class AppID(ServerBase, unittest.TestCase):
2018-02-21 08:38:19 +00:00
@inlineCallbacks
def setUp(self):
2018-02-21 08:38:19 +00:00
yield super(AppID, self).setUp()
self.cfg = cfg = config("send")
# common options for all tests in this suite
cfg.hide_progress = True
cfg.relay_url = self.relayurl
cfg.transit_helper = ""
cfg.stdout = io.StringIO()
cfg.stderr = io.StringIO()
@inlineCallbacks
def test_override(self):
# make sure we use the overridden appid, not the default
self.cfg.text = "hello"
self.cfg.appid = u"appid2"
self.cfg.code = u"1-abc"
send_d = cmd_send.send(self.cfg)
receive_d = cmd_receive.receive(self.cfg)
yield send_d
yield receive_d
2018-02-21 08:38:19 +00:00
used = self._usage_db.execute("SELECT DISTINCT `app_id`"
2018-04-21 07:30:08 +00:00
" FROM `nameplates`").fetchall()
self.assertEqual(len(used), 1, used)
self.assertEqual(used[0]["app_id"], u"appid2")
2018-04-21 07:30:08 +00:00
class Welcome(unittest.TestCase):
def do(self, welcome_message, my_version="2.0"):
stderr = io.StringIO()
welcome.handle_welcome(welcome_message, "url", my_version, stderr)
return stderr.getvalue()
def test_empty(self):
stderr = self.do({})
self.assertEqual(stderr, "")
def test_version_current(self):
stderr = self.do({"current_cli_version": "2.0"})
self.assertEqual(stderr, "")
def test_version_old(self):
stderr = self.do({"current_cli_version": "3.0"})
2018-04-21 07:30:08 +00:00
expected = ("Warning: errors may occur unless both sides are"
" running the same version\n"
"Server claims 3.0 is current, but ours is 2.0\n")
self.assertEqual(stderr, expected)
def test_version_unreleased(self):
2018-04-21 07:30:08 +00:00
stderr = self.do(
{
"current_cli_version": "3.0"
}, my_version="2.5+middle.something")
self.assertEqual(stderr, "")
def test_motd(self):
stderr = self.do({"motd": "hello"})
self.assertEqual(stderr, "Server (at url) says:\n hello\n")
2018-04-21 07:30:08 +00:00
class Dispatch(unittest.TestCase):
@inlineCallbacks
def test_success(self):
cfg = config("send")
cfg.stderr = io.StringIO()
called = []
2018-04-21 07:30:08 +00:00
def fake():
called.append(1)
2018-04-21 07:30:08 +00:00
yield cli._dispatch_command(reactor, cfg, fake)
self.assertEqual(called, [1])
self.assertEqual(cfg.stderr.getvalue(), "")
@inlineCallbacks
def test_timing(self):
cfg = config("send")
cfg.stderr = io.StringIO()
cfg.timing = mock.Mock()
cfg.dump_timing = "filename"
2018-04-21 07:30:08 +00:00
def fake():
pass
2018-04-21 07:30:08 +00:00
yield cli._dispatch_command(reactor, cfg, fake)
self.assertEqual(cfg.stderr.getvalue(), "")
self.assertEqual(cfg.timing.mock_calls[-1],
mock.call.write("filename", cfg.stderr))
@inlineCallbacks
def test_wrong_password_error(self):
cfg = config("send")
cfg.stderr = io.StringIO()
2018-04-21 07:30:08 +00:00
def fake():
raise WrongPasswordError("abcd")
2018-04-21 07:30:08 +00:00
yield self.assertFailure(
cli._dispatch_command(reactor, cfg, fake), SystemExit)
expected = fill("ERROR: " + dedent(WrongPasswordError.__doc__)) + "\n"
self.assertEqual(cfg.stderr.getvalue(), expected)
@inlineCallbacks
def test_welcome_error(self):
cfg = config("send")
cfg.stderr = io.StringIO()
2018-04-21 07:30:08 +00:00
def fake():
raise WelcomeError("abcd")
2018-04-21 07:30:08 +00:00
yield self.assertFailure(
cli._dispatch_command(reactor, cfg, fake), SystemExit)
expected = (
fill("ERROR: " + dedent(WelcomeError.__doc__)) + "\n\nabcd\n")
self.assertEqual(cfg.stderr.getvalue(), expected)
@inlineCallbacks
def test_transfer_error(self):
cfg = config("send")
cfg.stderr = io.StringIO()
2018-04-21 07:30:08 +00:00
def fake():
raise TransferError("abcd")
2018-04-21 07:30:08 +00:00
yield self.assertFailure(
cli._dispatch_command(reactor, cfg, fake), SystemExit)
expected = "TransferError: abcd\n"
self.assertEqual(cfg.stderr.getvalue(), expected)
@inlineCallbacks
def test_server_connection_error(self):
cfg = config("send")
cfg.stderr = io.StringIO()
2018-04-21 07:30:08 +00:00
def fake():
raise ServerConnectionError("URL", ValueError("abcd"))
2018-04-21 07:30:08 +00:00
yield self.assertFailure(
cli._dispatch_command(reactor, cfg, fake), SystemExit)
expected = fill(
"ERROR: " + dedent(ServerConnectionError.__doc__)) + "\n"
expected += "(relay URL was URL)\n"
expected += "abcd\n"
self.assertEqual(cfg.stderr.getvalue(), expected)
@inlineCallbacks
def test_other_error(self):
cfg = config("send")
cfg.stderr = io.StringIO()
2018-04-21 07:30:08 +00:00
def fake():
raise ValueError("abcd")
2018-04-21 07:30:08 +00:00
# I'm seeing unicode problems with the Failure().printTraceback, and
# the output would be kind of unpredictable anyways, so we'll mock it
# out here.
f = mock.Mock()
2018-04-21 07:30:08 +00:00
def mock_print(file):
file.write(u"<TRACEBACK>\n")
2018-04-21 07:30:08 +00:00
f.printTraceback = mock_print
with mock.patch("wormhole.cli.cli.Failure", return_value=f):
2018-04-21 07:30:08 +00:00
yield self.assertFailure(
cli._dispatch_command(reactor, cfg, fake), SystemExit)
expected = "<TRACEBACK>\nERROR: abcd\n"
self.assertEqual(cfg.stderr.getvalue(), expected)
2018-04-21 07:30:08 +00:00
2017-07-10 18:19:56 +00:00
class Help(unittest.TestCase):
def _check_top_level_help(self, got):
# the main wormhole.cli.cli.wormhole docstring should be in the
# output, but formatted differently
self.assertIn("Create a Magic Wormhole and communicate through it.",
got)
self.assertIn("--relay-url", got)
self.assertIn("Receive a text message, file, or directory", got)
def test_help(self):
result = CliRunner().invoke(cli.wormhole, ["help"])
self._check_top_level_help(result.output)
self.assertEqual(result.exit_code, 0)
def test_dash_dash_help(self):
result = CliRunner().invoke(cli.wormhole, ["--help"])
self._check_top_level_help(result.output)
self.assertEqual(result.exit_code, 0)