From 2c99294a4faa0e0f94fdb53ed60954e1e0724955 Mon Sep 17 00:00:00 2001
From: Daira Hopwood <daira@jacaranda.org>
Date: Tue, 28 Jul 2015 23:41:13 +0100
Subject: [PATCH] Don't show scary diagnostic warnings from
 --version[-and-path] (corrected). refs ticket:2436

The previous version would incorrectly add to the output of
get_package_versions_string each time it was called.

Signed-off-by: Daira Hopwood <daira@jacaranda.org>
---
 src/allmydata/__init__.py          | 68 +++++++++++++++---------------
 src/allmydata/test/test_import.py  | 15 ++++---
 src/allmydata/test/test_version.py | 27 ++++++++++--
 3 files changed, 66 insertions(+), 44 deletions(-)

diff --git a/src/allmydata/__init__.py b/src/allmydata/__init__.py
index e66538f7..0b0c5f81 100644
--- a/src/allmydata/__init__.py
+++ b/src/allmydata/__init__.py
@@ -178,7 +178,7 @@ def extract_openssl_version(ssl_module):
 def get_package_versions_and_locations():
     import warnings
     from _auto_deps import package_imports, global_deprecation_messages, deprecation_messages, \
-        runtime_warning_messages, warning_imports
+        runtime_warning_messages, warning_imports, ignorable
 
     def package_dir(srcfile):
         return os.path.dirname(os.path.dirname(os.path.normcase(os.path.realpath(srcfile))))
@@ -246,7 +246,26 @@ def get_package_versions_and_locations():
         elif pkgname == 'OpenSSL':
             packages.append( (pkgname, get_openssl_version()) )
 
-    return packages
+    cross_check_errors = []
+
+    if not hasattr(sys, 'frozen'):
+        import pkg_resources
+        from _auto_deps import install_requires
+
+        pkg_resources_vers_and_locs = dict([(p.project_name.lower(), (str(p.version), p.location))
+                                            for p in pkg_resources.require(install_requires)])
+
+        imported_packages = set([p.lower() for (p, _) in packages])
+        extra_packages = []
+
+        for pr_name, (pr_ver, pr_loc) in pkg_resources_vers_and_locs.iteritems():
+            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
 
 
 def check_requirement(req, vers_and_locs):
@@ -300,25 +319,10 @@ def match_requirement(req, reqlist, actualver):
     return True
 
 
-_vers_and_locs_list = get_package_versions_and_locations()
-
-
-def cross_check_pkg_resources_versus_import():
-    """This function returns a list of errors due to any failed cross-checks."""
-
-    import pkg_resources
-    from _auto_deps import install_requires
-
-    pkg_resources_vers_and_locs = dict([(p.project_name.lower(), (str(p.version), p.location))
-                                        for p in pkg_resources.require(install_requires)])
-
-    return cross_check(pkg_resources_vers_and_locs, _vers_and_locs_list)
-
-
 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, ignorable
+    from _auto_deps import not_import_versionable
 
     errors = []
     not_pkg_resourceable = ['python', 'platform', __appname__.lower(), 'openssl']
@@ -376,16 +380,12 @@ def cross_check(pkg_resources_vers_and_locs, imported_vers_and_locs_list):
                                               "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))
 
-    imported_packages = set([p.lower() for (p, _) in imported_vers_and_locs_list])
-    extra_vers_and_locs_list = []
-    for pr_name, (pr_ver, pr_loc) in pkg_resources_vers_and_locs.iteritems():
-        if pr_name not in imported_packages and pr_name not in ignorable:
-            errors.append("Warning: dependency %r (version %r) found by pkg_resources not found by import."
-                          % (pr_name, pr_ver))
-
     return errors
 
 
+_vers_and_locs_list, _cross_check_errors = get_package_versions_and_locations()
+
+
 def get_error_string(errors, debug=False):
     from allmydata._auto_deps import install_requires
 
@@ -406,7 +406,7 @@ def check_all_requirements():
 
     from allmydata._auto_deps import install_requires
 
-    errors = []
+    fatal_errors = []
 
     # We require at least 2.6 on all platforms.
     # (On Python 3, we'll have failed long before this point.)
@@ -415,18 +415,18 @@ def check_all_requirements():
             version_string = ".".join(map(str, sys.version_info))
         except Exception:
             version_string = repr(sys.version_info)
