Delete allmydata.version_checks and related functionality

It is not Tahoe-LAFS' job to manage package installation in this way.
Instead, we can declare our dependencies in setup.py and rely on installation
management tools and packagers to create a suitable execution environment.

Making this statement in the past required going much further out on a limb
than it does today.  This code has served its purpose and can now be retired.
This commit is contained in:
Jean-Paul Calderone 2020-11-23 15:10:18 -05:00
parent 224085c139
commit c694e8c7e2
16 changed files with 34 additions and 978 deletions

View File

@ -512,7 +512,6 @@ def create_introducer_clients(config, main_tub, _introducer_factory=None):
config.nickname,
str(allmydata.__full_version__),
str(_Client.OLDEST_SUPPORTED_VERSION),
list(node.get_app_versions()),
partial(_sequencer, config),
introducer_cache_filepath,
)

View File

@ -24,7 +24,7 @@ class IntroducerClient(service.Service, Referenceable):
def __init__(self, tub, introducer_furl,
nickname, my_version, oldest_supported,
app_versions, sequencer, cache_filepath):
sequencer, cache_filepath):
self._tub = tub
self.introducer_furl = introducer_furl
@ -32,13 +32,12 @@ class IntroducerClient(service.Service, Referenceable):
self._nickname = nickname
self._my_version = my_version
self._oldest_supported = oldest_supported
self._app_versions = app_versions
self._sequencer = sequencer
self._cache_filepath = cache_filepath
self._my_subscriber_info = { "version": 0,
"nickname": self._nickname,
"app-versions": self._app_versions,
"app-versions": [],
"my-version": self._my_version,
"oldest-supported": self._oldest_supported,
}
@ -190,7 +189,7 @@ class IntroducerClient(service.Service, Referenceable):
# "seqnum" and "nonce" will be populated with new values in
# publish(), each time we make a change
"nickname": self._nickname,
"app-versions": self._app_versions,
"app-versions": [],
"my-version": self._my_version,
"oldest-supported": self._oldest_supported,

View File

