test_find_packages.py 7.6 KB

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