• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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