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 from typing import Dict, Union
14
15 try:
16 from importlib import resources # type: ignore
17
18 getattr(resources, 'files')
19 getattr(resources, 'as_file')
20 except (ImportError, AttributeError):
21 import importlib_resources as resources # type: ignore
22
23
24 @contextlib.contextmanager
25 def tempdir():
26 tmpdir = tempfile.mkdtemp()
27 try:
28 yield pathlib.Path(tmpdir)
29 finally:
30 shutil.rmtree(tmpdir)
31
32
33 @contextlib.contextmanager
34 def save_cwd():
35 orig = os.getcwd()
36 try:
37 yield
38 finally:
39 os.chdir(orig)
40
41
42 @contextlib.contextmanager
43 def tempdir_as_cwd():
44 with tempdir() as tmp:
45 with save_cwd():
46 os.chdir(str(tmp))
47 yield tmp
48
49
50 @contextlib.contextmanager
51 def install_finder(finder):
52 sys.meta_path.append(finder)
53 try:
54 yield
55 finally:
56 sys.meta_path.remove(finder)
57
58
59 class ESC[4;38;5;81mFixtures:
60 def setUp(self):
61 self.fixtures = contextlib.ExitStack()
62 self.addCleanup(self.fixtures.close)
63
64
65 class ESC[4;38;5;81mSiteDir(ESC[4;38;5;149mFixtures):
66 def setUp(self):
67 super().setUp()
68 self.site_dir = self.fixtures.enter_context(tempdir())
69
70
71 class ESC[4;38;5;81mOnSysPath(ESC[4;38;5;149mFixtures):
72 @staticmethod
73 @contextlib.contextmanager
74 def add_sys_path(dir):
75 sys.path[:0] = [str(dir)]
76 try:
77 yield
78 finally:
79 sys.path.remove(str(dir))
80
81 def setUp(self):
82 super().setUp()
83 self.fixtures.enter_context(self.add_sys_path(self.site_dir))
84
85
86 # Except for python/mypy#731, prefer to define
87 # FilesDef = Dict[str, Union['FilesDef', str]]
88 FilesDef = Dict[str, Union[Dict[str, Union[Dict[str, str], str]], str]]
89
90
91 class ESC[4;38;5;81mDistInfoPkg(ESC[4;38;5;149mOnSysPath, ESC[4;38;5;149mSiteDir):
92 files: FilesDef = {
93 "distinfo_pkg-1.0.0.dist-info": {
94 "METADATA": """
95 Name: distinfo-pkg
96 Author: Steven Ma
97 Version: 1.0.0
98 Requires-Dist: wheel >= 1.0
99 Requires-Dist: pytest; extra == 'test'
100 Keywords: sample package
101
102 Once upon a time
103 There was a distinfo pkg
104 """,
105 "RECORD": "mod.py,sha256=abc,20\n",
106 "entry_points.txt": """
107 [entries]
108 main = mod:main
109 ns:sub = mod:main
110 """,
111 },
112 "mod.py": """
113 def main():
114 print("hello world")
115 """,
116 }
117
118 def setUp(self):
119 super().setUp()
120 build_files(DistInfoPkg.files, self.site_dir)
121
122 def make_uppercase(self):
123 """
124 Rewrite metadata with everything uppercase.
125 """
126 shutil.rmtree(self.site_dir / "distinfo_pkg-1.0.0.dist-info")
127 files = copy.deepcopy(DistInfoPkg.files)
128 info = files["distinfo_pkg-1.0.0.dist-info"]
129 info["METADATA"] = info["METADATA"].upper()
130 build_files(files, self.site_dir)
131
132
133 class ESC[4;38;5;81mDistInfoPkgWithDot(ESC[4;38;5;149mOnSysPath, ESC[4;38;5;149mSiteDir):
134 files: FilesDef = {
135 "pkg_dot-1.0.0.dist-info": {
136 "METADATA": """
137 Name: pkg.dot
138 Version: 1.0.0
139 """,
140 },
141 }
142
143 def setUp(self):
144 super().setUp()
145 build_files(DistInfoPkgWithDot.files, self.site_dir)
146
147
148 class ESC[4;38;5;81mDistInfoPkgWithDotLegacy(ESC[4;38;5;149mOnSysPath, ESC[4;38;5;149mSiteDir):
149 files: FilesDef = {
150 "pkg.dot-1.0.0.dist-info": {
151 "METADATA": """
152 Name: pkg.dot
153 Version: 1.0.0
154 """,
155 },
156 "pkg.lot.egg-info": {
157 "METADATA": """
158 Name: pkg.lot
159 Version: 1.0.0
160 """,
161 },
162 }
163
164 def setUp(self):
165 super().setUp()
166 build_files(DistInfoPkgWithDotLegacy.files, self.site_dir)
167
168
169 class ESC[4;38;5;81mDistInfoPkgOffPath(ESC[4;38;5;149mSiteDir):
170 def setUp(self):
171 super().setUp()
172 build_files(DistInfoPkg.files, self.site_dir)
173
174
175 class ESC[4;38;5;81mEggInfoPkg(ESC[4;38;5;149mOnSysPath, ESC[4;38;5;149mSiteDir):
176 files: FilesDef = {
177 "egginfo_pkg.egg-info": {
178 "PKG-INFO": """
179 Name: egginfo-pkg
180 Author: Steven Ma
181 License: Unknown
182 Version: 1.0.0
183 Classifier: Intended Audience :: Developers
184 Classifier: Topic :: Software Development :: Libraries
185 Keywords: sample package
186 Description: Once upon a time
187 There was an egginfo package
188 """,
189 "SOURCES.txt": """
190 mod.py
191 egginfo_pkg.egg-info/top_level.txt
192 """,
193 "entry_points.txt": """
194 [entries]
195 main = mod:main
196 """,
197 "requires.txt": """
198 wheel >= 1.0; python_version >= "2.7"
199 [test]
200 pytest
201 """,
202 "top_level.txt": "mod\n",
203 },
204 "mod.py": """
205 def main():
206 print("hello world")
207 """,
208 }
209
210 def setUp(self):
211 super().setUp()
212 build_files(EggInfoPkg.files, prefix=self.site_dir)
213
214
215 class ESC[4;38;5;81mEggInfoFile(ESC[4;38;5;149mOnSysPath, ESC[4;38;5;149mSiteDir):
216 files: FilesDef = {
217 "egginfo_file.egg-info": """
218 Metadata-Version: 1.0
219 Name: egginfo_file
220 Version: 0.1
221 Summary: An example package
222 Home-page: www.example.com
223 Author: Eric Haffa-Vee
224 Author-email: eric@example.coms
225 License: UNKNOWN
226 Description: UNKNOWN
227 Platform: UNKNOWN
228 """,
229 }
230
231 def setUp(self):
232 super().setUp()
233 build_files(EggInfoFile.files, prefix=self.site_dir)
234
235
236 def build_files(file_defs, prefix=pathlib.Path()):
237 """Build a set of files/directories, as described by the
238
239 file_defs dictionary. Each key/value pair in the dictionary is
240 interpreted as a filename/contents pair. If the contents value is a
241 dictionary, a directory is created, and the dictionary interpreted
242 as the files within it, recursively.
243
244 For example:
245
246 {"README.txt": "A README file",
247 "foo": {
248 "__init__.py": "",
249 "bar": {
250 "__init__.py": "",
251 },
252 "baz.py": "# Some code",
253 }
254 }
255 """
256 for name, contents in file_defs.items():
257 full_name = prefix / name
258 if isinstance(contents, dict):
259 full_name.mkdir()
260 build_files(contents, prefix=full_name)
261 else:
262 if isinstance(contents, bytes):
263 with full_name.open('wb') as f:
264 f.write(contents)
265 else:
266 with full_name.open('w', encoding='utf-8') as f:
267 f.write(DALS(contents))
268
269
270 class ESC[4;38;5;81mFileBuilder:
271 def unicode_filename(self):
272 return FS_NONASCII or self.skip("File system does not support non-ascii.")
273
274
275 def DALS(str):
276 "Dedent and left-strip"
277 return textwrap.dedent(str).lstrip()
278
279
280 class ESC[4;38;5;81mNullFinder:
281 def find_module(self, name):
282 pass
283
284
285 @requires_zlib()
286 class ESC[4;38;5;81mZipFixtures:
287 root = 'test.test_importlib.data'
288
289 def _fixture_on_path(self, filename):
290 pkg_file = resources.files(self.root).joinpath(filename)
291 file = self.resources.enter_context(resources.as_file(pkg_file))
292 assert file.name.startswith('example'), file.name
293 sys.path.insert(0, str(file))
294 self.resources.callback(sys.path.pop, 0)
295
296 def setUp(self):
297 # Add self.zip_name to the front of sys.path.
298 self.resources = contextlib.ExitStack()
299 self.addCleanup(self.resources.close)
300
301
302 def parameterize(*args_set):
303 """Run test method with a series of parameters."""
304
305 def wrapper(func):
306 @functools.wraps(func)
307 def _inner(self):
308 for args in args_set:
309 with self.subTest(**args):
310 func(self, **args)
311
312 return _inner
313
314 return wrapper