1import os 2import sys 3import shutil 4import pathlib 5import tempfile 6import textwrap 7import contextlib 8 9 10@contextlib.contextmanager 11def tempdir(): 12 tmpdir = tempfile.mkdtemp() 13 try: 14 yield pathlib.Path(tmpdir) 15 finally: 16 shutil.rmtree(tmpdir) 17 18 19@contextlib.contextmanager 20def save_cwd(): 21 orig = os.getcwd() 22 try: 23 yield 24 finally: 25 os.chdir(orig) 26 27 28@contextlib.contextmanager 29def tempdir_as_cwd(): 30 with tempdir() as tmp: 31 with save_cwd(): 32 os.chdir(str(tmp)) 33 yield tmp 34 35 36@contextlib.contextmanager 37def install_finder(finder): 38 sys.meta_path.append(finder) 39 try: 40 yield 41 finally: 42 sys.meta_path.remove(finder) 43 44 45class Fixtures: 46 def setUp(self): 47 self.fixtures = contextlib.ExitStack() 48 self.addCleanup(self.fixtures.close) 49 50 51class SiteDir(Fixtures): 52 def setUp(self): 53 super(SiteDir, self).setUp() 54 self.site_dir = self.fixtures.enter_context(tempdir()) 55 56 57class OnSysPath(Fixtures): 58 @staticmethod 59 @contextlib.contextmanager 60 def add_sys_path(dir): 61 sys.path[:0] = [str(dir)] 62 try: 63 yield 64 finally: 65 sys.path.remove(str(dir)) 66 67 def setUp(self): 68 super(OnSysPath, self).setUp() 69 self.fixtures.enter_context(self.add_sys_path(self.site_dir)) 70 71 72class DistInfoPkg(OnSysPath, SiteDir): 73 files = { 74 "distinfo_pkg-1.0.0.dist-info": { 75 "METADATA": """ 76 Name: distinfo-pkg 77 Author: Steven Ma 78 Version: 1.0.0 79 Requires-Dist: wheel >= 1.0 80 Requires-Dist: pytest; extra == 'test' 81 """, 82 "RECORD": "mod.py,sha256=abc,20\n", 83 "entry_points.txt": """ 84 [entries] 85 main = mod:main 86 ns:sub = mod:main 87 """ 88 }, 89 "mod.py": """ 90 def main(): 91 print("hello world") 92 """, 93 } 94 95 def setUp(self): 96 super(DistInfoPkg, self).setUp() 97 build_files(DistInfoPkg.files, self.site_dir) 98 99 100class DistInfoPkgOffPath(SiteDir): 101 def setUp(self): 102 super(DistInfoPkgOffPath, self).setUp() 103 build_files(DistInfoPkg.files, self.site_dir) 104 105 106class EggInfoPkg(OnSysPath, SiteDir): 107 files = { 108 "egginfo_pkg.egg-info": { 109 "PKG-INFO": """ 110 Name: egginfo-pkg 111 Author: Steven Ma 112 License: Unknown 113 Version: 1.0.0 114 Classifier: Intended Audience :: Developers 115 Classifier: Topic :: Software Development :: Libraries 116 """, 117 "SOURCES.txt": """ 118 mod.py 119 egginfo_pkg.egg-info/top_level.txt 120 """, 121 "entry_points.txt": """ 122 [entries] 123 main = mod:main 124 """, 125 "requires.txt": """ 126 wheel >= 1.0; python_version >= "2.7" 127 [test] 128 pytest 129 """, 130 "top_level.txt": "mod\n" 131 }, 132 "mod.py": """ 133 def main(): 134 print("hello world") 135 """, 136 } 137 138 def setUp(self): 139 super(EggInfoPkg, self).setUp() 140 build_files(EggInfoPkg.files, prefix=self.site_dir) 141 142 143class EggInfoFile(OnSysPath, SiteDir): 144 files = { 145 "egginfo_file.egg-info": """ 146 Metadata-Version: 1.0 147 Name: egginfo_file 148 Version: 0.1 149 Summary: An example package 150 Home-page: www.example.com 151 Author: Eric Haffa-Vee 152 Author-email: eric@example.coms 153 License: UNKNOWN 154 Description: UNKNOWN 155 Platform: UNKNOWN 156 """, 157 } 158 159 def setUp(self): 160 super(EggInfoFile, self).setUp() 161 build_files(EggInfoFile.files, prefix=self.site_dir) 162 163 164class LocalPackage: 165 files = { 166 "setup.py": """ 167 import setuptools 168 setuptools.setup(name="local-pkg", version="2.0.1") 169 """, 170 } 171 172 def setUp(self): 173 self.fixtures = contextlib.ExitStack() 174 self.addCleanup(self.fixtures.close) 175 self.fixtures.enter_context(tempdir_as_cwd()) 176 build_files(self.files) 177 178 179def build_files(file_defs, prefix=pathlib.Path()): 180 """Build a set of files/directories, as described by the 181 182 file_defs dictionary. Each key/value pair in the dictionary is 183 interpreted as a filename/contents pair. If the contents value is a 184 dictionary, a directory is created, and the dictionary interpreted 185 as the files within it, recursively. 186 187 For example: 188 189 {"README.txt": "A README file", 190 "foo": { 191 "__init__.py": "", 192 "bar": { 193 "__init__.py": "", 194 }, 195 "baz.py": "# Some code", 196 } 197 } 198 """ 199 for name, contents in file_defs.items(): 200 full_name = prefix / name 201 if isinstance(contents, dict): 202 full_name.mkdir() 203 build_files(contents, prefix=full_name) 204 else: 205 if isinstance(contents, bytes): 206 with full_name.open('wb') as f: 207 f.write(contents) 208 else: 209 with full_name.open('w') as f: 210 f.write(DALS(contents)) 211 212 213class FileBuilder: 214 def unicode_filename(self): 215 try: 216 import test.support 217 except ImportError: 218 # outside CPython, hard-code a unicode snowman 219 return '☃' 220 return test.support.FS_NONASCII or \ 221 self.skip("File system does not support non-ascii.") 222 223 224def DALS(str): 225 "Dedent and left-strip" 226 return textwrap.dedent(str).lstrip() 227 228 229class NullFinder: 230 def find_module(self, name): 231 pass 232