@ -28,9 +28,10 @@ import configparser
from twisted.python import log as twlog
from twisted.application import service
from twisted.python.failure import Failure
from foolscap.api import Tub, app_versions
from foolscap.api import Tub
import foolscap.logging.log
from allmydata.version_checks import get_package_versions, get_package_versions_string
from allmydata.util import log
from allmydata.util import fileutil, iputil
from allmydata.util.assertutil import _assert
@ -38,6 +39,10 @@ from allmydata.util.fileutil import abspath_expanduser_unicode
from allmydata.util.encodingutil import get_filesystem_encoding, quote_output
from allmydata.util import configutil
from . import (
__full_version__,
)
def _common_valid_config():
return configutil.ValidConfiguration({
"connections": (
@ -78,14 +83,6 @@ def _common_valid_config():
),
})
# Add our application versions to the data that Foolscap's LogPublisher
# reports. Foolscap requires native strings.
for thing, things_version in list(get_package_versions().items()):
app_versions.add_version(
ensure_str(thing),
None if things_version is None else ensure_str(things_version),
)
# group 1 will be addr (dotted quad string), group 3 if any will be portnum (string)
ADDR_RE = re.compile("^([1-9][0-9]*\.[1-9][0-9]*\.[1-9][0-9]*\.[1-9][0-9]*)(:([1-9][0-9]*))?$")
@ -231,13 +228,6 @@ def config_from_string(basedir, portnumfile, config_str, _valid_config=None):
return _Config(parser, portnumfile, basedir, fname)
def get_app_versions():
"""
:returns: dict of versions important to Foolscap
"""
return dict(app_versions.versions)
def _error_about_old_config_files(basedir, generated_files):
"""
If any old configuration files are detected, raise
@ -762,7 +752,7 @@ class Node(service.MultiService):
if self.control_tub is not None:
self.control_tub.setServiceParent(self)
self.log("Node constructed. " + get_package_versions_string())
self.log("Node constructed. " + __full_version__)
iputil.increase_rlimits()
def _is_tub_listening(self):

View File

@ -7,7 +7,6 @@ import six
from twisted.python import usage
from twisted.internet import defer, task, threads
from allmydata.version_checks import get_package_versions_string
from allmydata.scripts.common import get_default_nodedir
from allmydata.scripts import debug, create_node, cli, \
stats_gatherer, admin, tahoe_daemonize, tahoe_start, \
@ -19,6 +18,10 @@ from allmydata.util.eliotutil import (
eliot_logging_service,
)
from .. import (
__full_version__,
)
_default_nodedir = get_default_nodedir()
NODEDIR_HELP = ("Specify which Tahoe node directory should be used. The "
@ -77,12 +80,10 @@ class Options(usage.Options):
]
def opt_version(self):
print(get_package_versions_string(debug=True), file=self.stdout)
print(__full_version__, file=self.stdout)
self.no_command_needed = True
def opt_version_and_path(self):
print(get_package_versions_string(show_paths=True, debug=True), file=self.stdout)
self.no_command_needed = True
opt_version_and_path = opt_version
opt_eliot_destination = opt_eliot_destination
opt_help_eliot_destinations = opt_help_eliot_destinations

View File

@ -1266,7 +1266,7 @@ class Options(ReallyEqualMixin, unittest.TestCase):
# "tahoe --version" dumps text to stdout and exits
stdout = StringIO()
self.failUnlessRaises(SystemExit, self.parse, ["--version"], stdout)
self.failUnlessIn(allmydata.__appname__ + ":", stdout.getvalue())
self.failUnlessIn(allmydata.__full_version__, stdout.getvalue())
# but "tahoe SUBCOMMAND --version" should be rejected
self.failUnlessRaises(usage.UsageError, self.parse,
["start", "--version"])

View File

@ -110,7 +110,6 @@ class MemoryIntroducerClient(object):
nickname = attr.ib()
my_version = attr.ib()
oldest_supported = attr.ib()
app_versions = attr.ib()
sequencer = attr.ib()
cache_filepath = attr.ib()

View File

@ -41,9 +41,6 @@ import allmydata.util.log
from allmydata.node import OldConfigError, UnescapedHashError, create_node_dir
from allmydata.frontends.auth import NeedRootcapLookupScheme
from allmydata.version_checks import (
get_package_versions_string,
)
from allmydata import client
from allmydata.storage_client import (
StorageClientConfig,
@ -621,8 +618,6 @@ class Basic(testutil.ReallyEqualMixin, unittest.TestCase):
self.failIfEqual(str(allmydata.__version__), "unknown")
self.failUnless("." in str(allmydata.__full_version__),
"non-numeric version in '%s'" % allmydata.__version__)
all_versions = get_package_versions_string()
self.failUnless(allmydata.__appname__ in all_versions)
# also test stats
stats = c.get_stats()
self.failUnless("node.uptime" in stats)

View File

@ -155,7 +155,7 @@ class ServiceMixin(object):
class Introducer(ServiceMixin, AsyncTestCase):
def test_create(self):
ic = IntroducerClient(None, "introducer.furl", u"my_nickname",
"my_version", "oldest_version", {}, fakeseq,
"my_version", "oldest_version", fakeseq,
FilePath(self.mktemp()))
self.failUnless(isinstance(ic, IntroducerClient))
@ -188,13 +188,13 @@ class Client(AsyncTestCase):
def test_duplicate_receive_v2(self):
ic1 = IntroducerClient(None,
"introducer.furl", u"my_nickname",
"ver23", "oldest_version", {}, fakeseq,
"ver23", "oldest_version", fakeseq,
FilePath(self.mktemp()))
# we use a second client just to create a different-looking
# announcement
ic2 = IntroducerClient(None,
"introducer.furl", u"my_nickname",
"ver24","oldest_version",{}, fakeseq,
"ver24","oldest_version",fakeseq,
FilePath(self.mktemp()))
announcements = []
def _received(key_s, ann):
@ -298,7 +298,7 @@ class Server(AsyncTestCase):
i = IntroducerService()
ic1 = IntroducerClient(None,
"introducer.furl", u"my_nickname",
"ver23", "oldest_version", {}, realseq,
"ver23", "oldest_version", realseq,
FilePath(self.mktemp()))
furl1 = "pb://62ubehyunnyhzs7r6vdonnm2hpi52w6y@127.0.0.1:36106/gydnp"
@ -396,7 +396,7 @@ class Queue(SystemTestMixin, AsyncTestCase):
tub2 = Tub()
tub2.setServiceParent(self.parent)
c = IntroducerClient(tub2, ifurl,
u"nickname", "version", "oldest", {}, fakeseq,
u"nickname", "version", "oldest", fakeseq,
FilePath(self.mktemp()))
furl1 = "pb://onug64tu@127.0.0.1:123/short" # base32("short")
private_key, _ = ed25519.create_signing_keypair()
@ -477,7 +477,7 @@ class SystemTest(SystemTestMixin, AsyncTestCase):
c = IntroducerClient(tub, self.introducer_furl,
NICKNAME % str(i),
"version", "oldest",
{"component": "component-v1"}, fakeseq,
fakeseq,
FilePath(self.mktemp()))
received_announcements[c] = {}
def got(key_s_or_tubid, ann, announcements):
@ -737,9 +737,8 @@ class ClientInfo(AsyncTestCase):
def test_client_v2(self):
introducer = IntroducerService()
tub = introducer_furl = None
app_versions = {"whizzy": "fizzy"}
client_v2 = IntroducerClient(tub, introducer_furl, NICKNAME % u"v2",
"my_version", "oldest", app_versions,
"my_version", "oldest",
fakeseq, FilePath(self.mktemp()))
#furl1 = "pb://62ubehyunnyhzs7r6vdonnm2hpi52w6y@127.0.0.1:0/swissnum"
#ann_s = make_ann_t(client_v2, furl1, None, 10)
@ -751,7 +750,6 @@ class ClientInfo(AsyncTestCase):
self.failUnlessEqual(len(subs), 1)
s0 = subs[0]
self.failUnlessEqual(s0.service_name, "storage")
self.failUnlessEqual(s0.app_versions, app_versions)
self.failUnlessEqual(s0.nickname, NICKNAME % u"v2")
self.failUnlessEqual(s0.version, "my_version")
@ -760,9 +758,8 @@ class Announcements(AsyncTestCase):
def test_client_v2_signed(self):
introducer = IntroducerService()
tub = introducer_furl = None
app_versions = {"whizzy": "fizzy"}
client_v2 = IntroducerClient(tub, introducer_furl, u"nick-v2",
"my_version", "oldest", app_versions,
"my_version", "oldest",
fakeseq, FilePath(self.mktemp()))
furl1 = "pb://62ubehyunnyhzs7r6vdonnm2hpi52w6y@127.0.0.1:0/swissnum"
@ -776,7 +773,6 @@ class Announcements(AsyncTestCase):
self.failUnlessEqual(len(a), 1)
self.assertThat(a[0].canary, Is(canary0))
self.failUnlessEqual(a[0].index, ("storage", public_key_str))
self.failUnlessEqual(a[0].announcement["app-versions"], app_versions)
self.failUnlessEqual(a[0].nickname, u"nick-v2")
self.failUnlessEqual(a[0].service_name, "storage")
self.failUnlessEqual(a[0].version, "my_version")
@ -854,7 +850,7 @@ class Announcements(AsyncTestCase):
# test loading
yield flushEventualQueue()
ic2 = IntroducerClient(None, "introducer.furl", u"my_nickname",
"my_version", "oldest_version", {}, fakeseq,
"my_version", "oldest_version", fakeseq,
ic._cache_filepath)
announcements = {}
def got(key_s, ann):
@ -954,7 +950,7 @@ class NonV1Server(SystemTestMixin, AsyncTestCase):
tub.setServiceParent(self.parent)
listenOnUnused(tub)
c = IntroducerClient(tub, self.introducer_furl,
u"nickname-client", "version", "oldest", {},
u"nickname-client", "version", "oldest",
fakeseq, FilePath(self.mktemp()))
announcements = {}
def got(key_s, ann):
@ -1027,7 +1023,6 @@ class Signatures(SyncTestCase):
u"fake_nick",
"0.0.0",
"1.2.3",
{},
(0, u"i am a nonce"),
"invalid",
)

View File

@ -46,7 +46,6 @@ from allmydata.node import (
_tub_portlocation,
formatTimeTahoeStyle,
UnescapedHashError,
get_app_versions,
)
from allmydata.introducer.server import create_introducer
from allmydata import client
@ -101,16 +100,6 @@ class TestCase(testutil.SignalMixin, unittest.TestCase):
# conflict with another service to prove it.
self._available_port = 22
def test_application_versions(self):
"""
Application versions should all have the same type, the native string.
This test is due to the Foolscap limitations, if Foolscap is fixed or
removed it can be deleted.
"""
app_types = set(type(o) for o in get_app_versions())
self.assertEqual(app_types, {native_str})
def _test_location(
self,
expected_addresses,

View File

@ -12,7 +12,6 @@ from twisted.internet import reactor
from twisted.python import usage
from twisted.internet.defer import (
inlineCallbacks,
returnValue,
DeferredList,
)
from twisted.python.filepath import FilePath
@ -20,12 +19,9 @@ from twisted.python.runtime import (
platform,
)
from allmydata.util import fileutil, pollmixin
from allmydata.util.encodingutil import unicode_to_argv, unicode_to_output, \
get_filesystem_encoding
from allmydata.util.encodingutil import unicode_to_argv, unicode_to_output
from allmydata.test import common_util
from allmydata.version_checks import normalized_version
import allmydata
from allmydata import __appname__
from .common_util import parse_cli, run_cli
from .cli_node_api import (
CLINodeAPI,
@ -58,17 +54,6 @@ rootdir = get_root_from_file(srcfile)
class RunBinTahoeMixin(object):
@inlineCallbacks
def find_import_location(self):
res = yield self.run_bintahoe(["--version-and-path"])
out, err, rc_or_sig = res
self.assertEqual(rc_or_sig, 0, res)
lines = out.splitlines()
tahoe_pieces = lines[0].split()
self.assertEqual(tahoe_pieces[0], "%s:" % (__appname__,), (tahoe_pieces, res))
returnValue(tahoe_pieces[-1].strip("()"))
def run_bintahoe(self, args, stdin=None, python_options=[], env=None):
command = sys.executable
argv = python_options + ["-m", "allmydata.scripts.runner"] + args
@ -86,64 +71,6 @@ class RunBinTahoeMixin(object):
class BinTahoe(common_util.SignalMixin, unittest.TestCase, RunBinTahoeMixin):
@inlineCallbacks
def test_the_right_code(self):
# running "tahoe" in a subprocess should find the same code that
# holds this test file, else something is weird
test_path = os.path.dirname(os.path.dirname(os.path.normcase(os.path.realpath(srcfile))))
bintahoe_import_path = yield self.find_import_location()
same = (bintahoe_import_path == test_path)
if not same:
msg = ("My tests and my 'tahoe' executable are using different paths.\n"
"tahoe: %r\n"
"tests: %r\n"
"( according to the test source filename %r)\n" %
(bintahoe_import_path, test_path, srcfile))
if (not isinstance(rootdir, unicode) and
rootdir.decode(get_filesystem_encoding(), 'replace') != rootdir):
msg += ("However, this may be a false alarm because the import path\n"
"is not representable in the filesystem encoding.")
raise unittest.SkipTest(msg)
else:
msg += "Please run the tests in a virtualenv that includes both the Tahoe-LAFS library and the 'tahoe' executable."
self.fail(msg)
def test_path(self):
d = self.run_bintahoe(["--version-and-path"])
def _cb(res):
out, err, rc_or_sig = res
self.failUnlessEqual(rc_or_sig, 0, str(res))
# Fail unless the __appname__ package is *this* version *and*
# was loaded from *this* source directory.
required_verstr = str(allmydata.__version__)
self.failIfEqual(required_verstr, "unknown",
"We don't know our version, because this distribution didn't come "
"with a _version.py and 'setup.py update_version' hasn't been run.")
srcdir = os.path.dirname(os.path.dirname(os.path.normcase(os.path.realpath(srcfile))))
info = repr((res, allmydata.__appname__, required_verstr, srcdir))
appverpath = out.split(')')[0]
(appverfull, path) = appverpath.split('] (')
(appver, comment) = appverfull.split(' [')
(branch, full_version) = comment.split(': ')
(app, ver) = appver.split(': ')
self.failUnlessEqual(app, allmydata.__appname__, info)
norm_ver = normalized_version(ver)
norm_required = normalized_version(required_verstr)
self.failUnlessEqual(norm_ver, norm_required, info)
self.failUnlessEqual(path, srcdir, info)
self.failUnlessEqual(branch, allmydata.branch)
self.failUnlessEqual(full_version, allmydata.full_version)
d.addCallback(_cb)
return d
def test_unicode_arguments_and_output(self):
tricky = u"\u2621"
try:
@ -165,8 +92,8 @@ class BinTahoe(common_util.SignalMixin, unittest.TestCase, RunBinTahoeMixin):
d = self.run_bintahoe(["--version"], python_options=["-t"])
def _cb(res):
out, err, rc_or_sig = res
self.failUnlessEqual(rc_or_sig, 0, str(res))
self.failUnless(out.startswith(allmydata.__appname__+':'), str(res))
self.assertEqual(rc_or_sig, 0, str(res))
self.assertTrue(out.startswith(allmydata.__appname__ + '/'), str(res))
d.addCallback(_cb)
return d

View File

@ -1,372 +0,0 @@
"""
Tests for allmydata.util.verlib and allmydata.version_checks.
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 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 sys
import pkg_resources
from operator import (
setitem,
)
from twisted.trial import unittest
from allmydata.version_checks import (
_cross_check as cross_check,
_extract_openssl_version as extract_openssl_version,
_get_package_versions_and_locations as get_package_versions_and_locations,
_vers_and_locs_list,
get_package_versions,
get_package_versions_string,
_Dependency,
)
from allmydata.util.verlib import NormalizedVersion as V, \
IrrationalVersionError, \
suggest_normalized_version as suggest
class MockSSL(object):
SSLEAY_VERSION = 0
SSLEAY_CFLAGS = 2
def __init__(self, version, compiled_without_heartbeats=False):
self.opts = {
self.SSLEAY_VERSION: version,
self.SSLEAY_CFLAGS: compiled_without_heartbeats and 'compiler: gcc -DOPENSSL_NO_HEARTBEATS'
or 'compiler: gcc',
}
def SSLeay_version(self, which):
return self.opts[which]
class CheckRequirement(unittest.TestCase):
def test_packages_from_pkg_resources(self):
if hasattr(sys, 'frozen'):
raise unittest.SkipTest("This test doesn't apply to frozen builds.")
class MockPackage(object):
def __init__(self, project_name, version, location):
self.project_name = project_name
self.version = version
self.location = location
def call_pkg_resources_require(*args):
return [MockPackage("Foo", "1.0", "/path")]
self.patch(pkg_resources, 'require', call_pkg_resources_require)
(packages, errors) = get_package_versions_and_locations()
self.assertIn(
_Dependency("foo", "1.0", "/path", "according to pkg_resources"),
packages,
)
self.failIfEqual(errors, [])
self.failUnlessEqual([e for e in errors if "was not found by pkg_resources" not in e], [])
def test_cross_check_return_type(self):
"""
``cross_check`` returns a ``list`` of ``str``.
"""
self._cross_check_return_type(
{"distribute": ("unparseable", "path")},
[_Dependency("setuptools", "1.0", "path", None)],
)
self._cross_check_return_type(
{},
[_Dependency("foo", "1.0", "path", None)],
)
self._cross_check_return_type(
{},
[_Dependency("foo", "1.0", "path", None)],
)
self._cross_check_return_type(
{"foo": ("unparseable", "path")},
[_Dependency("foo", None, None, None)],
)
self._cross_check_return_type(
{"foo": ("1.2.3", "path")},
[_Dependency("foo", "unknown", None, None)],
)
def _cross_check_return_type(self, vers_and_locs, imported_vers_and_locs):
res = cross_check(vers_and_locs, imported_vers_and_locs)
self.assertIsInstance(res, list)
self.assertTrue(len(res) > 0)
for v in res:
self.assertIsInstance(v, str)
def test_cross_check_unparseable_versions(self):
# The bug in #1355 is triggered when a version string from either pkg_resources or import
# is not parseable at all by normalized_version.
res = cross_check({"foo": ("unparseable", "")}, [_Dependency("foo", "1.0", "", None)])
self.failUnlessEqual(res, [])
res = cross_check({"foo": ("1.0", "")}, [_Dependency("foo", "unparseable", "", None)])
self.failUnlessEqual(res, [])
res = cross_check({"foo": ("unparseable", "")}, [_Dependency("foo", "unparseable", "", None)])
self.failUnlessEqual(res, [])
def test_cross_check(self):
res = cross_check({}, [])
self.failUnlessEqual(res, [])
res = cross_check({}, [_Dependency("tahoe-lafs", "1.0", "", "blah")])
self.failUnlessEqual(res, [])
res = cross_check({"foo": ("unparseable", "")}, [])
self.failUnlessEqual(res, [])
res = cross_check({"argparse": ("unparseable", "")}, [])
self.failUnlessEqual(res, [])
res = cross_check({}, [_Dependency("foo", "unparseable", "", None)])
self.failUnlessEqual(len(res), 1)
self.assertTrue(("version 'unparseable'" in res[0]) or ("version u'unparseable'" in res[0]))
self.failUnlessIn("was not found by pkg_resources", res[0])
res = cross_check({"distribute": ("1.0", "/somewhere")}, [_Dependency("setuptools", "2.0", "/somewhere", "distribute")])
self.failUnlessEqual(res, [])
res = cross_check({"distribute": ("1.0", "/somewhere")}, [_Dependency("setuptools", "2.0", "/somewhere", None)])
self.failUnlessEqual(len(res), 1)
self.failUnlessIn("location mismatch", res[0])
res = cross_check({"distribute": ("1.0", "/somewhere")}, [_Dependency("setuptools", "2.0", "/somewhere_different", None)])
self.failUnlessEqual(len(res), 1)
self.failUnlessIn("location mismatch", res[0])
res = cross_check({"zope.interface": ("1.0", "")}, [_Dependency("zope.interface", "unknown", "", None)])
self.failUnlessEqual(res, [])
res = cross_check({"zope.interface": ("unknown", "")}, [_Dependency("zope.interface", "unknown", "", None)])
self.failUnlessEqual(res, [])
res = cross_check({"foo": ("1.0", "")}, [_Dependency("foo", "unknown", "", None)])
self.failUnlessEqual(len(res), 1)
self.failUnlessIn("could not find a version number", res[0])
res = cross_check({"foo": ("unknown", "")}, [_Dependency("foo", "unknown", "", None)])
self.failUnlessEqual(res, [])
# When pkg_resources and import both find a package, there is only a warning if both
# the version and the path fail to match.
res = cross_check({"foo": ("1.0", "/somewhere")}, [_Dependency("foo", "2.0", "/somewhere", None)])
self.failUnlessEqual(res, [])
res = cross_check({"foo": ("1.0", "/somewhere")}, [_Dependency("foo", "1.0", "/somewhere_different", None)])
self.failUnlessEqual(res, [])
res = cross_check({"foo": ("1.0-r123", "/somewhere")}, [_Dependency("foo", "1.0.post123", "/somewhere_different", None)])
self.failUnlessEqual(res, [])
res = cross_check({"foo": ("1.0", "/somewhere")}, [_Dependency("foo", "2.0", "/somewhere_different", None)])
self.failUnlessEqual(len(res), 1)
self.assertTrue(("but version '2.0'" in res[0]) or ("but version u'2.0'" in res[0]))
def test_extract_openssl_version(self):
self.failUnlessEqual(extract_openssl_version(MockSSL("")),
("", None, None))
self.failUnlessEqual(extract_openssl_version(MockSSL("NotOpenSSL a.b.c foo")),
("NotOpenSSL", None, "a.b.c foo"))
self.failUnlessEqual(extract_openssl_version(MockSSL("OpenSSL a.b.c")),
("a.b.c", None, None))
self.failUnlessEqual(extract_openssl_version(MockSSL("OpenSSL 1.0.1e 11 Feb 2013")),
("1.0.1e", None, "11 Feb 2013"))
self.failUnlessEqual(extract_openssl_version(MockSSL("OpenSSL 1.0.1e 11 Feb 2013", compiled_without_heartbeats=True)),
("1.0.1e", None, "11 Feb 2013, no heartbeats"))
# based on https://bitbucket.org/tarek/distutilsversion/src/17df9a7d96ef/test_verlib.py
class VersionTestCase(unittest.TestCase):
versions = ((V('1.0'), '1.0'),
(V('1.1'), '1.1'),
(V('1.2.3'), '1.2.3'),
(V('1.2'), '1.2'),
(V('1.2.3a4'), '1.2.3a4'),
(V('1.2c4'), '1.2c4'),
(V('1.2.3.4'), '1.2.3.4'),
(V('1.2.3.4.0b3'), '1.2.3.4b3'),
(V('1.2.0.0.0'), '1.2'),
(V('1.0.dev345'), '1.0.dev345'),
(V('1.0.post456.dev623'), '1.0.post456.dev623'))
def test_basic_versions(self):
for v, s in self.versions:
self.failUnlessEqual(str(v), s)
def test_from_parts(self):
for v, s in self.versions:
parts = v.parts
v2 = V.from_parts(*parts)
self.failUnlessEqual(v, v2)
self.failUnlessEqual(str(v), str(v2))
def test_irrational_versions(self):
irrational = ('1', '1.2a', '1.2.3b', '1.02', '1.2a03',
'1.2a3.04', '1.2.dev.2', '1.2dev', '1.2.dev',
'1.2.dev2.post2', '1.2.post2.dev3.post4')
for s in irrational:
self.failUnlessRaises(IrrationalVersionError, V, s)
def test_comparison(self):
self.failUnlessRaises(TypeError, lambda: V('1.2.0') == '1.2')
self.failUnlessEqual(V('1.2.0'), V('1.2'))
self.failIfEqual(V('1.2.0'), V('1.2.3'))
self.failUnless(V('1.2.0') < V('1.2.3'))
self.failUnless(V('1.0') > V('1.0b2'))
self.failUnless(V('1.0') > V('1.0c2') > V('1.0c1') > V('1.0b2') > V('1.0b1')
> V('1.0a2') > V('1.0a1'))
self.failUnless(V('1.0.0') > V('1.0.0c2') > V('1.0.0c1') > V('1.0.0b2') > V('1.0.0b1')
> V('1.0.0a2') > V('1.0.0a1'))
self.failUnless(V('1.0') < V('1.0.post456.dev623'))
self.failUnless(V('1.0.post456.dev623') < V('1.0.post456') < V('1.0.post1234'))
self.failUnless(V('1.0a1')
< V('1.0a2.dev456')
< V('1.0a2')
< V('1.0a2.1.dev456') # e.g. need to do a quick post release on 1.0a2
< V('1.0a2.1')
< V('1.0b1.dev456')
< V('1.0b2')
< V('1.0c1')
< V('1.0c2.dev456')
< V('1.0c2')
< V('1.0.dev7')
< V('1.0.dev18')
< V('1.0.dev456')
< V('1.0.dev1234')
< V('1.0')
< V('1.0.post456.dev623') # development version of a post release
< V('1.0.post456'))
def test_suggest_normalized_version(self):
self.failUnlessEqual(suggest('1.0'), '1.0')
self.failUnlessEqual(suggest('1.0-alpha1'), '1.0a1')
self.failUnlessEqual(suggest('1.0c2'), '1.0c2')
self.failUnlessEqual(suggest('walla walla washington'), None)
self.failUnlessEqual(suggest('2.4c1'), '2.4c1')
# from setuptools
self.failUnlessEqual(suggest('0.4a1.r10'), '0.4a1.post10')
self.failUnlessEqual(suggest('0.7a1dev-r66608'), '0.7a1.dev66608')
self.failUnlessEqual(suggest('0.6a9.dev-r41475'), '0.6a9.dev41475')
self.failUnlessEqual(suggest('2.4preview1'), '2.4c1')
self.failUnlessEqual(suggest('2.4pre1') , '2.4c1')
self.failUnlessEqual(suggest('2.1-rc2'), '2.1c2')
# from pypi
self.failUnlessEqual(suggest('0.1dev'), '0.1.dev0')
self.failUnlessEqual(suggest('0.1.dev'), '0.1.dev0')
# we want to be able to parse Twisted
# development versions are like post releases in Twisted
self.failUnlessEqual(suggest('9.0.0+r2363'), '9.0.0.post2363')
# pre-releases are using markers like "pre1"
self.failUnlessEqual(suggest('9.0.0pre1'), '9.0.0c1')
# we want to be able to parse Tcl-TK
# they use "p1" "p2" for post releases
self.failUnlessEqual(suggest('1.4p1'), '1.4.post1')
# from darcsver
self.failUnlessEqual(suggest('1.8.1-r4956'), '1.8.1.post4956')
# zetuptoolz
self.failUnlessEqual(suggest('0.6c16dev3'), '0.6c16.dev3')
class T(unittest.TestCase):
def test_report_import_error(self):
"""
get_package_versions_and_locations reports a dependency if a dependency
cannot be imported.
"""
# Make sure we don't leave the system in a bad state.
self.addCleanup(
lambda foolscap=sys.modules["foolscap"]: setitem(
sys.modules,
"foolscap",
foolscap,
),
)
# Make it look like Foolscap isn't installed.
sys.modules["foolscap"] = None
vers_and_locs, errors = get_package_versions_and_locations()
foolscap_stuffs = [pkg for pkg in vers_and_locs if pkg.name == 'foolscap']
self.failUnlessEqual(len(foolscap_stuffs), 1)
self.failUnless([e for e in errors if "\'foolscap\' could not be imported" in e])
class VersAndLocsTests(unittest.TestCase):
"""
Tests for ``_vers_and_locs_list``.
"""
def test_name_types(self):
"""
``_vers_and_locs_list`` is a list of ``_Dependency`` instances with
``name`` attributes which are instances of ``str``.
"""
for pkg in _vers_and_locs_list:
self.assertIsInstance(pkg.name, type(u""))
def test_version_types(self):
"""
``_vers_and_locs_list`` is a list of ``_Dependency`` instances with
``version`` attributes which are instances of ``str`` or
``NoneType``..
"""
for pkg in _vers_and_locs_list:
self.assertIsInstance(pkg.version, (type(u""), type(None)))
class GetPackageVersionsTests(unittest.TestCase):
"""
Tests for ``get_package_versions``.
"""
def test_key_types(self):
"""
Keys in the return value of ``get_package_versions`` are instances of
``str``
"""
for name, version in get_package_versions().items():
self.assertIsInstance(name, type(u""))
def test_value_types(self):
"""
Values in the return value of ``get_package_versions`` are instances of
``str`` or ``NoneType``.
"""
for name, version in get_package_versions().items():
self.assertIsInstance(version, (type(u""), type(None)))
class GetPackageVersionsStringTests(unittest.TestCase):
"""
Tests for ``get_package_versions_string``.
"""
def test_type(self):
"""
The return value of ``get_package_versions_string`` is an instance of
``str``.
"""
self.assertIsInstance(
get_package_versions_string(),
type(u""),
)

