2017-07-10 18:57:51 +00:00
|
|
|
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
|
2017-05-23 07:45:02 +00:00
|
|
|
from twisted.internet import endpoints, reactor
|
2017-04-04 05:52:26 +00:00
|
|
|
from twisted.internet.defer import gatherResults, inlineCallbacks, returnValue
|
2017-04-16 16:25:37 +00:00
|
|
|
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__
|
2017-05-23 07:45:02 +00:00
|
|
|
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
|
|
|
|
2016-05-25 00:43:17 +00:00
|
|
|
def build_offer(args):
|
|
|
|
s = cmd_send.Sender(args, None)
|
|
|
|
return s._build_offer()
|
|
|
|
|
2016-06-03 22:17:47 +00:00
|
|
|
|
2016-05-24 23:41:13 +00:00
|
|
|
class OfferData(unittest.TestCase):
|
2016-04-20 17:41:38 +00:00
|
|
|
def setUp(self):
|
|
|
|
self._things_to_delete = []
|
2016-07-15 04:22:01 +00:00
|
|
|
self.cfg = cfg = config("send")
|
2016-06-03 22:17:47 +00:00
|
|
|
cfg.stdout = io.StringIO()
|
|
|
|
cfg.stderr = io.StringIO()
|
2016-04-20 17:41:38 +00:00
|
|
|
|
|
|
|
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-04-20 17:41:38 +00:00
|
|
|
|
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)
|
2017-05-24 15:52:56 +00:00
|
|
|
|
|
|
|
# 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
|
|
|
|
2016-03-24 15:46:29 +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"))
|
|
|
|
|
2016-03-24 15:46:29 +00:00
|
|
|
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"])
|
2016-02-27 22:46:38 +00:00
|
|
|
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)
|
|
|
|
|
2016-03-24 15:46:29 +00:00
|
|
|
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)
|
2016-02-27 22:46:38 +00:00
|
|
|
except AttributeError:
|
2016-02-17 18:35:59 +00:00
|
|
|
raise unittest.SkipTest("is mkfifo supported on this platform?")
|
2016-04-20 17:41:38 +00:00
|
|
|
|
|
|
|
# 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
|
|
|
|
2017-10-22 03:32:22 +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"))
|
2017-10-22 03:32:22 +00:00
|
|
|
# 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
|
|
|
|
2017-11-02 00:42:51 +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.
|
2017-10-22 03:32:22 +00:00
|
|
|
|
2018-04-21 07:30:08 +00:00
|
|
|
|
2017-04-04 05:52:26 +00:00
|
|
|
class LocaleFinder:
|
|
|
|
def __init__(self):
|
|
|
|
self._run_once = False
|
|
|
|
|
|
|
|
@inlineCallbacks
|
|
|
|
def find_utf8_locale(self):
|
2017-04-07 18:09:57 +00:00
|
|
|
if sys.platform == "win32":
|
|
|
|
returnValue("en_US.UTF-8")
|
2017-04-04 05:52:26 +00:00
|
|
|
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, ))
|
2017-04-04 05:52:26 +00:00
|
|
|
returnValue(None)
|
2018-04-21 07:30:08 +00:00
|
|
|
out = out.decode("utf-8") # make sure we get a string
|
2017-04-04 05:52:26 +00:00
|
|
|
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
|
|
|
|
|
|
|
|
2017-04-04 05:52:26 +00:00
|
|
|
locale_finder = LocaleFinder()
|
2015-09-27 01:00:09 +00:00
|
|
|
|
2018-04-21 07:30:08 +00:00
|
|
|
|
2015-09-27 17:43:25 +00:00
|
|
|
class ScriptsBase:
|
2015-09-27 01:00:09 +00:00
|
|
|
def find_executable(self):
|
2015-09-27 01:29:46 +00:00
|
|
|
# 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
|
|
|
|
|
2017-04-04 05:52:26 +00:00
|
|
|
@inlineCallbacks
|
2015-09-27 17:43:25 +00:00
|
|
|
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.
|
|
|
|
|
2016-06-23 02:28:17 +00:00
|
|
|
# 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
|
2017-04-04 02:07:53 +00:00
|
|
|
# have the C.UTF-8 locale installed.
|
2017-04-04 05:52:26 +00:00
|
|
|
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)
|
2015-09-27 17:43:25 +00:00
|
|
|
wormhole = self.find_executable()
|
2018-04-21 07:30:08 +00:00
|
|
|
res = yield getProcessOutputAndValue(
|
|
|
|
wormhole, ["--version"], env=locale_env)
|
2017-04-04 05:52:26 +00:00
|
|
|
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)
|
2015-09-27 17:43:25 +00:00
|
|
|
|
2018-04-21 07:30:08 +00:00
|
|
|
|
2015-09-27 17:43:25 +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
|
2015-09-28 23:31:35 +00:00
|
|
|
# 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
|
|
|
|
2017-05-23 07:45:02 +00:00
|
|
|
@implementer(ITorManager)
|
|
|
|
class FakeTor:
|
rewrite Tor support (py2 only)
The new TorManager adds --launch-tor and --tor-control-port= arguments
(requiring the user to explicitly request a new Tor process, if that's what
they want). The default (when --tor is enabled) looks for a control port in
the usual places (/var/run/tor/control, localhost:9051, localhost:9151), then
falls back to hoping there's a SOCKS port in the usual
place (localhost:9050). (closes #64)
The ssh utilities should now accept the same tor arguments as ordinary
send/receive commands. There are now full tests for TorManager, and basic
tests for how send/receive use it. (closes #97)
Note that Tor is only supported on python2.7 for now, since txsocksx (and
therefore txtorcon) doesn't work on py3. You need to do "pip install
magic-wormhole[tor]" to get Tor support, and that will get you an inscrutable
error on py3 (referencing vcversioner, "install_requires must be a string or
list of strings", and "int object not iterable").
To run tests, you must install with the [dev] extra (to get "mock" and other
libraries). Our setup.py only includes "txtorcon" in the [dev] extra when on
py2, not on py3. Unit tests tolerate the lack of txtorcon (they mock out
everything txtorcon would provide), so they should provide the same coverage
on both py2 and py3.
2017-01-16 03:24:23 +00:00
|
|
|
# use normal endpoints, but record the fact that we were asked
|
|
|
|
def __init__(self):
|
|
|
|
self.endpoints = []
|
2018-04-21 07:30:08 +00:00
|
|
|
|
2020-01-17 02:57:53 +00:00
|
|
|
def stream_via(self, host, port, tls=False):
|
|
|
|
self.endpoints.append((host, port, tls))
|
rewrite Tor support (py2 only)
The new TorManager adds --launch-tor and --tor-control-port= arguments
(requiring the user to explicitly request a new Tor process, if that's what
they want). The default (when --tor is enabled) looks for a control port in
the usual places (/var/run/tor/control, localhost:9051, localhost:9151), then
falls back to hoping there's a SOCKS port in the usual
place (localhost:9050). (closes #64)
The ssh utilities should now accept the same tor arguments as ordinary
send/receive commands. There are now full tests for TorManager, and basic
tests for how send/receive use it. (closes #97)
Note that Tor is only supported on python2.7 for now, since txsocksx (and
therefore txtorcon) doesn't work on py3. You need to do "pip install
magic-wormhole[tor]" to get Tor support, and that will get you an inscrutable
error on py3 (referencing vcversioner, "install_requires must be a string or
list of strings", and "int object not iterable").
To run tests, you must install with the [dev] extra (to get "mock" and other
libraries). Our setup.py only includes "txtorcon" in the [dev] extra when on
py2, not on py3. Unit tests tolerate the lack of txtorcon (they mock out
everything txtorcon would provide), so they should provide the same coverage
on both py2 and py3.
2017-01-16 03:24:23 +00:00
|
|
|
return endpoints.HostnameEndpoint(reactor, host, port)
|
|
|
|
|
2021-01-10 23:02:05 +00:00
|
|
|
def strip_deprecations(stderr, NL):
|
|
|
|
lines = [line
|
|
|
|
for line in stderr.split(NL)
|
|
|
|
if not ("Python 2 is no longer supported" in line or
|
|
|
|
"from cryptography import utils" in line or
|
|
|
|
"support will be dropped in the next release of cryptography" in line
|
|
|
|
)
|
|
|
|
]
|
|
|
|
return NL.join(lines)
|
2018-04-21 07:30:08 +00:00
|
|
|
|
2016-02-17 18:22:31 +00:00
|
|
|
class PregeneratedCode(ServerBase, ScriptsBase, unittest.TestCase):
|
2015-09-27 17:43:25 +00:00
|
|
|
# we need Twisted to run the server, but we run the sender and receiver
|
|
|
|
# with deferToThread()
|
|
|
|
|
2017-04-04 05:52:26 +00:00
|
|
|
@inlineCallbacks
|
2015-09-27 17:43:25 +00:00
|
|
|
def setUp(self):
|
2017-04-04 05:52:26 +00:00
|
|
|
self._env = yield self.is_runnable()
|
|
|
|
yield ServerBase.setUp(self)
|
2015-09-27 17:43:25 +00:00
|
|
|
|
2016-02-17 18:22:31 +00:00
|
|
|
@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,
|
2019-08-18 00:06:22 +00:00
|
|
|
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")
|
rewrite Tor support (py2 only)
The new TorManager adds --launch-tor and --tor-control-port= arguments
(requiring the user to explicitly request a new Tor process, if that's what
they want). The default (when --tor is enabled) looks for a control port in
the usual places (/var/run/tor/control, localhost:9051, localhost:9151), then
falls back to hoping there's a SOCKS port in the usual
place (localhost:9050). (closes #64)
The ssh utilities should now accept the same tor arguments as ordinary
send/receive commands. There are now full tests for TorManager, and basic
tests for how send/receive use it. (closes #97)
Note that Tor is only supported on python2.7 for now, since txsocksx (and
therefore txtorcon) doesn't work on py3. You need to do "pip install
magic-wormhole[tor]" to get Tor support, and that will get you an inscrutable
error on py3 (referencing vcversioner, "install_requires must be a string or
list of strings", and "int object not iterable").
To run tests, you must install with the [dev] extra (to get "mock" and other
libraries). Our setup.py only includes "txtorcon" in the [dev] extra when on
py2, not on py3. Unit tests tolerate the lack of txtorcon (they mock out
everything txtorcon would provide), so they should provide the same coverage
on both py2 and py3.
2017-01-16 03:24:23 +00:00
|
|
|
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-02-17 18:22:31 +00:00
|
|
|
|
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
|
2017-07-10 18:57:51 +00:00
|
|
|
cfg.code = u"1-abc"
|
2016-06-03 22:17:47 +00:00
|
|
|
cfg.stdout = io.StringIO()
|
|
|
|
cfg.stderr = io.StringIO()
|
2019-08-18 00:06:22 +00:00
|
|
|
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)
|
2016-02-17 18:22:31 +00:00
|
|
|
|
2017-04-07 02:17:11 +00:00
|
|
|
if mode in ("text", "slow-text", "slow-sender-text"):
|
2016-06-03 22:17:47 +00:00
|
|
|
send_cfg.text = message
|
2016-02-17 18:22:31 +00:00
|
|
|
|
2017-01-16 22:29:40 +00:00
|
|
|
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
|
2016-02-17 18:22:31 +00:00
|
|
|
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
|
2016-02-17 18:22:31 +00:00
|
|
|
receive_filename = send_filename
|
|
|
|
|
2017-02-05 23:42:49 +00:00
|
|
|
recv_cfg.accept_file = False if mock_accept else True
|
2016-02-17 18:22:31 +00:00
|
|
|
if override_filename:
|
2017-07-23 04:19:43 +00:00
|
|
|
recv_cfg.output_file = receive_filename = u"outfile"
|
2017-02-05 03:27:09 +00:00
|
|
|
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')
|
2016-02-17 18:22:31 +00:00
|
|
|
|
|
|
|
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]
|
|
|
|
|
2017-07-23 04:19:43 +00:00
|
|
|
send_dirname = u"testdir"
|
2018-04-21 07:30:08 +00:00
|
|
|
|
2016-02-17 18:22:31 +00:00
|
|
|
def message(i):
|
|
|
|
return "test message %d\n" % i
|
2018-04-21 07:30:08 +00:00
|
|
|
|
2017-07-23 04:19:43 +00:00
|
|
|
os.mkdir(os.path.join(send_dir, u"middle"))
|
|
|
|
source_dir = os.path.join(send_dir, u"middle", send_dirname)
|
2016-02-17 18:22:31 +00:00
|
|
|
os.mkdir(source_dir)
|
2016-06-03 23:04:12 +00:00
|
|
|
modes = {}
|
2016-02-17 18:22:31 +00:00
|
|
|
for i in range(5):
|
2016-06-03 23:04:12 +00:00
|
|
|
path = os.path.join(source_dir, str(i))
|
|
|
|
with open(path, "w") as f:
|
2016-02-17 18:22:31 +00:00
|
|
|
f.write(message(i))
|
2016-06-03 23:04:12 +00:00
|
|
|
if i == 3:
|
|
|
|
os.chmod(path, 0o755)
|
|
|
|
modes[i] = stat.S_IMODE(os.stat(path).st_mode)
|
2017-07-23 04:19:43 +00:00
|
|
|
send_dirname_arg = os.path.join(u"middle", send_dirname)
|
2016-03-24 15:46:29 +00:00
|
|
|
if addslash:
|
|
|
|
send_dirname_arg += os.sep
|
2016-06-03 22:17:47 +00:00
|
|
|
send_cfg.what = send_dirname_arg
|
2016-02-17 18:22:31 +00:00
|
|
|
receive_dirname = send_dirname
|
|
|
|
|
2017-02-05 23:42:49 +00:00
|
|
|
recv_cfg.accept_file = False if mock_accept else True
|
2016-02-17 18:22:31 +00:00
|
|
|
if override_filename:
|
2017-07-23 04:19:43 +00:00
|
|
|
recv_cfg.output_file = receive_dirname = u"outdir"
|
2017-02-05 03:27:09 +00:00
|
|
|
if overwrite:
|
|
|
|
recv_cfg.output_file = receive_dirname
|
|
|
|
os.mkdir(os.path.join(receive_dir, receive_dirname))
|
2015-11-29 07:40:25 +00:00
|
|
|
|
2016-02-17 20:22:56 +00:00
|
|
|
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]
|
|
|
|
|
2018-02-21 02:14:57 +00:00
|
|
|
# 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,
|
2018-02-21 02:14:57 +00:00
|
|
|
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',
|
2016-07-28 00:52:21 +00:00
|
|
|
'--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,
|
2018-02-21 02:14:57 +00:00
|
|
|
env=env,
|
2016-06-03 22:17:47 +00:00
|
|
|
)
|
|
|
|
|
2016-04-26 00:11:52 +00:00
|
|
|
(send_res, receive_res) = yield gatherResults([send_d, receive_d],
|
|
|
|
True)
|
2016-02-17 20:22:56 +00:00
|
|
|
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]
|
2016-02-27 22:46:38 +00:00
|
|
|
NL = os.linesep
|
2021-01-10 23:02:05 +00:00
|
|
|
send_stderr = strip_deprecations(send_stderr, NL)
|
|
|
|
receive_stderr = strip_deprecations(receive_stderr, NL)
|
2016-04-26 00:11:52 +00:00
|
|
|
self.assertEqual((send_rc, receive_rc), (0, 0),
|
|
|
|
(send_res, receive_res))
|
2016-02-17 20:22:56 +00:00
|
|
|
else:
|
2016-06-03 22:17:47 +00:00
|
|
|
send_cfg.cwd = send_dir
|
|
|
|
recv_cfg.cwd = receive_dir
|
rewrite Tor support (py2 only)
The new TorManager adds --launch-tor and --tor-control-port= arguments
(requiring the user to explicitly request a new Tor process, if that's what
they want). The default (when --tor is enabled) looks for a control port in
the usual places (/var/run/tor/control, localhost:9051, localhost:9151), then
falls back to hoping there's a SOCKS port in the usual
place (localhost:9050). (closes #64)
The ssh utilities should now accept the same tor arguments as ordinary
send/receive commands. There are now full tests for TorManager, and basic
tests for how send/receive use it. (closes #97)
Note that Tor is only supported on python2.7 for now, since txsocksx (and
therefore txtorcon) doesn't work on py3. You need to do "pip install
magic-wormhole[tor]" to get Tor support, and that will get you an inscrutable
error on py3 (referencing vcversioner, "install_requires must be a string or
list of strings", and "int object not iterable").
To run tests, you must install with the [dev] extra (to get "mock" and other
libraries). Our setup.py only includes "txtorcon" in the [dev] extra when on
py2, not on py3. Unit tests tolerate the lack of txtorcon (they mock out
everything txtorcon would provide), so they should provide the same coverage
on both py2 and py3.
2017-01-16 03:24:23 +00:00
|
|
|
|
|
|
|
if fake_tor:
|
|
|
|
send_cfg.tor = True
|
|
|
|
send_cfg.transit_helper = self.transit
|
2017-05-23 07:45:02 +00:00
|
|
|
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:
|
rewrite Tor support (py2 only)
The new TorManager adds --launch-tor and --tor-control-port= arguments
(requiring the user to explicitly request a new Tor process, if that's what
they want). The default (when --tor is enabled) looks for a control port in
the usual places (/var/run/tor/control, localhost:9051, localhost:9151), then
falls back to hoping there's a SOCKS port in the usual
place (localhost:9050). (closes #64)
The ssh utilities should now accept the same tor arguments as ordinary
send/receive commands. There are now full tests for TorManager, and basic
tests for how send/receive use it. (closes #97)
Note that Tor is only supported on python2.7 for now, since txsocksx (and
therefore txtorcon) doesn't work on py3. You need to do "pip install
magic-wormhole[tor]" to get Tor support, and that will get you an inscrutable
error on py3 (referencing vcversioner, "install_requires must be a string or
list of strings", and "int object not iterable").
To run tests, you must install with the [dev] extra (to get "mock" and other
libraries). Our setup.py only includes "txtorcon" in the [dev] extra when on
py2, not on py3. Unit tests tolerate the lack of txtorcon (they mock out
everything txtorcon would provide), so they should provide the same coverage
on both py2 and py3.
2017-01-16 03:24:23 +00:00
|
|
|
send_d = cmd_send.send(send_cfg)
|
|
|
|
|
|
|
|
recv_cfg.tor = True
|
|
|
|
recv_cfg.transit_helper = self.transit
|
2017-05-23 07:45:02 +00:00
|
|
|
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:
|
rewrite Tor support (py2 only)
The new TorManager adds --launch-tor and --tor-control-port= arguments
(requiring the user to explicitly request a new Tor process, if that's what
they want). The default (when --tor is enabled) looks for a control port in
the usual places (/var/run/tor/control, localhost:9051, localhost:9151), then
falls back to hoping there's a SOCKS port in the usual
place (localhost:9050). (closes #64)
The ssh utilities should now accept the same tor arguments as ordinary
send/receive commands. There are now full tests for TorManager, and basic
tests for how send/receive use it. (closes #97)
Note that Tor is only supported on python2.7 for now, since txsocksx (and
therefore txtorcon) doesn't work on py3. You need to do "pip install
magic-wormhole[tor]" to get Tor support, and that will get you an inscrutable
error on py3 (referencing vcversioner, "install_requires must be a string or
list of strings", and "int object not iterable").
To run tests, you must install with the [dev] extra (to get "mock" and other
libraries). Our setup.py only includes "txtorcon" in the [dev] extra when on
py2, not on py3. Unit tests tolerate the lack of txtorcon (they mock out
everything txtorcon would provide), so they should provide the same coverage
on both py2 and py3.
2017-01-16 03:24:23 +00:00
|
|
|
receive_d = cmd_receive.receive(recv_cfg)
|
|
|
|
else:
|
2017-12-19 21:41:05 +00:00
|
|
|
KEY_TIMER = 0 if mode == "slow-sender-text" else 99999
|
2017-05-12 20:12:36 +00:00
|
|
|
rxw = []
|
2017-04-07 02:17:11 +00:00
|
|
|
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)
|
2017-05-12 20:12:36 +00:00
|
|
|
# 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()
|
2016-02-17 20:22:56 +00:00
|
|
|
|
2016-03-02 00:57:57 +00:00
|
|
|
# The sender might fail, leaving the receiver hanging, or vice
|
2016-04-26 00:11:52 +00:00
|
|
|
# versa. Make sure we don't wait on one side exclusively
|
2017-12-19 21:41:05 +00:00
|
|
|
VERIFY_TIMER = 0 if mode == "slow-text" else 99999
|
2017-04-07 02:17:11 +00:00
|
|
|
with mock.patch.object(cmd_receive, "VERIFY_TIMER", VERIFY_TIMER):
|
|
|
|
with mock.patch.object(cmd_send, "VERIFY_TIMER", VERIFY_TIMER):
|
2019-08-18 00:06:22 +00:00
|
|
|
if mock_accept or verify:
|
2018-04-21 07:30:08 +00:00
|
|
|
with mock.patch.object(
|
|
|
|
cmd_receive.six.moves, 'input',
|
2019-08-18 00:06:22 +00:00
|
|
|
return_value='yes') as i:
|
2017-04-07 02:17:11 +00:00
|
|
|
yield gatherResults([send_d, receive_d], True)
|
2019-08-18 00:06:22 +00:00
|
|
|
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)
|
2017-04-07 02:17:11 +00:00
|
|
|
else:
|
|
|
|
yield gatherResults([send_d, receive_d], True)
|
2016-06-18 05:03:58 +00:00
|
|
|
|
rewrite Tor support (py2 only)
The new TorManager adds --launch-tor and --tor-control-port= arguments
(requiring the user to explicitly request a new Tor process, if that's what
they want). The default (when --tor is enabled) looks for a control port in
the usual places (/var/run/tor/control, localhost:9051, localhost:9151), then
falls back to hoping there's a SOCKS port in the usual
place (localhost:9050). (closes #64)
The ssh utilities should now accept the same tor arguments as ordinary
send/receive commands. There are now full tests for TorManager, and basic
tests for how send/receive use it. (closes #97)
Note that Tor is only supported on python2.7 for now, since txsocksx (and
therefore txtorcon) doesn't work on py3. You need to do "pip install
magic-wormhole[tor]" to get Tor support, and that will get you an inscrutable
error on py3 (referencing vcversioner, "install_requires must be a string or
list of strings", and "int object not iterable").
To run tests, you must install with the [dev] extra (to get "mock" and other
libraries). Our setup.py only includes "txtorcon" in the [dev] extra when on
py2, not on py3. Unit tests tolerate the lack of txtorcon (they mock out
everything txtorcon would provide), so they should provide the same coverage
on both py2 and py3.
2017-01-16 03:24:23 +00:00
|
|
|
if fake_tor:
|
2020-01-17 02:57:53 +00:00
|
|
|
expected_endpoints = [("127.0.0.1", self.rdv_ws_port, False)]
|
rewrite Tor support (py2 only)
The new TorManager adds --launch-tor and --tor-control-port= arguments
(requiring the user to explicitly request a new Tor process, if that's what
they want). The default (when --tor is enabled) looks for a control port in
the usual places (/var/run/tor/control, localhost:9051, localhost:9151), then
falls back to hoping there's a SOCKS port in the usual
place (localhost:9050). (closes #64)
The ssh utilities should now accept the same tor arguments as ordinary
send/receive commands. There are now full tests for TorManager, and basic
tests for how send/receive use it. (closes #97)
Note that Tor is only supported on python2.7 for now, since txsocksx (and
therefore txtorcon) doesn't work on py3. You need to do "pip install
magic-wormhole[tor]" to get Tor support, and that will get you an inscrutable
error on py3 (referencing vcversioner, "install_requires must be a string or
list of strings", and "int object not iterable").
To run tests, you must install with the [dev] extra (to get "mock" and other
libraries). Our setup.py only includes "txtorcon" in the [dev] extra when on
py2, not on py3. Unit tests tolerate the lack of txtorcon (they mock out
everything txtorcon would provide), so they should provide the same coverage
on both py2 and py3.
2017-01-16 03:24:23 +00:00
|
|
|
if mode in ("file", "directory"):
|
2020-01-17 02:57:53 +00:00
|
|
|
expected_endpoints.append(("127.0.0.1", self.transitport, False))
|
rewrite Tor support (py2 only)
The new TorManager adds --launch-tor and --tor-control-port= arguments
(requiring the user to explicitly request a new Tor process, if that's what
they want). The default (when --tor is enabled) looks for a control port in
the usual places (/var/run/tor/control, localhost:9051, localhost:9151), then
falls back to hoping there's a SOCKS port in the usual
place (localhost:9050). (closes #64)
The ssh utilities should now accept the same tor arguments as ordinary
send/receive commands. There are now full tests for TorManager, and basic
tests for how send/receive use it. (closes #97)
Note that Tor is only supported on python2.7 for now, since txsocksx (and
therefore txtorcon) doesn't work on py3. You need to do "pip install
magic-wormhole[tor]" to get Tor support, and that will get you an inscrutable
error on py3 (referencing vcversioner, "install_requires must be a string or
list of strings", and "int object not iterable").
To run tests, you must install with the [dev] extra (to get "mock" and other
libraries). Our setup.py only includes "txtorcon" in the [dev] extra when on
py2, not on py3. Unit tests tolerate the lack of txtorcon (they mock out
everything txtorcon would provide), so they should provide the same coverage
on both py2 and py3.
2017-01-16 03:24:23 +00:00
|
|
|
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)])
|
rewrite Tor support (py2 only)
The new TorManager adds --launch-tor and --tor-control-port= arguments
(requiring the user to explicitly request a new Tor process, if that's what
they want). The default (when --tor is enabled) looks for a control port in
the usual places (/var/run/tor/control, localhost:9051, localhost:9151), then
falls back to hoping there's a SOCKS port in the usual
place (localhost:9050). (closes #64)
The ssh utilities should now accept the same tor arguments as ordinary
send/receive commands. There are now full tests for TorManager, and basic
tests for how send/receive use it. (closes #97)
Note that Tor is only supported on python2.7 for now, since txsocksx (and
therefore txtorcon) doesn't work on py3. You need to do "pip install
magic-wormhole[tor]" to get Tor support, and that will get you an inscrutable
error on py3 (referencing vcversioner, "install_requires must be a string or
list of strings", and "int object not iterable").
To run tests, you must install with the [dev] extra (to get "mock" and other
libraries). Our setup.py only includes "txtorcon" in the [dev] extra when on
py2, not on py3. Unit tests tolerate the lack of txtorcon (they mock out
everything txtorcon would provide), so they should provide the same coverage
on both py2 and py3.
2017-01-16 03:24:23 +00:00
|
|
|
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-04-26 00:11:52 +00:00
|
|
|
|
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()
|
2016-02-17 18:22:31 +00:00
|
|
|
|
2016-02-27 22:46:38 +00:00
|
|
|
# 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
|
2016-02-17 18:22:31 +00:00
|
|
|
|
2016-12-24 05:03:32 +00:00
|
|
|
key_established = ""
|
|
|
|
if mode == "slow-text":
|
|
|
|
key_established = "Key established, waiting for confirmation...\n"
|
|
|
|
|
|
|
|
self.assertEqual(send_stdout, "")
|
2016-02-18 01:22:54 +00:00
|
|
|
|
|
|
|
# check sender
|
2016-12-16 09:33:45 +00:00
|
|
|
if mode == "text" or mode == "slow-text":
|
2016-11-17 16:36:00 +00:00
|
|
|
expected = ("Sending text message ({bytes:d} Bytes){NL}"
|
2017-12-14 11:06:27 +00:00
|
|
|
"Wormhole code is: {code}{NL}"
|
|
|
|
"On the other computer, please run:{NL}{NL}"
|
2019-08-18 00:06:22 +00:00
|
|
|
"wormhole receive {verify}{code}{NL}{NL}"
|
2016-12-24 05:03:32 +00:00
|
|
|
"{KE}"
|
2018-04-21 07:30:08 +00:00
|
|
|
"text message sent{NL}").format(
|
|
|
|
bytes=len(message),
|
2019-08-18 00:06:22 +00:00
|
|
|
verify="--verify " if verify else "",
|
2018-04-21 07:30:08 +00:00
|
|
|
code=send_cfg.code,
|
|
|
|
NL=NL,
|
|
|
|
KE=key_established)
|
2016-12-24 05:03:32 +00:00
|
|
|
self.failUnlessEqual(send_stderr, expected)
|
2016-02-17 18:22:31 +00:00
|
|
|
elif mode == "file":
|
2017-07-23 04:19:43 +00:00
|
|
|
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)
|
2017-12-14 11:06:27 +00:00
|
|
|
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)
|
2016-02-17 18:22:31 +00:00
|
|
|
elif mode == "directory":
|
2017-07-23 04:19:43 +00:00
|
|
|
self.failUnlessIn(u"Sending directory", send_stderr)
|
|
|
|
self.failUnlessIn(u"named 'testdir'", send_stderr)
|
2017-12-14 11:06:27 +00:00
|
|
|
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)
|
2016-02-17 18:22:31 +00:00
|
|
|
|
|
|
|
# check receiver
|
2017-04-07 02:17:11 +00:00
|
|
|
if mode in ("text", "slow-text", "slow-sender-text"):
|
2018-04-21 07:30:08 +00:00
|
|
|
self.assertEqual(receive_stdout, message + NL)
|
2017-04-07 02:17:11 +00:00
|
|
|
if mode == "text":
|
2019-08-18 00:06:22 +00:00
|
|
|
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, "")
|
2017-04-07 02:17:11 +00:00
|
|
|
elif mode == "slow-text":
|
|
|
|
self.assertEqual(receive_stderr, key_established)
|
|
|
|
elif mode == "slow-sender-text":
|
|
|
|
self.assertEqual(receive_stderr, "Waiting for sender...\n")
|
2016-02-17 18:22:31 +00:00
|
|
|
elif mode == "file":
|
2016-12-24 05:03:32 +00:00
|
|
|
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)
|
2017-07-23 04:19:43 +00:00
|
|
|
self.failUnlessIn(u"Received file written to ", receive_stderr)
|
2016-02-17 18:22:31 +00:00
|
|
|
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":
|
2016-12-24 05:03:32 +00:00
|
|
|
self.failUnlessEqual(receive_stdout, "")
|
2016-11-09 20:14:01 +00:00
|
|
|
want = (r"Receiving directory \(\d+ \w+\) into: {name}/"
|
2016-02-28 08:52:49 +00:00
|
|
|
.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)
|
2016-02-17 18:22:31 +00:00
|
|
|
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):
|
2016-02-17 18:22:31 +00:00
|
|
|
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))
|
2016-02-18 01:22:54 +00:00
|
|
|
|
2016-02-17 18:22:31 +00:00
|
|
|
def test_text(self):
|
|
|
|
return self._do_test()
|
2018-04-21 07:30:08 +00:00
|
|
|
|
2016-02-17 20:22:56 +00:00
|
|
|
def test_text_subprocess(self):
|
|
|
|
return self._do_test(as_subprocess=True)
|
2018-04-21 07:30:08 +00:00
|
|
|
|
rewrite Tor support (py2 only)
The new TorManager adds --launch-tor and --tor-control-port= arguments
(requiring the user to explicitly request a new Tor process, if that's what
they want). The default (when --tor is enabled) looks for a control port in
the usual places (/var/run/tor/control, localhost:9051, localhost:9151), then
falls back to hoping there's a SOCKS port in the usual
place (localhost:9050). (closes #64)
The ssh utilities should now accept the same tor arguments as ordinary
send/receive commands. There are now full tests for TorManager, and basic
tests for how send/receive use it. (closes #97)
Note that Tor is only supported on python2.7 for now, since txsocksx (and
therefore txtorcon) doesn't work on py3. You need to do "pip install
magic-wormhole[tor]" to get Tor support, and that will get you an inscrutable
error on py3 (referencing vcversioner, "install_requires must be a string or
list of strings", and "int object not iterable").
To run tests, you must install with the [dev] extra (to get "mock" and other
libraries). Our setup.py only includes "txtorcon" in the [dev] extra when on
py2, not on py3. Unit tests tolerate the lack of txtorcon (they mock out
everything txtorcon would provide), so they should provide the same coverage
on both py2 and py3.
2017-01-16 03:24:23 +00:00
|
|
|
def test_text_tor(self):
|
|
|
|
return self._do_test(fake_tor=True)
|
2016-02-17 18:22:31 +00:00
|
|
|
|
2019-08-18 00:06:22 +00:00
|
|
|
def test_text_verify(self):
|
|
|
|
return self._do_test(verify=True)
|
|
|
|
|
2016-02-17 18:22:31 +00:00
|
|
|
def test_file(self):
|
|
|
|
return self._do_test(mode="file")
|
2018-04-21 07:30:08 +00:00
|
|
|
|
2016-02-17 18:22:31 +00:00
|
|
|
def test_file_override(self):
|
|
|
|
return self._do_test(mode="file", override_filename=True)
|
2018-04-21 07:30:08 +00:00
|
|
|
|
2017-02-05 03:27:09 +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
|
|
|
|
rewrite Tor support (py2 only)
The new TorManager adds --launch-tor and --tor-control-port= arguments
(requiring the user to explicitly request a new Tor process, if that's what
they want). The default (when --tor is enabled) looks for a control port in
the usual places (/var/run/tor/control, localhost:9051, localhost:9151), then
falls back to hoping there's a SOCKS port in the usual
place (localhost:9050). (closes #64)
The ssh utilities should now accept the same tor arguments as ordinary
send/receive commands. There are now full tests for TorManager, and basic
tests for how send/receive use it. (closes #97)
Note that Tor is only supported on python2.7 for now, since txsocksx (and
therefore txtorcon) doesn't work on py3. You need to do "pip install
magic-wormhole[tor]" to get Tor support, and that will get you an inscrutable
error on py3 (referencing vcversioner, "install_requires must be a string or
list of strings", and "int object not iterable").
To run tests, you must install with the [dev] extra (to get "mock" and other
libraries). Our setup.py only includes "txtorcon" in the [dev] extra when on
py2, not on py3. Unit tests tolerate the lack of txtorcon (they mock out
everything txtorcon would provide), so they should provide the same coverage
on both py2 and py3.
2017-01-16 03:24:23 +00:00
|
|
|
def test_file_tor(self):
|
|
|
|
return self._do_test(mode="file", fake_tor=True)
|
2018-04-21 07:30:08 +00:00
|
|
|
|
2017-01-16 22:29:40 +00:00
|
|
|
def test_empty_file(self):
|
|
|
|
return self._do_test(mode="empty-file")
|
2016-02-17 18:22:31 +00:00
|
|
|
|
|
|
|
def test_directory(self):
|
|
|
|
return self._do_test(mode="directory")
|
2018-04-21 07:30:08 +00:00
|
|
|
|
2016-03-24 15:46:29 +00:00
|
|
|
def test_directory_addslash(self):
|
|
|
|
return self._do_test(mode="directory", addslash=True)
|
2018-04-21 07:30:08 +00:00
|
|
|
|
2016-02-17 18:22:31 +00:00
|
|
|
def test_directory_override(self):
|
|
|
|
return self._do_test(mode="directory", override_filename=True)
|
2018-04-21 07:30:08 +00:00
|
|
|
|
2017-02-05 03:27:09 +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)
|
2016-04-25 23:49:04 +00:00
|
|
|
|
2016-12-16 09:33:45 +00:00
|
|
|
def test_slow_text(self):
|
|
|
|
return self._do_test(mode="slow-text")
|
2018-04-21 07:30:08 +00:00
|
|
|
|
2017-04-07 02:17:11 +00:00
|
|
|
def test_slow_sender_text(self):
|
|
|
|
return self._do_test(mode="slow-sender-text")
|
2016-06-18 05:03:58 +00:00
|
|
|
|
2016-05-26 05:44:18 +00:00
|
|
|
@inlineCallbacks
|
2016-12-10 23:30:51 +00:00
|
|
|
def _do_test_fail(self, mode, failmode):
|
|
|
|
assert mode in ("file", "directory")
|
|
|
|
assert failmode in ("noclobber", "toobig")
|
2016-07-15 04:22:01 +00:00
|
|
|
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
|
2017-07-10 18:57:51 +00:00
|
|
|
cfg.code = u"1-abc"
|
2016-06-03 22:17:47 +00:00
|
|
|
cfg.stdout = io.StringIO()
|
|
|
|
cfg.stderr = io.StringIO()
|
|
|
|
|
2016-05-26 05:44:18 +00:00
|
|
|
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
|
2016-05-26 05:44:18 +00:00
|
|
|
|
2016-12-10 23:30:51 +00:00
|
|
|
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:
|
2016-12-10 23:30:51 +00:00
|
|
|
f.write(message)
|
2016-12-11 00:14:54 +00:00
|
|
|
size = os.stat(fn).st_size
|
2016-12-10 23:30:51 +00:00
|
|
|
|
|
|
|
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-05-26 05:44:18 +00:00
|
|
|
|
2016-12-11 00:14:54 +00:00
|
|
|
size = 0
|
2016-12-10 23:30:51 +00:00
|
|
|
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
|
2016-12-10 23:30:51 +00:00
|
|
|
|
|
|
|
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-05-26 05:44:18 +00:00
|
|
|
|
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)
|
2016-05-26 05:44:18 +00:00
|
|
|
|
2016-12-10 23:30:51 +00:00
|
|
|
# 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):
|
2016-12-10 23:30:51 +00:00
|
|
|
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")
|
2016-12-10 23:30:51 +00:00
|
|
|
f = yield self.assertFailure(receive_d, TransferError)
|
|
|
|
self.assertEqual(str(f), "transfer rejected")
|
2016-05-26 05:44:18 +00:00
|
|
|
|
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()
|
2016-05-26 05:44:18 +00:00
|
|
|
|
|
|
|
# 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
|
2016-05-26 05:44:18 +00:00
|
|
|
|
2016-12-24 05:03:32 +00:00
|
|
|
self.assertEqual(send_stdout, "")
|
|
|
|
self.assertEqual(receive_stdout, "")
|
2016-05-26 05:44:18 +00:00
|
|
|
|
|
|
|
# check sender
|
2016-12-10 23:30:51 +00:00
|
|
|
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)
|
2017-12-14 11:06:27 +00:00
|
|
|
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)
|
2016-12-10 23:30:51 +00:00
|
|
|
elif mode == "directory":
|
2016-12-24 05:03:32 +00:00
|
|
|
self.failUnlessIn("Sending directory", send_stderr)
|
|
|
|
self.failUnlessIn("named 'testdir'", send_stderr)
|
2017-12-14 11:06:27 +00:00
|
|
|
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)
|
2016-05-26 05:44:18 +00:00
|
|
|
|
|
|
|
# check receiver
|
2016-12-10 23:30:51 +00:00
|
|
|
if mode == "file":
|
2016-12-24 05:03:32 +00:00
|
|
|
self.failIfIn("Received file written to ", receive_stderr)
|
2016-12-10 23:30:51 +00:00
|
|
|
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)
|
2016-12-10 23:30:51 +00:00
|
|
|
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)
|
2016-12-10 23:30:51 +00:00
|
|
|
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}/"
|
2016-12-10 23:30:51 +00:00
|
|
|
# .format(name=receive_name))
|
2018-04-21 07:30:08 +00:00
|
|
|
# self.failUnless(re.search(want, receive_stderr),
|
2016-12-24 05:03:32 +00:00
|
|
|
# (want, receive_stderr))
|
2016-12-10 23:30:51 +00:00
|
|
|
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)
|
2016-12-10 23:30:51 +00:00
|
|
|
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)
|
2016-12-10 23:30:51 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
2016-12-10 23:30:51 +00:00
|
|
|
def test_fail_directory_noclobber(self):
|
|
|
|
return self._do_test_fail("directory", "noclobber")
|
2018-04-21 07:30:08 +00:00
|
|
|
|
2016-12-10 23:30:51 +00:00
|
|
|
def test_fail_file_toobig(self):
|
|
|
|
return self._do_test_fail("file", "toobig")
|
2018-04-21 07:30:08 +00:00
|
|
|
|
2016-12-10 23:30:51 +00:00
|
|
|
def test_fail_directory_toobig(self):
|
|
|
|
return self._do_test_fail("directory", "toobig")
|
2016-05-26 05:44:18 +00:00
|
|
|
|
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(
|
2020-04-12 18:47:28 +00:00
|
|
|
bytes=len(message), 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
|
|
|
|
2016-05-26 22:37:24 +00:00
|
|
|
class NotWelcome(ServerBase, unittest.TestCase):
|
2018-02-21 08:38:19 +00:00
|
|
|
@inlineCallbacks
|
2016-05-26 22:37:24 +00:00
|
|
|
def setUp(self):
|
2018-02-21 08:38:19 +00:00
|
|
|
yield self._setup_relay(error="please upgrade XYZ")
|
2016-07-15 04:22:01 +00:00
|
|
|
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()
|
2016-05-26 22:37:24 +00:00
|
|
|
|
|
|
|
@inlineCallbacks
|
|
|
|
def test_sender(self):
|
2016-06-03 22:17:47 +00:00
|
|
|
self.cfg.text = "hi"
|
2017-07-10 18:57:51 +00:00
|
|
|
self.cfg.code = u"1-abc"
|
2016-06-03 22:17:47 +00:00
|
|
|
|
|
|
|
send_d = cmd_send.send(self.cfg)
|
2016-05-26 22:37:24 +00:00
|
|
|
f = yield self.assertFailure(send_d, WelcomeError)
|
|
|
|
self.assertEqual(str(f), "please upgrade XYZ")
|
|
|
|
|
|
|
|
@inlineCallbacks
|
|
|
|
def test_receiver(self):
|
2017-07-10 18:57:51 +00:00
|
|
|
self.cfg.code = u"1-abc"
|
2016-06-03 22:17:47 +00:00
|
|
|
|
|
|
|
receive_d = cmd_receive.receive(self.cfg)
|
2016-05-26 22:37:24 +00:00
|
|
|
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
|
|
|
|
2017-04-16 16:25:37 +00:00
|
|
|
class NoServer(ServerBase, unittest.TestCase):
|
|
|
|
@inlineCallbacks
|
|
|
|
def setUp(self):
|
2018-02-21 08:38:19 +00:00
|
|
|
yield self._setup_relay(None)
|
2017-04-16 16:25:37 +00:00
|
|
|
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"
|
2017-07-10 18:57:51 +00:00
|
|
|
cfg.code = u"1-abc"
|
2017-04-16 16:25:37 +00:00
|
|
|
|
|
|
|
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()
|
|
|
|
|
2017-07-10 18:57:51 +00:00
|
|
|
cfg.code = u"1-abc"
|
2017-04-16 16:25:37 +00:00
|
|
|
|
|
|
|
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):
|
2017-05-12 20:12:36 +00:00
|
|
|
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()
|
2017-05-12 20:12:36 +00:00
|
|
|
return cfg
|
2016-06-03 22:17:47 +00:00
|
|
|
|
2016-04-25 23:49:04 +00:00
|
|
|
@inlineCallbacks
|
2016-06-03 22:17:47 +00:00
|
|
|
@mock.patch('sys.stdout')
|
|
|
|
def test_text(self, stdout):
|
2016-04-25 23:49:04 +00:00
|
|
|
# the rendezvous channel should be deleted after success
|
2017-05-12 20:12:36 +00:00
|
|
|
cfg = self.make_config()
|
|
|
|
cfg.text = "hello"
|
2017-07-10 18:57:51 +00:00
|
|
|
cfg.code = u"1-abc"
|
2016-04-25 23:49:04 +00:00
|
|
|
|
2017-05-12 20:12:36 +00:00
|
|
|
send_d = cmd_send.send(cfg)
|
|
|
|
receive_d = cmd_receive.receive(cfg)
|
2016-06-03 22:17:47 +00:00
|
|
|
|
2016-04-25 23:49:04 +00:00
|
|
|
yield send_d
|
|
|
|
yield receive_d
|
|
|
|
|
2016-05-24 07:00:44 +00:00
|
|
|
cids = self._rendezvous.get_app(cmd_send.APPID).get_nameplate_ids()
|
2016-04-25 23:49:04 +00:00
|
|
|
self.assertEqual(len(cids), 0)
|
|
|
|
|
|
|
|
@inlineCallbacks
|
|
|
|
def test_text_wrong_password(self):
|
|
|
|
# if the password was wrong, the rendezvous channel should still be
|
|
|
|
# deleted
|
2017-05-12 20:12:36 +00:00
|
|
|
send_cfg = self.make_config()
|
|
|
|
send_cfg.text = "secret message"
|
2017-07-10 18:57:51 +00:00
|
|
|
send_cfg.code = u"1-abc"
|
2017-05-12 20:12:36 +00:00
|
|
|
send_d = cmd_send.send(send_cfg)
|
2016-06-03 22:17:47 +00:00
|
|
|
|
2017-05-12 20:12:36 +00:00
|
|
|
rx_cfg = self.make_config()
|
2017-07-10 18:57:51 +00:00
|
|
|
rx_cfg.code = u"1-WRONG"
|
2017-05-12 20:12:36 +00:00
|
|
|
receive_d = cmd_receive.receive(rx_cfg)
|
2016-04-25 23:49:04 +00:00
|
|
|
|
|
|
|
# both sides should be capable of detecting the mismatch
|
|
|
|
yield self.assertFailure(send_d, WrongPasswordError)
|
|
|
|
yield self.assertFailure(receive_d, WrongPasswordError)
|
|
|
|
|
2016-05-24 07:00:44 +00:00
|
|
|
cids = self._rendezvous.get_app(cmd_send.APPID).get_nameplate_ids()
|
2016-04-25 23:49:04 +00:00
|
|
|
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()
|
2017-07-10 18:57:51 +00:00
|
|
|
args.relay_url = u""
|
2017-04-16 20:07:05 +00:00
|
|
|
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))
|
2016-12-22 20:44:13 +00:00
|
|
|
|
2018-04-21 07:30:08 +00:00
|
|
|
|
2016-12-22 20:44:13 +00:00
|
|
|
class AppID(ServerBase, unittest.TestCase):
|
2018-02-21 08:38:19 +00:00
|
|
|
@inlineCallbacks
|
2016-12-22 20:44:13 +00:00
|
|
|
def setUp(self):
|
2018-02-21 08:38:19 +00:00
|
|
|
yield super(AppID, self).setUp()
|
2016-12-22 20:44:13 +00:00
|
|
|
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"
|
2017-07-10 18:57:51 +00:00
|
|
|
self.cfg.appid = u"appid2"
|
|
|
|
self.cfg.code = u"1-abc"
|
2016-12-22 20:44:13 +00:00
|
|
|
|
|
|
|
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()
|
2016-12-22 20:44:13 +00:00
|
|
|
self.assertEqual(len(used), 1, used)
|
|
|
|
self.assertEqual(used[0]["app_id"], u"appid2")
|
2017-04-06 19:29:58 +00:00
|
|
|
|
2018-04-21 07:30:08 +00:00
|
|
|
|
2017-04-06 19:29:58 +00:00
|
|
|
class Welcome(unittest.TestCase):
|
2017-05-12 20:12:36 +00:00
|
|
|
def do(self, welcome_message, my_version="2.0"):
|
2017-04-06 19:29:58 +00:00
|
|
|
stderr = io.StringIO()
|
2017-05-12 20:12:36 +00:00
|
|
|
welcome.handle_welcome(welcome_message, "url", my_version, stderr)
|
2017-04-06 19:29:58 +00:00
|
|
|
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"
|
2017-04-06 19:29:58 +00:00
|
|
|
"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")
|
2017-04-06 19:29:58 +00:00
|
|
|
self.assertEqual(stderr, "")
|
|
|
|
|
|
|
|
def test_motd(self):
|
|
|
|
stderr = self.do({"motd": "hello"})
|
|
|
|
self.assertEqual(stderr, "Server (at url) says:\n hello\n")
|
2017-04-23 19:56:19 +00:00
|
|
|
|
2018-04-21 07:30:08 +00:00
|
|
|
|
2017-04-23 19:56:19 +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
|
|
|
|
2017-04-23 19:56:19 +00:00
|
|
|
def fake():
|
|
|
|
called.append(1)
|
2018-04-21 07:30:08 +00:00
|
|
|
|
2017-04-23 19:56:19 +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
|
|
|
|
2017-04-23 19:56:19 +00:00
|
|
|
def fake():
|
|
|
|
pass
|
2018-04-21 07:30:08 +00:00
|
|
|
|
2017-04-23 19:56:19 +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
|
|
|
|
2017-04-23 19:56:19 +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"
|
2017-04-23 19:56:19 +00:00
|
|
|
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
|
|
|
|
2017-04-23 19:56:19 +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")
|
2017-04-23 19:56:19 +00:00
|
|
|
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
|
|
|
|
2017-04-23 19:56:19 +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)
|
2017-04-23 19:56:19 +00:00
|
|
|
expected = "TransferError: abcd\n"
|
|
|
|
self.assertEqual(cfg.stderr.getvalue(), expected)
|
|
|
|
|
2017-04-16 16:25:37 +00:00
|
|
|
@inlineCallbacks
|
|
|
|
def test_server_connection_error(self):
|
|
|
|
cfg = config("send")
|
|
|
|
cfg.stderr = io.StringIO()
|
2018-04-21 07:30:08 +00:00
|
|
|
|
2017-04-16 16:25:37 +00:00
|
|
|
def fake():
|
2017-06-26 16:28:25 +00:00
|
|
|
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"
|
2017-06-26 16:28:25 +00:00
|
|
|
expected += "(relay URL was URL)\n"
|
2017-04-16 16:25:37 +00:00
|
|
|
expected += "abcd\n"
|
|
|
|
self.assertEqual(cfg.stderr.getvalue(), expected)
|
|
|
|
|
2017-04-23 19:56:19 +00:00
|
|
|
@inlineCallbacks
|
|
|
|
def test_other_error(self):
|
|
|
|
cfg = config("send")
|
|
|
|
cfg.stderr = io.StringIO()
|
2018-04-21 07:30:08 +00:00
|
|
|
|
2017-04-23 19:56:19 +00:00
|
|
|
def fake():
|
|
|
|
raise ValueError("abcd")
|
2018-04-21 07:30:08 +00:00
|
|
|
|
2017-04-23 19:56:19 +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
|
|
|
|
2017-04-23 19:56:19 +00:00
|
|
|
def mock_print(file):
|
2017-07-10 18:57:51 +00:00
|
|
|
file.write(u"<TRACEBACK>\n")
|
2018-04-21 07:30:08 +00:00
|
|
|
|
2017-04-23 19:56:19 +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)
|
2017-04-23 19:56:19 +00:00
|
|
|
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)
|