blob: c8d941527eaa4ffa1355c4b997d73666371d3b8a [file] [log] [blame]
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001import os
2import io
3import csv
4import imp
5import sys
6import shutil
7import zipfile
8import tempfile
9from os.path import relpath # separate import for backport concerns
10from hashlib import md5
11
12from packaging.errors import PackagingError
13from packaging.metadata import Metadata
14from packaging.tests import unittest, run_unittest, support, TESTFN
15
16from packaging.database import (
17 Distribution, EggInfoDistribution, get_distribution, get_distributions,
18 provides_distribution, obsoletes_distribution, get_file_users,
19 enable_cache, disable_cache, distinfo_dirname, _yield_distributions)
20
21# TODO Add a test for getting a distribution provided by another distribution
22# TODO Add a test for absolute pathed RECORD items (e.g. /etc/myapp/config.ini)
23# TODO Add tests from the former pep376 project (zipped site-packages, etc.)
24
25
26def get_hexdigest(filename):
27 with open(filename, 'rb') as file:
28 checksum = md5(file.read())
29 return checksum.hexdigest()
30
31
32def record_pieces(file):
33 path = relpath(file, sys.prefix)
34 digest = get_hexdigest(file)
35 size = os.path.getsize(file)
36 return [path, digest, size]
37
38
39class CommonDistributionTests:
40 """Mixin used to test the interface common to both Distribution classes.
41
42 Derived classes define cls, sample_dist, dirs and records. These
43 attributes are used in test methods. See source code for details.
44 """
45
46 def setUp(self):
47 super(CommonDistributionTests, self).setUp()
48 self.addCleanup(enable_cache)
49 disable_cache()
50 self.fake_dists_path = os.path.abspath(
51 os.path.join(os.path.dirname(__file__), 'fake_dists'))
52
53 def test_instantiation(self):
54 # check that useful attributes are here
55 name, version, distdir = self.sample_dist
56 here = os.path.abspath(os.path.dirname(__file__))
57 dist_path = os.path.join(here, 'fake_dists', distdir)
58
59 dist = self.dist = self.cls(dist_path)
60 self.assertEqual(dist.path, dist_path)
61 self.assertEqual(dist.name, name)
62 self.assertEqual(dist.metadata['Name'], name)
63 self.assertIsInstance(dist.metadata, Metadata)
64 self.assertEqual(dist.version, version)
65 self.assertEqual(dist.metadata['Version'], version)
66
67 def test_repr(self):
68 dist = self.cls(self.dirs[0])
69 # just check that the class name is in the repr
70 self.assertIn(self.cls.__name__, repr(dist))
71
72 def test_comparison(self):
73 # tests for __eq__ and __hash__
74 dist = self.cls(self.dirs[0])
75 dist2 = self.cls(self.dirs[0])
76 dist3 = self.cls(self.dirs[1])
77 self.assertIn(dist, {dist: True})
78 self.assertEqual(dist, dist)
79
80 self.assertIsNot(dist, dist2)
81 self.assertEqual(dist, dist2)
82 self.assertNotEqual(dist, dist3)
83 self.assertNotEqual(dist, ())
84
85 def test_list_installed_files(self):
86 for dir_ in self.dirs:
87 dist = self.cls(dir_)
88 for path, md5_, size in dist.list_installed_files():
89 record_data = self.records[dist.path]
90 self.assertIn(path, record_data)
91 self.assertEqual(md5_, record_data[path][0])
92 self.assertEqual(size, record_data[path][1])
93
94
95class TestDistribution(CommonDistributionTests, unittest.TestCase):
96
97 cls = Distribution
98 sample_dist = 'choxie', '2.0.0.9', 'choxie-2.0.0.9.dist-info'
99
100 def setUp(self):
101 super(TestDistribution, self).setUp()
102 self.dirs = [os.path.join(self.fake_dists_path, f)
103 for f in os.listdir(self.fake_dists_path)
104 if f.endswith('.dist-info')]
105
106 self.records = {}
107 for distinfo_dir in self.dirs:
108 record_file = os.path.join(distinfo_dir, 'RECORD')
109 with open(record_file, 'w') as file:
110 record_writer = csv.writer(
111 file, delimiter=',', quoting=csv.QUOTE_NONE)
112
113 dist_location = distinfo_dir.replace('.dist-info', '')
114
115 for path, dirs, files in os.walk(dist_location):
116 for f in files:
117 record_writer.writerow(record_pieces(
118 os.path.join(path, f)))
119 for file in ('INSTALLER', 'METADATA', 'REQUESTED'):
120 record_writer.writerow(record_pieces(
121 os.path.join(distinfo_dir, file)))
122 record_writer.writerow([relpath(record_file, sys.prefix)])
123
124 with open(record_file) as file:
125 record_reader = csv.reader(file)
126 record_data = {}
127 for row in record_reader:
128 path, md5_, size = (row[:] +
129 [None for i in range(len(row), 3)])
130 record_data[path] = md5_, size
131 self.records[distinfo_dir] = record_data
132
133 def tearDown(self):
134 for distinfo_dir in self.dirs:
135 record_file = os.path.join(distinfo_dir, 'RECORD')
136 open(record_file, 'w').close()
137 super(TestDistribution, self).tearDown()
138
139 def test_instantiation(self):
140 super(TestDistribution, self).test_instantiation()
141 self.assertIsInstance(self.dist.requested, bool)
142
143 def test_uses(self):
144 # Test to determine if a distribution uses a specified file.
145 # Criteria to test against
146 distinfo_name = 'grammar-1.0a4'
147 distinfo_dir = os.path.join(self.fake_dists_path,
148 distinfo_name + '.dist-info')
149 true_path = [self.fake_dists_path, distinfo_name,
150 'grammar', 'utils.py']
151 true_path = relpath(os.path.join(*true_path), sys.prefix)
152 false_path = [self.fake_dists_path, 'towel_stuff-0.1', 'towel_stuff',
153 '__init__.py']
154 false_path = relpath(os.path.join(*false_path), sys.prefix)
155
156 # Test if the distribution uses the file in question
157 dist = Distribution(distinfo_dir)
158 self.assertTrue(dist.uses(true_path))
159 self.assertFalse(dist.uses(false_path))
160
161 def test_get_distinfo_file(self):
162 # Test the retrieval of dist-info file objects.
163 distinfo_name = 'choxie-2.0.0.9'
164 other_distinfo_name = 'grammar-1.0a4'
165 distinfo_dir = os.path.join(self.fake_dists_path,
166 distinfo_name + '.dist-info')
167 dist = Distribution(distinfo_dir)
168 # Test for known good file matches
169 distinfo_files = [
170 # Relative paths
171 'INSTALLER', 'METADATA',
172 # Absolute paths
173 os.path.join(distinfo_dir, 'RECORD'),
174 os.path.join(distinfo_dir, 'REQUESTED'),
175 ]
176
177 for distfile in distinfo_files:
178 with dist.get_distinfo_file(distfile) as value:
179 self.assertIsInstance(value, io.TextIOWrapper)
180 # Is it the correct file?
181 self.assertEqual(value.name,
182 os.path.join(distinfo_dir, distfile))
183
184 # Test an absolute path that is part of another distributions dist-info
185 other_distinfo_file = os.path.join(
186 self.fake_dists_path, other_distinfo_name + '.dist-info',
187 'REQUESTED')
188 self.assertRaises(PackagingError, dist.get_distinfo_file,
189 other_distinfo_file)
190 # Test for a file that should not exist
191 self.assertRaises(PackagingError, dist.get_distinfo_file,
192 'MAGICFILE')
193
194 def test_list_distinfo_files(self):
195 # Test for the iteration of RECORD path entries.
196 distinfo_name = 'towel_stuff-0.1'
197 distinfo_dir = os.path.join(self.fake_dists_path,
198 distinfo_name + '.dist-info')
199 dist = Distribution(distinfo_dir)
200 # Test for the iteration of the raw path
201 distinfo_record_paths = self.records[distinfo_dir].keys()
202 found = dist.list_distinfo_files()
203 self.assertEqual(sorted(found), sorted(distinfo_record_paths))
204 # Test for the iteration of local absolute paths
205 distinfo_record_paths = [os.path.join(sys.prefix, path)
206 for path in self.records[distinfo_dir]]
207 found = dist.list_distinfo_files(local=True)
208 self.assertEqual(sorted(found), sorted(distinfo_record_paths))
209
210 def test_get_resources_path(self):
211 distinfo_name = 'babar-0.1'
212 distinfo_dir = os.path.join(self.fake_dists_path,
213 distinfo_name + '.dist-info')
214 dist = Distribution(distinfo_dir)
215 resource_path = dist.get_resource_path('babar.png')
216 self.assertEqual(resource_path, 'babar.png')
217 self.assertRaises(KeyError, dist.get_resource_path, 'notexist')
218
219
220class TestEggInfoDistribution(CommonDistributionTests,
221 support.LoggingCatcher,
222 unittest.TestCase):
223
224 cls = EggInfoDistribution
225 sample_dist = 'bacon', '0.1', 'bacon-0.1.egg-info'
226
227 def setUp(self):
228 super(TestEggInfoDistribution, self).setUp()
229
230 self.dirs = [os.path.join(self.fake_dists_path, f)
231 for f in os.listdir(self.fake_dists_path)
232 if f.endswith('.egg') or f.endswith('.egg-info')]
233
234 self.records = {}
235
236 @unittest.skip('not implemented yet')
237 def test_list_installed_files(self):
238 # EggInfoDistribution defines list_installed_files but there is no
239 # test for it yet; someone with setuptools expertise needs to add a
240 # file with the list of installed files for one of the egg fake dists
241 # and write the support code to populate self.records (and then delete
242 # this method)
243 pass
244
245
246class TestDatabase(support.LoggingCatcher,
247 unittest.TestCase):
248
249 def setUp(self):
250 super(TestDatabase, self).setUp()
251 disable_cache()
252 # Setup the path environment with our fake distributions
253 current_path = os.path.abspath(os.path.dirname(__file__))
254 self.sys_path = sys.path[:]
255 self.fake_dists_path = os.path.join(current_path, 'fake_dists')
256 sys.path.insert(0, self.fake_dists_path)
257
258 def tearDown(self):
259 sys.path[:] = self.sys_path
260 enable_cache()
261 super(TestDatabase, self).tearDown()
262
263 def test_distinfo_dirname(self):
264 # Given a name and a version, we expect the distinfo_dirname function
265 # to return a standard distribution information directory name.
266
267 items = [
268 # (name, version, standard_dirname)
269 # Test for a very simple single word name and decimal version
270 # number
271 ('docutils', '0.5', 'docutils-0.5.dist-info'),
272 # Test for another except this time with a '-' in the name, which
273 # needs to be transformed during the name lookup
274 ('python-ldap', '2.5', 'python_ldap-2.5.dist-info'),
275 # Test for both '-' in the name and a funky version number
276 ('python-ldap', '2.5 a---5', 'python_ldap-2.5 a---5.dist-info'),
277 ]
278
279 # Loop through the items to validate the results
280 for name, version, standard_dirname in items:
281 dirname = distinfo_dirname(name, version)
282 self.assertEqual(dirname, standard_dirname)
283
284 def test_get_distributions(self):
285 # Lookup all distributions found in the ``sys.path``.
286 # This test could potentially pick up other installed distributions
287 fake_dists = [('grammar', '1.0a4'), ('choxie', '2.0.0.9'),
288 ('towel-stuff', '0.1'), ('babar', '0.1')]
289 found_dists = []
290
291 # Verify the fake dists have been found.
292 dists = [dist for dist in get_distributions()]
293 for dist in dists:
294 self.assertIsInstance(dist, Distribution)
295 if (dist.name in dict(fake_dists) and
296 dist.path.startswith(self.fake_dists_path)):
297 found_dists.append((dist.name, dist.metadata['version'], ))
298 else:
299 # check that it doesn't find anything more than this
300 self.assertFalse(dist.path.startswith(self.fake_dists_path))
301 # otherwise we don't care what other distributions are found
302
303 # Finally, test that we found all that we were looking for
304 self.assertEqual(sorted(found_dists), sorted(fake_dists))
305
306 # Now, test if the egg-info distributions are found correctly as well
307 fake_dists += [('bacon', '0.1'), ('cheese', '2.0.2'),
308 ('coconuts-aster', '10.3'),
309 ('banana', '0.4'), ('strawberry', '0.6'),
310 ('truffles', '5.0'), ('nut', 'funkyversion')]
311 found_dists = []
312
313 dists = [dist for dist in get_distributions(use_egg_info=True)]
314 for dist in dists:
315 self.assertIsInstance(dist, (Distribution, EggInfoDistribution))
316 if (dist.name in dict(fake_dists) and
317 dist.path.startswith(self.fake_dists_path)):
318 found_dists.append((dist.name, dist.metadata['version']))
319 else:
320 self.assertFalse(dist.path.startswith(self.fake_dists_path))
321
322 self.assertEqual(sorted(fake_dists), sorted(found_dists))
323
324 def test_get_distribution(self):
325 # Test for looking up a distribution by name.
326 # Test the lookup of the towel-stuff distribution
327 name = 'towel-stuff' # Note: This is different from the directory name
328
329 # Lookup the distribution
330 dist = get_distribution(name)
331 self.assertIsInstance(dist, Distribution)
332 self.assertEqual(dist.name, name)
333
334 # Verify that an unknown distribution returns None
335 self.assertIsNone(get_distribution('bogus'))
336
337 # Verify partial name matching doesn't work
338 self.assertIsNone(get_distribution('towel'))
339
340 # Verify that it does not find egg-info distributions, when not
341 # instructed to
342 self.assertIsNone(get_distribution('bacon'))
343 self.assertIsNone(get_distribution('cheese'))
344 self.assertIsNone(get_distribution('strawberry'))
345 self.assertIsNone(get_distribution('banana'))
346
347 # Now check that it works well in both situations, when egg-info
348 # is a file and directory respectively.
349 dist = get_distribution('cheese', use_egg_info=True)
350 self.assertIsInstance(dist, EggInfoDistribution)
351 self.assertEqual(dist.name, 'cheese')
352
353 dist = get_distribution('bacon', use_egg_info=True)
354 self.assertIsInstance(dist, EggInfoDistribution)
355 self.assertEqual(dist.name, 'bacon')
356
357 dist = get_distribution('banana', use_egg_info=True)
358 self.assertIsInstance(dist, EggInfoDistribution)
359 self.assertEqual(dist.name, 'banana')
360
361 dist = get_distribution('strawberry', use_egg_info=True)
362 self.assertIsInstance(dist, EggInfoDistribution)
363 self.assertEqual(dist.name, 'strawberry')
364
365 def test_get_file_users(self):
366 # Test the iteration of distributions that use a file.
367 name = 'towel_stuff-0.1'
368 path = os.path.join(self.fake_dists_path, name,
369 'towel_stuff', '__init__.py')
370 for dist in get_file_users(path):
371 self.assertIsInstance(dist, Distribution)
372 self.assertEqual(dist.name, name)
373
374 def test_provides(self):
375 # Test for looking up distributions by what they provide
376 checkLists = lambda x, y: self.assertEqual(sorted(x), sorted(y))
377
378 l = [dist.name for dist in provides_distribution('truffles')]
379 checkLists(l, ['choxie', 'towel-stuff'])
380
381 l = [dist.name for dist in provides_distribution('truffles', '1.0')]
382 checkLists(l, ['choxie'])
383
384 l = [dist.name for dist in provides_distribution('truffles', '1.0',
385 use_egg_info=True)]
386 checkLists(l, ['choxie', 'cheese'])
387
388 l = [dist.name for dist in provides_distribution('truffles', '1.1.2')]
389 checkLists(l, ['towel-stuff'])
390
391 l = [dist.name for dist in provides_distribution('truffles', '1.1')]
392 checkLists(l, ['towel-stuff'])
393
394 l = [dist.name for dist in provides_distribution('truffles',
395 '!=1.1,<=2.0')]
396 checkLists(l, ['choxie'])
397
398 l = [dist.name for dist in provides_distribution('truffles',
399 '!=1.1,<=2.0',
400 use_egg_info=True)]
401 checkLists(l, ['choxie', 'bacon', 'cheese'])
402
403 l = [dist.name for dist in provides_distribution('truffles', '>1.0')]
404 checkLists(l, ['towel-stuff'])
405
406 l = [dist.name for dist in provides_distribution('truffles', '>1.5')]
407 checkLists(l, [])
408
409 l = [dist.name for dist in provides_distribution('truffles', '>1.5',
410 use_egg_info=True)]
411 checkLists(l, ['bacon'])
412
413 l = [dist.name for dist in provides_distribution('truffles', '>=1.0')]
414 checkLists(l, ['choxie', 'towel-stuff'])
415
416 l = [dist.name for dist in provides_distribution('strawberry', '0.6',
417 use_egg_info=True)]
418 checkLists(l, ['coconuts-aster'])
419
420 l = [dist.name for dist in provides_distribution('strawberry', '>=0.5',
421 use_egg_info=True)]
422 checkLists(l, ['coconuts-aster'])
423
424 l = [dist.name for dist in provides_distribution('strawberry', '>0.6',
425 use_egg_info=True)]
426 checkLists(l, [])
427
428 l = [dist.name for dist in provides_distribution('banana', '0.4',
429 use_egg_info=True)]
430 checkLists(l, ['coconuts-aster'])
431
432 l = [dist.name for dist in provides_distribution('banana', '>=0.3',
433 use_egg_info=True)]
434 checkLists(l, ['coconuts-aster'])
435
436 l = [dist.name for dist in provides_distribution('banana', '!=0.4',
437 use_egg_info=True)]
438 checkLists(l, [])
439
440 def test_obsoletes(self):
441 # Test looking for distributions based on what they obsolete
442 checkLists = lambda x, y: self.assertEqual(sorted(x), sorted(y))
443
444 l = [dist.name for dist in obsoletes_distribution('truffles', '1.0')]
445 checkLists(l, [])
446
447 l = [dist.name for dist in obsoletes_distribution('truffles', '1.0',
448 use_egg_info=True)]
449 checkLists(l, ['cheese', 'bacon'])
450
451 l = [dist.name for dist in obsoletes_distribution('truffles', '0.8')]
452 checkLists(l, ['choxie'])
453
454 l = [dist.name for dist in obsoletes_distribution('truffles', '0.8',
455 use_egg_info=True)]
456 checkLists(l, ['choxie', 'cheese'])
457
458 l = [dist.name for dist in obsoletes_distribution('truffles', '0.9.6')]
459 checkLists(l, ['choxie', 'towel-stuff'])
460
461 l = [dist.name for dist in obsoletes_distribution('truffles',
462 '0.5.2.3')]
463 checkLists(l, ['choxie', 'towel-stuff'])
464
465 l = [dist.name for dist in obsoletes_distribution('truffles', '0.2')]
466 checkLists(l, ['towel-stuff'])
467
468 def test_yield_distribution(self):
469 # tests the internal function _yield_distributions
470 checkLists = lambda x, y: self.assertEqual(sorted(x), sorted(y))
471
472 eggs = [('bacon', '0.1'), ('banana', '0.4'), ('strawberry', '0.6'),
473 ('truffles', '5.0'), ('cheese', '2.0.2'),
474 ('coconuts-aster', '10.3'), ('nut', 'funkyversion')]
475 dists = [('choxie', '2.0.0.9'), ('grammar', '1.0a4'),
476 ('towel-stuff', '0.1'), ('babar', '0.1')]
477
478 checkLists([], _yield_distributions(False, False))
479
480 found = [(dist.name, dist.metadata['Version'])
481 for dist in _yield_distributions(False, True)
482 if dist.path.startswith(self.fake_dists_path)]
483 checkLists(eggs, found)
484
485 found = [(dist.name, dist.metadata['Version'])
486 for dist in _yield_distributions(True, False)
487 if dist.path.startswith(self.fake_dists_path)]
488 checkLists(dists, found)
489
490 found = [(dist.name, dist.metadata['Version'])
491 for dist in _yield_distributions(True, True)
492 if dist.path.startswith(self.fake_dists_path)]
493 checkLists(dists + eggs, found)
494
495
496def test_suite():
497 suite = unittest.TestSuite()
498 load = unittest.defaultTestLoader.loadTestsFromTestCase
499 suite.addTest(load(TestDistribution))
500 suite.addTest(load(TestEggInfoDistribution))
501 suite.addTest(load(TestDatabase))
502 return suite
503
504
505if __name__ == "__main__":
506 unittest.main(defaultTest='test_suite')