View File

@ -127,7 +127,7 @@ class IntroducerWeb(unittest.TestCase):
assert_soup_has_text(
self,
soup,
u"%s: %s" % (allmydata.__appname__, allmydata.__version__),
allmydata.__full_version__,
)
assert_soup_has_text(self, soup, u"no peers!")
assert_soup_has_text(self, soup, u"subscribers!")

View File

@ -159,5 +159,4 @@ PORTED_TEST_MODULES = [
"allmydata.test.test_upload",
"allmydata.test.test_uri",
"allmydata.test.test_util",
"allmydata.test.test_version",
]

View File

@ -1,463 +0,0 @@
"""
Produce reports about the versions of Python software in use by Tahoe-LAFS
for debugging and auditing purposes.
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 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
__all__ = [
"PackagingError",
"get_package_versions",
"get_package_versions_string",
"normalized_version",
]
import attr
import os, platform, re, sys, traceback, pkg_resources
import six
import distro
from . import (
__appname__,
full_version,
branch,
)
from .util import (
verlib,
)
if getattr(sys, 'frozen', None):
# "Frozen" python interpreters (i.e., standalone executables
# generated by PyInstaller and other, similar utilities) run
# independently of a traditional setuptools-based packaging
# environment, and so pkg_resources.get_distribution() cannot be
# used in such cases to gather a list of requirements at runtime
# (and because a frozen application is one that has already been
# "installed", an empty list suffices here).
_INSTALL_REQUIRES = []
else:
_INSTALL_REQUIRES = list(
str(req)
for req
in pkg_resources.get_distribution(__appname__).requires()
)
class PackagingError(EnvironmentError):
"""
Raised when there is an error in packaging of Tahoe-LAFS or its
dependencies which makes it impossible to proceed safely.
"""
def get_package_versions():
"""
:return {str: str|NoneType}: A mapping from dependency name to dependency version
for all discernable Tahoe-LAFS' dependencies.
"""
return {
dep.name: dep.version
for dep
in _vers_and_locs_list
}
def get_package_versions_string(show_paths=False, debug=False):
"""
:return str: A string describing the version of all Tahoe-LAFS
dependencies.
"""
version_format = "{}: {}".format
comment_format = " [{}]".format
path_format = " ({})".format
res = []
for dep in _vers_and_locs_list:
info = version_format(dep.name, dep.version)
if dep.comment:
info = info + comment_format(dep.comment)
if show_paths:
info = info + path_format(dep.location)
res.append(info)
output = "\n".join(res) + "\n"
if _cross_check_errors:
output += _get_error_string(_cross_check_errors, debug=debug)
return output
_distributor_id_cmdline_re = re.compile("(?:Distributor ID:)\s*(.*)", re.I)
_release_cmdline_re = re.compile("(?:Release:)\s*(.*)", re.I)
_distributor_id_file_re = re.compile("(?:DISTRIB_ID\s*=)\s*(.*)", re.I)
_release_file_re = re.compile("(?:DISTRIB_RELEASE\s*=)\s*(.*)", re.I)
_distname = None
_version = None
def normalized_version(verstr, what=None):
try:
suggested = verlib.suggest_normalized_version(verstr) or verstr
return verlib.NormalizedVersion(suggested)
except verlib.IrrationalVersionError:
raise
except Exception:
cls, value, trace = sys.exc_info()
new_exc = PackagingError("could not parse %s due to %s: %s"
% (what or repr(verstr), cls.__name__, value))
six.reraise(cls, new_exc, trace)
def _get_error_string(errors, debug=False):
msg = "\n%s\n" % ("\n".join(errors),)
if debug:
msg += (
"\n"
"For debugging purposes, the PYTHONPATH was\n"
" %r\n"
"install_requires was\n"
" %r\n"
"sys.path after importing pkg_resources was\n"
" %s\n"
% (
os.environ.get('PYTHONPATH'),
_INSTALL_REQUIRES,
(os.pathsep+"\n ").join(sys.path),
)
)
return msg
def _cross_check(pkg_resources_vers_and_locs, imported_vers_and_locs_list):
"""
This function returns a list of errors due to any failed cross-checks.
:rtype: [str]
"""
from ._auto_deps import not_import_versionable
errors = []
not_pkg_resourceable = ['python', 'platform', __appname__.lower(), 'openssl']
for dep in imported_vers_and_locs_list:
name = dep.name.lower()
imp_ver = dep.version
imp_loc = dep.location
imp_comment = dep.comment
if name not in not_pkg_resourceable:
if name not in pkg_resources_vers_and_locs:
if name == "setuptools" and "distribute" in pkg_resources_vers_and_locs:
pr_ver, pr_loc = pkg_resources_vers_and_locs["distribute"]
if not (os.path.normpath(os.path.realpath(pr_loc)) == os.path.normpath(os.path.realpath(imp_loc))
and imp_comment == "distribute"):
errors.append("Warning: dependency 'setuptools' found to be version %r of 'distribute' from %r "
"by pkg_resources, but 'import setuptools' gave version %r [%s] from %r. "
"A version mismatch is expected, but a location mismatch is not."
% (pr_ver, pr_loc, imp_ver, imp_comment or 'probably *not* distribute', imp_loc))
else:
errors.append("Warning: dependency %r (version %r imported from %r) was not found by pkg_resources."
% (name, imp_ver, imp_loc))
continue
pr_ver, pr_loc = pkg_resources_vers_and_locs[name]
if imp_ver is None and imp_loc is None:
errors.append("Warning: dependency %r could not be imported. pkg_resources thought it should be possible "
"to import version %r from %r.\nThe exception trace was %r."
% (name, pr_ver, pr_loc, imp_comment))
continue
# If the pkg_resources version is identical to the imported version, don't attempt
# to normalize them, since it is unnecessary and may fail (ticket #2499).
if imp_ver != 'unknown' and pr_ver == imp_ver:
continue
try:
pr_normver = normalized_version(pr_ver)
except verlib.IrrationalVersionError:
continue
except Exception as e:
errors.append("Warning: version number %r found for dependency %r by pkg_resources could not be parsed. "
"The version found by import was %r from %r. "
"pkg_resources thought it should be found at %r. "
"The exception was %s: %s"
% (pr_ver, name, imp_ver, imp_loc, pr_loc, e.__class__.__name__, e))
else:
if imp_ver == 'unknown':
if name not in not_import_versionable:
errors.append("Warning: unexpectedly could not find a version number for dependency %r imported from %r. "
"pkg_resources thought it should be version %r at %r."
% (name, imp_loc, pr_ver, pr_loc))
else:
try:
imp_normver = normalized_version(imp_ver)
except verlib.IrrationalVersionError:
continue
except Exception as e:
errors.append("Warning: version number %r found for dependency %r (imported from %r) could not be parsed. "
"pkg_resources thought it should be version %r at %r. "
"The exception was %s: %s"
% (imp_ver, name, imp_loc, pr_ver, pr_loc, e.__class__.__name__, e))
else:
if pr_ver == 'unknown' or (pr_normver != imp_normver):
if not os.path.normpath(os.path.realpath(pr_loc)) == os.path.normpath(os.path.realpath(imp_loc)):
errors.append("Warning: dependency %r found to have version number %r (normalized to %r, from %r) "
"by pkg_resources, but version %r (normalized to %r, from %r) by import."
% (name, pr_ver, str(pr_normver), pr_loc, imp_ver, str(imp_normver), imp_loc))
return errors
def _get_openssl_version():
try:
from OpenSSL import SSL
return _extract_openssl_version(SSL)
except Exception:
return ("unknown", None, None)
def _extract_openssl_version(ssl_module):
openssl_version = ssl_module.SSLeay_version(ssl_module.SSLEAY_VERSION)
if openssl_version.startswith('OpenSSL '):
openssl_version = openssl_version[8 :]
(version, _, comment) = openssl_version.partition(' ')
try:
openssl_cflags = ssl_module.SSLeay_version(ssl_module.SSLEAY_CFLAGS)
if '-DOPENSSL_NO_HEARTBEATS' in openssl_cflags.split(' '):
comment += ", no heartbeats"
except Exception:
pass
return (version, None, comment if comment else None)
def _get_platform():
# Our version of platform.platform(), telling us both less and more than the
# Python Standard Library's version does.
# We omit details such as the Linux kernel version number, but we add a
# more detailed and correct rendition of the Linux distribution and
# distribution-version.
if "linux" in platform.system().lower():
return (
platform.system() + "-" +
"_".join(distro.linux_distribution()[:2]) + "-" +
platform.machine() + "-" +
"_".join([x for x in platform.architecture() if x])
)
else:
return platform.platform()
def _ensure_text_optional(o):
"""
Convert a value to the maybe-Future-ized native string type or pass through
``None`` unchanged.
:type o: NoneType|bytes|str
:rtype: NoneType|str
"""
if o is None:
return None
return six.ensure_text(o)
@attr.s
class _Dependency(object):
"""
A direct or indirect Tahoe-LAFS dependency.
:ivar name: The name of this dependency.
:ivar version: If known, a string giving the version of this dependency.
:ivar location: If known, a string giving the path to this dependency.
:ivar comment: If relevant, some additional free-form information.
"""
name = attr.ib(
converter=six.ensure_text,
validator=attr.validators.instance_of(str),
)
version = attr.ib(
converter=_ensure_text_optional,
validator=attr.validators.optional(attr.validators.instance_of(str)),
)
location = attr.ib(
converter=_ensure_text_optional,
validator=attr.validators.optional(attr.validators.instance_of(str)),
)
comment = attr.ib()
def _get_package_versions_and_locations():
"""
Look up information about the software available to this process.
:return: A two tuple. The first element is a list of ``_Dependency``
instances. The second element is like the value returned by
``_cross_check``.
"""
import warnings
from ._auto_deps import package_imports, global_deprecation_messages, deprecation_messages, \
runtime_warning_messages, warning_imports, ignorable
# pkg_resources.require returns the distribution that pkg_resources attempted to put
# on sys.path, which can differ from the one that we actually import due to #1258,
# or any other bug that causes sys.path to be set up incorrectly. Therefore we
# must import the packages in order to check their versions and paths.
# This is to suppress all UserWarnings and various DeprecationWarnings and RuntimeWarnings
# (listed in _auto_deps.py).
warnings.filterwarnings("ignore", category=UserWarning, append=True)
for msg in global_deprecation_messages + deprecation_messages:
warnings.filterwarnings("ignore", category=DeprecationWarning, message=msg, append=True)
for msg in runtime_warning_messages:
warnings.filterwarnings("ignore", category=RuntimeWarning, message=msg, append=True)
try:
for modulename in warning_imports:
try:
__import__(modulename)
except (ImportError, SyntaxError):
pass
finally:
# Leave suppressions for UserWarnings and global_deprecation_messages active.
for _ in runtime_warning_messages + deprecation_messages:
warnings.filters.pop()
pkg_resources_vers_and_locs = _compute_pkg_resources_vers_and_locs(_INSTALL_REQUIRES)
packages = list(_compute_imported_packages(
[(__appname__, 'allmydata')] + package_imports,
pkg_resources_vers_and_locs,
))
cross_check_errors = []
if len(pkg_resources_vers_and_locs) > 0:
imported_packages = set(dep.name.lower() for dep in packages)
extra_packages = []
for pr_name, (pr_ver, pr_loc) in pkg_resources_vers_and_locs.items():
if pr_name not in imported_packages and pr_name not in ignorable:
extra_packages.append(
_Dependency(
pr_name,
pr_ver,
pr_loc,
"according to pkg_resources",
),
)
cross_check_errors = _cross_check(pkg_resources_vers_and_locs, packages)
packages += extra_packages
return packages, cross_check_errors
def _compute_pkg_resources_vers_and_locs(requires):
"""
Get the ``pkg_resources`` idea of the dependencies for all of the given
requirements.
If the execution context is a frozen interpreter, just return an empty
dictionary.
:param [str] requires: Information about the dependencies of these
requirements strings will be looked up and returned.
:return {str: (str, str)}: A mapping from dependency name to a two-tuple
of dependency version and location.
"""
if not hasattr(sys, 'frozen'):
return {
p.project_name.lower(): (str(p.version), p.location)
for p
in pkg_resources.require(requires)
}
return {}
def _compute_imported_packages(packages, pkg_resources_vers_and_locs):
"""
Get the import system's idea of all of the given packages.
:param packages:
"""
def package_dir(srcfile):
return os.path.dirname(os.path.dirname(os.path.normcase(os.path.realpath(srcfile))))
def get_version(module):
if hasattr(module, '__version__'):
return str(getattr(module, '__version__'))
elif hasattr(module, 'version'):
ver = getattr(module, 'version')
if isinstance(ver, tuple):
return '.'.join(map(str, ver))
else:
return str(ver)
else:
return 'unknown'
for pkgname, modulename in packages:
if modulename:
try:
__import__(modulename)
module = sys.modules[modulename]
except (ImportError, SyntaxError):
etype, emsg, etrace = sys.exc_info()
trace_info = (etype, str(emsg), ([None] + traceback.extract_tb(etrace))[-1])
yield _Dependency(
pkgname,
None,
None,
trace_info,
)
else:
comment = None
if pkgname == __appname__:
comment = "%s: %s" % (branch, full_version)
elif pkgname == 'setuptools' and hasattr(module, '_distribute'):
# distribute does not report its version in any module variables
comment = 'distribute'
ver = get_version(module)
loc = package_dir(module.__file__)
if ver == "unknown" and pkgname in pkg_resources_vers_and_locs:
(pr_ver, pr_loc) = pkg_resources_vers_and_locs[pkgname]
if loc == os.path.normcase(os.path.realpath(pr_loc)):
ver = pr_ver
yield _Dependency(
pkgname,
ver,
loc,
comment,
)
elif pkgname == 'python':
yield _Dependency(
pkgname,
platform.python_version(),
sys.executable,
None,
)
elif pkgname == 'platform':
yield _Dependency(
pkgname,
_get_platform(),
None,
None,
)
elif pkgname == 'OpenSSL':
yield _Dependency(
pkgname,
*_get_openssl_version()
)
_vers_and_locs_list, _cross_check_errors = _get_package_versions_and_locations()

View File

@ -6,7 +6,6 @@ from twisted.python.filepath import FilePath
from twisted.web import static
import allmydata
import json
from allmydata.version_checks import get_package_versions_string
from allmydata.util import idlib
from allmydata.web.common import (
render_time,
@ -89,7 +88,7 @@ class IntroducerRootElement(Element):
self.introducer_service = introducer_service
self.node_data_dict = {
"my_nodeid": idlib.nodeid_b2a(self.introducer_node.nodeid),
"version": get_package_versions_string(),
"version": allmydata.__full_version__,
"import_path": str(allmydata).replace("/", "/ "), # XXX kludge for wrapping
"rendered_at": render_time(time.time()),
}

View File

@ -21,7 +21,6 @@ from twisted.web.template import (
)
import allmydata # to display import path
from allmydata.version_checks import get_package_versions_string
from allmydata.util import log
from allmydata.interfaces import IFileNode
from allmydata.web import (
@ -566,7 +565,7 @@ class RootElement(Element):
@renderer
def version(self, req, tag):
return tag(get_package_versions_string())
return tag(allmydata.__full_version__)
@renderer
def import_path(self, req, tag):