]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blob - src/allmydata/test/test_version.py
7851f91ad38aa4d6c4048eab6f288727cafc440d
[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_ticket_1355(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
102         res = cross_check({"foo": ("1.0", "")}, [("foo", ("unparseable", "", None))])
103         self.failUnlessEqual(len(res), 1)
104         self.failUnlessIn(") could not be parsed", res[0])
105
106     def test_cross_check(self):
107         res = cross_check({}, [])
108         self.failUnlessEqual(res, [])
109
110         res = cross_check({}, [("allmydata-tahoe", ("1.0", "", "blah"))])
111         self.failUnlessEqual(res, [])
112
113         res = cross_check({"foo": ("unparseable", "")}, [])
114         self.failUnlessEqual(res, [])
115
116         res = cross_check({"argparse": ("unparseable", "")}, [])
117         self.failUnlessEqual(res, [])
118
119         res = cross_check({}, [("foo", ("unparseable", "", None))])
120         self.failUnlessEqual(len(res), 1)
121         self.failUnlessIn("not found by pkg_resources", res[0])
122
123         res = cross_check({"distribute": ("1.0", "/somewhere")}, [("setuptools", ("2.0", "/somewhere", "distribute"))])
124         self.failUnlessEqual(res, [])
125
126         res = cross_check({"distribute": ("1.0", "/somewhere")}, [("setuptools", ("2.0", "/somewhere", None))])
127         self.failUnlessEqual(len(res), 1)
128         self.failUnlessIn("location mismatch", res[0])
129
130         res = cross_check({"distribute": ("1.0", "/somewhere")}, [("setuptools", ("2.0", "/somewhere_different", None))])
131         self.failUnlessEqual(len(res), 1)
132         self.failUnlessIn("location mismatch", res[0])
133
134         res = cross_check({"zope.interface": ("1.0", "")}, [("zope.interface", ("unknown", "", None))])
135         self.failUnlessEqual(res, [])
136
137         res = cross_check({"foo": ("1.0", "")}, [("foo", ("unknown", "", None))])
138         self.failUnlessEqual(len(res), 1)
139         self.failUnlessIn("could not find a version number", res[0])
140
141         # When pkg_resources and import both find a package, there is only a warning if both
142         # the version and the path fail to match.
143
144         res = cross_check({"foo": ("1.0", "/somewhere")}, [("foo", ("2.0", "/somewhere", None))])
145         self.failUnlessEqual(res, [])
146
147         res = cross_check({"foo": ("1.0", "/somewhere")}, [("foo", ("1.0", "/somewhere_different", None))])
148         self.failUnlessEqual(res, [])
149
150         res = cross_check({"foo": ("1.0-r123", "/somewhere")}, [("foo", ("1.0.post123", "/somewhere_different", None))])
151         self.failUnlessEqual(res, [])
152
153         res = cross_check({"foo": ("1.0", "/somewhere")}, [("foo", ("2.0", "/somewhere_different", None))])
154         self.failUnlessEqual(len(res), 1)
155         self.failUnlessIn("but version '2.0'", res[0])
156
157     def test_extract_openssl_version(self):
158         self.failUnlessEqual(extract_openssl_version(MockSSL("")),
159                                                      ("", None, None))
160         self.failUnlessEqual(extract_openssl_version(MockSSL("NotOpenSSL a.b.c foo")),
161                                                      ("NotOpenSSL", None, "a.b.c foo"))
162         self.failUnlessEqual(extract_openssl_version(MockSSL("OpenSSL a.b.c")),
163                                                      ("a.b.c", None, None))
164         self.failUnlessEqual(extract_openssl_version(MockSSL("OpenSSL 1.0.1e 11 Feb 2013")),
165                                                      ("1.0.1e", None, "11 Feb 2013"))
166         self.failUnlessEqual(extract_openssl_version(MockSSL("OpenSSL 1.0.1e 11 Feb 2013", compiled_without_heartbeats=True)),
167                                                      ("1.0.1e", None, "11 Feb 2013, no heartbeats"))
168
169
170 # based on https://bitbucket.org/tarek/distutilsversion/src/17df9a7d96ef/test_verlib.py
171
172 class VersionTestCase(unittest.TestCase):
173     versions = ((V('1.0'), '1.0'),
174                 (V('1.1'), '1.1'),
175                 (V('1.2.3'), '1.2.3'),
176                 (V('1.2'), '1.2'),
177                 (V('1.2.3a4'), '1.2.3a4'),
178                 (V('1.2c4'), '1.2c4'),
179                 (V('1.2.3.4'), '1.2.3.4'),
180                 (V('1.2.3.4.0b3'), '1.2.3.4b3'),
181                 (V('1.2.0.0.0'), '1.2'),
182                 (V('1.0.dev345'), '1.0.dev345'),
183                 (V('1.0.post456.dev623'), '1.0.post456.dev623'))
184
185     def test_basic_versions(self):
186         for v, s in self.versions:
187             self.failUnlessEqual(str(v), s)
188
189     def test_from_parts(self):
190         for v, s in self.versions:
191             parts = v.parts
192             v2 = V.from_parts(*parts)
193             self.failUnlessEqual(v, v2)
194             self.failUnlessEqual(str(v), str(v2))
195
196     def test_irrational_versions(self):
197         irrational = ('1', '1.2a', '1.2.3b', '1.02', '1.2a03',
198                       '1.2a3.04', '1.2.dev.2', '1.2dev', '1.2.dev',
199                       '1.2.dev2.post2', '1.2.post2.dev3.post4')
200
201         for s in irrational:
202             self.failUnlessRaises(IrrationalVersionError, V, s)
203
204     def test_comparison(self):
205         self.failUnlessRaises(TypeError, lambda: V('1.2.0') == '1.2')
206
207         self.failUnlessEqual(V('1.2.0'), V('1.2'))
208         self.failIfEqual(V('1.2.0'), V('1.2.3'))
209         self.failUnless(V('1.2.0') < V('1.2.3'))
210         self.failUnless(V('1.0') > V('1.0b2'))
211         self.failUnless(V('1.0') > V('1.0c2') > V('1.0c1') > V('1.0b2') > V('1.0b1')
212                         > V('1.0a2') > V('1.0a1'))
213         self.failUnless(V('1.0.0') > V('1.0.0c2') > V('1.0.0c1') > V('1.0.0b2') > V('1.0.0b1')
214                         > V('1.0.0a2') > V('1.0.0a1'))
215
216         self.failUnless(V('1.0') < V('1.0.post456.dev623'))
217         self.failUnless(V('1.0.post456.dev623') < V('1.0.post456')  < V('1.0.post1234'))
218
219         self.failUnless(V('1.0a1')
220                         < V('1.0a2.dev456')
221                         < V('1.0a2')
222                         < V('1.0a2.1.dev456')  # e.g. need to do a quick post release on 1.0a2
223                         < V('1.0a2.1')
224                         < V('1.0b1.dev456')
225                         < V('1.0b2')
226                         < V('1.0c1')
227                         < V('1.0c2.dev456')
228                         < V('1.0c2')
229                         < V('1.0.dev7')
230                         < V('1.0.dev18')
231                         < V('1.0.dev456')
232                         < V('1.0.dev1234')
233                         < V('1.0')
234                         < V('1.0.post456.dev623')  # development version of a post release
235                         < V('1.0.post456'))
236
237     def test_suggest_normalized_version(self):
238         self.failUnlessEqual(suggest('1.0'), '1.0')
239         self.failUnlessEqual(suggest('1.0-alpha1'), '1.0a1')
240         self.failUnlessEqual(suggest('1.0c2'), '1.0c2')
241         self.failUnlessEqual(suggest('walla walla washington'), None)
242         self.failUnlessEqual(suggest('2.4c1'), '2.4c1')
243
244         # from setuptools
245         self.failUnlessEqual(suggest('0.4a1.r10'), '0.4a1.post10')
246         self.failUnlessEqual(suggest('0.7a1dev-r66608'), '0.7a1.dev66608')
247         self.failUnlessEqual(suggest('0.6a9.dev-r41475'), '0.6a9.dev41475')
248         self.failUnlessEqual(suggest('2.4preview1'), '2.4c1')
249         self.failUnlessEqual(suggest('2.4pre1') , '2.4c1')
250         self.failUnlessEqual(suggest('2.1-rc2'), '2.1c2')
251
252         # from pypi
253         self.failUnlessEqual(suggest('0.1dev'), '0.1.dev0')
254         self.failUnlessEqual(suggest('0.1.dev'), '0.1.dev0')
255
256         # we want to be able to parse Twisted
257         # development versions are like post releases in Twisted
258         self.failUnlessEqual(suggest('9.0.0+r2363'), '9.0.0.post2363')
259
260         # pre-releases are using markers like "pre1"
261         self.failUnlessEqual(suggest('9.0.0pre1'), '9.0.0c1')
262
263         # we want to be able to parse Tcl-TK
264         # they use "p1" "p2" for post releases
265         self.failUnlessEqual(suggest('1.4p1'), '1.4.post1')
266
267         # from darcsver
268         self.failUnlessEqual(suggest('1.8.1-r4956'), '1.8.1.post4956')
269
270         # zetuptoolz
271         self.failUnlessEqual(suggest('0.6c16dev3'), '0.6c16.dev3')