mirror of
https://github.com/tahoe-lafs/tahoe-lafs.git
synced 2025-06-01 15:20:55 +00:00
Merge branch '3716.allmydata-scripts-python-3-part-1' into 3718.allmydata-scripts-python-3-part-2
This commit is contained in:
commit
43138d16d1
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
@ -18,6 +18,7 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
os:
|
os:
|
||||||
|
- windows-latest
|
||||||
- macos-latest
|
- macos-latest
|
||||||
- ubuntu-latest
|
- ubuntu-latest
|
||||||
python-version:
|
python-version:
|
||||||
@ -26,11 +27,6 @@ jobs:
|
|||||||
- 3.7
|
- 3.7
|
||||||
- 3.8
|
- 3.8
|
||||||
- 3.9
|
- 3.9
|
||||||
include:
|
|
||||||
# For now we're only doing Windows on 2.7, will be fixed in
|
|
||||||
# https://tahoe-lafs.org/trac/tahoe-lafs/ticket/3701
|
|
||||||
- os: windows-latest
|
|
||||||
python-version: 2.7
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
# See https://github.com/actions/checkout. A fetch-depth of 0
|
# See https://github.com/actions/checkout. A fetch-depth of 0
|
||||||
|
@ -72,7 +72,7 @@ You can find the full Tahoe-LAFS documentation at our `documentation site <http:
|
|||||||
|
|
||||||
Get involved with the Tahoe-LAFS community:
|
Get involved with the Tahoe-LAFS community:
|
||||||
|
|
||||||
- Chat with Tahoe-LAFS developers at #tahoe-lafs chat on irc.freenode.net or `Slack <https://join.slack.com/t/tahoe-lafs/shared_invite/zt-jqfj12r5-ZZ5z3RvHnubKVADpP~JINQ>`__.
|
- Chat with Tahoe-LAFS developers at ``#tahoe-lafs`` channel on `libera.chat <https://libera.chat/>`__ IRC network or `Slack <https://join.slack.com/t/tahoe-lafs/shared_invite/zt-jqfj12r5-ZZ5z3RvHnubKVADpP~JINQ>`__.
|
||||||
|
|
||||||
- Join our `weekly conference calls <https://www.tahoe-lafs.org/trac/tahoe-lafs/wiki/WeeklyMeeting>`__ with core developers and interested community members.
|
- Join our `weekly conference calls <https://www.tahoe-lafs.org/trac/tahoe-lafs/wiki/WeeklyMeeting>`__ with core developers and interested community members.
|
||||||
|
|
||||||
|
@ -514,10 +514,10 @@ Command Examples
|
|||||||
the pattern will be matched against any level of the directory tree;
|
the pattern will be matched against any level of the directory tree;
|
||||||
it's still impossible to specify absolute path exclusions.
|
it's still impossible to specify absolute path exclusions.
|
||||||
|
|
||||||
``tahoe backup --exclude-from=/path/to/filename ~ work:backups``
|
``tahoe backup --exclude-from-utf-8=/path/to/filename ~ work:backups``
|
||||||
|
|
||||||
``--exclude-from`` is similar to ``--exclude``, but reads exclusion
|
``--exclude-from-utf-8`` is similar to ``--exclude``, but reads exclusion
|
||||||
patterns from ``/path/to/filename``, one per line.
|
patterns from a UTF-8-encoded ``/path/to/filename``, one per line.
|
||||||
|
|
||||||
``tahoe backup --exclude-vcs ~ work:backups``
|
``tahoe backup --exclude-vcs ~ work:backups``
|
||||||
|
|
||||||
|
@ -235,7 +235,7 @@ Socialize
|
|||||||
=========
|
=========
|
||||||
|
|
||||||
You can chat with other users of and hackers of this software on the
|
You can chat with other users of and hackers of this software on the
|
||||||
#tahoe-lafs IRC channel at ``irc.freenode.net``, or on the `tahoe-dev mailing
|
#tahoe-lafs IRC channel at ``irc.libera.chat``, or on the `tahoe-dev mailing
|
||||||
list`_.
|
list`_.
|
||||||
|
|
||||||
.. _tahoe-dev mailing list: https://tahoe-lafs.org/cgi-bin/mailman/listinfo/tahoe-dev
|
.. _tahoe-dev mailing list: https://tahoe-lafs.org/cgi-bin/mailman/listinfo/tahoe-dev
|
||||||
|
64
integration/test_get_put.py
Normal file
64
integration/test_get_put.py
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
"""
|
||||||
|
Integration tests for getting and putting files, including reading from stdin
|
||||||
|
and stdout.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from subprocess import Popen, PIPE
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from .util import run_in_thread, cli
|
||||||
|
|
||||||
|
DATA = b"abc123 this is not utf-8 decodable \xff\x00\x33 \x11"
|
||||||
|
try:
|
||||||
|
DATA.decode("utf-8")
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
pass # great, what we want
|
||||||
|
else:
|
||||||
|
raise ValueError("BUG, the DATA string was decoded from UTF-8")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def get_put_alias(alice):
|
||||||
|
cli(alice, "create-alias", "getput")
|
||||||
|
|
||||||
|
|
||||||
|
def read_bytes(path):
|
||||||
|
with open(path, "rb") as f:
|
||||||
|
return f.read()
|
||||||
|
|
||||||
|
|
||||||
|
@run_in_thread
|
||||||
|
def test_put_from_stdin(alice, get_put_alias, tmpdir):
|
||||||
|
"""
|
||||||
|
It's possible to upload a file via `tahoe put`'s STDIN, and then download
|
||||||
|
it to a file.
|
||||||
|
"""
|
||||||
|
tempfile = str(tmpdir.join("file"))
|
||||||
|
p = Popen(
|
||||||
|
["tahoe", "--node-directory", alice.node_dir, "put", "-", "getput:fromstdin"],
|
||||||
|
stdin=PIPE
|
||||||
|
)
|
||||||
|
p.stdin.write(DATA)
|
||||||
|
p.stdin.close()
|
||||||
|
assert p.wait() == 0
|
||||||
|
|
||||||
|
cli(alice, "get", "getput:fromstdin", tempfile)
|
||||||
|
assert read_bytes(tempfile) == DATA
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_to_stdout(alice, get_put_alias, tmpdir):
|
||||||
|
"""
|
||||||
|
It's possible to upload a file, and then download it to stdout.
|
||||||
|
"""
|
||||||
|
tempfile = tmpdir.join("file")
|
||||||
|
with tempfile.open("wb") as f:
|
||||||
|
f.write(DATA)
|
||||||
|
cli(alice, "put", str(tempfile), "getput:tostdout")
|
||||||
|
|
||||||
|
p = Popen(
|
||||||
|
["tahoe", "--node-directory", alice.node_dir, "get", "getput:tostdout", "-"],
|
||||||
|
stdout=PIPE
|
||||||
|
)
|
||||||
|
assert p.stdout.read() == DATA
|
||||||
|
assert p.wait() == 0
|
0
newsfragments/3701.minor
Normal file
0
newsfragments/3701.minor
Normal file
0
newsfragments/3714.minor
Normal file
0
newsfragments/3714.minor
Normal file
0
newsfragments/3715.minor
Normal file
0
newsfragments/3715.minor
Normal file
1
newsfragments/3716.incompat
Normal file
1
newsfragments/3716.incompat
Normal file
@ -0,0 +1 @@
|
|||||||
|
tahoe backup's --exclude-from has been renamed to --exclude-from-utf-8, and correspondingly requires the file to be UTF-8 encoded.
|
1
newsfragments/3721.documentation
Normal file
1
newsfragments/3721.documentation
Normal file
@ -0,0 +1 @@
|
|||||||
|
Our IRC channel, #tahoe-lafs, has been moved to irc.libera.chat.
|
@ -357,12 +357,12 @@ class BackupOptions(FileStoreOptions):
|
|||||||
exclude = self['exclude']
|
exclude = self['exclude']
|
||||||
exclude.add(g)
|
exclude.add(g)
|
||||||
|
|
||||||
def opt_exclude_from(self, filepath):
|
def opt_exclude_from_utf_8(self, filepath):
|
||||||
"""Ignore file matching glob patterns listed in file, one per
|
"""Ignore file matching glob patterns listed in file, one per
|
||||||
line. The file is assumed to be in the argv encoding."""
|
line. The file is assumed to be in the argv encoding."""
|
||||||
abs_filepath = argv_to_abspath(filepath)
|
abs_filepath = argv_to_abspath(filepath)
|
||||||
try:
|
try:
|
||||||
exclude_file = open(abs_filepath)
|
exclude_file = open(abs_filepath, "r", encoding="utf-8")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise BackupConfigurationError('Error opening exclude file %s. (Error: %s)' % (
|
raise BackupConfigurationError('Error opening exclude file %s. (Error: %s)' % (
|
||||||
quote_local_unicode_path(abs_filepath), e))
|
quote_local_unicode_path(abs_filepath), e))
|
||||||
|
@ -701,6 +701,8 @@ class Copier(object):
|
|||||||
|
|
||||||
|
|
||||||
def need_to_copy_bytes(self, source, target):
|
def need_to_copy_bytes(self, source, target):
|
||||||
|
# This should likley be a method call! but enabling that triggers
|
||||||
|
# additional bugs. https://tahoe-lafs.org/trac/tahoe-lafs/ticket/3719
|
||||||
if source.need_to_copy_bytes:
|
if source.need_to_copy_bytes:
|
||||||
# mutable tahoe files, and local files
|
# mutable tahoe files, and local files
|
||||||
return True
|
return True
|
||||||
|
@ -10,7 +10,6 @@ from future.utils import PY2
|
|||||||
if PY2:
|
if PY2:
|
||||||
from future.builtins import filter, map, zip, ascii, chr, hex, input, next, oct, open, pow, round, super, bytes, dict, list, object, range, str, max, min # noqa: F401
|
from future.builtins import filter, map, zip, ascii, chr, hex, input, next, oct, open, pow, round, super, bytes, dict, list, object, range, str, max, min # noqa: F401
|
||||||
|
|
||||||
|
|
||||||
import os.path
|
import os.path
|
||||||
from six.moves import cStringIO as StringIO
|
from six.moves import cStringIO as StringIO
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
@ -354,14 +353,14 @@ class Backup(GridTestMixin, CLITestMixin, StallMixin, unittest.TestCase):
|
|||||||
exclusion_string = "_darcs\n*py\n.svn"
|
exclusion_string = "_darcs\n*py\n.svn"
|
||||||
excl_filepath = os.path.join(basedir, 'exclusion')
|
excl_filepath = os.path.join(basedir, 'exclusion')
|
||||||
fileutil.write(excl_filepath, exclusion_string)
|
fileutil.write(excl_filepath, exclusion_string)
|
||||||
backup_options = parse(['--exclude-from', excl_filepath, 'from', 'to'])
|
backup_options = parse(['--exclude-from-utf-8', excl_filepath, 'from', 'to'])
|
||||||
filtered = list(backup_options.filter_listdir(subdir_listdir))
|
filtered = list(backup_options.filter_listdir(subdir_listdir))
|
||||||
self._check_filtering(filtered, subdir_listdir, (u'another_doc.lyx', u'CVS'),
|
self._check_filtering(filtered, subdir_listdir, (u'another_doc.lyx', u'CVS'),
|
||||||
(u'.svn', u'_darcs', u'run_snake_run.py'))
|
(u'.svn', u'_darcs', u'run_snake_run.py'))
|
||||||
# test BackupConfigurationError
|
# test BackupConfigurationError
|
||||||
self.failUnlessRaises(cli.BackupConfigurationError,
|
self.failUnlessRaises(cli.BackupConfigurationError,
|
||||||
parse,
|
parse,
|
||||||
['--exclude-from', excl_filepath + '.no', 'from', 'to'])
|
['--exclude-from-utf-8', excl_filepath + '.no', 'from', 'to'])
|
||||||
|
|
||||||
# test that an iterator works too
|
# test that an iterator works too
|
||||||
backup_options = parse(['--exclude', '*lyx', 'from', 'to'])
|
backup_options = parse(['--exclude', '*lyx', 'from', 'to'])
|
||||||
@ -372,7 +371,9 @@ class Backup(GridTestMixin, CLITestMixin, StallMixin, unittest.TestCase):
|
|||||||
def test_exclude_options_unicode(self):
|
def test_exclude_options_unicode(self):
|
||||||
nice_doc = u"nice_d\u00F8c.lyx"
|
nice_doc = u"nice_d\u00F8c.lyx"
|
||||||
try:
|
try:
|
||||||
doc_pattern_arg = u"*d\u00F8c*".encode(get_io_encoding())
|
doc_pattern_arg_unicode = doc_pattern_arg = u"*d\u00F8c*"
|
||||||
|
if PY2:
|
||||||
|
doc_pattern_arg = doc_pattern_arg.encode(get_io_encoding())
|
||||||
except UnicodeEncodeError:
|
except UnicodeEncodeError:
|
||||||
raise unittest.SkipTest("A non-ASCII command argument could not be encoded on this platform.")
|
raise unittest.SkipTest("A non-ASCII command argument could not be encoded on this platform.")
|
||||||
|
|
||||||
@ -394,10 +395,10 @@ class Backup(GridTestMixin, CLITestMixin, StallMixin, unittest.TestCase):
|
|||||||
self._check_filtering(filtered, root_listdir, (u'_darcs', u'subdir'),
|
self._check_filtering(filtered, root_listdir, (u'_darcs', u'subdir'),
|
||||||
(nice_doc, u'lib.a'))
|
(nice_doc, u'lib.a'))
|
||||||
# read exclude patterns from file
|
# read exclude patterns from file
|
||||||
exclusion_string = doc_pattern_arg + b"\nlib.?"
|
exclusion_string = (doc_pattern_arg_unicode + "\nlib.?").encode("utf-8")
|
||||||
excl_filepath = os.path.join(basedir, 'exclusion')
|
excl_filepath = os.path.join(basedir, 'exclusion')
|
||||||
fileutil.write(excl_filepath, exclusion_string)
|
fileutil.write(excl_filepath, exclusion_string)
|
||||||
backup_options = parse(['--exclude-from', excl_filepath, 'from', 'to'])
|
backup_options = parse(['--exclude-from-utf-8', excl_filepath, 'from', 'to'])
|
||||||
filtered = list(backup_options.filter_listdir(root_listdir))
|
filtered = list(backup_options.filter_listdir(root_listdir))
|
||||||
self._check_filtering(filtered, root_listdir, (u'_darcs', u'subdir'),
|
self._check_filtering(filtered, root_listdir, (u'_darcs', u'subdir'),
|
||||||
(nice_doc, u'lib.a'))
|
(nice_doc, u'lib.a'))
|
||||||
@ -420,20 +421,20 @@ class Backup(GridTestMixin, CLITestMixin, StallMixin, unittest.TestCase):
|
|||||||
ns = Namespace()
|
ns = Namespace()
|
||||||
ns.called = False
|
ns.called = False
|
||||||
original_open = open
|
original_open = open
|
||||||
def call_file(name, *args):
|
def call_file(name, *args, **kwargs):
|
||||||
if name.endswith("excludes.dummy"):
|
if name.endswith("excludes.dummy"):
|
||||||
ns.called = True
|
ns.called = True
|
||||||
self.failUnlessEqual(name, abspath_expanduser_unicode(exclude_file))
|
self.failUnlessEqual(name, abspath_expanduser_unicode(exclude_file))
|
||||||
return StringIO()
|
return StringIO()
|
||||||
else:
|
else:
|
||||||
return original_open(name, *args)
|
return original_open(name, *args, **kwargs)
|
||||||
|
|
||||||
if PY2:
|
if PY2:
|
||||||
from allmydata.scripts import cli as module_to_patch
|
from allmydata.scripts import cli as module_to_patch
|
||||||
else:
|
else:
|
||||||
import builtins as module_to_patch
|
import builtins as module_to_patch
|
||||||
patcher = MonkeyPatcher((module_to_patch, 'open', call_file))
|
patcher = MonkeyPatcher((module_to_patch, 'open', call_file))
|
||||||
patcher.runWithPatches(parse_options, basedir, "backup", ['--exclude-from', unicode_to_argv(exclude_file), 'from', 'to'])
|
patcher.runWithPatches(parse_options, basedir, "backup", ['--exclude-from-utf-8', unicode_to_argv(exclude_file), 'from', 'to'])
|
||||||
self.failUnless(ns.called)
|
self.failUnless(ns.called)
|
||||||
|
|
||||||
def test_ignore_symlinks(self):
|
def test_ignore_symlinks(self):
|
||||||
|
@ -15,7 +15,7 @@ from six.moves import cStringIO as StringIO
|
|||||||
|
|
||||||
from allmydata import uri
|
from allmydata import uri
|
||||||
from allmydata.util import base32
|
from allmydata.util import base32
|
||||||
from allmydata.util.encodingutil import to_bytes
|
from allmydata.util.encodingutil import to_bytes, quote_output_u
|
||||||
from allmydata.mutable.publish import MutableData
|
from allmydata.mutable.publish import MutableData
|
||||||
from allmydata.immutable import upload
|
from allmydata.immutable import upload
|
||||||
from allmydata.scripts import debug
|
from allmydata.scripts import debug
|
||||||
@ -168,7 +168,7 @@ class Check(GridTestMixin, CLITestMixin, unittest.TestCase):
|
|||||||
self.uris = {}
|
self.uris = {}
|
||||||
self.fileurls = {}
|
self.fileurls = {}
|
||||||
DATA = b"data" * 100
|
DATA = b"data" * 100
|
||||||
quoted_good = u"'g\u00F6\u00F6d'"
|
quoted_good = quote_output_u("g\u00F6\u00F6d")
|
||||||
|
|
||||||
d = c0.create_dirnode()
|
d = c0.create_dirnode()
|
||||||
def _stash_root_and_create_file(n):
|
def _stash_root_and_create_file(n):
|
||||||
|
@ -238,6 +238,66 @@ class Cp(GridTestMixin, CLITestMixin, unittest.TestCase):
|
|||||||
|
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def test_cp_duplicate_directories(self):
|
||||||
|
self.basedir = "cli/Cp/cp_duplicate_directories"
|
||||||
|
self.set_up_grid(oneshare=True)
|
||||||
|
|
||||||
|
filename = os.path.join(self.basedir, "file")
|
||||||
|
data = b"abc\xff\x00\xee"
|
||||||
|
with open(filename, "wb") as f:
|
||||||
|
f.write(data)
|
||||||
|
|
||||||
|
yield self.do_cli("create-alias", "tahoe")
|
||||||
|
(rc, out, err) = yield self.do_cli("mkdir", "tahoe:test1")
|
||||||
|
self.assertEqual(rc, 0, (rc, err))
|
||||||
|
dircap = out.strip()
|
||||||
|
|
||||||
|
(rc, out, err) = yield self.do_cli("cp", filename, "tahoe:test1/file")
|
||||||
|
self.assertEqual(rc, 0, (rc, err))
|
||||||
|
|
||||||
|
# Now duplicate dirnode, testing duplicates on destination side:
|
||||||
|
(rc, out, err) = yield self.do_cli(
|
||||||
|
"cp", "--recursive", dircap, "tahoe:test2/")
|
||||||
|
self.assertEqual(rc, 0, (rc, err))
|
||||||
|
(rc, out, err) = yield self.do_cli(
|
||||||
|
"cp", "--recursive", dircap, "tahoe:test3/")
|
||||||
|
self.assertEqual(rc, 0, (rc, err))
|
||||||
|
|
||||||
|
# Now copy to local directory, testing duplicates on origin side:
|
||||||
|
yield self.do_cli("cp", "--recursive", "tahoe:", self.basedir)
|
||||||
|
|
||||||
|
for i in range(1, 4):
|
||||||
|
with open(os.path.join(self.basedir, "test%d" % (i,), "file"), "rb") as f:
|
||||||
|
self.assertEquals(f.read(), data)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def test_cp_immutable_file(self):
|
||||||
|
self.basedir = "cli/Cp/cp_immutable_file"
|
||||||
|
self.set_up_grid(oneshare=True)
|
||||||
|
|
||||||
|
filename = os.path.join(self.basedir, "source_file")
|
||||||
|
data = b"abc\xff\x00\xee"
|
||||||
|
with open(filename, "wb") as f:
|
||||||
|
f.write(data)
|
||||||
|
|
||||||
|
# Create immutable file:
|
||||||
|
yield self.do_cli("create-alias", "tahoe")
|
||||||
|
(rc, out, _) = yield self.do_cli("put", filename, "tahoe:file1")
|
||||||
|
filecap = out.strip()
|
||||||
|
self.assertEqual(rc, 0)
|
||||||
|
|
||||||
|
# Copy it:
|
||||||
|
(rc, _, _) = yield self.do_cli("cp", "tahoe:file1", "tahoe:file2")
|
||||||
|
self.assertEqual(rc, 0)
|
||||||
|
|
||||||
|
# Make sure resulting file is the same:
|
||||||
|
(rc, _, _) = yield self.do_cli("cp", "--recursive", "--caps-only",
|
||||||
|
"tahoe:", self.basedir)
|
||||||
|
self.assertEqual(rc, 0)
|
||||||
|
with open(os.path.join(self.basedir, "file2")) as f:
|
||||||
|
self.assertEqual(f.read().strip(), filecap)
|
||||||
|
|
||||||
def test_cp_replaces_mutable_file_contents(self):
|
def test_cp_replaces_mutable_file_contents(self):
|
||||||
self.basedir = "cli/Cp/cp_replaces_mutable_file_contents"
|
self.basedir = "cli/Cp/cp_replaces_mutable_file_contents"
|
||||||
self.set_up_grid(oneshare=True)
|
self.set_up_grid(oneshare=True)
|
||||||
|
@ -10,7 +10,6 @@ from future.utils import PY2
|
|||||||
if PY2:
|
if PY2:
|
||||||
from future.builtins import filter, map, zip, ascii, chr, hex, input, next, oct, open, pow, round, super, bytes, dict, list, object, range, str, max, min # noqa: F401
|
from future.builtins import filter, map, zip, ascii, chr, hex, input, next, oct, open, pow, round, super, bytes, dict, list, object, range, str, max, min # noqa: F401
|
||||||
|
|
||||||
from six import ensure_text
|
|
||||||
from six.moves import StringIO
|
from six.moves import StringIO
|
||||||
import os.path
|
import os.path
|
||||||
from twisted.trial import unittest
|
from twisted.trial import unittest
|
||||||
@ -20,7 +19,7 @@ from allmydata.util import fileutil
|
|||||||
from allmydata.scripts.common import get_aliases
|
from allmydata.scripts.common import get_aliases
|
||||||
from allmydata.scripts import cli, runner
|
from allmydata.scripts import cli, runner
|
||||||
from ..no_network import GridTestMixin
|
from ..no_network import GridTestMixin
|
||||||
from allmydata.util.encodingutil import quote_output
|
from allmydata.util.encodingutil import quote_output_u
|
||||||
from .common import CLITestMixin
|
from .common import CLITestMixin
|
||||||
|
|
||||||
class CreateAlias(GridTestMixin, CLITestMixin, unittest.TestCase):
|
class CreateAlias(GridTestMixin, CLITestMixin, unittest.TestCase):
|
||||||
@ -182,7 +181,7 @@ class CreateAlias(GridTestMixin, CLITestMixin, unittest.TestCase):
|
|||||||
(rc, out, err) = args
|
(rc, out, err) = args
|
||||||
self.failUnlessReallyEqual(rc, 0)
|
self.failUnlessReallyEqual(rc, 0)
|
||||||
self.assertEqual(len(err), 0, err)
|
self.assertEqual(len(err), 0, err)
|
||||||
self.failUnlessIn(u"Alias %s created" % ensure_text(quote_output(etudes_arg)), out)
|
self.failUnlessIn(u"Alias %s created" % (quote_output_u(etudes_arg),), out)
|
||||||
|
|
||||||
aliases = get_aliases(self.get_clientdir())
|
aliases = get_aliases(self.get_clientdir())
|
||||||
self.failUnless(aliases[u"\u00E9tudes"].startswith(b"URI:DIR2:"))
|
self.failUnless(aliases[u"\u00E9tudes"].startswith(b"URI:DIR2:"))
|
||||||
|
@ -486,3 +486,20 @@ class Put(GridTestMixin, CLITestMixin, unittest.TestCase):
|
|||||||
self.failUnlessReallyEqual(rc_out_err[1], DATA))
|
self.failUnlessReallyEqual(rc_out_err[1], DATA))
|
||||||
|
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
def test_no_leading_slash(self):
|
||||||
|
self.basedir = "cli/Put/leading_slash"
|
||||||
|
self.set_up_grid(oneshare=True)
|
||||||
|
|
||||||
|
fn1 = os.path.join(self.basedir, "DATA1")
|
||||||
|
|
||||||
|
d = self.do_cli("create-alias", "tahoe")
|
||||||
|
d.addCallback(lambda res:
|
||||||
|
self.do_cli("put", fn1, "tahoe:/uploaded.txt"))
|
||||||
|
def _check(args):
|
||||||
|
(rc, out, err) = args
|
||||||
|
self.assertEqual(rc, 1)
|
||||||
|
self.failUnlessIn("must not start with a slash", err)
|
||||||
|
self.assertEqual(len(out), 0, out)
|
||||||
|
d.addCallback(_check)
|
||||||
|
return d
|
||||||
|
@ -130,9 +130,10 @@ class Integration(GridTestMixin, CLITestMixin, unittest.TestCase):
|
|||||||
d.addCallback(_check)
|
d.addCallback(_check)
|
||||||
return d
|
return d
|
||||||
|
|
||||||
@mock.patch('sys.stdout')
|
@defer.inlineCallbacks
|
||||||
def test_help(self, fake):
|
def test_help(self):
|
||||||
return self.do_cli('status', '--help')
|
rc, _, _ = yield self.do_cli('status', '--help')
|
||||||
|
self.assertEqual(rc, 0)
|
||||||
|
|
||||||
|
|
||||||
class CommandStatus(unittest.TestCase):
|
class CommandStatus(unittest.TestCase):
|
||||||
|
@ -12,6 +12,7 @@ if PY2:
|
|||||||
from future.builtins import filter, map, zip, ascii, chr, hex, input, next, oct, open, pow, round, super, dict, list, object, range, str, max, min # noqa: F401
|
from future.builtins import filter, map, zip, ascii, chr, hex, input, next, oct, open, pow, round, super, dict, list, object, range, str, max, min # noqa: F401
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
import time
|
import time
|
||||||
import signal
|
import signal
|
||||||
from random import randrange
|
from random import randrange
|
||||||
@ -85,7 +86,7 @@ def run_cli_native(verb, *args, **kwargs):
|
|||||||
bytes.
|
bytes.
|
||||||
"""
|
"""
|
||||||
nodeargs = kwargs.pop("nodeargs", [])
|
nodeargs = kwargs.pop("nodeargs", [])
|
||||||
encoding = kwargs.pop("encoding", None) or "utf-8"
|
encoding = kwargs.pop("encoding", None) or getattr(sys.stdout, "encoding") or "utf-8"
|
||||||
return_bytes = kwargs.pop("return_bytes", False)
|
return_bytes = kwargs.pop("return_bytes", False)
|
||||||
verb = maybe_unicode_to_argv(verb)
|
verb = maybe_unicode_to_argv(verb)
|
||||||
args = [maybe_unicode_to_argv(a) for a in args]
|
args = [maybe_unicode_to_argv(a) for a in args]
|
||||||
|
@ -379,7 +379,10 @@ class QuoteOutput(ReallyEqualMixin, unittest.TestCase):
|
|||||||
check(u"\n", u"\"\\x0a\"", quote_newlines=True)
|
check(u"\n", u"\"\\x0a\"", quote_newlines=True)
|
||||||
|
|
||||||
def test_quote_output_default(self):
|
def test_quote_output_default(self):
|
||||||
self.test_quote_output_utf8(None)
|
"""Default is the encoding of sys.stdout if known, otherwise utf-8."""
|
||||||
|
encoding = getattr(sys.stdout, "encoding") or "utf-8"
|
||||||
|
self.assertEqual(quote_output(u"\u2621"),
|
||||||
|
quote_output(u"\u2621", encoding=encoding))
|
||||||
|
|
||||||
|
|
||||||
def win32_other(win32, other):
|
def win32_other(win32, other):
|
||||||
|
@ -17,6 +17,7 @@ from six import ensure_text
|
|||||||
|
|
||||||
import os.path, re, sys
|
import os.path, re, sys
|
||||||
from os import linesep
|
from os import linesep
|
||||||
|
import locale
|
||||||
|
|
||||||
from eliot import (
|
from eliot import (
|
||||||
log_call,
|
log_call,
|
||||||
@ -92,8 +93,12 @@ def run_bintahoe(extra_argv, python_options=None):
|
|||||||
argv.extend(extra_argv)
|
argv.extend(extra_argv)
|
||||||
argv = list(unicode_to_argv(arg) for arg in argv)
|
argv = list(unicode_to_argv(arg) for arg in argv)
|
||||||
p = Popen(argv, stdout=PIPE, stderr=PIPE)
|
p = Popen(argv, stdout=PIPE, stderr=PIPE)
|
||||||
out = p.stdout.read().decode("utf-8")
|
if PY2:
|
||||||
err = p.stderr.read().decode("utf-8")
|
encoding = "utf-8"
|
||||||
|
else:
|
||||||
|
encoding = locale.getpreferredencoding(False)
|
||||||
|
out = p.stdout.read().decode(encoding)
|
||||||
|
err = p.stderr.read().decode(encoding)
|
||||||
returncode = p.wait()
|
returncode = p.wait()
|
||||||
return (out, err, returncode)
|
return (out, err, returncode)
|
||||||
|
|
||||||
@ -103,7 +108,7 @@ class BinTahoe(common_util.SignalMixin, unittest.TestCase):
|
|||||||
"""
|
"""
|
||||||
The runner script receives unmangled non-ASCII values in argv.
|
The runner script receives unmangled non-ASCII values in argv.
|
||||||
"""
|
"""
|
||||||
tricky = u"\u2621"
|
tricky = u"\u00F6"
|
||||||
out, err, returncode = run_bintahoe([tricky])
|
out, err, returncode = run_bintahoe([tricky])
|
||||||
self.assertEqual(returncode, 1)
|
self.assertEqual(returncode, 1)
|
||||||
self.assertIn(u"Unknown command: " + tricky, out)
|
self.assertIn(u"Unknown command: " + tricky, out)
|
||||||
|
@ -79,6 +79,7 @@ slow_settings = settings(
|
|||||||
)
|
)
|
||||||
|
|
||||||
@skipUnless(platform.isWindows(), "get_argv is Windows-only")
|
@skipUnless(platform.isWindows(), "get_argv is Windows-only")
|
||||||
|
@skipUnless(PY2, "Not used on Python 3.")
|
||||||
class GetArgvTests(SyncTestCase):
|
class GetArgvTests(SyncTestCase):
|
||||||
"""
|
"""
|
||||||
Tests for ``get_argv``.
|
Tests for ``get_argv``.
|
||||||
@ -172,6 +173,7 @@ class GetArgvTests(SyncTestCase):
|
|||||||
|
|
||||||
|
|
||||||
@skipUnless(platform.isWindows(), "intended for Windows-only codepaths")
|
@skipUnless(platform.isWindows(), "intended for Windows-only codepaths")
|
||||||
|
@skipUnless(PY2, "Not used on Python 3.")
|
||||||
class UnicodeOutputTests(SyncTestCase):
|
class UnicodeOutputTests(SyncTestCase):
|
||||||
"""
|
"""
|
||||||
Tests for writing unicode to stdout and stderr.
|
Tests for writing unicode to stdout and stderr.
|
||||||
|
@ -256,7 +256,11 @@ def quote_output_u(*args, **kwargs):
|
|||||||
result = quote_output(*args, **kwargs)
|
result = quote_output(*args, **kwargs)
|
||||||
if isinstance(result, unicode):
|
if isinstance(result, unicode):
|
||||||
return result
|
return result
|
||||||
return result.decode(kwargs.get("encoding", None) or io_encoding)
|
# Since we're quoting, the assumption is this will be read by a human, and
|
||||||
|
# therefore printed, so stdout's encoding is the plausible one. io_encoding
|
||||||
|
# is now always utf-8.
|
||||||
|
return result.decode(kwargs.get("encoding", None) or
|
||||||
|
getattr(sys.stdout, "encoding") or io_encoding)
|
||||||
|
|
||||||
|
|
||||||
def quote_output(s, quotemarks=True, quote_newlines=None, encoding=None):
|
def quote_output(s, quotemarks=True, quote_newlines=None, encoding=None):
|
||||||
@ -276,7 +280,10 @@ def quote_output(s, quotemarks=True, quote_newlines=None, encoding=None):
|
|||||||
On Python 3, returns Unicode strings.
|
On Python 3, returns Unicode strings.
|
||||||
"""
|
"""
|
||||||
precondition(isinstance(s, (bytes, unicode)), s)
|
precondition(isinstance(s, (bytes, unicode)), s)
|
||||||
encoding = encoding or io_encoding
|
# Since we're quoting, the assumption is this will be read by a human, and
|
||||||
|
# therefore printed, so stdout's encoding is the plausible one. io_encoding
|
||||||
|
# is now always utf-8.
|
||||||
|
encoding = encoding or getattr(sys.stdout, "encoding") or io_encoding
|
||||||
|
|
||||||
if quote_newlines is None:
|
if quote_newlines is None:
|
||||||
quote_newlines = quotemarks
|
quote_newlines = quotemarks
|
||||||
@ -284,7 +291,7 @@ def quote_output(s, quotemarks=True, quote_newlines=None, encoding=None):
|
|||||||
def _encode(s):
|
def _encode(s):
|
||||||
if isinstance(s, bytes):
|
if isinstance(s, bytes):
|
||||||
try:
|
try:
|
||||||
s = s.decode('utf-8')
|
s = s.decode("utf-8")
|
||||||
except UnicodeDecodeError:
|
except UnicodeDecodeError:
|
||||||
return b'b"%s"' % (ESCAPABLE_8BIT.sub(lambda m: _bytes_escape(m, quote_newlines), s),)
|
return b'b"%s"' % (ESCAPABLE_8BIT.sub(lambda m: _bytes_escape(m, quote_newlines), s),)
|
||||||
|
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
|
from future.utils import PY3
|
||||||
from past.builtins import unicode
|
from past.builtins import unicode
|
||||||
|
|
||||||
# This code isn't loadable or sensible except on Windows. Importers all know
|
# This code isn't loadable or sensible except on Windows. Importers all know
|
||||||
@ -122,6 +124,10 @@ def initialize():
|
|||||||
|
|
||||||
SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX)
|
SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX)
|
||||||
|
|
||||||
|
if PY3:
|
||||||
|
# The rest of this appears to be Python 2-specific
|
||||||
|
return
|
||||||
|
|
||||||
original_stderr = sys.stderr
|
original_stderr = sys.stderr
|
||||||
|
|
||||||
# If any exception occurs in this code, we'll probably try to print it on stderr,
|
# If any exception occurs in this code, we'll probably try to print it on stderr,
|
||||||
|
4
tox.ini
4
tox.ini
@ -9,9 +9,9 @@
|
|||||||
python =
|
python =
|
||||||
2.7: py27-coverage,codechecks
|
2.7: py27-coverage,codechecks
|
||||||
3.6: py36-coverage
|
3.6: py36-coverage
|
||||||
3.7: py37-coverage
|
3.7: py37-coverage,typechecks,codechecks3
|
||||||
3.8: py38-coverage
|
3.8: py38-coverage
|
||||||
3.9: py39-coverage,typechecks,codechecks3
|
3.9: py39-coverage
|
||||||
pypy-3.7: pypy3
|
pypy-3.7: pypy3
|
||||||
|
|
||||||
[pytest]
|
[pytest]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user