1# coding: utf-8 2 3import re 4import json 5import pickle 6import textwrap 7import unittest 8import importlib.metadata 9 10try: 11 import pyfakefs.fake_filesystem_unittest as ffs 12except ImportError: 13 from .stubs import fake_filesystem_unittest as ffs 14 15from . import fixtures 16from importlib.metadata import ( 17 Distribution, EntryPoint, 18 PackageNotFoundError, distributions, 19 entry_points, metadata, version, 20 ) 21 22 23class BasicTests(fixtures.DistInfoPkg, unittest.TestCase): 24 version_pattern = r'\d+\.\d+(\.\d)?' 25 26 def test_retrieves_version_of_self(self): 27 dist = Distribution.from_name('distinfo-pkg') 28 assert isinstance(dist.version, str) 29 assert re.match(self.version_pattern, dist.version) 30 31 def test_for_name_does_not_exist(self): 32 with self.assertRaises(PackageNotFoundError): 33 Distribution.from_name('does-not-exist') 34 35 def test_new_style_classes(self): 36 self.assertIsInstance(Distribution, type) 37 38 39class ImportTests(fixtures.DistInfoPkg, unittest.TestCase): 40 def test_import_nonexistent_module(self): 41 # Ensure that the MetadataPathFinder does not crash an import of a 42 # non-existent module. 43 with self.assertRaises(ImportError): 44 importlib.import_module('does_not_exist') 45 46 def test_resolve(self): 47 entries = dict(entry_points()['entries']) 48 ep = entries['main'] 49 self.assertEqual(ep.load().__name__, "main") 50 51 def test_entrypoint_with_colon_in_name(self): 52 entries = dict(entry_points()['entries']) 53 ep = entries['ns:sub'] 54 self.assertEqual(ep.value, 'mod:main') 55 56 def test_resolve_without_attr(self): 57 ep = EntryPoint( 58 name='ep', 59 value='importlib.metadata', 60 group='grp', 61 ) 62 assert ep.load() is importlib.metadata 63 64 65class NameNormalizationTests( 66 fixtures.OnSysPath, fixtures.SiteDir, unittest.TestCase): 67 @staticmethod 68 def pkg_with_dashes(site_dir): 69 """ 70 Create minimal metadata for a package with dashes 71 in the name (and thus underscores in the filename). 72 """ 73 metadata_dir = site_dir / 'my_pkg.dist-info' 74 metadata_dir.mkdir() 75 metadata = metadata_dir / 'METADATA' 76 with metadata.open('w') as strm: 77 strm.write('Version: 1.0\n') 78 return 'my-pkg' 79 80 def test_dashes_in_dist_name_found_as_underscores(self): 81 """ 82 For a package with a dash in the name, the dist-info metadata 83 uses underscores in the name. Ensure the metadata loads. 84 """ 85 pkg_name = self.pkg_with_dashes(self.site_dir) 86 assert version(pkg_name) == '1.0' 87 88 @staticmethod 89 def pkg_with_mixed_case(site_dir): 90 """ 91 Create minimal metadata for a package with mixed case 92 in the name. 93 """ 94 metadata_dir = site_dir / 'CherryPy.dist-info' 95 metadata_dir.mkdir() 96 metadata = metadata_dir / 'METADATA' 97 with metadata.open('w') as strm: 98 strm.write('Version: 1.0\n') 99 return 'CherryPy' 100 101 def test_dist_name_found_as_any_case(self): 102 """ 103 Ensure the metadata loads when queried with any case. 104 """ 105 pkg_name = self.pkg_with_mixed_case(self.site_dir) 106 assert version(pkg_name) == '1.0' 107 assert version(pkg_name.lower()) == '1.0' 108 assert version(pkg_name.upper()) == '1.0' 109 110 111class NonASCIITests(fixtures.OnSysPath, fixtures.SiteDir, unittest.TestCase): 112 @staticmethod 113 def pkg_with_non_ascii_description(site_dir): 114 """ 115 Create minimal metadata for a package with non-ASCII in 116 the description. 117 """ 118 metadata_dir = site_dir / 'portend.dist-info' 119 metadata_dir.mkdir() 120 metadata = metadata_dir / 'METADATA' 121 with metadata.open('w', encoding='utf-8') as fp: 122 fp.write('Description: pôrˈtend\n') 123 return 'portend' 124 125 @staticmethod 126 def pkg_with_non_ascii_description_egg_info(site_dir): 127 """ 128 Create minimal metadata for an egg-info package with 129 non-ASCII in the description. 130 """ 131 metadata_dir = site_dir / 'portend.dist-info' 132 metadata_dir.mkdir() 133 metadata = metadata_dir / 'METADATA' 134 with metadata.open('w', encoding='utf-8') as fp: 135 fp.write(textwrap.dedent(""" 136 Name: portend 137 138 pôrˈtend 139 """).lstrip()) 140 return 'portend' 141 142 def test_metadata_loads(self): 143 pkg_name = self.pkg_with_non_ascii_description(self.site_dir) 144 meta = metadata(pkg_name) 145 assert meta['Description'] == 'pôrˈtend' 146 147 def test_metadata_loads_egg_info(self): 148 pkg_name = self.pkg_with_non_ascii_description_egg_info(self.site_dir) 149 meta = metadata(pkg_name) 150 assert meta.get_payload() == 'pôrˈtend\n' 151 152 153class DiscoveryTests(fixtures.EggInfoPkg, 154 fixtures.DistInfoPkg, 155 unittest.TestCase): 156 157 def test_package_discovery(self): 158 dists = list(distributions()) 159 assert all( 160 isinstance(dist, Distribution) 161 for dist in dists 162 ) 163 assert any( 164 dist.metadata['Name'] == 'egginfo-pkg' 165 for dist in dists 166 ) 167 assert any( 168 dist.metadata['Name'] == 'distinfo-pkg' 169 for dist in dists 170 ) 171 172 def test_invalid_usage(self): 173 with self.assertRaises(ValueError): 174 list(distributions(context='something', name='else')) 175 176 177class DirectoryTest(fixtures.OnSysPath, fixtures.SiteDir, unittest.TestCase): 178 def test_egg_info(self): 179 # make an `EGG-INFO` directory that's unrelated 180 self.site_dir.joinpath('EGG-INFO').mkdir() 181 # used to crash with `IsADirectoryError` 182 with self.assertRaises(PackageNotFoundError): 183 version('unknown-package') 184 185 def test_egg(self): 186 egg = self.site_dir.joinpath('foo-3.6.egg') 187 egg.mkdir() 188 with self.add_sys_path(egg): 189 with self.assertRaises(PackageNotFoundError): 190 version('foo') 191 192 193class MissingSysPath(fixtures.OnSysPath, unittest.TestCase): 194 site_dir = '/does-not-exist' 195 196 def test_discovery(self): 197 """ 198 Discovering distributions should succeed even if 199 there is an invalid path on sys.path. 200 """ 201 importlib.metadata.distributions() 202 203 204class InaccessibleSysPath(fixtures.OnSysPath, ffs.TestCase): 205 site_dir = '/access-denied' 206 207 def setUp(self): 208 super(InaccessibleSysPath, self).setUp() 209 self.setUpPyfakefs() 210 self.fs.create_dir(self.site_dir, perm_bits=000) 211 212 def test_discovery(self): 213 """ 214 Discovering distributions should succeed even if 215 there is an invalid path on sys.path. 216 """ 217 list(importlib.metadata.distributions()) 218 219 220class TestEntryPoints(unittest.TestCase): 221 def __init__(self, *args): 222 super(TestEntryPoints, self).__init__(*args) 223 self.ep = importlib.metadata.EntryPoint('name', 'value', 'group') 224 225 def test_entry_point_pickleable(self): 226 revived = pickle.loads(pickle.dumps(self.ep)) 227 assert revived == self.ep 228 229 def test_immutable(self): 230 """EntryPoints should be immutable""" 231 with self.assertRaises(AttributeError): 232 self.ep.name = 'badactor' 233 234 def test_repr(self): 235 assert 'EntryPoint' in repr(self.ep) 236 assert 'name=' in repr(self.ep) 237 assert "'name'" in repr(self.ep) 238 239 def test_hashable(self): 240 """EntryPoints should be hashable""" 241 hash(self.ep) 242 243 def test_json_dump(self): 244 """ 245 json should not expect to be able to dump an EntryPoint 246 """ 247 with self.assertRaises(Exception): 248 json.dumps(self.ep) 249 250 def test_module(self): 251 assert self.ep.module == 'value' 252 253 def test_attr(self): 254 assert self.ep.attr is None 255 256 257class FileSystem( 258 fixtures.OnSysPath, fixtures.SiteDir, fixtures.FileBuilder, 259 unittest.TestCase): 260 def test_unicode_dir_on_sys_path(self): 261 """ 262 Ensure a Unicode subdirectory of a directory on sys.path 263 does not crash. 264 """ 265 fixtures.build_files( 266 {self.unicode_filename(): {}}, 267 prefix=self.site_dir, 268 ) 269 list(distributions()) 270