mirror of
				https://github.com/python/cpython.git
				synced 2025-11-03 23:21:29 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			395 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			395 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
import sys
 | 
						|
import copy
 | 
						|
import json
 | 
						|
import shutil
 | 
						|
import pathlib
 | 
						|
import textwrap
 | 
						|
import functools
 | 
						|
import contextlib
 | 
						|
 | 
						|
from test.support import import_helper
 | 
						|
from test.support import os_helper
 | 
						|
from test.support import requires_zlib
 | 
						|
 | 
						|
from . import _path
 | 
						|
from ._path import FilesSpec
 | 
						|
 | 
						|
 | 
						|
try:
 | 
						|
    from importlib import resources  # type: ignore
 | 
						|
 | 
						|
    getattr(resources, 'files')
 | 
						|
    getattr(resources, 'as_file')
 | 
						|
except (ImportError, AttributeError):
 | 
						|
    import importlib_resources as resources  # type: ignore
 | 
						|
 | 
						|
 | 
						|
@contextlib.contextmanager
 | 
						|
def tmp_path():
 | 
						|
    """
 | 
						|
    Like os_helper.temp_dir, but yields a pathlib.Path.
 | 
						|
    """
 | 
						|
    with os_helper.temp_dir() as path:
 | 
						|
        yield pathlib.Path(path)
 | 
						|
 | 
						|
 | 
						|
@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().setUp()
 | 
						|
        self.site_dir = self.fixtures.enter_context(tmp_path())
 | 
						|
 | 
						|
 | 
						|
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().setUp()
 | 
						|
        self.fixtures.enter_context(self.add_sys_path(self.site_dir))
 | 
						|
        self.fixtures.enter_context(import_helper.isolated_modules())
 | 
						|
 | 
						|
 | 
						|
class SiteBuilder(SiteDir):
 | 
						|
    def setUp(self):
 | 
						|
        super().setUp()
 | 
						|
        for cls in self.__class__.mro():
 | 
						|
            with contextlib.suppress(AttributeError):
 | 
						|
                build_files(cls.files, prefix=self.site_dir)
 | 
						|
 | 
						|
 | 
						|
