1import os
2import sys
3import functools
4import platform
5import textwrap
6
7import pytest
8
9
10IS_PYPY = '__pypy__' in sys.builtin_module_names
11
12
13def popen_text(call):
14    """
15    Augment the Popen call with the parameters to ensure unicode text.
16    """
17    return functools.partial(call, universal_newlines=True) \
18        if sys.version_info < (3, 7) else functools.partial(call, text=True)
19
20
21def win_sr(env):
22    """
23    On Windows, SYSTEMROOT must be present to avoid
24
25    > Fatal Python error: _Py_HashRandomization_Init: failed to
26    > get random numbers to initialize Python
27    """
28    if env is None:
29        return
30    if platform.system() == 'Windows':
31        env['SYSTEMROOT'] = os.environ['SYSTEMROOT']
32    return env
33
34
35def find_distutils(venv, imports='distutils', env=None, **kwargs):
36    py_cmd = 'import {imports}; print(distutils.__file__)'.format(**locals())
37    cmd = ['python', '-c', py_cmd]
38    return popen_text(venv.run)(cmd, env=win_sr(env), **kwargs)
39
40
41def count_meta_path(venv, env=None):
42    py_cmd = textwrap.dedent(
43        """
44        import sys
45        is_distutils = lambda finder: finder.__class__.__name__ == "DistutilsMetaFinder"
46        print(len(list(filter(is_distutils, sys.meta_path))))
47        """)
48    cmd = ['python', '-c', py_cmd]
49    return int(popen_text(venv.run)(cmd, env=win_sr(env)))
50
51
52def test_distutils_stdlib(venv):
53    """
54    Ensure stdlib distutils is used when appropriate.
55    """
56    env = dict(SETUPTOOLS_USE_DISTUTILS='stdlib')
57    assert venv.name not in find_distutils(venv, env=env).split(os.sep)
58    assert count_meta_path(venv, env=env) == 0
59
60
61def test_distutils_local_with_setuptools(venv):
62    """
63    Ensure local distutils is used when appropriate.
64    """
65    env = dict(SETUPTOOLS_USE_DISTUTILS='local')
66    loc = find_distutils(venv, imports='setuptools, distutils', env=env)
67    assert venv.name in loc.split(os.sep)
68    assert count_meta_path(venv, env=env) <= 1
69
70
71@pytest.mark.xfail('IS_PYPY', reason='pypy imports distutils on startup')
72def test_distutils_local(venv):
73    """
74    Even without importing, the setuptools-local copy of distutils is
75    preferred.
76    """
77    env = dict(SETUPTOOLS_USE_DISTUTILS='local')
78    assert venv.name in find_distutils(venv, env=env).split(os.sep)
79    assert count_meta_path(venv, env=env) <= 1
80
81
82def test_pip_import(venv):
83    """
84    Ensure pip can be imported.
85    Regression test for #3002.
86    """
87    cmd = ['python', '-c', 'import pip']
88    popen_text(venv.run)(cmd)
89
90
91def test_distutils_has_origin():
92    """
93    Distutils module spec should have an origin. #2990.
94    """
95    assert __import__('distutils').__spec__.origin
96
97
98ENSURE_IMPORTS_ARE_NOT_DUPLICATED = r"""
99# Depending on the importlib machinery and _distutils_hack, some imports are
100# duplicated resulting in different module objects being loaded, which prevents
101# patches as shown in #3042.
102# This script provides a way of verifying if this duplication is happening.
103
104from distutils import cmd
105import distutils.command.sdist as sdist
106
107# import last to prevent caching
108from distutils import {imported_module}
109
110for mod in (cmd, sdist):
111    assert mod.{imported_module} == {imported_module}, (
112        f"\n{{mod.dir_util}}\n!=\n{{{imported_module}}}"
113    )
114
115print("success")
116"""
117
118
119@pytest.mark.parametrize(
120    "distutils_version, imported_module",
121    [
122        ("stdlib", "dir_util"),
123        ("stdlib", "file_util"),
124        ("stdlib", "archive_util"),
125        ("local", "dir_util"),
126        ("local", "file_util"),
127        ("local", "archive_util"),
128    ]
129)
130def test_modules_are_not_duplicated_on_import(
131        distutils_version, imported_module, tmpdir_cwd, venv
132):
133    env = dict(SETUPTOOLS_USE_DISTUTILS=distutils_version)
134    script = ENSURE_IMPORTS_ARE_NOT_DUPLICATED.format(imported_module=imported_module)
135    cmd = ['python', '-c', script]
136    output = popen_text(venv.run)(cmd, env=win_sr(env)).strip()
137    assert output == "success"
138
139
140ENSURE_LOG_IMPORT_IS_NOT_DUPLICATED = r"""
141# Similar to ENSURE_IMPORTS_ARE_NOT_DUPLICATED
142import distutils.dist as dist
143from distutils import log
144
145assert dist.log == log, (
146    f"\n{dist.log}\n!=\n{log}"
147)
148
149print("success")
150"""
151
152
153@pytest.mark.parametrize("distutils_version", "local stdlib".split())
154def test_log_module_is_not_duplicated_on_import(distutils_version, tmpdir_cwd, venv):
155    env = dict(SETUPTOOLS_USE_DISTUTILS=distutils_version)
156    cmd = ['python', '-c', ENSURE_LOG_IMPORT_IS_NOT_DUPLICATED]
157    output = popen_text(venv.run)(cmd, env=win_sr(env)).strip()
158    assert output == "success"
159