codekingpro/portable-devtools
114k
1"""Tests for automatic package discovery"""2 3import os4import shutil5import tempfile6 7import pytest8 9from setuptools import find_namespace_packages, find_packages10from setuptools.discovery import FlatLayoutPackageFinder11 12from .compat.py39 import os_helper13 14 15class TestFindPackages:16 def setup_method(self, method):17 self.dist_dir = tempfile.mkdtemp()18 self._make_pkg_structure()19 20 def teardown_method(self, method):21 shutil.rmtree(self.dist_dir)22 23 def _make_pkg_structure(self):24 """Make basic package structure.25 26 dist/27 docs/28 conf.py29 pkg/30 __pycache__/31 nspkg/32 mod.py33 subpkg/34 assets/35 asset36 __init__.py37 setup.py38 39 """40 self.docs_dir = self._mkdir('docs', self.dist_dir)41 self._touch('conf.py', self.docs_dir)42 self.pkg_dir = self._mkdir('pkg', self.dist_dir)43 self._mkdir('__pycache__', self.pkg_dir)44 self.ns_pkg_dir = self._mkdir('nspkg', self.pkg_dir)45 self._touch('mod.py', self.ns_pkg_dir)46 self.sub_pkg_dir = self._mkdir('subpkg', self.pkg_dir)47 self.asset_dir = self._mkdir('assets', self.sub_pkg_dir)48 self._touch('asset', self.asset_dir)49 self._touch('__init__.py', self.sub_pkg_dir)50 self._touch('setup.py', self.dist_dir)51 52 def _mkdir(self, path, parent_dir=None):53 if parent_dir:54 path = os.path.join(parent_dir, path)55 os.mkdir(path)56 return path57 58 def _touch(self, path, dir_=None):59 if dir_:60 path = os.path.join(dir_, path)61 open(path, 'wb').close()62 return path63 64 def test_regular_package(self):65 self._touch('__init__.py', self.pkg_dir)66 packages = find_packages(self.dist_dir)67 assert packages == ['pkg', 'pkg.subpkg']68 69 def test_exclude(self):70 self._touch('__init__.py', self.pkg_dir)71 packages = find_packages(self.dist_dir, exclude=('pkg.*',))72 assert packages == ['pkg']73 74 def test_exclude_recursive(self):75 """76 Excluding a parent package should not exclude child packages as well.77 """78 self._touch('__init__.py', self.pkg_dir)79 self._touch('__init__.py', self.sub_pkg_dir)80 packages = find_packages(self.dist_dir, exclude=('pkg',))81 assert packages == ['pkg.subpkg']82 83 def test_include_excludes_other(self):84 """85 If include is specified, other packages should be excluded.86 """87 self._touch('__init__.py', self.pkg_dir)88 alt_dir = self._mkdir('other_pkg', self.dist_dir)89 self._touch('__init__.py', alt_dir)90 packages = find_packages(self.dist_dir, include=['other_pkg'])91 assert packages == ['other_pkg']92 93 def test_dir_with_dot_is_skipped(self):94 shutil.rmtree(os.path.join(self.dist_dir, 'pkg/subpkg/assets'))95 data_dir = self._mkdir('some.data', self.pkg_dir)96 self._touch('__init__.py', data_dir)97 self._touch('file.dat', data_dir)98 packages = find_packages(self.dist_dir)99 assert 'pkg.some.data' not in packages100 101 def test_dir_with_packages_in_subdir_is_excluded(self):102 """103 Ensure that a package in a non-package such as build/pkg/__init__.py104 is excluded.105 """106 build_dir = self._mkdir('build', self.dist_dir)107 build_pkg_dir = self._mkdir('pkg', build_dir)108 self._touch('__init__.py', build_pkg_dir)109 packages = find_packages(self.dist_dir)110 assert 'build.pkg' not in packages111 112 @pytest.mark.skipif(not os_helper.can_symlink(), reason='Symlink support required')113 def test_symlinked_packages_are_included(self):114 """115 A symbolically-linked directory should be treated like any other116 directory when matched as a package.117 118 Create a link from lpkg -> pkg.119 """120 self._touch('__init__.py', self.pkg_dir)121 linked_pkg = os.path.join(self.dist_dir, 'lpkg')122 os.symlink('pkg', linked_pkg)123 assert os.path.isdir(linked_pkg)124 packages = find_packages(self.dist_dir)125 assert 'lpkg' in packages126 127 def _assert_packages(self, actual, expected):128 assert set(actual) == set(expected)129 130 def test_pep420_ns_package(self):131 packages = find_namespace_packages(132 self.dist_dir, include=['pkg*'], exclude=['pkg.subpkg.assets']133 )134 self._assert_packages(packages, ['pkg', 'pkg.nspkg', 'pkg.subpkg'])135 136 def test_pep420_ns_package_no_includes(self):137 packages = find_namespace_packages(self.dist_dir, exclude=['pkg.subpkg.assets'])138 self._assert_packages(packages, ['docs', 'pkg', 'pkg.nspkg', 'pkg.subpkg'])139 140 def test_pep420_ns_package_no_includes_or_excludes(self):141 packages = find_namespace_packages(self.dist_dir)142 expected = ['docs', 'pkg', 'pkg.nspkg', 'pkg.subpkg', 'pkg.subpkg.assets']143 self._assert_packages(packages, expected)144 145 def test_regular_package_with_nested_pep420_ns_packages(self):146 self._touch('__init__.py', self.pkg_dir)147 packages = find_namespace_packages(148 self.dist_dir, exclude=['docs', 'pkg.subpkg.assets']149 )150 self._assert_packages(packages, ['pkg', 'pkg.nspkg', 'pkg.subpkg'])151 152 def test_pep420_ns_package_no_non_package_dirs(self):153 shutil.rmtree(self.docs_dir)154 shutil.rmtree(os.path.join(self.dist_dir, 'pkg/subpkg/assets'))155 packages = find_namespace_packages(self.dist_dir)156 self._assert_packages(packages, ['pkg', 'pkg.nspkg', 'pkg.subpkg'])157 158 159class TestFlatLayoutPackageFinder:160 EXAMPLES = {161 "hidden-folders": (162 [".pkg/__init__.py", "pkg/__init__.py", "pkg/nested/file.txt"],163 ["pkg", "pkg.nested"],164 ),165 "private-packages": (166 ["_pkg/__init__.py", "pkg/_private/__init__.py"],167 ["pkg", "pkg._private"],168 ),169 "invalid-name": (170 ["invalid-pkg/__init__.py", "other.pkg/__init__.py", "yet,another/file.py"],171 [],172 ),173 "docs": (["pkg/__init__.py", "docs/conf.py", "docs/readme.rst"], ["pkg"]),174 "tests": (175 ["pkg/__init__.py", "tests/test_pkg.py", "tests/__init__.py"],176 ["pkg"],177 ),178 "examples": (179 [180 "pkg/__init__.py",181 "examples/__init__.py",182 "examples/file.py",183 "example/other_file.py",184 # Sub-packages should always be fine185 "pkg/example/__init__.py",186 "pkg/examples/__init__.py",187 ],188 ["pkg", "pkg.examples", "pkg.example"],189 ),190 "tool-specific": (191 [192 "htmlcov/index.html",193 "pkg/__init__.py",194 "tasks/__init__.py",195 "tasks/subpackage/__init__.py",196 "fabfile/__init__.py",197 "fabfile/subpackage/__init__.py",198 # Sub-packages should always be fine199 "pkg/tasks/__init__.py",200 "pkg/fabfile/__init__.py",201 ],202 ["pkg", "pkg.tasks", "pkg.fabfile"],203 ),204 }205 206 @pytest.mark.parametrize("example", EXAMPLES.keys())207 def test_unwanted_directories_not_included(self, tmp_path, example):208 files, expected_packages = self.EXAMPLES[example]209 ensure_files(tmp_path, files)210 found_packages = FlatLayoutPackageFinder.find(str(tmp_path))211 assert set(found_packages) == set(expected_packages)212 213 214def ensure_files(root_path, files):215 for file in files:216 path = root_path / file217 path.parent.mkdir(parents=True, exist_ok=True)218 path.touch()219 