-        errors.append("Tahoe-LAFS currently requires Python v2.6 or greater (but less than v3), not %s"
-                      % (version_string,))
+        fatal_errors.append("Tahoe-LAFS currently requires Python v2.6 or greater (but less than v3), not %s"
+                            % (version_string,))
 
     vers_and_locs = dict(_vers_and_locs_list)
     for requirement in install_requires:
         try:
             check_requirement(requirement, vers_and_locs)
         except (ImportError, PackagingError), e:
-            errors.append("%s: %s" % (e.__class__.__name__, e))
+            fatal_errors.append("%s: %s" % (e.__class__.__name__, e))
 
-    if errors:
-        raise PackagingError(get_error_string(errors, debug=True))
+    if fatal_errors:
+        raise PackagingError(get_error_string(fatal_errors + _cross_check_errors, debug=True))
 
 check_all_requirements()
 
@@ -449,9 +449,7 @@ def get_package_versions_string(show_paths=False, debug=False):
 
     output = "\n".join(res) + "\n"
 
-    if not hasattr(sys, 'frozen'):
-        errors = cross_check_pkg_resources_versus_import()
-        if errors:
-            output += get_error_string(errors, debug=debug)
+    if _cross_check_errors:
+        output += get_error_string(_cross_check_errors, debug=debug)
 
     return output
diff --git a/src/allmydata/test/test_import.py b/src/allmydata/test/test_import.py
index 287c4f5a..b34012d6 100644
--- a/src/allmydata/test/test_import.py
+++ b/src/allmydata/test/test_import.py
@@ -8,19 +8,22 @@ import __builtin__
 
 class T(unittest.TestCase):
     def test_report_import_error(self):
+        marker = "wheeeyo"
         real_import_func = __import__
         def raiseIE_from_this_particular_func(name, *args):
             if name == "foolscap":
-                marker = "wheeeyo"
                 raise ImportError(marker + " foolscap cant be imported")
             else:
                 return real_import_func(name, *args)
 
         # Let's run as little code as possible with __import__ patched.
         patcher = MonkeyPatcher((__builtin__, '__import__', raiseIE_from_this_particular_func))
-        vers_and_locs = patcher.runWithPatches(allmydata.get_package_versions_and_locations)
+        vers_and_locs, errors = patcher.runWithPatches(allmydata.get_package_versions_and_locations)
 
-        for (pkgname, stuff) in vers_and_locs:
-            if pkgname == 'foolscap':
-                self.failUnless('wheeeyo' in str(stuff[2]), stuff)
-                self.failUnless('raiseIE_from_this_particular_func' in str(stuff[2]), stuff)
+        foolscap_stuffs = [stuff for (pkg, stuff) in vers_and_locs if pkg == 'foolscap']
+        self.failUnlessEqual(len(foolscap_stuffs), 1)
+        comment = str(foolscap_stuffs[0][2])
+        self.failUnlessIn(marker, comment)
+        self.failUnlessIn('raiseIE_from_this_particular_func', comment)
+
+        self.failUnless([e for e in errors if "dependency \'foolscap\' could not be imported" in e])
diff --git a/src/allmydata/test/test_version.py b/src/allmydata/test/test_version.py
index 9433e365..8c605303 100644
--- a/src/allmydata/test/test_version.py
+++ b/src/allmydata/test/test_version.py
@@ -1,9 +1,12 @@
 
+import sys
+import pkg_resources
 from pkg_resources import Requirement
 
 from twisted.trial import unittest
 
-from allmydata import check_requirement, cross_check, extract_openssl_version, PackagingError
+from allmydata import check_requirement, cross_check, get_package_versions_and_locations, \
+     extract_openssl_version, PackagingError
 from allmydata.util.verlib import NormalizedVersion as V, \
                                   IrrationalVersionError, \
                                   suggest_normalized_version as suggest
@@ -69,6 +72,25 @@ class CheckRequirement(unittest.TestCase):
         for pkg, ver in vers_and_locs.items():
             self.failIf(ver[0] in Requirement.parse(req), str((ver, req)))
 
+    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_ticket_1355(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.
@@ -89,8 +111,7 @@ class CheckRequirement(unittest.TestCase):
         self.failUnlessEqual(res, [])
 
         res = cross_check({"foo": ("unparseable", "")}, [])
-        self.failUnlessEqual(len(res), 1)
-        self.failUnlessIn("not found by import", res[0])
+        self.failUnlessEqual(res, [])
 
         res = cross_check({"argparse": ("unparseable", "")}, [])
         self.failUnlessEqual(len(res), 0)
-- 
2.45.2