python (3.12.0)
1 import os
2 import sys
3 import copy
4 import shutil
5 import pathlib
6 import tempfile
7 import textwrap
8 import functools
9 import contextlib
10
11 from test.support.os_helper import FS_NONASCII
12 from test.support import requires_zlib
13
14 from . import _path
15 from ._path import FilesSpec
16
17
18 try:
19 from importlib import resources # type: ignore
20
21 getattr(resources, 'files')
22 getattr(resources, 'as_file')
23 except (ImportError, AttributeError):
24 import importlib_resources as resources # type: ignore
25
26
27 @contextlib.contextmanager
28 def tempdir():
29 tmpdir = tempfile.mkdtemp()
30 try:
31 yield pathlib.Path(tmpdir)
32 finally:
33 shutil.rmtree(tmpdir)
34
35
36 @contextlib.contextmanager
37 def save_cwd():
38 orig = os.getcwd()
39 try:
40 yield
41 finally:
42 os.chdir(orig)
43
44
45 @contextlib.contextmanager
46 def tempdir_as_cwd():
47 with tempdir() as tmp:
48 with save_cwd():
49 os.chdir(str(tmp))
50 yield tmp
51
52
53 @contextlib.contextmanager
54 def install_finder(finder):
55 sys.meta_path.append(finder)
56 try:
57 yield
58 finally:
59 sys.meta_path.remove(finder)
60
61
62 class ESC[4;38;5;81mFixtures:
63 def setUp(self):
64 self.fixtures = contextlib.ExitStack()
65 self.addCleanup(self.fixtures.close)
66
67
68 class ESC[4;38;5;81mSiteDir(ESC[4;38;5;149mFixtures):
69 def setUp(self):
70 super().setUp()
71 self.site_dir = self.fixtures.enter_context(tempdir())
72
73
74 class ESC[4;38;5;81mOnSysPath(ESC[4;38;5;149mFixtures):
75 @staticmethod
76 @contextlib.contextmanager
77 def add_sys_path(dir):
78 sys.path[:0] = [str(dir)]
79 try:
80 yield
81 finally:
82 sys.path.remove(str(dir))
83
84 def setUp(self):
85 super().setUp()
86 self.fixtures.enter_context(self.add_sys_path(self.site_dir))
87
88
89 class ESC[4;38;5;81mDistInfoPkg(ESC[4;38;5;149mOnSysPath, ESC[4;38;5;149mSiteDir):
90 files: FilesSpec = {
91 "distinfo_pkg-1.0.0.dist-info": {
92 "METADATA": """
93 Name: distinfo-pkg
94 Author: Steven Ma
95 Version: 1.0.0
96 Requires-Dist: wheel >= 1.0
97 Requires-Dist: pytest; extra == 'test'
98 Keywords: sample package
99
100 Once upon a time
101 There was a distinfo pkg
102 """,
103 "RECORD": "mod.py,sha256=abc,20\n",
104 "entry_points.txt": """
105 [entries]
106 main = mod:main
107 ns:sub = mod:main
108 """,
109 },
110 "mod.py": """
111 def main():
112 print("hello world")
113 """,
114 }
115
116 def setUp(self):
117 super().setUp()
118 build_files(DistInfoPkg.files, self.site_dir)
119
120 def make_uppercase(self):
121 """
122 Rewrite metadata with everything uppercase.
123 """
124 shutil.rmtree(self.site_dir / "distinfo_pkg-1.0.0.dist-info")
125 files = copy.deepcopy(DistInfoPkg.files)
126 info = files["distinfo_pkg-1.0.0.dist-info"]
127 info["METADATA"] = info["METADATA"].upper()
128 build_files(files, self.site_dir)
129
130
131 class ESC[4;38;5;81mDistInfoPkgWithDot(ESC[4;38;5;149mOnSysPath, ESC[4;38;5;149mSiteDir):
132 files: FilesSpec = {
133 "pkg_dot-1.0.0.dist-info": {
134 "METADATA": """
135 Name: pkg.dot
136 Version: 1.0.0
137 """,
138 },
139 }
140
141 def setUp(self):
142 super().setUp()
143 build_files(DistInfoPkgWithDot.files, self.site_dir)
144
145
146 class ESC[4;38;5;81mDistInfoPkgWithDotLegacy(ESC[4;38;5;149mOnSysPath, ESC[4;38;5;149mSiteDir):
147 files: FilesSpec = {
148 "pkg.dot-1.0.0.dist-info": {
149 "METADATA": """
150 Name: pkg.dot
151 Version: 1.0.0
152 """,
153 },
154 "pkg.lot.egg-info": {
155 "METADATA": """
156 Name: pkg.lot
157 Version: 1.0.0
158 """,
159 },
160 }
161
162 def setUp(self):
163 super().setUp()
164 build_files(DistInfoPkgWithDotLegacy.files, self.site_dir)
165
166
167 class ESC[4;38;5;81mDistInfoPkgOffPath(ESC[4;38;5;149mSiteDir):
168 def setUp(self):
169 super().setUp()
170 build_files(DistInfoPkg.files, self.site_dir)
171
172
173 class ESC[4;38;5;81mEggInfoPkg(ESC[4;38;5;149mOnSysPath, ESC[4;38;5;149mSiteDir):
174 files: FilesSpec = {
175 "egginfo_pkg.egg-info": {
176 "PKG-INFO": """
177 Name: egginfo-pkg
178 Author: Steven Ma
179 License: Unknown
180 Version: 1.0.0
181 Classifier: Intended Audience :: Developers
182 Classifier: Topic :: Software Development :: Libraries
183 Keywords: sample package
184 Description: Once upon a time
185 There was an egginfo package
186 """,
187 "SOURCES.txt": """
188 mod.py
189 egginfo_pkg.egg-info/top_level.txt
190 """,
191 "entry_points.txt": """
192 [entries]
193 main = mod:main
194 """,
195 "requires.txt": """
196 wheel >= 1.0; python_version >= "2.7"
197 [test]
198 pytest
199 """,
200 "top_level.txt": "mod\n",
201 },
202 "mod.py": """
203 def main():
204 print("hello world")
205 """,
206 }
207
208 def setUp(self):
209 super().setUp()
210 build_files(EggInfoPkg.files, prefix=self.site_dir)
211
212
213 class ESC[4;38;5;81mEggInfoPkgPipInstalledNoToplevel(ESC[4;38;5;149mOnSysPath, ESC[4;38;5;149mSiteDir):
214 files: FilesSpec = {
215 "egg_with_module_pkg.egg-info": {
216 "PKG-INFO": "Name: egg_with_module-pkg",
217 # SOURCES.txt is made from the source archive, and contains files
218 # (setup.py) that are not present after installation.
219 "SOURCES.txt": """
220 egg_with_module.py
221 setup.py
222 egg_with_module_pkg.egg-info/PKG-INFO
223 egg_with_module_pkg.egg-info/SOURCES.txt
224 egg_with_module_pkg.egg-info/top_level.txt
225 """,
226 # installed-files.txt is written by pip, and is a strictly more
227 # accurate source than SOURCES.txt as to the installed contents of
228 # the package.
229 "installed-files.txt": """
230 ../egg_with_module.py
231 PKG-INFO
232 SOURCES.txt
233 top_level.txt
234 """,
235 # missing top_level.txt (to trigger fallback to installed-files.txt)
236 },
237 "egg_with_module.py": """
238 def main():
239 print("hello world")
240 """,
241 }
242
243 def setUp(self):
244 super().setUp()
245 build_files(EggInfoPkgPipInstalledNoToplevel.files, prefix=self.site_dir)
246
247
248 class ESC[4;38;5;81mEggInfoPkgPipInstalledNoModules(ESC[4;38;5;149mOnSysPath, ESC[4;38;5;149mSiteDir):
249 files: FilesSpec = {
250 "egg_with_no_modules_pkg.egg-info": {
251 "PKG-INFO": "Name: egg_with_no_modules-pkg",
252 # SOURCES.txt is made from the source archive, and contains files
253 # (setup.py) that are not present after installation.
254 "SOURCES.txt": """
255 setup.py
256 egg_with_no_modules_pkg.egg-info/PKG-INFO
257 egg_with_no_modules_pkg.egg-info/SOURCES.txt
258 egg_with_no_modules_pkg.egg-info/top_level.txt
259 """,
260 # installed-files.txt is written by pip, and is a strictly more
261 # accurate source than SOURCES.txt as to the installed contents of
262 # the package.
263 "installed-files.txt": """
264 PKG-INFO
265 SOURCES.txt
266 top_level.txt
267 """,
268 # top_level.txt correctly reflects that no modules are installed
269 "top_level.txt": b"\n",
270 },
271 }
272
273 def setUp(self):
274 super().setUp()
275 build_files(EggInfoPkgPipInstalledNoModules.files, prefix=self.site_dir)
276
277
278 class ESC[4;38;5;81mEggInfoPkgSourcesFallback(ESC[4;38;5;149mOnSysPath, ESC[4;38;5;149mSiteDir):
279 files: FilesSpec = {
280 "sources_fallback_pkg.egg-info": {
281 "PKG-INFO": "Name: sources_fallback-pkg",
282 # SOURCES.txt is made from the source archive, and contains files
283 # (setup.py) that are not present after installation.
284 "SOURCES.txt": """
285 sources_fallback.py
286 setup.py
287 sources_fallback_pkg.egg-info/PKG-INFO
288 sources_fallback_pkg.egg-info/SOURCES.txt
289 """,
290 # missing installed-files.txt (i.e. not installed by pip) and
291 # missing top_level.txt (to trigger fallback to SOURCES.txt)
292 },
293 "sources_fallback.py": """
294 def main():
295 print("hello world")
296 """,
297 }
298
299 def setUp(self):
300 super().setUp()
301 build_files(EggInfoPkgSourcesFallback.files, prefix=self.site_dir)
302
303
304 class ESC[4;38;5;81mEggInfoFile(ESC[4;38;5;149mOnSysPath, ESC[4;38;5;149mSiteDir):
305 files: FilesSpec = {
306 "egginfo_file.egg-info": """
307 Metadata-Version: 1.0
308 Name: egginfo_file
309 Version: 0.1
310 Summary: An example package
311 Home-page: www.example.com
312 Author: Eric Haffa-Vee
313 Author-email: eric@example.coms
314 License: UNKNOWN
315 Description: UNKNOWN
316 Platform: UNKNOWN
317 """,
318 }
319
320 def setUp(self):
321 super().setUp()
322 build_files(EggInfoFile.files, prefix=self.site_dir)
323
324
325 # dedent all text strings before writing
326 orig = _path.create.registry[str]
327 _path.create.register(str, lambda content, path: orig(DALS(content), path))
328
329
330 build_files = _path.build
331
332
333 def build_record(file_defs):
334 return ''.join(f'{name},,\n' for name in record_names(file_defs))
335
336
337 def record_names(file_defs):
338 recording = _path.Recording()
339 _path.build(file_defs, recording)
340 return recording.record
341
342
343 class ESC[4;38;5;81mFileBuilder:
344 def unicode_filename(self):
345 return FS_NONASCII or self.skip("File system does not support non-ascii.")
346
347
348 def DALS(str):
349 "Dedent and left-strip"
350 return textwrap.dedent(str).lstrip()
351
352
353 @requires_zlib()
354 class ESC[4;38;5;81mZipFixtures:
355 root = 'test.test_importlib.data'
356
357 def _fixture_on_path(self, filename):
358 pkg_file = resources.files(self.root).joinpath(filename)
359 file = self.resources.enter_context(resources.as_file(pkg_file))
360 assert file.name.startswith('example'), file.name
361 sys.path.insert(0, str(file))
362 self.resources.callback(sys.path.pop, 0)
363
364 def setUp(self):
365 # Add self.zip_name to the front of sys.path.
366 self.resources = contextlib.ExitStack()
367 self.addCleanup(self.resources.close)
368
369
370 def parameterize(*args_set):
371 """Run test method with a series of parameters."""
372
373 def wrapper(func):
374 @functools.wraps(func)
375 def _inner(self):
376 for args in args_set:
377 with self.subTest(**args):
378 func(self, **args)
379
380 return _inner
381
382 return wrapper