Merge remote-tracking branch 'origin/3552.test_system-python-3' into 3565.web-tests-python-3-part-1

This commit is contained in:
Itamar Turner-Trauring 2020-12-17 09:51:52 -05:00
commit 9b59e7e245
16 changed files with 200 additions and 115 deletions

0
newsfragments/3533.minor Normal file
View File

0
newsfragments/3552.minor Normal file
View File

View File

@ -23,17 +23,12 @@ python.pkgs.buildPythonPackage rec {
# This list is over-zealous because it's more work to disable individual # This list is over-zealous because it's more work to disable individual
# tests with in a module. # tests with in a module.
# test_system is a lot of integration-style tests that do a lot of real
# networking between many processes. They sometimes fail spuriously.
rm src/allmydata/test/test_system.py
# Many of these tests don't properly skip when i2p or tor dependencies are # Many of these tests don't properly skip when i2p or tor dependencies are
# not supplied (and we are not supplying them). # not supplied (and we are not supplying them).
rm src/allmydata/test/test_i2p_provider.py rm src/allmydata/test/test_i2p_provider.py
rm src/allmydata/test/test_connections.py rm src/allmydata/test/test_connections.py
rm src/allmydata/test/cli/test_create.py rm src/allmydata/test/cli/test_create.py
rm src/allmydata/test/test_client.py rm src/allmydata/test/test_client.py
rm src/allmydata/test/test_runner.py
''; '';

View File

@ -714,7 +714,7 @@ class _Client(node.Node, pollmixin.PollMixin):
def get_long_nodeid(self): def get_long_nodeid(self):
# this matches what IServer.get_longname() says about us elsewhere # this matches what IServer.get_longname() says about us elsewhere
vk_string = ed25519.string_from_verifying_key(self._node_public_key) vk_string = ed25519.string_from_verifying_key(self._node_public_key)
return remove_prefix(vk_string, "pub-") return remove_prefix(vk_string, b"pub-")
def get_long_tubid(self): def get_long_tubid(self):
return idlib.nodeid_b2a(self.nodeid) return idlib.nodeid_b2a(self.nodeid)
@ -898,10 +898,6 @@ class _Client(node.Node, pollmixin.PollMixin):
if helper_furl in ("None", ""): if helper_furl in ("None", ""):
helper_furl = None helper_furl = None
# FURLs need to be bytes:
if helper_furl is not None:
helper_furl = helper_furl.encode("utf-8")
DEP = self.encoding_params DEP = self.encoding_params
DEP["k"] = int(self.config.get_config("client", "shares.needed", DEP["k"])) DEP["k"] = int(self.config.get_config("client", "shares.needed", DEP["k"]))
DEP["n"] = int(self.config.get_config("client", "shares.total", DEP["n"])) DEP["n"] = int(self.config.get_config("client", "shares.total", DEP["n"]))

View File

@ -9,6 +9,7 @@ from __future__ import unicode_literals
from future.utils import PY2 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_str
import time import time
now = time.time now = time.time
@ -98,7 +99,7 @@ class ShareFinder(object):
# internal methods # internal methods
def loop(self): def loop(self):
pending_s = ",".join([rt.server.get_name() pending_s = ",".join([ensure_str(rt.server.get_name())
for rt in self.pending_requests]) # sort? for rt in self.pending_requests]) # sort?
self.log(format="ShareFinder loop: running=%(running)s" self.log(format="ShareFinder loop: running=%(running)s"
" hungry=%(hungry)s, pending=%(pending)s", " hungry=%(hungry)s, pending=%(pending)s",

View File

@ -11,6 +11,7 @@ from future.utils import PY2, native_str
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 past.builtins import long, unicode from past.builtins import long, unicode
from six import ensure_str
import os, time, weakref, itertools import os, time, weakref, itertools
from zope.interface import implementer from zope.interface import implementer
@ -1825,7 +1826,7 @@ class Uploader(service.MultiService, log.PrefixingLogMixin):
def startService(self): def startService(self):
service.MultiService.startService(self) service.MultiService.startService(self)
if self._helper_furl: if self._helper_furl:
self.parent.tub.connectTo(self._helper_furl, self.parent.tub.connectTo(ensure_str(self._helper_furl),
self._got_helper) self._got_helper)
def _got_helper(self, helper): def _got_helper(self, helper):

View File

@ -11,7 +11,7 @@ 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 past.builtins import long from past.builtins import long
from six import ensure_text from six import ensure_text, ensure_str
import time import time
from zope.interface import implementer from zope.interface import implementer
@ -39,8 +39,6 @@ class IntroducerClient(service.Service, Referenceable):
nickname, my_version, oldest_supported, nickname, my_version, oldest_supported,
sequencer, cache_filepath): sequencer, cache_filepath):
self._tub = tub self._tub = tub
if isinstance(introducer_furl, str):
introducer_furl = introducer_furl.encode("utf-8")
self.introducer_furl = introducer_furl self.introducer_furl = introducer_furl
assert isinstance(nickname, str) assert isinstance(nickname, str)
@ -96,7 +94,7 @@ class IntroducerClient(service.Service, Referenceable):
def startService(self): def startService(self):
service.Service.startService(self) service.Service.startService(self)
self._introducer_error = None self._introducer_error = None
rc = self._tub.connectTo(self.introducer_furl, self._got_introducer) rc = self._tub.connectTo(ensure_str(self.introducer_furl), self._got_introducer)
self._introducer_reconnector = rc self._introducer_reconnector = rc
def connect_failed(failure): def connect_failed(failure):
self.log("Initial Introducer connection failed: perhaps it's down", self.log("Initial Introducer connection failed: perhaps it's down",

View File

@ -1,5 +1,8 @@
from __future__ import print_function from __future__ import print_function
from future.utils import PY2, native_str
from future.builtins import str as future_str
import os import os
import time import time
import signal import signal
@ -48,24 +51,23 @@ def _getvalue(io):
return io.read() return io.read()
def run_cli_bytes(verb, *args, **kwargs): def run_cli_native(verb, *args, **kwargs):
""" """
Run a Tahoe-LAFS CLI command specified as bytes. Run a Tahoe-LAFS CLI command specified as bytes (on Python 2) or Unicode
(on Python 3); basically, it accepts a native string.
Most code should prefer ``run_cli_unicode`` which deals with all the Most code should prefer ``run_cli_unicode`` which deals with all the
necessary encoding considerations. This helper still exists so that novel necessary encoding considerations.
misconfigurations can be explicitly tested (for example, receiving UTF-8
bytes when the system encoding claims to be ASCII).
:param bytes verb: The command to run. For example, ``b"create-node"``. :param native_str verb: The command to run. For example, ``b"create-node"``.
:param [bytes] args: The arguments to pass to the command. For example, :param [native_str] args: The arguments to pass to the command. For example,
``(b"--hostname=localhost",)``. ``(b"--hostname=localhost",)``.
:param [bytes] nodeargs: Extra arguments to pass to the Tahoe executable :param [native_str] nodeargs: Extra arguments to pass to the Tahoe executable
before ``verb``. before ``verb``.
:param bytes stdin: Text to pass to the command via stdin. :param native_str stdin: Text to pass to the command via stdin.
:param NoneType|str encoding: The name of an encoding which stdout and :param NoneType|str encoding: The name of an encoding which stdout and
stderr will be configured to use. ``None`` means stdout and stderr stderr will be configured to use. ``None`` means stdout and stderr
@ -75,8 +77,8 @@ def run_cli_bytes(verb, *args, **kwargs):
nodeargs = kwargs.pop("nodeargs", []) nodeargs = kwargs.pop("nodeargs", [])
encoding = kwargs.pop("encoding", None) encoding = kwargs.pop("encoding", None)
precondition( precondition(
all(isinstance(arg, bytes) for arg in [verb] + nodeargs + list(args)), all(isinstance(arg, native_str) for arg in [verb] + nodeargs + list(args)),
"arguments to run_cli must be bytes -- convert using unicode_to_argv", "arguments to run_cli must be a native string -- convert using unicode_to_argv",
verb=verb, verb=verb,
args=args, args=args,
nodeargs=nodeargs, nodeargs=nodeargs,
@ -135,15 +137,19 @@ def run_cli_unicode(verb, argv, nodeargs=None, stdin=None, encoding=None):
if nodeargs is None: if nodeargs is None:
nodeargs = [] nodeargs = []
precondition( precondition(
all(isinstance(arg, unicode) for arg in [verb] + nodeargs + argv), all(isinstance(arg, future_str) for arg in [verb] + nodeargs + argv),
"arguments to run_cli_unicode must be unicode", "arguments to run_cli_unicode must be unicode",
verb=verb, verb=verb,
nodeargs=nodeargs, nodeargs=nodeargs,
argv=argv, argv=argv,
) )
codec = encoding or "ascii" codec = encoding or "ascii"
if PY2:
encode = lambda t: None if t is None else t.encode(codec) encode = lambda t: None if t is None else t.encode(codec)
d = run_cli_bytes( else:
# On Python 3 command-line parsing expects Unicode!
encode = lambda t: t
d = run_cli_native(
encode(verb), encode(verb),
nodeargs=list(encode(arg) for arg in nodeargs), nodeargs=list(encode(arg) for arg in nodeargs),
stdin=encode(stdin), stdin=encode(stdin),
@ -161,7 +167,7 @@ def run_cli_unicode(verb, argv, nodeargs=None, stdin=None, encoding=None):
return d return d
run_cli = run_cli_bytes run_cli = run_cli_native
def parse_cli(*argv): def parse_cli(*argv):

View File

@ -47,12 +47,17 @@ class VerboseError(Error):
@inlineCallbacks @inlineCallbacks
def do_http(method, url, **kwargs): def do_http(method, url, **kwargs):
"""
Run HTTP query, return Deferred of body as bytes.
"""
response = yield treq.request(method, url, persistent=False, **kwargs) response = yield treq.request(method, url, persistent=False, **kwargs)
body = yield treq.content(response) body = yield treq.content(response)
# TODO: replace this with response.fail_for_status when # TODO: replace this with response.fail_for_status when
# https://github.com/twisted/treq/pull/159 has landed # https://github.com/twisted/treq/pull/159 has landed
if 400 <= response.code < 600: if 400 <= response.code < 600:
raise VerboseError(response.code, response=body) raise VerboseError(
response.code, response="For request {} to {}, got: {}".format(
method, url, body))
returnValue(body) returnValue(body)

View File

@ -1,7 +1,22 @@
"""
Ported to Python 3, partially: test_filesystem* will be done in a future round.
"""
from __future__ import print_function from __future__ import print_function
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from future.utils import PY2, PY3
if PY2:
# Don't import bytes since it causes issues on (so far unported) modules on Python 2.
from future.builtins import filter, map, zip, ascii, chr, hex, input, next, oct, open, pow, round, super, dict, list, object, range, max, min, str # noqa: F401
from past.builtins import chr as byteschr, long
from six import ensure_text, ensure_str
import os, re, sys, time, json import os, re, sys, time, json
from functools import partial from functools import partial
from unittest import skipIf
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
@ -40,7 +55,7 @@ from .common import (
TEST_RSA_KEY_SIZE, TEST_RSA_KEY_SIZE,
SameProcessStreamEndpointAssigner, SameProcessStreamEndpointAssigner,
) )
from .common_web import do_http, Error from .common_web import do_http as do_http_bytes, Error
from .web.common import ( from .web.common import (
assert_soup_has_tag_with_attributes assert_soup_has_tag_with_attributes
) )
@ -48,12 +63,28 @@ from .web.common import (
# TODO: move this to common or common_util # TODO: move this to common or common_util
from allmydata.test.test_runner import RunBinTahoeMixin from allmydata.test.test_runner import RunBinTahoeMixin
from . import common_util as testutil from . import common_util as testutil
from .common_util import run_cli from .common_util import run_cli_unicode
from ..scripts.common import ( from ..scripts.common import (
write_introducer, write_introducer,
) )
LARGE_DATA = """ def run_cli(*args, **kwargs):
"""
Backwards compatible version so we don't have to change all the tests.
"""
nodeargs = [ensure_text(a) for a in kwargs.pop("nodeargs", [])]
kwargs["nodeargs"] = nodeargs
return run_cli_unicode(
ensure_text(args[0]), [ensure_text(a) for a in args[1:]], **kwargs)
def do_http(*args, **kwargs):
"""Wrapper for do_http() that returns Unicode."""
return do_http_bytes(*args, **kwargs).addCallback(
lambda b: str(b, "utf-8"))
LARGE_DATA = b"""
This is some data to publish to the remote grid.., which needs to be large This is some data to publish to the remote grid.., which needs to be large
enough to not fit inside a LIT uri. enough to not fit inside a LIT uri.
""" """
@ -629,7 +660,7 @@ def _render_config(config):
""" """
Convert a ``dict`` of ``dict`` of ``bytes`` to an ini-format string. Convert a ``dict`` of ``dict`` of ``bytes`` to an ini-format string.
""" """
return "\n\n".join(list( return u"\n\n".join(list(
_render_config_section(k, v) _render_config_section(k, v)
for (k, v) for (k, v)
in config.items() in config.items()
@ -640,7 +671,7 @@ def _render_config_section(heading, values):
Convert a ``bytes`` heading and a ``dict`` of ``bytes`` to an ini-format Convert a ``bytes`` heading and a ``dict`` of ``bytes`` to an ini-format
section as ``bytes``. section as ``bytes``.
""" """
return "[{}]\n{}\n".format( return u"[{}]\n{}\n".format(
heading, _render_section_values(values) heading, _render_section_values(values)
) )
@ -649,8 +680,8 @@ def _render_section_values(values):
Convert a ``dict`` of ``bytes`` to the body of an ini-format section as Convert a ``dict`` of ``bytes`` to the body of an ini-format section as
``bytes``. ``bytes``.
""" """
return "\n".join(list( return u"\n".join(list(
"{} = {}".format(k, v) u"{} = {}".format(k, v)
for (k, v) for (k, v)
in sorted(values.items()) in sorted(values.items())
)) ))
@ -753,7 +784,7 @@ class SystemTestMixin(pollmixin.PollMixin, testutil.StallMixin):
self.helper_furl = helper_furl self.helper_furl = helper_furl
if self.numclients >= 4: if self.numclients >= 4:
with open(os.path.join(basedirs[3], 'tahoe.cfg'), 'ab+') as f: with open(os.path.join(basedirs[3], 'tahoe.cfg'), 'a+') as f:
f.write( f.write(
"[client]\n" "[client]\n"
"helper.furl = {}\n".format(helper_furl) "helper.furl = {}\n".format(helper_furl)
@ -796,8 +827,6 @@ class SystemTestMixin(pollmixin.PollMixin, testutil.StallMixin):
def setconf(config, which, section, feature, value): def setconf(config, which, section, feature, value):
if which in feature_matrix.get((section, feature), {which}): if which in feature_matrix.get((section, feature), {which}):
if isinstance(value, unicode):
value = value.encode("utf-8")
config.setdefault(section, {})[feature] = value config.setdefault(section, {})[feature] = value
setnode = partial(setconf, config, which, "node") setnode = partial(setconf, config, which, "node")
@ -870,7 +899,7 @@ class SystemTestMixin(pollmixin.PollMixin, testutil.StallMixin):
config = "[client]\n" config = "[client]\n"
if helper_furl: if helper_furl:
config += "helper.furl = %s\n" % helper_furl config += "helper.furl = %s\n" % helper_furl
basedir.child("tahoe.cfg").setContent(config) basedir.child("tahoe.cfg").setContent(config.encode("utf-8"))
private = basedir.child("private") private = basedir.child("private")
private.makedirs() private.makedirs()
write_introducer( write_introducer(
@ -980,12 +1009,12 @@ class SystemTest(SystemTestMixin, RunBinTahoeMixin, unittest.TestCase):
def test_upload_and_download_convergent(self): def test_upload_and_download_convergent(self):
self.basedir = "system/SystemTest/test_upload_and_download_convergent" self.basedir = "system/SystemTest/test_upload_and_download_convergent"
return self._test_upload_and_download(convergence="some convergence string") return self._test_upload_and_download(convergence=b"some convergence string")
def _test_upload_and_download(self, convergence): def _test_upload_and_download(self, convergence):
# we use 4000 bytes of data, which will result in about 400k written # we use 4000 bytes of data, which will result in about 400k written
# to disk among all our simulated nodes # to disk among all our simulated nodes
DATA = "Some data to upload\n" * 200 DATA = b"Some data to upload\n" * 200
d = self.set_up_nodes() d = self.set_up_nodes()
def _check_connections(res): def _check_connections(res):
for c in self.clients: for c in self.clients:
@ -993,7 +1022,7 @@ class SystemTest(SystemTestMixin, RunBinTahoeMixin, unittest.TestCase):
all_peerids = c.get_storage_broker().get_all_serverids() all_peerids = c.get_storage_broker().get_all_serverids()
self.failUnlessEqual(len(all_peerids), self.numclients) self.failUnlessEqual(len(all_peerids), self.numclients)
sb = c.storage_broker sb = c.storage_broker
permuted_peers = sb.get_servers_for_psi("a") permuted_peers = sb.get_servers_for_psi(b"a")
self.failUnlessEqual(len(permuted_peers), self.numclients) self.failUnlessEqual(len(permuted_peers), self.numclients)
d.addCallback(_check_connections) d.addCallback(_check_connections)
@ -1016,7 +1045,7 @@ class SystemTest(SystemTestMixin, RunBinTahoeMixin, unittest.TestCase):
theuri = results.get_uri() theuri = results.get_uri()
log.msg("upload finished: uri is %s" % (theuri,)) log.msg("upload finished: uri is %s" % (theuri,))
self.uri = theuri self.uri = theuri
assert isinstance(self.uri, str), self.uri assert isinstance(self.uri, bytes), self.uri
self.cap = uri.from_string(self.uri) self.cap = uri.from_string(self.uri)
self.n = self.clients[1].create_node_from_uri(self.uri) self.n = self.clients[1].create_node_from_uri(self.uri)
d.addCallback(_upload_done) d.addCallback(_upload_done)
@ -1050,17 +1079,17 @@ class SystemTest(SystemTestMixin, RunBinTahoeMixin, unittest.TestCase):
d.addCallback(lambda ign: d.addCallback(lambda ign:
n.read(MemoryConsumer(), offset=1, size=4)) n.read(MemoryConsumer(), offset=1, size=4))
def _read_portion_done(mc): def _read_portion_done(mc):
self.failUnlessEqual("".join(mc.chunks), DATA[1:1+4]) self.failUnlessEqual(b"".join(mc.chunks), DATA[1:1+4])
d.addCallback(_read_portion_done) d.addCallback(_read_portion_done)
d.addCallback(lambda ign: d.addCallback(lambda ign:
n.read(MemoryConsumer(), offset=2, size=None)) n.read(MemoryConsumer(), offset=2, size=None))
def _read_tail_done(mc): def _read_tail_done(mc):
self.failUnlessEqual("".join(mc.chunks), DATA[2:]) self.failUnlessEqual(b"".join(mc.chunks), DATA[2:])
d.addCallback(_read_tail_done) d.addCallback(_read_tail_done)
d.addCallback(lambda ign: d.addCallback(lambda ign:
n.read(MemoryConsumer(), size=len(DATA)+1000)) n.read(MemoryConsumer(), size=len(DATA)+1000))
def _read_too_much(mc): def _read_too_much(mc):
self.failUnlessEqual("".join(mc.chunks), DATA) self.failUnlessEqual(b"".join(mc.chunks), DATA)
d.addCallback(_read_too_much) d.addCallback(_read_too_much)
return d return d
@ -1110,7 +1139,7 @@ class SystemTest(SystemTestMixin, RunBinTahoeMixin, unittest.TestCase):
return connected return connected
d.addCallback(lambda ign: self.poll(_has_helper)) d.addCallback(lambda ign: self.poll(_has_helper))
HELPER_DATA = "Data that needs help to upload" * 1000 HELPER_DATA = b"Data that needs help to upload" * 1000
def _upload_with_helper(res): def _upload_with_helper(res):
u = upload.Data(HELPER_DATA, convergence=convergence) u = upload.Data(HELPER_DATA, convergence=convergence)
d = self.extra_node.upload(u) d = self.extra_node.upload(u)
@ -1144,7 +1173,7 @@ class SystemTest(SystemTestMixin, RunBinTahoeMixin, unittest.TestCase):
d.addCallback(fireEventually) d.addCallback(fireEventually)
def _upload_resumable(res): def _upload_resumable(res):
DATA = "Data that needs help to upload and gets interrupted" * 1000 DATA = b"Data that needs help to upload and gets interrupted" * 1000
u1 = CountingDataUploadable(DATA, convergence=convergence) u1 = CountingDataUploadable(DATA, convergence=convergence)
u2 = CountingDataUploadable(DATA, convergence=convergence) u2 = CountingDataUploadable(DATA, convergence=convergence)
@ -1266,7 +1295,9 @@ class SystemTest(SystemTestMixin, RunBinTahoeMixin, unittest.TestCase):
s = stats["stats"] s = stats["stats"]
self.failUnlessEqual(s["storage_server.accepting_immutable_shares"], 1) self.failUnlessEqual(s["storage_server.accepting_immutable_shares"], 1)
c = stats["counters"] c = stats["counters"]
self.failUnless("storage_server.allocate" in c) # Probably this should be Unicode eventually? But we haven't ported
# stats code yet.
self.failUnless(b"storage_server.allocate" in c)
d.addCallback(_grab_stats) d.addCallback(_grab_stats)
return d return d
@ -1287,7 +1318,7 @@ class SystemTest(SystemTestMixin, RunBinTahoeMixin, unittest.TestCase):
assert pieces[-5].startswith("client") assert pieces[-5].startswith("client")
client_num = int(pieces[-5][-1]) client_num = int(pieces[-5][-1])
storage_index_s = pieces[-1] storage_index_s = pieces[-1]
storage_index = si_a2b(storage_index_s) storage_index = si_a2b(storage_index_s.encode("ascii"))
for sharename in filenames: for sharename in filenames:
shnum = int(sharename) shnum = int(sharename)
filename = os.path.join(dirpath, sharename) filename = os.path.join(dirpath, sharename)
@ -1320,7 +1351,7 @@ class SystemTest(SystemTestMixin, RunBinTahoeMixin, unittest.TestCase):
elif which == "signature": elif which == "signature":
signature = self.flip_bit(signature) signature = self.flip_bit(signature)
elif which == "share_hash_chain": elif which == "share_hash_chain":
nodenum = share_hash_chain.keys()[0] nodenum = list(share_hash_chain.keys())[0]
share_hash_chain[nodenum] = self.flip_bit(share_hash_chain[nodenum]) share_hash_chain[nodenum] = self.flip_bit(share_hash_chain[nodenum])
elif which == "block_hash_tree": elif which == "block_hash_tree":
block_hash_tree[-1] = self.flip_bit(block_hash_tree[-1]) block_hash_tree[-1] = self.flip_bit(block_hash_tree[-1])
@ -1343,11 +1374,11 @@ class SystemTest(SystemTestMixin, RunBinTahoeMixin, unittest.TestCase):
def test_mutable(self): def test_mutable(self):
self.basedir = "system/SystemTest/test_mutable" self.basedir = "system/SystemTest/test_mutable"
DATA = "initial contents go here." # 25 bytes % 3 != 0 DATA = b"initial contents go here." # 25 bytes % 3 != 0
DATA_uploadable = MutableData(DATA) DATA_uploadable = MutableData(DATA)
NEWDATA = "new contents yay" NEWDATA = b"new contents yay"
NEWDATA_uploadable = MutableData(NEWDATA) NEWDATA_uploadable = MutableData(NEWDATA)
NEWERDATA = "this is getting old" NEWERDATA = b"this is getting old"
NEWERDATA_uploadable = MutableData(NEWERDATA) NEWERDATA_uploadable = MutableData(NEWERDATA)
d = self.set_up_nodes() d = self.set_up_nodes()
@ -1396,7 +1427,7 @@ class SystemTest(SystemTestMixin, RunBinTahoeMixin, unittest.TestCase):
self.failUnless(" share_hash_chain: " in output) self.failUnless(" share_hash_chain: " in output)
self.failUnless(" block_hash_tree: 1 nodes\n" in output) self.failUnless(" block_hash_tree: 1 nodes\n" in output)
expected = (" verify-cap: URI:SSK-Verifier:%s:" % expected = (" verify-cap: URI:SSK-Verifier:%s:" %
base32.b2a(storage_index)) str(base32.b2a(storage_index), "ascii"))
self.failUnless(expected in output) self.failUnless(expected in output)
except unittest.FailTest: except unittest.FailTest:
print() print()
@ -1475,7 +1506,7 @@ class SystemTest(SystemTestMixin, RunBinTahoeMixin, unittest.TestCase):
for (client_num, storage_index, filename, shnum) for (client_num, storage_index, filename, shnum)
in shares ]) in shares ])
assert len(where) == 10 # this test is designed for 3-of-10 assert len(where) == 10 # this test is designed for 3-of-10
for shnum, filename in where.items(): for shnum, filename in list(where.items()):
# shares 7,8,9 are left alone. read will check # shares 7,8,9 are left alone. read will check
# (share_hash_chain, block_hash_tree, share_data). New # (share_hash_chain, block_hash_tree, share_data). New
# seqnum+R pairs will trigger a check of (seqnum, R, IV, # seqnum+R pairs will trigger a check of (seqnum, R, IV,
@ -1525,9 +1556,9 @@ class SystemTest(SystemTestMixin, RunBinTahoeMixin, unittest.TestCase):
def _check_empty_file(res): def _check_empty_file(res):
# make sure we can create empty files, this usually screws up the # make sure we can create empty files, this usually screws up the
# segsize math # segsize math
d1 = self.clients[2].create_mutable_file(MutableData("")) d1 = self.clients[2].create_mutable_file(MutableData(b""))
d1.addCallback(lambda newnode: newnode.download_best_version()) d1.addCallback(lambda newnode: newnode.download_best_version())
d1.addCallback(lambda res: self.failUnlessEqual("", res)) d1.addCallback(lambda res: self.failUnlessEqual(b"", res))
return d1 return d1
d.addCallback(_check_empty_file) d.addCallback(_check_empty_file)
@ -1550,7 +1581,7 @@ class SystemTest(SystemTestMixin, RunBinTahoeMixin, unittest.TestCase):
return d return d
def flip_bit(self, good): def flip_bit(self, good):
return good[:-1] + chr(ord(good[-1]) ^ 0x01) return good[:-1] + byteschr(ord(good[-1:]) ^ 0x01)
def mangle_uri(self, gooduri): def mangle_uri(self, gooduri):
# change the key, which changes the storage index, which means we'll # change the key, which changes the storage index, which means we'll
@ -1571,6 +1602,7 @@ class SystemTest(SystemTestMixin, RunBinTahoeMixin, unittest.TestCase):
# the key, which should cause the download to fail the post-download # the key, which should cause the download to fail the post-download
# plaintext_hash check. # plaintext_hash check.
@skipIf(PY3, "Python 3 web support hasn't happened yet.")
def test_filesystem(self): def test_filesystem(self):
self.basedir = "system/SystemTest/test_filesystem" self.basedir = "system/SystemTest/test_filesystem"
self.data = LARGE_DATA self.data = LARGE_DATA
@ -1632,7 +1664,7 @@ class SystemTest(SystemTestMixin, RunBinTahoeMixin, unittest.TestCase):
d1.addCallback(self.log, "publish finished") d1.addCallback(self.log, "publish finished")
def _stash_uri(filenode): def _stash_uri(filenode):
self.uri = filenode.get_uri() self.uri = filenode.get_uri()
assert isinstance(self.uri, str), (self.uri, filenode) assert isinstance(self.uri, bytes), (self.uri, filenode)
d1.addCallback(_stash_uri) d1.addCallback(_stash_uri)
return d1 return d1
d.addCallback(_made_subdir1) d.addCallback(_made_subdir1)
@ -1650,7 +1682,7 @@ class SystemTest(SystemTestMixin, RunBinTahoeMixin, unittest.TestCase):
return res return res
def _do_publish_private(self, res): def _do_publish_private(self, res):
self.smalldata = "sssh, very secret stuff" self.smalldata = b"sssh, very secret stuff"
ut = upload.Data(self.smalldata, convergence=None) ut = upload.Data(self.smalldata, convergence=None)
d = self.clients[0].create_dirnode() d = self.clients[0].create_dirnode()
d.addCallback(self.log, "GOT private directory") d.addCallback(self.log, "GOT private directory")
@ -1737,7 +1769,7 @@ class SystemTest(SystemTestMixin, RunBinTahoeMixin, unittest.TestCase):
d1.addCallback(lambda res: self.shouldFail2(NotWriteableError, "mkdir(nope)", None, dirnode.create_subdirectory, u"nope")) d1.addCallback(lambda res: self.shouldFail2(NotWriteableError, "mkdir(nope)", None, dirnode.create_subdirectory, u"nope"))
d1.addCallback(self.log, "doing add_file(ro)") d1.addCallback(self.log, "doing add_file(ro)")
ut = upload.Data("I will disappear, unrecorded and unobserved. The tragedy of my demise is made more poignant by its silence, but this beauty is not for you to ever know.", convergence="99i-p1x4-xd4-18yc-ywt-87uu-msu-zo -- completely and totally unguessable string (unless you read this)") ut = upload.Data(b"I will disappear, unrecorded and unobserved. The tragedy of my demise is made more poignant by its silence, but this beauty is not for you to ever know.", convergence=b"99i-p1x4-xd4-18yc-ywt-87uu-msu-zo -- completely and totally unguessable string (unless you read this)")
d1.addCallback(lambda res: self.shouldFail2(NotWriteableError, "add_file(nope)", None, dirnode.add_file, u"hope", ut)) d1.addCallback(lambda res: self.shouldFail2(NotWriteableError, "add_file(nope)", None, dirnode.add_file, u"hope", ut))
d1.addCallback(self.log, "doing get(ro)") d1.addCallback(self.log, "doing get(ro)")
@ -1801,7 +1833,7 @@ class SystemTest(SystemTestMixin, RunBinTahoeMixin, unittest.TestCase):
"largest-directory-children": 3, "largest-directory-children": 3,
"largest-immutable-file": 112, "largest-immutable-file": 112,
} }
for k,v in expected.iteritems(): for k,v in list(expected.items()):
self.failUnlessEqual(stats[k], v, self.failUnlessEqual(stats[k], v,
"stats[%s] was %s, not %s" % "stats[%s] was %s, not %s" %
(k, stats[k], v)) (k, stats[k], v))
@ -1850,33 +1882,33 @@ class SystemTest(SystemTestMixin, RunBinTahoeMixin, unittest.TestCase):
return do_http("get", self.webish_url + urlpath) return do_http("get", self.webish_url + urlpath)
def POST(self, urlpath, use_helper=False, **fields): def POST(self, urlpath, use_helper=False, **fields):
sepbase = "boogabooga" sepbase = b"boogabooga"
sep = "--" + sepbase sep = b"--" + sepbase
form = [] form = []
form.append(sep) form.append(sep)
form.append('Content-Disposition: form-data; name="_charset"') form.append(b'Content-Disposition: form-data; name="_charset"')
form.append('') form.append(b'')
form.append('UTF-8') form.append(b'UTF-8')
form.append(sep) form.append(sep)
for name, value in fields.iteritems(): for name, value in fields.items():
if isinstance(value, tuple): if isinstance(value, tuple):
filename, value = value filename, value = value
form.append('Content-Disposition: form-data; name="%s"; ' form.append(b'Content-Disposition: form-data; name="%s"; '
'filename="%s"' % (name, filename.encode("utf-8"))) b'filename="%s"' % (name, filename.encode("utf-8")))
else: else:
form.append('Content-Disposition: form-data; name="%s"' % name) form.append(b'Content-Disposition: form-data; name="%s"' % name)
form.append('') form.append(b'')
form.append(str(value)) form.append(b"%s" % (value,))
form.append(sep) form.append(sep)
form[-1] += "--" form[-1] += b"--"
body = "" body = b""
headers = {} headers = {}
if fields: if fields:
body = "\r\n".join(form) + "\r\n" body = b"\r\n".join(form) + b"\r\n"
headers["content-type"] = "multipart/form-data; boundary=%s" % sepbase headers["content-type"] = "multipart/form-data; boundary=%s" % str(sepbase, "ascii")
return self.POST2(urlpath, body, headers, use_helper) return self.POST2(urlpath, body, headers, use_helper)
def POST2(self, urlpath, body="", headers={}, use_helper=False): def POST2(self, urlpath, body=b"", headers={}, use_helper=False):
if use_helper: if use_helper:
url = self.helper_webish_url + urlpath url = self.helper_webish_url + urlpath
else: else:
@ -1884,7 +1916,7 @@ class SystemTest(SystemTestMixin, RunBinTahoeMixin, unittest.TestCase):
return do_http("post", url, data=body, headers=headers) return do_http("post", url, data=body, headers=headers)
def _test_web(self, res): def _test_web(self, res):
public = "uri/" + self._root_directory_uri public = "uri/" + str(self._root_directory_uri, "ascii")
d = self.GET("") d = self.GET("")
def _got_welcome(page): def _got_welcome(page):
html = page.replace('\n', ' ') html = page.replace('\n', ' ')
@ -1893,7 +1925,7 @@ class SystemTest(SystemTestMixin, RunBinTahoeMixin, unittest.TestCase):
"I didn't see the right '%s' message in:\n%s" % (connected_re, page)) "I didn't see the right '%s' message in:\n%s" % (connected_re, page))
# nodeids/tubids don't have any regexp-special characters # nodeids/tubids don't have any regexp-special characters
nodeid_re = r'<th>Node ID:</th>\s*<td title="TubID: %s">%s</td>' % ( nodeid_re = r'<th>Node ID:</th>\s*<td title="TubID: %s">%s</td>' % (
self.clients[0].get_long_tubid(), self.clients[0].get_long_nodeid()) self.clients[0].get_long_tubid(), str(self.clients[0].get_long_nodeid(), "ascii"))
self.failUnless(re.search(nodeid_re, html), self.failUnless(re.search(nodeid_re, html),
"I didn't see the right '%s' message in:\n%s" % (nodeid_re, page)) "I didn't see the right '%s' message in:\n%s" % (nodeid_re, page))
self.failUnless("Helper: 0 active uploads" in page) self.failUnless("Helper: 0 active uploads" in page)
@ -1954,7 +1986,7 @@ class SystemTest(SystemTestMixin, RunBinTahoeMixin, unittest.TestCase):
# upload a file with PUT # upload a file with PUT
d.addCallback(self.log, "about to try PUT") d.addCallback(self.log, "about to try PUT")
d.addCallback(lambda res: self.PUT(public + "/subdir3/new.txt", d.addCallback(lambda res: self.PUT(public + "/subdir3/new.txt",
"new.txt contents")) b"new.txt contents"))
d.addCallback(lambda res: self.GET(public + "/subdir3/new.txt")) d.addCallback(lambda res: self.GET(public + "/subdir3/new.txt"))
d.addCallback(self.failUnlessEqual, "new.txt contents") d.addCallback(self.failUnlessEqual, "new.txt contents")
# and again with something large enough to use multiple segments, # and again with something large enough to use multiple segments,
@ -1965,23 +1997,23 @@ class SystemTest(SystemTestMixin, RunBinTahoeMixin, unittest.TestCase):
c.encoding_params['happy'] = 1 c.encoding_params['happy'] = 1
d.addCallback(_new_happy_semantics) d.addCallback(_new_happy_semantics)
d.addCallback(lambda res: self.PUT(public + "/subdir3/big.txt", d.addCallback(lambda res: self.PUT(public + "/subdir3/big.txt",
"big" * 500000)) # 1.5MB b"big" * 500000)) # 1.5MB
d.addCallback(lambda res: self.GET(public + "/subdir3/big.txt")) d.addCallback(lambda res: self.GET(public + "/subdir3/big.txt"))
d.addCallback(lambda res: self.failUnlessEqual(len(res), 1500000)) d.addCallback(lambda res: self.failUnlessEqual(len(res), 1500000))
# can we replace files in place? # can we replace files in place?
d.addCallback(lambda res: self.PUT(public + "/subdir3/new.txt", d.addCallback(lambda res: self.PUT(public + "/subdir3/new.txt",
"NEWER contents")) b"NEWER contents"))
d.addCallback(lambda res: self.GET(public + "/subdir3/new.txt")) d.addCallback(lambda res: self.GET(public + "/subdir3/new.txt"))
d.addCallback(self.failUnlessEqual, "NEWER contents") d.addCallback(self.failUnlessEqual, "NEWER contents")
# test unlinked POST # test unlinked POST
d.addCallback(lambda res: self.POST("uri", t="upload", d.addCallback(lambda res: self.POST("uri", t=b"upload",
file=("new.txt", "data" * 10000))) file=("new.txt", b"data" * 10000)))
# and again using the helper, which exercises different upload-status # and again using the helper, which exercises different upload-status
# display code # display code
d.addCallback(lambda res: self.POST("uri", use_helper=True, t="upload", d.addCallback(lambda res: self.POST("uri", use_helper=True, t=b"upload",
file=("foo.txt", "data2" * 10000))) file=("foo.txt", b"data2" * 10000)))
# check that the status page exists # check that the status page exists
d.addCallback(lambda res: self.GET("status")) d.addCallback(lambda res: self.GET("status"))
@ -2105,7 +2137,7 @@ class SystemTest(SystemTestMixin, RunBinTahoeMixin, unittest.TestCase):
# exercise some of the diagnostic tools in runner.py # exercise some of the diagnostic tools in runner.py
# find a share # find a share
for (dirpath, dirnames, filenames) in os.walk(unicode(self.basedir)): for (dirpath, dirnames, filenames) in os.walk(ensure_text(self.basedir)):
if "storage" not in dirpath: if "storage" not in dirpath:
continue continue
if not filenames: if not filenames:
@ -2119,7 +2151,7 @@ class SystemTest(SystemTestMixin, RunBinTahoeMixin, unittest.TestCase):
filename = os.path.join(dirpath, filenames[0]) filename = os.path.join(dirpath, filenames[0])
# peek at the magic to see if it is a chk share # peek at the magic to see if it is a chk share
magic = open(filename, "rb").read(4) magic = open(filename, "rb").read(4)
if magic == '\x00\x00\x00\x01': if magic == b'\x00\x00\x00\x01':
break break
else: else:
self.fail("unable to find any uri_extension files in %r" self.fail("unable to find any uri_extension files in %r"
@ -2152,7 +2184,6 @@ class SystemTest(SystemTestMixin, RunBinTahoeMixin, unittest.TestCase):
# 'find-shares' tool # 'find-shares' tool
sharedir, shnum = os.path.split(filename) sharedir, shnum = os.path.split(filename)
storagedir, storage_index_s = os.path.split(sharedir) storagedir, storage_index_s = os.path.split(sharedir)
storage_index_s = str(storage_index_s)
nodedirs = [self.getdir("client%d" % i) for i in range(self.numclients)] nodedirs = [self.getdir("client%d" % i) for i in range(self.numclients)]
rc,out,err = yield run_cli("debug", "find-shares", storage_index_s, rc,out,err = yield run_cli("debug", "find-shares", storage_index_s,
*nodedirs) *nodedirs)
@ -2176,7 +2207,7 @@ class SystemTest(SystemTestMixin, RunBinTahoeMixin, unittest.TestCase):
# allmydata.control (mostly used for performance tests) # allmydata.control (mostly used for performance tests)
c0 = self.clients[0] c0 = self.clients[0]
control_furl_file = c0.config.get_private_path("control.furl") control_furl_file = c0.config.get_private_path("control.furl")
control_furl = open(control_furl_file, "r").read().strip() control_furl = ensure_str(open(control_furl_file, "r").read().strip())
# it doesn't really matter which Tub we use to connect to the client, # it doesn't really matter which Tub we use to connect to the client,
# so let's just use our IntroducerNode's # so let's just use our IntroducerNode's
d = self.introducer.tub.getReference(control_furl) d = self.introducer.tub.getReference(control_furl)
@ -2208,7 +2239,7 @@ class SystemTest(SystemTestMixin, RunBinTahoeMixin, unittest.TestCase):
# sure that works, before we add other aliases. # sure that works, before we add other aliases.
root_file = os.path.join(client0_basedir, "private", "root_dir.cap") root_file = os.path.join(client0_basedir, "private", "root_dir.cap")
f = open(root_file, "w") f = open(root_file, "wb")
f.write(private_uri) f.write(private_uri)
f.close() f.close()
@ -2551,6 +2582,7 @@ class SystemTest(SystemTestMixin, RunBinTahoeMixin, unittest.TestCase):
return d return d
@skipIf(PY3, "Python 3 CLI support hasn't happened yet.")
def test_filesystem_with_cli_in_subprocess(self): def test_filesystem_with_cli_in_subprocess(self):
# We do this in a separate test so that test_filesystem doesn't skip if we can't run bin/tahoe. # We do this in a separate test so that test_filesystem doesn't skip if we can't run bin/tahoe.
@ -2574,12 +2606,12 @@ class SystemTest(SystemTestMixin, RunBinTahoeMixin, unittest.TestCase):
out, err, rc_or_sig = res out, err, rc_or_sig = res
self.failUnlessEqual(rc_or_sig, 0, str(res)) self.failUnlessEqual(rc_or_sig, 0, str(res))
if check_stderr: if check_stderr:
self.failUnlessEqual(err, "") self.failUnlessEqual(err, b"")
d.addCallback(_run_in_subprocess, "create-alias", "newalias") d.addCallback(_run_in_subprocess, "create-alias", "newalias")
d.addCallback(_check_succeeded) d.addCallback(_check_succeeded)
STDIN_DATA = "This is the file to upload from stdin." STDIN_DATA = b"This is the file to upload from stdin."
d.addCallback(_run_in_subprocess, "put", "-", "newalias:tahoe-file", stdin=STDIN_DATA) d.addCallback(_run_in_subprocess, "put", "-", "newalias:tahoe-file", stdin=STDIN_DATA)
d.addCallback(_check_succeeded, check_stderr=False) d.addCallback(_check_succeeded, check_stderr=False)
@ -2601,7 +2633,7 @@ class SystemTest(SystemTestMixin, RunBinTahoeMixin, unittest.TestCase):
return d return d
def _test_checker(self, res): def _test_checker(self, res):
ut = upload.Data("too big to be literal" * 200, convergence=None) ut = upload.Data(b"too big to be literal" * 200, convergence=None)
d = self._personal_node.add_file(u"big file", ut) d = self._personal_node.add_file(u"big file", ut)
d.addCallback(lambda res: self._personal_node.check(Monitor())) d.addCallback(lambda res: self._personal_node.check(Monitor()))

View File

@ -33,7 +33,9 @@ if six.PY3:
class IDLib(unittest.TestCase): class IDLib(unittest.TestCase):
def test_nodeid_b2a(self): def test_nodeid_b2a(self):
self.failUnlessEqual(idlib.nodeid_b2a(b"\x00"*20), "a"*32) result = idlib.nodeid_b2a(b"\x00"*20)
self.assertEqual(result, "a"*32)
self.assertIsInstance(result, str)
class MyList(list): class MyList(list):

View File

@ -25,7 +25,8 @@ def assert_soup_has_tag_with_attributes(testcase, soup, tag_name, attrs):
tags = soup.find_all(tag_name) tags = soup.find_all(tag_name)
for tag in tags: for tag in tags:
if all(v in tag.attrs.get(k, []) for k, v in attrs.items()): if all(v in tag.attrs.get(k, []) for k, v in attrs.items()):
return # we found every attr in this tag; done # we found every attr in this tag; done
return tag
testcase.fail( testcase.fail(
u"No <{}> tags contain attributes: {}".format(tag_name, attrs) u"No <{}> tags contain attributes: {}".format(tag_name, attrs)
) )

View File

@ -1,7 +1,13 @@
from mock import Mock
import time import time
from urllib import (
quote,
)
from bs4 import (
BeautifulSoup,
)
from twisted.trial import unittest from twisted.trial import unittest
from twisted.web.template import Tag from twisted.web.template import Tag
from twisted.web.test.requesthelper import DummyRequest from twisted.web.test.requesthelper import DummyRequest
@ -16,6 +22,9 @@ from ...util.connection_status import ConnectionStatus
from allmydata.web.root import URIHandler from allmydata.web.root import URIHandler
from allmydata.client import _Client from allmydata.client import _Client
from .common import (
assert_soup_has_tag_with_attributes,
)
from ..common_web import ( from ..common_web import (
render, render,
) )
@ -30,28 +39,37 @@ class RenderSlashUri(unittest.TestCase):
""" """
def setUp(self): def setUp(self):
self.client = Mock() self.client = object()
self.res = URIHandler(self.client) self.res = URIHandler(self.client)
def test_valid(self): def test_valid_query_redirect(self):
""" """
A valid capbility does not result in error A syntactically valid capability given in the ``uri`` query argument
results in a redirect.
""" """
query_args = {b"uri": [ cap = (
b"URI:CHK:nt2xxmrccp7sursd6yh2thhcky:" b"URI:CHK:nt2xxmrccp7sursd6yh2thhcky:"
b"mukesarwdjxiyqsjinbfiiro6q7kgmmekocxfjcngh23oxwyxtzq:2:5:5874882" b"mukesarwdjxiyqsjinbfiiro6q7kgmmekocxfjcngh23oxwyxtzq:2:5:5874882"
]} )
query_args = {b"uri": [cap]}
response_body = self.successResultOf( response_body = self.successResultOf(
render(self.res, query_args), render(self.res, query_args),
) )
self.assertNotEqual( soup = BeautifulSoup(response_body, 'html5lib')
response_body, tag = assert_soup_has_tag_with_attributes(
"Invalid capability", self,
soup,
u"meta",
{u"http-equiv": "refresh"},
)
self.assertIn(
quote(cap, safe=""),
tag.attrs.get(u"content"),
) )
def test_invalid(self): def test_invalid(self):
""" """
A (trivially) invalid capbility is an error A syntactically invalid capbility results in an error.
""" """
query_args = {b"uri": [b"not a capability"]} query_args = {b"uri": [b"not a capability"]}
response_body = self.successResultOf( response_body = self.successResultOf(

View File

@ -97,6 +97,7 @@ PORTED_MODULES = [
"allmydata.util.happinessutil", "allmydata.util.happinessutil",
"allmydata.util.hashutil", "allmydata.util.hashutil",
"allmydata.util.humanreadable", "allmydata.util.humanreadable",
"allmydata.util.idlib",
"allmydata.util.iputil", "allmydata.util.iputil",
"allmydata.util.jsonbytes", "allmydata.util.jsonbytes",
"allmydata.util.log", "allmydata.util.log",
@ -168,6 +169,12 @@ PORTED_TEST_MODULES = [
"allmydata.test.test_storage", "allmydata.test.test_storage",
"allmydata.test.test_storage_client", "allmydata.test.test_storage_client",
"allmydata.test.test_storage_web", "allmydata.test.test_storage_web",
# Only partially ported, test_filesystem_with_cli_in_subprocess and
# test_filesystem methods aren't ported yet, should be done once CLI and
# web are ported respectively.
"allmydata.test.test_system",
"allmydata.test.test_time_format", "allmydata.test.test_time_format",
"allmydata.test.test_upload", "allmydata.test.test_upload",
"allmydata.test.test_uri", "allmydata.test.test_uri",

View File

@ -133,6 +133,9 @@ def a2b(cs):
""" """
@param cs the base-32 encoded data (as bytes) @param cs the base-32 encoded data (as bytes)
""" """
# Workaround Future newbytes issues by converting to real bytes on Python 2:
if hasattr(cs, "__native__"):
cs = cs.__native__()
precondition(could_be_base32_encoded(cs), "cs is required to be possibly base32 encoded data.", cs=cs) precondition(could_be_base32_encoded(cs), "cs is required to be possibly base32 encoded data.", cs=cs)
precondition(isinstance(cs, bytes), cs) precondition(isinstance(cs, bytes), cs)

View File

@ -1,9 +1,29 @@
"""
Ported to Python 3.
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from future.utils import 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 six import ensure_text
from foolscap import base32 from foolscap import base32
def nodeid_b2a(nodeid): def nodeid_b2a(nodeid):
# we display nodeids using the same base32 alphabet that Foolscap uses """
return base32.encode(nodeid) We display nodeids using the same base32 alphabet that Foolscap uses.
Returns a Unicode string.
"""
return ensure_text(base32.encode(nodeid))
def shortnodeid_b2a(nodeid): def shortnodeid_b2a(nodeid):
"""
Short version of nodeid_b2a() output, Unicode string.
"""
return nodeid_b2a(nodeid)[:8] return nodeid_b2a(nodeid)[:8]