import os import sys import shutil import pathlib import tempfile import textwrap import contextlib from test.support.os_helper import FS_NONASCII from typing import Dict, Union @contextlib.contextmanager def tempdir(): tmpdir = tempfile.mkdtemp() try: yield pathlib.Path(tmpdir) finally: shutil.rmtree(tmpdir) @contextlib.contextmanager def save_cwd(): orig = os.getcwd() try: yield finally: os.chdir(orig) @contextlib.contextmanager def tempdir_as_cwd(): with tempdir() as tmp: with save_cwd(): os.chdir(str(tmp)) yield tmp @contextlib.contextmanager def install_finder(finder): sys.meta_path.append(finder) try: yield finally: sys.meta_path.remove(finder) class Fixtures: def setUp(self): self.fixtures = contextlib.ExitStack() self.addCleanup(self.fixtures.close) class SiteDir(Fixtures): def setUp(self): super(SiteDir, self).setUp() self.site_dir = self.fixtures.enter_context(tempdir()) class OnSysPath(Fixtures): @staticmethod @contextlib.contextmanager def add_sys_path(dir): sys.path[:0] = [str(dir)] try: yield finally: sys.path.remove(str(dir)) def setUp(self): super(OnSysPath, self).setUp() self.fixtures.enter_context(self.add_sys_path(self.site_dir)) # Except for python/mypy#731, prefer to define # FilesDef = Dict[str, Union['FilesDef', str]] FilesDef = Dict[str, Union[Dict[str, Union[Dict[str, str], str]], str]] class DistInfoPkg(OnSysPath, SiteDir): files: FilesDef = { "distinfo_pkg-1.0.0.dist-info": { "METADATA": """ Name: distinfo-pkg Author: Steven Ma Version: 1.0.0 Requires-Dist: wheel >= 1.0 Requires-Dist: pytest; extra == 'test' """, "RECORD": "mod.py,sha256=abc,20\n", "entry_points.txt": """ [entries] main = mod:main ns:sub = mod:main """, }, "mod.py": """ def main(): print("hello world") """, } def setUp(self): super(DistInfoPkg, self).setUp() build_files(DistInfoPkg.files, self.site_dir) class DistInfoPkgWithDot(OnSysPath, SiteDir): files: FilesDef = { "pkg_dot-1.0.0.dist-info": { "METADATA": """ Name: pkg.dot Version: 1.0.0 """, }, } def setUp(self): super(DistInfoPkgWithDot, self).setUp() build_files(DistInfoPkgWithDot.files, self.site_dir) class DistInfoPkgWithDotLegacy(OnSysPath, SiteDir): files: FilesDef = { "pkg.dot-1.0.0.dist-info": { "METADATA": """ Name: pkg.dot Version: 1.0.0 """, }, "pkg.lot.egg-info": { "METADATA": """ Name: pkg.lot Version: 1.0.0 """, }, } def setUp(self): super(DistInfoPkgWithDotLegacy, self).setUp() build_files(DistInfoPkgWithDotLegacy.files, self.site_dir) class DistInfoPkgOffPath(SiteDir): def setUp(self): super(DistInfoPkgOffPath, self).setUp() build_files(DistInfoPkg.files, self.site_dir) class EggInfoPkg(OnSysPath, SiteDir): files: FilesDef = { "egginfo_pkg.egg-info": { "PKG-INFO": """ Name: egginfo-pkg Author: Steven Ma License: Unknown Version: 1.0.0 Classifier: Intended Audience :: Developers Classifier: Topic :: Software Development :: Libraries """, "SOURCES.txt": """ mod.py egginfo_pkg.egg-info/top_level.txt """, "entry_points.txt": """ [entries] main = mod:main """, "requires.txt": """ wheel >= 1.0; python_version >= "2.7" [test] pytest """, "top_level.txt": "mod\n", }, "mod.py": """ def main(): print("hello world") """, } def setUp(self): super(EggInfoPkg, self).setUp() build_files(EggInfoPkg.files, prefix=self.site_dir) class EggInfoFile(OnSysPath, SiteDir): files: FilesDef = { "egginfo_file.egg-info": """ Metadata-Version: 1.0 Name: egginfo_file Version: 0.1 Summary: An example package Home-page: www.example.com Author: Eric Haffa-Vee Author-email: eric@example.coms License: UNKNOWN Description: UNKNOWN Platform: UNKNOWN """, } def setUp(self): super(EggInfoFile, self).setUp() build_files(EggInfoFile.files, prefix=self.site_dir) class LocalPackage: files: FilesDef = { "setup.py": """ import setuptools setuptools.setup(name="local-pkg", version="2.0.1") """, } def setUp(self): self.fixtures = contextlib.ExitStack() self.addCleanup(self.fixtures.close) self.fixtures.enter_context(tempdir_as_cwd()) build_files(self.files) def build_files(file_defs, prefix=pathlib.Path()): """Build a set of files/directories, as described by the file_defs dictionary. Each key/value pair in the dictionary is interpreted as a filename/contents pair. If the contents value is a dictionary, a directory is created, and the dictionary interpreted as the files within it, recursively. For example: {"README.txt": "A README file", "foo": { "__init__.py": "", "bar": { "__init__.py": "", }, "baz.py": "# Some code", } } """ for name, contents in file_defs.items(): full_name = prefix / name if isinstance(contents, dict): full_name.mkdir() build_files(contents, prefix=full_name) else: if isinstance(contents, bytes): with full_name.open('wb') as f: f.write(contents) else: with full_name.open('w') as f: f.write(DALS(contents)) class FileBuilder: def unicode_filename(self): return FS_NONASCII or self.skip("File system does not support non-ascii.") def DALS(str): "Dedent and left-strip" return textwrap.dedent(str).lstrip() class NullFinder: def find_module(self, name): pass