class DistInfoPkg(OnSysPath, SiteBuilder):
 | 
						|
    files: FilesSpec = {
 | 
						|
        "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 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 DistInfoPkgEditable(DistInfoPkg):
 | 
						|
    """
 | 
						|
    Package with a PEP 660 direct_url.json.
 | 
						|
    """
 | 
						|
 | 
						|
    some_hash = '524127ce937f7cb65665130c695abd18ca386f60bb29687efb976faa1596fdcc'
 | 
						|
    files: FilesSpec = {
 | 
						|
        'distinfo_pkg-1.0.0.dist-info': {
 | 
						|
            'direct_url.json': json.dumps({
 | 
						|
                "archive_info": {
 | 
						|
                    "hash": f"sha256={some_hash}",
 | 
						|
                    "hashes": {"sha256": f"{some_hash}"},
 | 
						|
                },
 | 
						|
                "url": "file:///path/to/distinfo_pkg-1.0.0.editable-py3-none-any.whl",
 | 
						|
            })
 | 
						|
        },
 | 
						|
    }
 | 
						|
 | 
						|
 | 
						|
class DistInfoPkgWithDot(OnSysPath, SiteBuilder):
 | 
						|
    files: FilesSpec = {
 | 
						|
        "pkg_dot-1.0.0.dist-info": {
 | 
						|
            "METADATA": """
 | 
						|
                Name: pkg.dot
 | 
						|
                Version: 1.0.0
 | 
						|
                """,
 | 
						|
        },
 | 
						|
    }
 | 
						|
 | 
						|
 | 
						|
class DistInfoPkgWithDotLegacy(OnSysPath, SiteBuilder):
 | 
						|
    files: FilesSpec = {
 | 
						|
        "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
 | 
						|
                """,
 | 
						|
        },
 | 
						|
    }
 | 
						|
 | 
						|
 | 
						|
class DistInfoPkgOffPath(SiteBuilder):
 | 
						|
    files = DistInfoPkg.files
 | 
						|
 | 
						|
 | 
						|
class EggInfoPkg(OnSysPath, SiteBuilder):
 | 
						|
    files: FilesSpec = {
 | 
						|
        "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")
 | 
						|
            """,
 | 
						|
    }
 | 
						|
 | 
						|
 | 
						|
class EggInfoPkgPipInstalledNoToplevel(OnSysPath, SiteBuilder):
 | 
						|
    files: FilesSpec = {
 | 
						|
        "egg_with_module_pkg.egg-info": {
 | 
						|
            "PKG-INFO": "Name: egg_with_module-pkg",
 | 
						|
            # SOURCES.txt is made from the source archive, and contains files
 | 
						|
            # (setup.py) that are not present after installation.
 | 
						|
            "SOURCES.txt": """
 | 
						|
                egg_with_module.py
 | 
						|
                setup.py
 | 
						|
                egg_with_module_pkg.egg-info/PKG-INFO
 | 
						|
                egg_with_module_pkg.egg-info/SOURCES.txt
 | 
						|
                egg_with_module_pkg.egg-info/top_level.txt
 | 
						|
            """,
 | 
						|
            # installed-files.txt is written by pip, and is a strictly more
 | 
						|
            # accurate source than SOURCES.txt as to the installed contents of
 | 
						|
            # the package.
 | 
						|
            "installed-files.txt": """
 | 
						|
                ../egg_with_module.py
 | 
						|
                PKG-INFO
 | 
						|
                SOURCES.txt
 | 
						|
                top_level.txt
 | 
						|
            """,
 | 
						|
            # missing top_level.txt (to trigger fallback to installed-files.txt)
 | 
						|
        },
 | 
						|
        "egg_with_module.py": """
 | 
						|
            def main():
 | 
						|
                print("hello world")
 | 
						|
            """,
 | 
						|
    }
 | 
						|
 | 
						|
 | 
						|
class EggInfoPkgPipInstalledExternalDataFiles(OnSysPath, SiteBuilder):
 | 
						|
    files: FilesSpec = {
 | 
						|
        "egg_with_module_pkg.egg-info": {
 | 
						|
            "PKG-INFO": "Name: egg_with_module-pkg",
 | 
						|
            # SOURCES.txt is made from the source archive, and contains files
 | 
						|
            # (setup.py) that are not present after installation.
 | 
						|
            "SOURCES.txt": """
 | 
						|
                egg_with_module.py
 | 
						|
                setup.py
 | 
						|
                egg_with_module.json
 | 
						|
                egg_with_module_pkg.egg-info/PKG-INFO
 | 
						|
                egg_with_module_pkg.egg-info/SOURCES.txt
 | 
						|
                egg_with_module_pkg.egg-info/top_level.txt
 | 
						|
            """,
 | 
						|
            # installed-files.txt is written by pip, and is a strictly more
 | 
						|
            # accurate source than SOURCES.txt as to the installed contents of
 | 
						|
            # the package.
 | 
						|
            "installed-files.txt": """
 | 
						|
                ../../../etc/jupyter/jupyter_notebook_config.d/relative.json
 | 
						|
                /etc/jupyter/jupyter_notebook_config.d/absolute.json
 | 
						|
                ../egg_with_module.py
 | 
						|
                PKG-INFO
 | 
						|
                SOURCES.txt
 | 
						|
                top_level.txt
 | 
						|
            """,
 | 
						|
            # missing top_level.txt (to trigger fallback to installed-files.txt)
 | 
						|
        },
 | 
						|
        "egg_with_module.py": """
 | 
						|
            def main():
 | 
						|
                print("hello world")
 | 
						|
            """,
 | 
						|
    }
 | 
						|
 | 
						|
 | 
						|
class EggInfoPkgPipInstalledNoModules(OnSysPath, SiteBuilder):
 | 
						|
    files: FilesSpec = {
 | 
						|
        "egg_with_no_modules_pkg.egg-info": {
 | 
						|
            "PKG-INFO": "Name: egg_with_no_modules-pkg",
 | 
						|
            # SOURCES.txt is made from the source archive, and contains files
 | 
						|
            # (setup.py) that are not present after installation.
 | 
						|
            "SOURCES.txt": """
 | 
						|
                setup.py
 | 
						|
                egg_with_no_modules_pkg.egg-info/PKG-INFO
 | 
						|
                egg_with_no_modules_pkg.egg-info/SOURCES.txt
 | 
						|
                egg_with_no_modules_pkg.egg-info/top_level.txt
 | 
						|
            """,
 | 
						|
            # installed-files.txt is written by pip, and is a strictly more
 | 
						|
            # accurate source than SOURCES.txt as to the installed contents of
 | 
						|
            # the package.
 | 
						|
            "installed-files.txt": """
 | 
						|
                PKG-INFO
 | 
						|
                SOURCES.txt
 | 
						|
                top_level.txt
 | 
						|
            """,
 | 
						|
            # top_level.txt correctly reflects that no modules are installed
 | 
						|
            "top_level.txt": b"\n",
 | 
						|
        },
 | 
						|
    }
 | 
						|
 | 
						|
 | 
						|
class EggInfoPkgSourcesFallback(OnSysPath, SiteBuilder):
 | 
						|
    files: FilesSpec = {
 | 
						|
        "sources_fallback_pkg.egg-info": {
 | 
						|
            "PKG-INFO": "Name: sources_fallback-pkg",
 | 
						|
            # SOURCES.txt is made from the source archive, and contains files
 | 
						|
            # (setup.py) that are not present after installation.
 | 
						|
            "SOURCES.txt": """
 | 
						|
                sources_fallback.py
 | 
						|
                setup.py
 | 
						|
                sources_fallback_pkg.egg-info/PKG-INFO
 | 
						|
                sources_fallback_pkg.egg-info/SOURCES.txt
 | 
						|
            """,
 | 
						|
            # missing installed-files.txt (i.e. not installed by pip) and
 | 
						|
            # missing top_level.txt (to trigger fallback to SOURCES.txt)
 | 
						|
        },
 | 
						|
        "sources_fallback.py": """
 | 
						|
            def main():
 | 
						|
                print("hello world")
 | 
						|
            """,
 | 
						|
    }
 | 
						|
 | 
						|
 | 
						|
class EggInfoFile(OnSysPath, SiteBuilder):
 | 
						|
    files: FilesSpec = {
 | 
						|
        "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
 | 
						|
            """,
 | 
						|
    }
 | 
						|
 | 
						|
 | 
						|
# dedent all text strings before writing
 | 
						|
orig = _path.create.registry[str]
 | 
						|
_path.create.register(str, lambda content, path: orig(DALS(content), path))
 | 
						|
 | 
						|
 | 
						|
build_files = _path.build
 | 
						|
 | 
						|
 | 
						|
def build_record(file_defs):
 | 
						|
    return ''.join(f'{name},,\n' for name in record_names(file_defs))
 | 
						|
 | 
						|
 | 
						|
def record_names(file_defs):
 | 
						|
    recording = _path.Recording()
 | 
						|
    _path.build(file_defs, recording)
 | 
						|
    return recording.record
 | 
						|
 | 
						|
 | 
						|
class FileBuilder:
 | 
						|
    def unicode_filename(self):
 | 
						|
        return os_helper.FS_NONASCII or self.skip(
 | 
						|
            "File system does not support non-ascii."
 | 
						|
        )
 | 
						|
 | 
						|
 | 
						|
def DALS(str):
 | 
						|
    "Dedent and left-strip"
 | 
						|
    return textwrap.dedent(str).lstrip()
 | 
						|
 | 
						|
 | 
						|
@requires_zlib()
 | 
						|
class ZipFixtures:
 | 
						|
    root = 'test.test_importlib.metadata.data'
 | 
						|
 | 
						|
    def _fixture_on_path(self, filename):
 | 
						|
        pkg_file = resources.files(self.root).joinpath(filename)
 | 
						|
        file = self.resources.enter_context(resources.as_file(pkg_file))
 | 
						|
        assert file.name.startswith('example'), file.name
 | 
						|
        sys.path.insert(0, str(file))
 | 
						|
        self.resources.callback(sys.path.pop, 0)
 | 
						|
 | 
						|
    def setUp(self):
 | 
						|
        # Add self.zip_name to the front of sys.path.
 | 
						|
        self.resources = contextlib.ExitStack()
 | 
						|
        self.addCleanup(self.resources.close)
 | 
						|
 | 
						|
 | 
						|
def parameterize(*args_set):
 | 
						|
    """Run test method with a series of parameters."""
 | 
						|
 | 
						|
    def wrapper(func):
 | 
						|
        @functools.wraps(func)
 | 
						|
        def _inner(self):
 | 
						|
            for args in args_set:
 | 
						|
                with self.subTest(**args):
 | 
						|
                    func(self, **args)
 | 
						|
 | 
						|
        return _inner
 | 
						|
 | 
						|
    return wrapper
 |