diff --git a/newsfragments/3518.removed b/newsfragments/3518.removed new file mode 100644 index 000000000..460af5142 --- /dev/null +++ b/newsfragments/3518.removed @@ -0,0 +1 @@ +Announcements delivered through the introducer system are no longer automatically annotated with copious information about the Tahoe-LAFS software version nor the versions of its dependencies. diff --git a/src/allmydata/client.py b/src/allmydata/client.py index a768ba354..1306f2831 100644 --- a/src/allmydata/client.py +++ b/src/allmydata/client.py @@ -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, ) diff --git a/src/allmydata/introducer/client.py b/src/allmydata/introducer/client.py index 36adae474..0a6352317 100644 --- a/src/allmydata/introducer/client.py +++ b/src/allmydata/introducer/client.py @@ -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, diff --git a/src/allmydata/node.py b/src/allmydata/node.py index 4725ff813..0036ce03f 100644 --- a/src/allmydata/node.py +++ b/src/allmydata/node.py @@ -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,11 +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), 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]*))?$") @@ -228,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 @@ -759,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): diff --git a/src/allmydata/scripts/runner.py b/src/allmydata/scripts/runner.py index cfd22694b..3436a1b84 100644 --- a/src/allmydata/scripts/runner.py +++ b/src/allmydata/scripts/runner.py @@ -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 diff --git a/src/allmydata/test/cli/test_cli.py b/src/allmydata/test/cli/test_cli.py index 72e4fe69d..7f4f4140e 100644 --- a/src/allmydata/test/cli/test_cli.py +++ b/src/allmydata/test/cli/test_cli.py @@ -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"]) diff --git a/src/allmydata/test/common.py b/src/allmydata/test/common.py index 1cf1d6428..e9ee4f5b1 100644 --- a/src/allmydata/test/common.py +++ b/src/allmydata/test/common.py @@ -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() diff --git a/src/allmydata/test/test_client.py b/src/allmydata/test/test_client.py index 0f0648a4c..54c5be8e5 100644 --- a/src/allmydata/test/test_client.py +++ b/src/allmydata/test/test_client.py @@ -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) diff --git a/src/allmydata/test/test_introducer.py b/src/allmydata/test/test_introducer.py index d99e18c4a..d77b637e3 100644 --- a/src/allmydata/test/test_introducer.py +++ b/src/allmydata/test/test_introducer.py @@ -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", ) diff --git a/src/allmydata/test/test_node.py b/src/allmydata/test/test_node.py index 010030a6f..693183ea4 100644 --- a/src/allmydata/test/test_node.py +++ b/src/allmydata/test/test_node.py @@ -6,7 +6,7 @@ from __future__ import division from __future__ import print_function from __future__ import unicode_literals -from future.utils import PY2, native_str +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 @@ -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, diff --git a/src/allmydata/test/test_runner.py b/src/allmydata/test/test_runner.py index d7fa08a0c..7d614d486 100644 --- a/src/allmydata/test/test_runner.py +++ b/src/allmydata/test/test_runner.py @@ -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 diff --git a/src/allmydata/test/test_version.py b/src/allmydata/test/test_version.py deleted file mode 100644 index f5f92ef9b..000000000 --- a/src/allmydata/test/test_version.py +++ /dev/null @@ -1,275 +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, -) -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.failUnlessIn(("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_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", "")}, [("foo", ("1.0", "", None))]) - self.failUnlessEqual(res, []) - - res = cross_check({"foo": ("1.0", "")}, [("foo", ("unparseable", "", None))]) - self.failUnlessEqual(res, []) - - res = cross_check({"foo": ("unparseable", "")}, [("foo", ("unparseable", "", None))]) - self.failUnlessEqual(res, []) - - def test_cross_check(self): - res = cross_check({}, []) - self.failUnlessEqual(res, []) - - res = cross_check({}, [("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({}, [("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")}, [("setuptools", ("2.0", "/somewhere", "distribute"))]) - self.failUnlessEqual(res, []) - - res = cross_check({"distribute": ("1.0", "/somewhere")}, [("setuptools", ("2.0", "/somewhere", None))]) - self.failUnlessEqual(len(res), 1) - self.failUnlessIn("location mismatch", res[0]) - - res = cross_check({"distribute": ("1.0", "/somewhere")}, [("setuptools", ("2.0", "/somewhere_different", None))]) - self.failUnlessEqual(len(res), 1) - self.failUnlessIn("location mismatch", res[0]) - - res = cross_check({"zope.interface": ("1.0", "")}, [("zope.interface", ("unknown", "", None))]) - self.failUnlessEqual(res, []) - - res = cross_check({"zope.interface": ("unknown", "")}, [("zope.interface", ("unknown", "", None))]) - self.failUnlessEqual(res, []) - - res = cross_check({"foo": ("1.0", "")}, [("foo", ("unknown", "", None))]) - self.failUnlessEqual(len(res), 1) - self.failUnlessIn("could not find a version number", res[0]) - - res = cross_check({"foo": ("unknown", "")}, [("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")}, [("foo", ("2.0", "/somewhere", None))]) - self.failUnlessEqual(res, []) - - res = cross_check({"foo": ("1.0", "/somewhere")}, [("foo", ("1.0", "/somewhere_different", None))]) - self.failUnlessEqual(res, []) - - res = cross_check({"foo": ("1.0-r123", "/somewhere")}, [("foo", ("1.0.post123", "/somewhere_different", None))]) - self.failUnlessEqual(res, []) - - res = cross_check({"foo": ("1.0", "/somewhere")}, [("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 = [stuff for (pkg, stuff) in vers_and_locs if pkg == 'foolscap'] - self.failUnlessEqual(len(foolscap_stuffs), 1) - self.failUnless([e for e in errors if "\'foolscap\' could not be imported" in e]) diff --git a/src/allmydata/test/web/test_introducer.py b/src/allmydata/test/web/test_introducer.py index bf6ef6a4b..929fba507 100644 --- a/src/allmydata/test/web/test_introducer.py +++ b/src/allmydata/test/web/test_introducer.py @@ -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!") diff --git a/src/allmydata/util/_python3.py b/src/allmydata/util/_python3.py index 511ae39d4..f972fa0a1 100644 --- a/src/allmydata/util/_python3.py +++ b/src/allmydata/util/_python3.py @@ -159,5 +159,4 @@ PORTED_TEST_MODULES = [ "allmydata.test.test_upload", "allmydata.test.test_uri", "allmydata.test.test_util", - "allmydata.test.test_version", ] diff --git a/src/allmydata/version_checks.py b/src/allmydata/version_checks.py deleted file mode 100644 index d022055ea..000000000 --- a/src/allmydata/version_checks.py +++ /dev/null @@ -1,334 +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 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 dict([(k, v) for k, (v, l, c) in _vers_and_locs_list]) - -def get_package_versions_string(show_paths=False, debug=False): - res = [] - for p, (v, loc, comment) in _vers_and_locs_list: - info = str(p) + ": " + str(v) - if comment: - info = info + " [%s]" % str(comment) - if show_paths: - info = info + " (%s)" % str(loc) - 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.""" - - from ._auto_deps import not_import_versionable - - errors = [] - not_pkg_resourceable = ['python', 'platform', __appname__.lower(), 'openssl'] - - for name, (imp_ver, imp_loc, imp_comment) in imported_vers_and_locs_list: - name = name.lower() - 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 _get_package_versions_and_locations(): - import warnings - from ._auto_deps import package_imports, global_deprecation_messages, deprecation_messages, \ - runtime_warning_messages, warning_imports, ignorable - - def package_dir(srcfile): - return os.path.dirname(os.path.dirname(os.path.normcase(os.path.realpath(srcfile)))) - - # 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() - - packages = [] - pkg_resources_vers_and_locs = dict() - - if not hasattr(sys, 'frozen'): - pkg_resources_vers_and_locs = { - p.project_name.lower(): (str(p.version), p.location) - for p - in pkg_resources.require(_INSTALL_REQUIRES) - } - - 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 [(__appname__, 'allmydata')] + package_imports: - 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]) - packages.append( (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 - packages.append( (pkgname, (ver, loc, comment)) ) - elif pkgname == 'python': - packages.append( (pkgname, (platform.python_version(), sys.executable, None)) ) - elif pkgname == 'platform': - packages.append( (pkgname, (_get_platform(), None, None)) ) - elif pkgname == 'OpenSSL': - packages.append( (pkgname, _get_openssl_version()) ) - - cross_check_errors = [] - - if len(pkg_resources_vers_and_locs) > 0: - imported_packages = set([p.lower() for (p, _) 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( (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 - - -_vers_and_locs_list, _cross_check_errors = _get_package_versions_and_locations() diff --git a/src/allmydata/web/introweb.py b/src/allmydata/web/introweb.py index f57a5232a..42e353dc1 100644 --- a/src/allmydata/web/introweb.py +++ b/src/allmydata/web/introweb.py @@ -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()), } diff --git a/src/allmydata/web/root.py b/src/allmydata/web/root.py index 91f14bd91..cb5ddc070 100644 --- a/src/allmydata/web/root.py +++ b/src/allmydata/web/root.py @@ -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):