1import glob
2import os
3import sys
4import itertools
5
6import pathlib
7
8import pytest
9from pytest_fixture_config import yield_requires_config
10
11import pytest_virtualenv
12
13from .textwrap import DALS
14from .test_easy_install import make_nspkg_sdist
15
16
17@pytest.fixture(autouse=True)
18def pytest_virtualenv_works(virtualenv):
19    """
20    pytest_virtualenv may not work. if it doesn't, skip these
21    tests. See #1284.
22    """
23    venv_prefix = virtualenv.run(
24        'python -c "import sys; print(sys.prefix)"',
25        capture=True,
26    ).strip()
27    if venv_prefix == sys.prefix:
28        pytest.skip("virtualenv is broken (see pypa/setuptools#1284)")
29
30
31@yield_requires_config(pytest_virtualenv.CONFIG, ['virtualenv_executable'])
32@pytest.fixture(scope='function')
33def bare_virtualenv():
34    """ Bare virtualenv (no pip/setuptools/wheel).
35    """
36    with pytest_virtualenv.VirtualEnv(args=(
37        '--no-wheel',
38        '--no-pip',
39        '--no-setuptools',
40    )) as venv:
41        yield venv
42
43
44def test_clean_env_install(bare_virtualenv, tmp_src):
45    """
46    Check setuptools can be installed in a clean environment.
47    """
48    bare_virtualenv.run(['python', 'setup.py', 'install'], cd=tmp_src)
49
50
51def _get_pip_versions():
52    # This fixture will attempt to detect if tests are being run without
53    # network connectivity and if so skip some tests
54
55    network = True
56    if not os.environ.get('NETWORK_REQUIRED', False):  # pragma: nocover
57        try:
58            from urllib.request import urlopen
59            from urllib.error import URLError
60        except ImportError:
61            from urllib2 import urlopen, URLError  # Python 2.7 compat
62
63        try:
64            urlopen('https://pypi.org', timeout=1)
65        except URLError:
66            # No network, disable most of these tests
67            network = False
68
69    def mark(param, *marks):
70        if not isinstance(param, type(pytest.param(''))):
71            param = pytest.param(param)
72        return param._replace(marks=param.marks + marks)
73
74    def skip_network(param):
75        return param if network else mark(param, pytest.mark.skip(reason="no network"))
76
77    issue2599 = pytest.mark.skipif(
78        sys.version_info > (3, 10),
79        reason="pypa/setuptools#2599",
80    )
81
82    network_versions = [
83        mark('pip==9.0.3', issue2599),
84        mark('pip==10.0.1', issue2599),
85        mark('pip==18.1', issue2599),
86        mark('pip==19.3.1', pytest.mark.xfail(reason='pypa/pip#6599')),
87        'pip==20.0.2',
88        'https://github.com/pypa/pip/archive/main.zip',
89    ]
90
91    versions = itertools.chain(
92        [None],
93        map(skip_network, network_versions)
94    )
95
96    return list(versions)
97
98
99@pytest.mark.parametrize('pip_version', _get_pip_versions())
100def test_pip_upgrade_from_source(pip_version, tmp_src, virtualenv):
101    """
102    Check pip can upgrade setuptools from source.
103    """
104    # Install pip/wheel, and remove setuptools (as it
105    # should not be needed for bootstraping from source)
106    if pip_version is None:
107        upgrade_pip = ()
108    else:
109        upgrade_pip = ('python -m pip install -U {pip_version} --retries=1',)
110    virtualenv.run(' && '.join((
111        'pip uninstall -y setuptools',
112        'pip install -U wheel',
113    ) + upgrade_pip).format(pip_version=pip_version))
114    dist_dir = virtualenv.workspace
115    # Generate source distribution / wheel.
116    virtualenv.run(' && '.join((
117        'python setup.py -q sdist -d {dist}',
118        'python setup.py -q bdist_wheel -d {dist}',
119    )).format(dist=dist_dir), cd=tmp_src)
120    sdist = glob.glob(os.path.join(dist_dir, '*.zip'))[0]
121    wheel = glob.glob(os.path.join(dist_dir, '*.whl'))[0]
122    # Then update from wheel.
123    virtualenv.run('pip install ' + wheel)
124    # And finally try to upgrade from source.
125    virtualenv.run('pip install --no-cache-dir --upgrade ' + sdist)
126
127
128def _check_test_command_install_requirements(virtualenv, tmpdir, cwd):
129    """
130    Check the test command will install all required dependencies.
131    """
132    # Install setuptools.
133    virtualenv.run('python setup.py develop', cd=cwd)
134
135    def sdist(distname, version):
136        dist_path = tmpdir.join('%s-%s.tar.gz' % (distname, version))
137        make_nspkg_sdist(str(dist_path), distname, version)
138        return dist_path
139    dependency_links = [
140        pathlib.Path(str(dist_path)).as_uri()
141        for dist_path in (
142            sdist('foobar', '2.4'),
143            sdist('bits', '4.2'),
144            sdist('bobs', '6.0'),
145            sdist('pieces', '0.6'),
146        )
147    ]
148    with tmpdir.join('setup.py').open('w') as fp:
149        fp.write(DALS(
150            '''
151            from setuptools import setup
152
153            setup(
154                dependency_links={dependency_links!r},
155                install_requires=[
156                    'barbazquux1; sys_platform in ""',
157                    'foobar==2.4',
158                ],
159                setup_requires='bits==4.2',
160                tests_require="""
161                    bobs==6.0
162                """,
163                extras_require={{
164                    'test': ['barbazquux2'],
165                    ':"" in sys_platform': 'pieces==0.6',
166                    ':python_version > "1"': """
167                        pieces
168                        foobar
169                    """,
170                }}
171            )
172            '''.format(dependency_links=dependency_links)))
173    with tmpdir.join('test.py').open('w') as fp:
174        fp.write(DALS(
175            '''
176            import foobar
177            import bits
178            import bobs
179            import pieces
180
181            open('success', 'w').close()
182            '''))
183    # Run test command for test package.
184    # use 'virtualenv.python' as workaround for man-group/pytest-plugins#166
185    cmd = [virtualenv.python, 'setup.py', 'test', '-s', 'test']
186    virtualenv.run(cmd, cd=str(tmpdir))
187    assert tmpdir.join('success').check()
188
189
190def test_test_command_install_requirements(virtualenv, tmpdir, request):
191    # Ensure pip/wheel packages are installed.
192    virtualenv.run(
193        "python -c \"__import__('pkg_resources').require(['pip', 'wheel'])\"")
194    # uninstall setuptools so that 'setup.py develop' works
195    virtualenv.run("python -m pip uninstall -y setuptools")
196    # disable index URL so bits and bobs aren't requested from PyPI
197    virtualenv.env['PIP_NO_INDEX'] = '1'
198    _check_test_command_install_requirements(virtualenv, tmpdir, request.config.rootdir)
199
200
201def test_no_missing_dependencies(bare_virtualenv, request):
202    """
203    Quick and dirty test to ensure all external dependencies are vendored.
204    """
205    for command in ('upload',):  # sorted(distutils.command.__all__):
206        cmd = ['python', 'setup.py', command, '-h']
207        bare_virtualenv.run(cmd, cd=request.config.rootdir)
208