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