]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blob - src/allmydata/test/test_version.py
Avoid spurious errors when an imported version is consistent with pkg_resources
[tahoe-lafs/tahoe-lafs.git] / src / allmydata / test / test_version.py
1
2 import sys
3 import pkg_resources
4 from pkg_resources import Requirement
5
6 from twisted.trial import unittest
7
8 from allmydata import check_requirement, cross_check, get_package_versions_and_locations, \
9      extract_openssl_version, PackagingError
10 from allmydata.util.verlib import NormalizedVersion as V, \
11                                   IrrationalVersionError, \
12                                   suggest_normalized_version as suggest
13
14
15 class MockSSL(object):
16     SSLEAY_VERSION = 0
17     SSLEAY_CFLAGS = 2
18
19     def __init__(self, version, compiled_without_heartbeats=False):
20         self.opts = {
21             self.SSLEAY_VERSION: version,
22             self.SSLEAY_CFLAGS: compiled_without_heartbeats and 'compiler: gcc -DOPENSSL_NO_HEARTBEATS'
23                                                              or 'compiler: gcc',
24         }
25
26     def SSLeay_version(self, which):
27         return self.opts[which]
28
29
30 class CheckRequirement(unittest.TestCase):
31     def test_check_requirement(self):
32         self._check_success("setuptools >= 0.6c6", {"setuptools": ("0.6", "", None)})
33         self._check_success("setuptools >= 0.6c6", {"setuptools": ("0.6", "", "distribute")})
34         self._check_success("pycrypto >= 2.1.0, != 2.2, != 2.4", {"pycrypto": ("2.1.0", "", None)})
35         self._check_success("pycrypto >= 2.1.0, != 2.2, != 2.4", {"pycrypto": ("2.3.0", "", None)})
36         self._check_success("pycrypto >= 2.1.0, != 2.2, != 2.4", {"pycrypto": ("2.4.1", "", None)})
37         self._check_success("Twisted >= 11.0.0, <= 12.2.0", {"Twisted": ("11.0.0", "", None)})
38         self._check_success("Twisted >= 11.0.0, <= 12.2.0", {"Twisted": ("12.2.0", "", None)})
39
40         self._check_success("zope.interface", {"zope.interface": ("unknown", "", None)})
41         self._check_success("mock", {"mock": ("0.6.0", "", None)})
42         self._check_success("foo >= 1.0", {"foo": ("1.0", "", None), "bar": ("2.0", "", None)})
43
44         self._check_success("foolscap[secure_connections] >= 0.6.0", {"foolscap": ("0.7.0", "", None)})
45
46         try:
47             self._check_success("foolscap[secure_connections] >= 0.6.0", {"foolscap": ("0.6.1+", "", None)})
48             # succeeding is ok
49         except PackagingError, e:
50             self.failUnlessIn("could not parse", str(e))
51
52         self._check_failure("foolscap[secure_connections] >= 0.6.0", {"foolscap": ("0.5.1", "", None)})
53         self._check_failure("pycrypto >= 2.1.0, != 2.2, != 2.4", {"pycrypto": ("2.2.0", "", None)})
54         self._check_failure("pycrypto >= 2.1.0, != 2.2, != 2.4", {"pycrypto": ("2.0.0", "", None)})
55         self._check_failure("Twisted >= 11.0.0, <= 12.2.0", {"Twisted": ("10.2.0", "", None)})
56         self._check_failure("Twisted >= 11.0.0, <= 12.2.0", {"Twisted": ("13.0.0", "", None)})
57         self._check_failure("foo >= 1.0", {})
58         self._check_failure("foo >= 1.0", {"foo": ("irrational", "", None)})
59
60         self.failUnlessRaises(ImportError, check_requirement,
61                               "foo >= 1.0", {"foo": (None, None, "foomodule")})
62
63     def _check_success(self, req, vers_and_locs):
64         check_requirement(req, vers_and_locs)
65
66         for pkg, ver in vers_and_locs.items():
67             self.failUnless(ver[0] in Requirement.parse(req), str((ver, req)))
68
69     def _check_failure(self, req, vers_and_locs):
70         self.failUnlessRaises(PackagingError, check_requirement, req, vers_and_locs)
71
72         for pkg, ver in vers_and_locs.items():
73             self.failIf(ver[0] in Requirement.parse(req), str((ver, req)))
74
75     def test_packages_from_pkg_resources(self):
76         if hasattr(sys, 'frozen'):
77             raise unittest.SkipTest("This test doesn't apply to frozen builds.")
78
79         class MockPackage(object):
80             def __init__(self, project_name, version, location):
81                 self.project_name = project_name
82                 self.version = version
83                 self.location = location
84
85         def call_pkg_resources_require(*args):
86             return [MockPackage("Foo", "1.0", "/path")]
87         self.patch(pkg_resources, 'require', call_pkg_resources_require)
88
89         (packages, errors) = get_package_versions_and_locations()
90         self.failUnlessIn(("foo", ("1.0", "/path", "according to pkg_resources")), packages)
91         self.failIfEqual(errors, [])
92         self.failUnlessEqual([e for e in errors if "was not found by pkg_resources" not in e], [])
93
94     def test_cross_check_unparseable_versions(self):
95         # The bug in #1355 is triggered when a version string from either pkg_resources or import
96         # is not parseable at all by normalized_version.
97
98         res = cross_check({"foo": ("unparseable", "")}, [("foo", ("1.0", "", None))])
99         self.failUnlessEqual(len(res), 1)
100         self.failUnlessIn("by pkg_resources could not be parsed", res[0])
101         self.failUnlessIn("due to IrrationalVersionError", res[0])
102
103         res = cross_check({"foo": ("1.0", "")}, [("foo", ("unparseable", "", None))])
104         self.failUnlessEqual(len(res), 1)
105         self.failUnlessIn(") could not be parsed", res[0])
106         self.failUnlessIn("due to IrrationalVersionError", res[0])
107
108         # However, an error should not be triggered when the version strings are unparseable
109         # but equal (#2499).
110         res = cross_check({"foo": ("unparseable", "")}, [("foo", ("unparseable", "", None))])
111         self.failUnlessEqual(res, [])
112
113     def test_cross_check(self):
114         res = cross_check({}, [])
115         self.failUnlessEqual(res, [])
116
117         res = cross_check({}, [("allmydata-tahoe", ("1.0", "", "blah"))])
118         self.failUnlessEqual(res, [])
119
120         res = cross_check({"foo": ("unparseable", "")}, [])
121         self.failUnlessEqual(res, [])
122
123         res = cross_check({"argparse": ("unparseable", "")}, [])
124         self.failUnlessEqual(res, [])
125
126         res = cross_check({}, [("foo", ("unparseable", "", None))])
127         self.failUnlessEqual(len(res), 1)
128         self.failUnlessIn("not found by pkg_resources", res[0])
129
130         res = cross_check({"distribute": ("1.0", "/somewhere")}, [("setuptools", ("2.0", "/somewhere", "distribute"))])
131         self.failUnlessEqual(res, [])
132
133         res = cross_check({"distribute": ("1.0", "/somewhere")}, [("setuptools", ("2.0", "/somewhere", None))])
134         self.failUnlessEqual(len(res), 1)
135         self.failUnlessIn("location mismatch", res[0])
136
137         res = cross_check({"distribute": ("1.0", "/somewhere")}, [("setuptools", ("2.0", "/somewhere_different", None))])
138         self.failUnlessEqual(len(res), 1)
139         self.failUnlessIn("location mismatch", res[0])
140
141         res = cross_check({"zope.interface": ("1.0", "")}, [("zope.interface", ("unknown", "", None))])
142         self.failUnlessEqual(res, [])
143
144         res = cross_check({"zope.interface": ("unknown", "")}, [("zope.interface", ("unknown", "", None))])
145         self.failUnlessEqual(len(res), 1)
146         self.failUnlessIn("could not be parsed", res[0])
147
148         res = cross_check({"foo": ("1.0", "")}, [("foo", ("unknown", "", None))])
149         self.failUnlessEqual(len(res), 1)
150         self.failUnlessIn("could not find a version number", res[0])
151
152         res = cross_check({"foo": ("unknown", "")}, [("foo", ("unknown", "", None))])
153         self.failUnlessEqual(len(res), 1)
154         self.failUnlessIn("could not be parsed", res[0])
155
156         # When pkg_resources and import both find a package, there is only a warning if both
157         # the version and the path fail to match.
158
159         res = cross_check({"foo": ("1.0", "/somewhere")}, [("foo", ("2.0", "/somewhere", None))])
160         self.failUnlessEqual(res, [])
161
162         res = cross_check({"foo": ("1.0", "/somewhere")}, [("foo", ("1.0", "/somewhere_different", None))])
163         self.failUnlessEqual(res, [])
164
165         res = cross_check({"foo": ("1.0-r123", "/somewhere")}, [("foo", ("1.0.post123", "/somewhere_different", None))])
166         self.failUnlessEqual(res, [])
167
168         res = cross_check({"foo": ("1.0", "/somewhere")}, [("foo", ("2.0", "/somewhere_different", None))])
169         self.failUnlessEqual(len(res), 1)
170         self.failUnlessIn("but version '2.0'", res[0])
171
172     def test_extract_openssl_version(self):
173         self.failUnlessEqual(extract_openssl_version(MockSSL("")),
174                                                      ("", None, None))
175         self.failUnlessEqual(extract_openssl_version(MockSSL("NotOpenSSL a.b.c foo")),
176                                                      ("NotOpenSSL", None, "a.b.c foo"))
177         self.failUnlessEqual(extract_openssl_version(MockSSL("OpenSSL a.b.c")),
178                                                      ("a.b.c", None, None))
179         self.failUnlessEqual(extract_openssl_version(MockSSL("OpenSSL 1.0.1e 11 Feb 2013")),
180                                                      ("1.0.1e", None, "11 Feb 2013"))
181         self.failUnlessEqual(extract_openssl_version(MockSSL("OpenSSL 1.0.1e 11 Feb 2013", compiled_without_heartbeats=True)),
182                                                      ("1.0.1e", None, "11 Feb 2013, no heartbeats"))
183
184
185 # based on https://bitbucket.org/tarek/distutilsversion/src/17df9a7d96ef/test_verlib.py
186
187 class VersionTestCase(unittest.TestCase):
188     versions = ((V('1.0'), '1.0'),
189                 (V('1.1'), '1.1'),
190                 (V('1.2.3'), '1.2.3'),
191                 (V('1.2'), '1.2'),
192                 (V('1.2.3a4'), '1.2.3a4'),
193                 (V('1.2c4'), '1.2c4'),
194                 (V('1.2.3.4'), '1.2.3.4'),
195                 (V('1.2.3.4.0b3'), '1.2.3.4b3'),
196                 (V('1.2.0.0.0'), '1.2'),
197                 (V('1.0.dev345'), '1.0.dev345'),
198                 (V('1.0.post456.dev623'), '1.0.post456.dev623'))
199
200     def test_basic_versions(self):
201         for v, s in self.versions:
202             self.failUnlessEqual(str(v), s)
203
204     def test_from_parts(self):
205         for v, s in self.versions:
206             parts = v.parts
207             v2 = V.from_parts(*parts)
208             self.failUnlessEqual(v, v2)
209             self.failUnlessEqual(str(v), str(v2))
210
211     def test_irrational_versions(self):
212         irrational = ('1', '1.2a', '1.2.3b', '1.02', '1.2a03',
213                       '1.2a3.04', '1.2.dev.2', '1.2dev', '1.2.dev',
214                       '1.2.dev2.post2', '1.2.post2.dev3.post4')
215
216         for s in irrational:
217             self.failUnlessRaises(IrrationalVersionError, V, s)
218
219     def test_comparison(self):
220         self.failUnlessRaises(TypeError, lambda: V('1.2.0') == '1.2')
221
222         self.failUnlessEqual(V('1.2.0'), V('1.2'))
223         self.failIfEqual(V('1.2.0'), V('1.2.3'))
224         self.failUnless(V('1.2.0') < V('1.2.3'))
225         self.failUnless(V('1.0') > V('1.0b2'))
226         self.failUnless(V('1.0') > V('1.0c2') > V('1.0c1') > V('1.0b2') > V('1.0b1')
227                         > V('1.0a2') > V('1.0a1'))
228         self.failUnless(V('1.0.0') > V('1.0.0c2') > V('1.0.0c1') > V('1.0.0b2') > V('1.0.0b1')
229                         > V('1.0.0a2') > V('1.0.0a1'))
230
231         self.failUnless(V('1.0') < V('1.0.post456.dev623'))
232         self.failUnless(V('1.0.post456.dev623') < V('1.0.post456')  < V('1.0.post1234'))
233
234         self.failUnless(V('1.0a1')
235                         < V('1.0a2.dev456')
236                         < V('1.0a2')
237                         < V('1.0a2.1.dev456')  # e.g. need to do a quick post release on 1.0a2
238                         < V('1.0a2.1')
239                         < V('1.0b1.dev456')
240                         < V('1.0b2')
241                         < V('1.0c1')
242                         < V('1.0c2.dev456')
243                         < V('1.0c2')
244                         < V('1.0.dev7')
245                         < V('1.0.dev18')
246                         < V('1.0.dev456')
247                         < V('1.0.dev1234')
248                         < V('1.0')
249                         < V('1.0.post456.dev623')  # development version of a post release
250                         < V('1.0.post456'))
251
252     def test_suggest_normalized_version(self):
253         self.failUnlessEqual(suggest('1.0'), '1.0')
254         self.failUnlessEqual(suggest('1.0-alpha1'), '1.0a1')
255         self.failUnlessEqual(suggest('1.0c2'), '1.0c2')
256         self.failUnlessEqual(suggest('walla walla washington'), None)
257         self.failUnlessEqual(suggest('2.4c1'), '2.4c1')
258
259         # from setuptools
260         self.failUnlessEqual(suggest('0.4a1.r10'), '0.4a1.post10')
261         self.failUnlessEqual(suggest('0.7a1dev-r66608'), '0.7a1.dev66608')
262         self.failUnlessEqual(suggest('0.6a9.dev-r41475'), '0.6a9.dev41475')
263         self.failUnlessEqual(suggest('2.4preview1'), '2.4c1')
264         self.failUnlessEqual(suggest('2.4pre1') , '2.4c1')
265         self.failUnlessEqual(suggest('2.1-rc2'), '2.1c2')
266
267         # from pypi
268         self.failUnlessEqual(suggest('0.1dev'), '0.1.dev0')
269         self.failUnlessEqual(suggest('0.1.dev'), '0.1.dev0')
270
271         # we want to be able to parse Twisted
272         # development versions are like post releases in Twisted
273         self.failUnlessEqual(suggest('9.0.0+r2363'), '9.0.0.post2363')
274
275         # pre-releases are using markers like "pre1"
276         self.failUnlessEqual(suggest('9.0.0pre1'), '9.0.0c1')
277
278         # we want to be able to parse Tcl-TK
279         # they use "p1" "p2" for post releases
280         self.failUnlessEqual(suggest('1.4p1'), '1.4.post1')
281
282         # from darcsver
283         self.failUnlessEqual(suggest('1.8.1-r4956'), '1.8.1.post4956')
284
285         # zetuptoolz
286         self.failUnlessEqual(suggest('0.6c16dev3'), '0.6c16.dev3')