17db96d56Sopenharmony_ciimport re
27db96d56Sopenharmony_ciimport json
37db96d56Sopenharmony_ciimport pickle
47db96d56Sopenharmony_ciimport unittest
57db96d56Sopenharmony_ciimport warnings
67db96d56Sopenharmony_ciimport importlib.metadata
77db96d56Sopenharmony_ci
87db96d56Sopenharmony_citry:
97db96d56Sopenharmony_ci    import pyfakefs.fake_filesystem_unittest as ffs
107db96d56Sopenharmony_ciexcept ImportError:
117db96d56Sopenharmony_ci    from .stubs import fake_filesystem_unittest as ffs
127db96d56Sopenharmony_ci
137db96d56Sopenharmony_cifrom . import fixtures
147db96d56Sopenharmony_cifrom importlib.metadata import (
157db96d56Sopenharmony_ci    Distribution,
167db96d56Sopenharmony_ci    EntryPoint,
177db96d56Sopenharmony_ci    PackageNotFoundError,
187db96d56Sopenharmony_ci    _unique,
197db96d56Sopenharmony_ci    distributions,
207db96d56Sopenharmony_ci    entry_points,
217db96d56Sopenharmony_ci    metadata,
227db96d56Sopenharmony_ci    packages_distributions,
237db96d56Sopenharmony_ci    version,
247db96d56Sopenharmony_ci)
257db96d56Sopenharmony_ci
267db96d56Sopenharmony_ci
277db96d56Sopenharmony_ciclass BasicTests(fixtures.DistInfoPkg, unittest.TestCase):
287db96d56Sopenharmony_ci    version_pattern = r'\d+\.\d+(\.\d)?'
297db96d56Sopenharmony_ci
307db96d56Sopenharmony_ci    def test_retrieves_version_of_self(self):
317db96d56Sopenharmony_ci        dist = Distribution.from_name('distinfo-pkg')
327db96d56Sopenharmony_ci        assert isinstance(dist.version, str)
337db96d56Sopenharmony_ci        assert re.match(self.version_pattern, dist.version)
347db96d56Sopenharmony_ci
357db96d56Sopenharmony_ci    def test_for_name_does_not_exist(self):
367db96d56Sopenharmony_ci        with self.assertRaises(PackageNotFoundError):
377db96d56Sopenharmony_ci            Distribution.from_name('does-not-exist')
387db96d56Sopenharmony_ci
397db96d56Sopenharmony_ci    def test_package_not_found_mentions_metadata(self):
407db96d56Sopenharmony_ci        """
417db96d56Sopenharmony_ci        When a package is not found, that could indicate that the
427db96d56Sopenharmony_ci        packgae is not installed or that it is installed without
437db96d56Sopenharmony_ci        metadata. Ensure the exception mentions metadata to help
447db96d56Sopenharmony_ci        guide users toward the cause. See #124.
457db96d56Sopenharmony_ci        """
467db96d56Sopenharmony_ci        with self.assertRaises(PackageNotFoundError) as ctx:
477db96d56Sopenharmony_ci            Distribution.from_name('does-not-exist')
487db96d56Sopenharmony_ci
497db96d56Sopenharmony_ci        assert "metadata" in str(ctx.exception)
507db96d56Sopenharmony_ci
517db96d56Sopenharmony_ci    def test_new_style_classes(self):
527db96d56Sopenharmony_ci        self.assertIsInstance(Distribution, type)
537db96d56Sopenharmony_ci
547db96d56Sopenharmony_ci    @fixtures.parameterize(
557db96d56Sopenharmony_ci        dict(name=None),
567db96d56Sopenharmony_ci        dict(name=''),
577db96d56Sopenharmony_ci    )
587db96d56Sopenharmony_ci    def test_invalid_inputs_to_from_name(self, name):
597db96d56Sopenharmony_ci        with self.assertRaises(Exception):
607db96d56Sopenharmony_ci            Distribution.from_name(name)
617db96d56Sopenharmony_ci
627db96d56Sopenharmony_ci
637db96d56Sopenharmony_ciclass ImportTests(fixtures.DistInfoPkg, unittest.TestCase):
647db96d56Sopenharmony_ci    def test_import_nonexistent_module(self):
657db96d56Sopenharmony_ci        # Ensure that the MetadataPathFinder does not crash an import of a
667db96d56Sopenharmony_ci        # non-existent module.
677db96d56Sopenharmony_ci        with self.assertRaises(ImportError):
687db96d56Sopenharmony_ci            importlib.import_module('does_not_exist')
697db96d56Sopenharmony_ci
707db96d56Sopenharmony_ci    def test_resolve(self):
717db96d56Sopenharmony_ci        ep = entry_points(group='entries')['main']
727db96d56Sopenharmony_ci        self.assertEqual(ep.load().__name__, "main")
737db96d56Sopenharmony_ci
747db96d56Sopenharmony_ci    def test_entrypoint_with_colon_in_name(self):
757db96d56Sopenharmony_ci        ep = entry_points(group='entries')['ns:sub']
767db96d56Sopenharmony_ci        self.assertEqual(ep.value, 'mod:main')
777db96d56Sopenharmony_ci
787db96d56Sopenharmony_ci    def test_resolve_without_attr(self):
797db96d56Sopenharmony_ci        ep = EntryPoint(
807db96d56Sopenharmony_ci            name='ep',
817db96d56Sopenharmony_ci            value='importlib.metadata',
827db96d56Sopenharmony_ci            group='grp',
837db96d56Sopenharmony_ci        )
847db96d56Sopenharmony_ci        assert ep.load() is importlib.metadata
857db96d56Sopenharmony_ci
867db96d56Sopenharmony_ci
877db96d56Sopenharmony_ciclass NameNormalizationTests(fixtures.OnSysPath, fixtures.SiteDir, unittest.TestCase):
887db96d56Sopenharmony_ci    @staticmethod
897db96d56Sopenharmony_ci    def make_pkg(name):
907db96d56Sopenharmony_ci        """
917db96d56Sopenharmony_ci        Create minimal metadata for a dist-info package with
927db96d56Sopenharmony_ci        the indicated name on the file system.
937db96d56Sopenharmony_ci        """
947db96d56Sopenharmony_ci        return {
957db96d56Sopenharmony_ci            f'{name}.dist-info': {
967db96d56Sopenharmony_ci                'METADATA': 'VERSION: 1.0\n',
977db96d56Sopenharmony_ci            },
987db96d56Sopenharmony_ci        }
997db96d56Sopenharmony_ci
1007db96d56Sopenharmony_ci    def test_dashes_in_dist_name_found_as_underscores(self):
1017db96d56Sopenharmony_ci        """
1027db96d56Sopenharmony_ci        For a package with a dash in the name, the dist-info metadata
1037db96d56Sopenharmony_ci        uses underscores in the name. Ensure the metadata loads.
1047db96d56Sopenharmony_ci        """
1057db96d56Sopenharmony_ci        fixtures.build_files(self.make_pkg('my_pkg'), self.site_dir)
1067db96d56Sopenharmony_ci        assert version('my-pkg') == '1.0'
1077db96d56Sopenharmony_ci
1087db96d56Sopenharmony_ci    def test_dist_name_found_as_any_case(self):
1097db96d56Sopenharmony_ci        """
1107db96d56Sopenharmony_ci        Ensure the metadata loads when queried with any case.
1117db96d56Sopenharmony_ci        """
1127db96d56Sopenharmony_ci        pkg_name = 'CherryPy'
1137db96d56Sopenharmony_ci        fixtures.build_files(self.make_pkg(pkg_name), self.site_dir)
1147db96d56Sopenharmony_ci        assert version(pkg_name) == '1.0'
1157db96d56Sopenharmony_ci        assert version(pkg_name.lower()) == '1.0'
1167db96d56Sopenharmony_ci        assert version(pkg_name.upper()) == '1.0'
1177db96d56Sopenharmony_ci
1187db96d56Sopenharmony_ci    def test_unique_distributions(self):
1197db96d56Sopenharmony_ci        """
1207db96d56Sopenharmony_ci        Two distributions varying only by non-normalized name on
1217db96d56Sopenharmony_ci        the file system should resolve as the same.
1227db96d56Sopenharmony_ci        """
1237db96d56Sopenharmony_ci        fixtures.build_files(self.make_pkg('abc'), self.site_dir)
1247db96d56Sopenharmony_ci        before = list(_unique(distributions()))
1257db96d56Sopenharmony_ci
1267db96d56Sopenharmony_ci        alt_site_dir = self.fixtures.enter_context(fixtures.tempdir())
1277db96d56Sopenharmony_ci        self.fixtures.enter_context(self.add_sys_path(alt_site_dir))
1287db96d56Sopenharmony_ci        fixtures.build_files(self.make_pkg('ABC'), alt_site_dir)
1297db96d56Sopenharmony_ci        after = list(_unique(distributions()))
1307db96d56Sopenharmony_ci
1317db96d56Sopenharmony_ci        assert len(after) == len(before)
1327db96d56Sopenharmony_ci
1337db96d56Sopenharmony_ci
1347db96d56Sopenharmony_ciclass NonASCIITests(fixtures.OnSysPath, fixtures.SiteDir, unittest.TestCase):
1357db96d56Sopenharmony_ci    @staticmethod
1367db96d56Sopenharmony_ci    def pkg_with_non_ascii_description(site_dir):
1377db96d56Sopenharmony_ci        """
1387db96d56Sopenharmony_ci        Create minimal metadata for a package with non-ASCII in
1397db96d56Sopenharmony_ci        the description.
1407db96d56Sopenharmony_ci        """
1417db96d56Sopenharmony_ci        contents = {
1427db96d56Sopenharmony_ci            'portend.dist-info': {
1437db96d56Sopenharmony_ci                'METADATA': 'Description: pôrˈtend',
1447db96d56Sopenharmony_ci            },
1457db96d56Sopenharmony_ci        }
1467db96d56Sopenharmony_ci        fixtures.build_files(contents, site_dir)
1477db96d56Sopenharmony_ci        return 'portend'
1487db96d56Sopenharmony_ci
1497db96d56Sopenharmony_ci    @staticmethod
1507db96d56Sopenharmony_ci    def pkg_with_non_ascii_description_egg_info(site_dir):
1517db96d56Sopenharmony_ci        """
1527db96d56Sopenharmony_ci        Create minimal metadata for an egg-info package with
1537db96d56Sopenharmony_ci        non-ASCII in the description.
1547db96d56Sopenharmony_ci        """
1557db96d56Sopenharmony_ci        contents = {
1567db96d56Sopenharmony_ci            'portend.dist-info': {
1577db96d56Sopenharmony_ci                'METADATA': """
1587db96d56Sopenharmony_ci                Name: portend
1597db96d56Sopenharmony_ci
1607db96d56Sopenharmony_ci                pôrˈtend""",
1617db96d56Sopenharmony_ci            },
1627db96d56Sopenharmony_ci        }
1637db96d56Sopenharmony_ci        fixtures.build_files(contents, site_dir)
1647db96d56Sopenharmony_ci        return 'portend'
1657db96d56Sopenharmony_ci
1667db96d56Sopenharmony_ci    def test_metadata_loads(self):
1677db96d56Sopenharmony_ci        pkg_name = self.pkg_with_non_ascii_description(self.site_dir)
1687db96d56Sopenharmony_ci        meta = metadata(pkg_name)
1697db96d56Sopenharmony_ci        assert meta['Description'] == 'pôrˈtend'
1707db96d56Sopenharmony_ci
1717db96d56Sopenharmony_ci    def test_metadata_loads_egg_info(self):
1727db96d56Sopenharmony_ci        pkg_name = self.pkg_with_non_ascii_description_egg_info(self.site_dir)
1737db96d56Sopenharmony_ci        meta = metadata(pkg_name)
1747db96d56Sopenharmony_ci        assert meta['Description'] == 'pôrˈtend'
1757db96d56Sopenharmony_ci
1767db96d56Sopenharmony_ci
1777db96d56Sopenharmony_ciclass DiscoveryTests(fixtures.EggInfoPkg, fixtures.DistInfoPkg, unittest.TestCase):
1787db96d56Sopenharmony_ci    def test_package_discovery(self):
1797db96d56Sopenharmony_ci        dists = list(distributions())
1807db96d56Sopenharmony_ci        assert all(isinstance(dist, Distribution) for dist in dists)
1817db96d56Sopenharmony_ci        assert any(dist.metadata['Name'] == 'egginfo-pkg' for dist in dists)
1827db96d56Sopenharmony_ci        assert any(dist.metadata['Name'] == 'distinfo-pkg' for dist in dists)
1837db96d56Sopenharmony_ci
1847db96d56Sopenharmony_ci    def test_invalid_usage(self):
1857db96d56Sopenharmony_ci        with self.assertRaises(ValueError):
1867db96d56Sopenharmony_ci            list(distributions(context='something', name='else'))
1877db96d56Sopenharmony_ci
1887db96d56Sopenharmony_ci
1897db96d56Sopenharmony_ciclass DirectoryTest(fixtures.OnSysPath, fixtures.SiteDir, unittest.TestCase):
1907db96d56Sopenharmony_ci    def test_egg_info(self):
1917db96d56Sopenharmony_ci        # make an `EGG-INFO` directory that's unrelated
1927db96d56Sopenharmony_ci        self.site_dir.joinpath('EGG-INFO').mkdir()
1937db96d56Sopenharmony_ci        # used to crash with `IsADirectoryError`
1947db96d56Sopenharmony_ci        with self.assertRaises(PackageNotFoundError):
1957db96d56Sopenharmony_ci            version('unknown-package')
1967db96d56Sopenharmony_ci
1977db96d56Sopenharmony_ci    def test_egg(self):
1987db96d56Sopenharmony_ci        egg = self.site_dir.joinpath('foo-3.6.egg')
1997db96d56Sopenharmony_ci        egg.mkdir()
2007db96d56Sopenharmony_ci        with self.add_sys_path(egg):
2017db96d56Sopenharmony_ci            with self.assertRaises(PackageNotFoundError):
2027db96d56Sopenharmony_ci                version('foo')
2037db96d56Sopenharmony_ci
2047db96d56Sopenharmony_ci
2057db96d56Sopenharmony_ciclass MissingSysPath(fixtures.OnSysPath, unittest.TestCase):
2067db96d56Sopenharmony_ci    site_dir = '/does-not-exist'
2077db96d56Sopenharmony_ci
2087db96d56Sopenharmony_ci    def test_discovery(self):
2097db96d56Sopenharmony_ci        """
2107db96d56Sopenharmony_ci        Discovering distributions should succeed even if
2117db96d56Sopenharmony_ci        there is an invalid path on sys.path.
2127db96d56Sopenharmony_ci        """
2137db96d56Sopenharmony_ci        importlib.metadata.distributions()
2147db96d56Sopenharmony_ci
2157db96d56Sopenharmony_ci
2167db96d56Sopenharmony_ciclass InaccessibleSysPath(fixtures.OnSysPath, ffs.TestCase):
2177db96d56Sopenharmony_ci    site_dir = '/access-denied'
2187db96d56Sopenharmony_ci
2197db96d56Sopenharmony_ci    def setUp(self):
2207db96d56Sopenharmony_ci        super().setUp()
2217db96d56Sopenharmony_ci        self.setUpPyfakefs()
2227db96d56Sopenharmony_ci        self.fs.create_dir(self.site_dir, perm_bits=000)
2237db96d56Sopenharmony_ci
2247db96d56Sopenharmony_ci    def test_discovery(self):
2257db96d56Sopenharmony_ci        """
2267db96d56Sopenharmony_ci        Discovering distributions should succeed even if
2277db96d56Sopenharmony_ci        there is an invalid path on sys.path.
2287db96d56Sopenharmony_ci        """
2297db96d56Sopenharmony_ci        list(importlib.metadata.distributions())
2307db96d56Sopenharmony_ci
2317db96d56Sopenharmony_ci
2327db96d56Sopenharmony_ciclass TestEntryPoints(unittest.TestCase):
2337db96d56Sopenharmony_ci    def __init__(self, *args):
2347db96d56Sopenharmony_ci        super().__init__(*args)
2357db96d56Sopenharmony_ci        self.ep = importlib.metadata.EntryPoint(
2367db96d56Sopenharmony_ci            name='name', value='value', group='group'
2377db96d56Sopenharmony_ci        )
2387db96d56Sopenharmony_ci
2397db96d56Sopenharmony_ci    def test_entry_point_pickleable(self):
2407db96d56Sopenharmony_ci        revived = pickle.loads(pickle.dumps(self.ep))
2417db96d56Sopenharmony_ci        assert revived == self.ep
2427db96d56Sopenharmony_ci
2437db96d56Sopenharmony_ci    def test_positional_args(self):
2447db96d56Sopenharmony_ci        """
2457db96d56Sopenharmony_ci        Capture legacy (namedtuple) construction, discouraged.
2467db96d56Sopenharmony_ci        """
2477db96d56Sopenharmony_ci        EntryPoint('name', 'value', 'group')
2487db96d56Sopenharmony_ci
2497db96d56Sopenharmony_ci    def test_immutable(self):
2507db96d56Sopenharmony_ci        """EntryPoints should be immutable"""
2517db96d56Sopenharmony_ci        with self.assertRaises(AttributeError):
2527db96d56Sopenharmony_ci            self.ep.name = 'badactor'
2537db96d56Sopenharmony_ci
2547db96d56Sopenharmony_ci    def test_repr(self):
2557db96d56Sopenharmony_ci        assert 'EntryPoint' in repr(self.ep)
2567db96d56Sopenharmony_ci        assert 'name=' in repr(self.ep)
2577db96d56Sopenharmony_ci        assert "'name'" in repr(self.ep)
2587db96d56Sopenharmony_ci
2597db96d56Sopenharmony_ci    def test_hashable(self):
2607db96d56Sopenharmony_ci        """EntryPoints should be hashable"""
2617db96d56Sopenharmony_ci        hash(self.ep)
2627db96d56Sopenharmony_ci
2637db96d56Sopenharmony_ci    def test_json_dump(self):
2647db96d56Sopenharmony_ci        """
2657db96d56Sopenharmony_ci        json should not expect to be able to dump an EntryPoint
2667db96d56Sopenharmony_ci        """
2677db96d56Sopenharmony_ci        with self.assertRaises(Exception):
2687db96d56Sopenharmony_ci            with warnings.catch_warnings(record=True):
2697db96d56Sopenharmony_ci                json.dumps(self.ep)
2707db96d56Sopenharmony_ci
2717db96d56Sopenharmony_ci    def test_module(self):
2727db96d56Sopenharmony_ci        assert self.ep.module == 'value'
2737db96d56Sopenharmony_ci
2747db96d56Sopenharmony_ci    def test_attr(self):
2757db96d56Sopenharmony_ci        assert self.ep.attr is None
2767db96d56Sopenharmony_ci
2777db96d56Sopenharmony_ci    def test_sortable(self):
2787db96d56Sopenharmony_ci        """
2797db96d56Sopenharmony_ci        EntryPoint objects are sortable, but result is undefined.
2807db96d56Sopenharmony_ci        """
2817db96d56Sopenharmony_ci        sorted(
2827db96d56Sopenharmony_ci            [
2837db96d56Sopenharmony_ci                EntryPoint(name='b', value='val', group='group'),
2847db96d56Sopenharmony_ci                EntryPoint(name='a', value='val', group='group'),
2857db96d56Sopenharmony_ci            ]
2867db96d56Sopenharmony_ci        )
2877db96d56Sopenharmony_ci
2887db96d56Sopenharmony_ci
2897db96d56Sopenharmony_ciclass FileSystem(
2907db96d56Sopenharmony_ci    fixtures.OnSysPath, fixtures.SiteDir, fixtures.FileBuilder, unittest.TestCase
2917db96d56Sopenharmony_ci):
2927db96d56Sopenharmony_ci    def test_unicode_dir_on_sys_path(self):
2937db96d56Sopenharmony_ci        """
2947db96d56Sopenharmony_ci        Ensure a Unicode subdirectory of a directory on sys.path
2957db96d56Sopenharmony_ci        does not crash.
2967db96d56Sopenharmony_ci        """
2977db96d56Sopenharmony_ci        fixtures.build_files(
2987db96d56Sopenharmony_ci            {self.unicode_filename(): {}},
2997db96d56Sopenharmony_ci            prefix=self.site_dir,
3007db96d56Sopenharmony_ci        )
3017db96d56Sopenharmony_ci        list(distributions())
3027db96d56Sopenharmony_ci
3037db96d56Sopenharmony_ci
3047db96d56Sopenharmony_ciclass PackagesDistributionsPrebuiltTest(fixtures.ZipFixtures, unittest.TestCase):
3057db96d56Sopenharmony_ci    def test_packages_distributions_example(self):
3067db96d56Sopenharmony_ci        self._fixture_on_path('example-21.12-py3-none-any.whl')
3077db96d56Sopenharmony_ci        assert packages_distributions()['example'] == ['example']
3087db96d56Sopenharmony_ci
3097db96d56Sopenharmony_ci    def test_packages_distributions_example2(self):
3107db96d56Sopenharmony_ci        """
3117db96d56Sopenharmony_ci        Test packages_distributions on a wheel built
3127db96d56Sopenharmony_ci        by trampolim.
3137db96d56Sopenharmony_ci        """
3147db96d56Sopenharmony_ci        self._fixture_on_path('example2-1.0.0-py3-none-any.whl')
3157db96d56Sopenharmony_ci        assert packages_distributions()['example2'] == ['example2']
3167db96d56Sopenharmony_ci
3177db96d56Sopenharmony_ci
3187db96d56Sopenharmony_ciclass PackagesDistributionsTest(
3197db96d56Sopenharmony_ci    fixtures.OnSysPath, fixtures.SiteDir, unittest.TestCase
3207db96d56Sopenharmony_ci):
3217db96d56Sopenharmony_ci    def test_packages_distributions_neither_toplevel_nor_files(self):
3227db96d56Sopenharmony_ci        """
3237db96d56Sopenharmony_ci        Test a package built without 'top-level.txt' or a file list.
3247db96d56Sopenharmony_ci        """
3257db96d56Sopenharmony_ci        fixtures.build_files(
3267db96d56Sopenharmony_ci            {
3277db96d56Sopenharmony_ci                'trim_example-1.0.0.dist-info': {
3287db96d56Sopenharmony_ci                    'METADATA': """
3297db96d56Sopenharmony_ci                Name: trim_example
3307db96d56Sopenharmony_ci                Version: 1.0.0
3317db96d56Sopenharmony_ci                """,
3327db96d56Sopenharmony_ci                }
3337db96d56Sopenharmony_ci            },
3347db96d56Sopenharmony_ci            prefix=self.site_dir,
3357db96d56Sopenharmony_ci        )
3367db96d56Sopenharmony_ci        packages_distributions()
337