1"""Tests for setuptools.find_packages().""" 2import os 3import sys 4import shutil 5import tempfile 6import platform 7 8import pytest 9 10import setuptools 11from setuptools import find_packages 12 13find_420_packages = setuptools.PEP420PackageFinder.find 14 15# modeled after CPython's test.support.can_symlink 16 17 18def can_symlink(): 19 TESTFN = tempfile.mktemp() 20 symlink_path = TESTFN + "can_symlink" 21 try: 22 os.symlink(TESTFN, symlink_path) 23 can = True 24 except (OSError, NotImplementedError, AttributeError): 25 can = False 26 else: 27 os.remove(symlink_path) 28 globals().update(can_symlink=lambda: can) 29 return can 30 31 32def has_symlink(): 33 bad_symlink = ( 34 # Windows symlink directory detection is broken on Python 3.2 35 platform.system() == 'Windows' and sys.version_info[:2] == (3, 2) 36 ) 37 return can_symlink() and not bad_symlink 38 39 40class TestFindPackages: 41 def setup_method(self, method): 42 self.dist_dir = tempfile.mkdtemp() 43 self._make_pkg_structure() 44 45 def teardown_method(self, method): 46 shutil.rmtree(self.dist_dir) 47 48 def _make_pkg_structure(self): 49 """Make basic package structure. 50 51 dist/ 52 docs/ 53 conf.py 54 pkg/ 55 __pycache__/ 56 nspkg/ 57 mod.py 58 subpkg/ 59 assets/ 60 asset 61 __init__.py 62 setup.py 63 64 """ 65 self.docs_dir = self._mkdir('docs', self.dist_dir) 66 self._touch('conf.py', self.docs_dir) 67 self.pkg_dir = self._mkdir('pkg', self.dist_dir) 68 self._mkdir('__pycache__', self.pkg_dir) 69 self.ns_pkg_dir = self._mkdir('nspkg', self.pkg_dir) 70 self._touch('mod.py', self.ns_pkg_dir) 71 self.sub_pkg_dir = self._mkdir('subpkg', self.pkg_dir) 72 self.asset_dir = self._mkdir('assets', self.sub_pkg_dir) 73 self._touch('asset', self.asset_dir) 74 self._touch('__init__.py', self.sub_pkg_dir) 75 self._touch('setup.py', self.dist_dir) 76 77 def _mkdir(self, path, parent_dir=None): 78 if parent_dir: 79 path = os.path.join(parent_dir, path) 80 os.mkdir(path) 81 return path 82 83 def _touch(self, path, dir_=None): 84 if dir_: 85 path = os.path.join(dir_, path) 86 fp = open(path, 'w') 87 fp.close() 88 return path 89 90 def test_regular_package(self): 91 self._touch('__init__.py', self.pkg_dir) 92 packages = find_packages(self.dist_dir) 93 assert packages == ['pkg', 'pkg.subpkg'] 94 95 def test_exclude(self): 96 self._touch('__init__.py', self.pkg_dir) 97 packages = find_packages(self.dist_dir, exclude=('pkg.*',)) 98 assert packages == ['pkg'] 99 100 def test_exclude_recursive(self): 101 """ 102 Excluding a parent package should not exclude child packages as well. 103 """ 104 self._touch('__init__.py', self.pkg_dir) 105 self._touch('__init__.py', self.sub_pkg_dir) 106 packages = find_packages(self.dist_dir, exclude=('pkg',)) 107 assert packages == ['pkg.subpkg'] 108 109 def test_include_excludes_other(self): 110 """ 111 If include is specified, other packages should be excluded. 112 """ 113 self._touch('__init__.py', self.pkg_dir) 114 alt_dir = self._mkdir('other_pkg', self.dist_dir) 115 self._touch('__init__.py', alt_dir) 116 packages = find_packages(self.dist_dir, include=['other_pkg']) 117 assert packages == ['other_pkg'] 118 119 def test_dir_with_dot_is_skipped(self): 120 shutil.rmtree(os.path.join(self.dist_dir, 'pkg/subpkg/assets')) 121 data_dir = self._mkdir('some.data', self.pkg_dir) 122 self._touch('__init__.py', data_dir) 123 self._touch('file.dat', data_dir) 124 packages = find_packages(self.dist_dir) 125 assert 'pkg.some.data' not in packages 126 127 def test_dir_with_packages_in_subdir_is_excluded(self): 128 """ 129 Ensure that a package in a non-package such as build/pkg/__init__.py 130 is excluded. 131 """ 132 build_dir = self._mkdir('build', self.dist_dir) 133 build_pkg_dir = self._mkdir('pkg', build_dir) 134 self._touch('__init__.py', build_pkg_dir) 135 packages = find_packages(self.dist_dir) 136 assert 'build.pkg' not in packages 137 138 @pytest.mark.skipif(not has_symlink(), reason='Symlink support required') 139 def test_symlinked_packages_are_included(self): 140 """ 141 A symbolically-linked directory should be treated like any other 142 directory when matched as a package. 143 144 Create a link from lpkg -> pkg. 145 """ 146 self._touch('__init__.py', self.pkg_dir) 147 linked_pkg = os.path.join(self.dist_dir, 'lpkg') 148 os.symlink('pkg', linked_pkg) 149 assert os.path.isdir(linked_pkg) 150 packages = find_packages(self.dist_dir) 151 assert 'lpkg' in packages 152 153 def _assert_packages(self, actual, expected): 154 assert set(actual) == set(expected) 155 156 def test_pep420_ns_package(self): 157 packages = find_420_packages( 158 self.dist_dir, include=['pkg*'], exclude=['pkg.subpkg.assets']) 159 self._assert_packages(packages, ['pkg', 'pkg.nspkg', 'pkg.subpkg']) 160 161 def test_pep420_ns_package_no_includes(self): 162 packages = find_420_packages( 163 self.dist_dir, exclude=['pkg.subpkg.assets']) 164 self._assert_packages(packages, ['docs', 'pkg', 'pkg.nspkg', 'pkg.subpkg']) 165 166 def test_pep420_ns_package_no_includes_or_excludes(self): 167 packages = find_420_packages(self.dist_dir) 168 expected = [ 169 'docs', 'pkg', 'pkg.nspkg', 'pkg.subpkg', 'pkg.subpkg.assets'] 170 self._assert_packages(packages, expected) 171 172 def test_regular_package_with_nested_pep420_ns_packages(self): 173 self._touch('__init__.py', self.pkg_dir) 174 packages = find_420_packages( 175 self.dist_dir, exclude=['docs', 'pkg.subpkg.assets']) 176 self._assert_packages(packages, ['pkg', 'pkg.nspkg', 'pkg.subpkg']) 177 178 def test_pep420_ns_package_no_non_package_dirs(self): 179 shutil.rmtree(self.docs_dir) 180 shutil.rmtree(os.path.join(self.dist_dir, 'pkg/subpkg/assets')) 181 packages = find_420_packages(self.dist_dir) 182 self._assert_packages(packages, ['pkg', 'pkg.nspkg', 'pkg.subpkg']) 183