1"""Tests for the 'setuptools' package"""
2
3import sys
4import os
5import distutils.core
6import distutils.cmd
7from distutils.errors import DistutilsOptionError
8from distutils.errors import DistutilsSetupError
9from distutils.core import Extension
10from zipfile import ZipFile
11
12import pytest
13
14from setuptools.extern.packaging import version
15
16import setuptools
17import setuptools.dist
18import setuptools.depends as dep
19from setuptools.depends import Require
20
21
22@pytest.fixture(autouse=True)
23def isolated_dir(tmpdir_cwd):
24    yield
25
26
27def makeSetup(**args):
28    """Return distribution from 'setup(**args)', without executing commands"""
29
30    distutils.core._setup_stop_after = "commandline"
31
32    # Don't let system command line leak into tests!
33    args.setdefault('script_args', ['install'])
34
35    try:
36        return setuptools.setup(**args)
37    finally:
38        distutils.core._setup_stop_after = None
39
40
41needs_bytecode = pytest.mark.skipif(
42    not hasattr(dep, 'get_module_constant'),
43    reason="bytecode support not available",
44)
45
46
47class TestDepends:
48    def testExtractConst(self):
49        if not hasattr(dep, 'extract_constant'):
50            # skip on non-bytecode platforms
51            return
52
53        def f1():
54            global x, y, z
55            x = "test"
56            y = z
57
58        fc = f1.__code__
59
60        # unrecognized name
61        assert dep.extract_constant(fc, 'q', -1) is None
62
63        # constant assigned
64        dep.extract_constant(fc, 'x', -1) == "test"
65
66        # expression assigned
67        dep.extract_constant(fc, 'y', -1) == -1
68
69        # recognized name, not assigned
70        dep.extract_constant(fc, 'z', -1) is None
71
72    def testFindModule(self):
73        with pytest.raises(ImportError):
74            dep.find_module('no-such.-thing')
75        with pytest.raises(ImportError):
76            dep.find_module('setuptools.non-existent')
77        f, p, i = dep.find_module('setuptools.tests')
78        f.close()
79
80    @needs_bytecode
81    def testModuleExtract(self):
82        from json import __version__
83        assert dep.get_module_constant('json', '__version__') == __version__
84        assert dep.get_module_constant('sys', 'version') == sys.version
85        assert dep.get_module_constant(
86            'setuptools.tests.test_setuptools', '__doc__') == __doc__
87
88    @needs_bytecode
89    def testRequire(self):
90        req = Require('Json', '1.0.3', 'json')
91
92        assert req.name == 'Json'
93        assert req.module == 'json'
94        assert req.requested_version == version.Version('1.0.3')
95        assert req.attribute == '__version__'
96        assert req.full_name() == 'Json-1.0.3'
97
98        from json import __version__
99        assert str(req.get_version()) == __version__
100        assert req.version_ok('1.0.9')
101        assert not req.version_ok('0.9.1')
102        assert not req.version_ok('unknown')
103
104        assert req.is_present()
105        assert req.is_current()
106
107        req = Require('Do-what-I-mean', '1.0', 'd-w-i-m')
108        assert not req.is_present()
109        assert not req.is_current()
110
111    @needs_bytecode
112    def test_require_present(self):
113        # In #1896, this test was failing for months with the only
114        # complaint coming from test runners (not end users).
115        # TODO: Evaluate if this code is needed at all.
116        req = Require('Tests', None, 'tests', homepage="http://example.com")
117        assert req.format is None
118        assert req.attribute is None
119        assert req.requested_version is None
120        assert req.full_name() == 'Tests'
121        assert req.homepage == 'http://example.com'
122
123        from setuptools.tests import __path__
124        paths = [os.path.dirname(p) for p in __path__]
125        assert req.is_present(paths)
126        assert req.is_current(paths)
127
128
129class TestDistro:
130    def setup_method(self, method):
131        self.e1 = Extension('bar.ext', ['bar.c'])
132        self.e2 = Extension('c.y', ['y.c'])
133
134        self.dist = makeSetup(
135            packages=['a', 'a.b', 'a.b.c', 'b', 'c'],
136            py_modules=['b.d', 'x'],
137            ext_modules=(self.e1, self.e2),
138            package_dir={},
139        )
140
141    def testDistroType(self):
142        assert isinstance(self.dist, setuptools.dist.Distribution)
143
144    def testExcludePackage(self):
145        self.dist.exclude_package('a')
146        assert self.dist.packages == ['b', 'c']
147
148        self.dist.exclude_package('b')
149        assert self.dist.packages == ['c']
150        assert self.dist.py_modules == ['x']
151        assert self.dist.ext_modules == [self.e1, self.e2]
152
153        self.dist.exclude_package('c')
154        assert self.dist.packages == []
155        assert self.dist.py_modules == ['x']
156        assert self.dist.ext_modules == [self.e1]
157
158        # test removals from unspecified options
159        makeSetup().exclude_package('x')
160
161    def testIncludeExclude(self):
162        # remove an extension
163        self.dist.exclude(ext_modules=[self.e1])
164        assert self.dist.ext_modules == [self.e2]
165
166        # add it back in
167        self.dist.include(ext_modules=[self.e1])
168        assert self.dist.ext_modules == [self.e2, self.e1]
169
170        # should not add duplicate
171        self.dist.include(ext_modules=[self.e1])
172        assert self.dist.ext_modules == [self.e2, self.e1]
173
174    def testExcludePackages(self):
175        self.dist.exclude(packages=['c', 'b', 'a'])
176        assert self.dist.packages == []
177        assert self.dist.py_modules == ['x']
178        assert self.dist.ext_modules == [self.e1]
179
180    def testEmpty(self):
181        dist = makeSetup()
182        dist.include(packages=['a'], py_modules=['b'], ext_modules=[self.e2])
183        dist = makeSetup()
184        dist.exclude(packages=['a'], py_modules=['b'], ext_modules=[self.e2])
185
186    def testContents(self):
187        assert self.dist.has_contents_for('a')
188        self.dist.exclude_package('a')
189        assert not self.dist.has_contents_for('a')
190
191        assert self.dist.has_contents_for('b')
192        self.dist.exclude_package('b')
193        assert not self.dist.has_contents_for('b')
194
195        assert self.dist.has_contents_for('c')
196        self.dist.exclude_package('c')
197        assert not self.dist.has_contents_for('c')
198
199    def testInvalidIncludeExclude(self):
200        with pytest.raises(DistutilsSetupError):
201            self.dist.include(nonexistent_option='x')
202        with pytest.raises(DistutilsSetupError):
203            self.dist.exclude(nonexistent_option='x')
204        with pytest.raises(DistutilsSetupError):
205            self.dist.include(packages={'x': 'y'})
206        with pytest.raises(DistutilsSetupError):
207            self.dist.exclude(packages={'x': 'y'})
208        with pytest.raises(DistutilsSetupError):
209            self.dist.include(ext_modules={'x': 'y'})
210        with pytest.raises(DistutilsSetupError):
211            self.dist.exclude(ext_modules={'x': 'y'})
212
213        with pytest.raises(DistutilsSetupError):
214            self.dist.include(package_dir=['q'])
215        with pytest.raises(DistutilsSetupError):
216            self.dist.exclude(package_dir=['q'])
217
218
219class TestCommandTests:
220    def testTestIsCommand(self):
221        test_cmd = makeSetup().get_command_obj('test')
222        assert (isinstance(test_cmd, distutils.cmd.Command))
223
224    def testLongOptSuiteWNoDefault(self):
225        ts1 = makeSetup(script_args=['test', '--test-suite=foo.tests.suite'])
226        ts1 = ts1.get_command_obj('test')
227        ts1.ensure_finalized()
228        assert ts1.test_suite == 'foo.tests.suite'
229
230    def testDefaultSuite(self):
231        ts2 = makeSetup(test_suite='bar.tests.suite').get_command_obj('test')
232        ts2.ensure_finalized()
233        assert ts2.test_suite == 'bar.tests.suite'
234
235    def testDefaultWModuleOnCmdLine(self):
236        ts3 = makeSetup(
237            test_suite='bar.tests',
238            script_args=['test', '-m', 'foo.tests']
239        ).get_command_obj('test')
240        ts3.ensure_finalized()
241        assert ts3.test_module == 'foo.tests'
242        assert ts3.test_suite == 'foo.tests.test_suite'
243
244    def testConflictingOptions(self):
245        ts4 = makeSetup(
246            script_args=['test', '-m', 'bar.tests', '-s', 'foo.tests.suite']
247        ).get_command_obj('test')
248        with pytest.raises(DistutilsOptionError):
249            ts4.ensure_finalized()
250
251    def testNoSuite(self):
252        ts5 = makeSetup().get_command_obj('test')
253        ts5.ensure_finalized()
254        assert ts5.test_suite is None
255
256
257@pytest.fixture
258def example_source(tmpdir):
259    tmpdir.mkdir('foo')
260    (tmpdir / 'foo/bar.py').write('')
261    (tmpdir / 'readme.txt').write('')
262    return tmpdir
263
264
265def test_findall(example_source):
266    found = list(setuptools.findall(str(example_source)))
267    expected = ['readme.txt', 'foo/bar.py']
268    expected = [example_source.join(fn) for fn in expected]
269    assert found == expected
270
271
272def test_findall_curdir(example_source):
273    with example_source.as_cwd():
274        found = list(setuptools.findall())
275    expected = ['readme.txt', os.path.join('foo', 'bar.py')]
276    assert found == expected
277
278
279@pytest.fixture
280def can_symlink(tmpdir):
281    """
282    Skip if cannot create a symbolic link
283    """
284    link_fn = 'link'
285    target_fn = 'target'
286    try:
287        os.symlink(target_fn, link_fn)
288    except (OSError, NotImplementedError, AttributeError):
289        pytest.skip("Cannot create symbolic links")
290    os.remove(link_fn)
291
292
293def test_findall_missing_symlink(tmpdir, can_symlink):
294    with tmpdir.as_cwd():
295        os.symlink('foo', 'bar')
296        found = list(setuptools.findall())
297        assert found == []
298
299
300def test_its_own_wheel_does_not_contain_tests(setuptools_wheel):
301    with ZipFile(setuptools_wheel) as zipfile:
302        contents = [f.replace(os.sep, '/') for f in zipfile.namelist()]
303
304    for member in contents:
305        assert '/tests/' not in member
306
307
308def test_convert_path_deprecated():
309    with pytest.warns(setuptools.SetuptoolsDeprecationWarning):
310        setuptools.convert_path('setuptools/tests')
311