288 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			288 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			Python
		
	
	
	
| import os
 | |
| import sys
 | |
| import copy
 | |
| 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'
 | |
|                 Keywords: sample package
 | |
| 
 | |
|                 Once upon a time
 | |
|                 There was a distinfo pkg
 | |
|                 """,
 | |
|             "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)
 | |
| 
 | |
|     def make_uppercase(self):
 | |
|         """
 | |
|         Rewrite metadata with everything uppercase.
 | |
|         """
 | |
|         shutil.rmtree(self.site_dir / "distinfo_pkg-1.0.0.dist-info")
 | |
|         files = copy.deepcopy(DistInfoPkg.files)
 | |
|         info = files["distinfo_pkg-1.0.0.dist-info"]
 | |
|         info["METADATA"] = info["METADATA"].upper()
 | |
|         build_files(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
 | |
|                 Keywords: sample package
 | |
|                 Description: Once upon a time
 | |
|                         There was an egginfo package
 | |
|                 """,
 | |
|             "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', encoding='utf-8') 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
 |