1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
|
# SPDX-License-Identifier: MIT
import collections
import inspect
import logging
import platform
import subprocess
import sys
import sysconfig
import pytest
from packaging.version import Version
import build.env
IS_PYPY3 = platform.python_implementation() == 'PyPy'
@pytest.mark.isolated
def test_isolation():
subprocess.check_call([sys.executable, '-c', 'import build.env'])
with build.env.IsolatedEnvBuilder() as env:
with pytest.raises(subprocess.CalledProcessError):
debug = 'import sys; import os; print(os.linesep.join(sys.path));'
subprocess.check_call([env.executable, '-c', f'{debug} import build.env'])
@pytest.mark.isolated
def test_isolated_environment_install(mocker):
with build.env.IsolatedEnvBuilder() as env:
mocker.patch('build.env._subprocess')
env.install([])
build.env._subprocess.assert_not_called()
env.install(['some', 'requirements'])
build.env._subprocess.assert_called()
args = build.env._subprocess.call_args[0][0][:-1]
assert args == [
env.executable,
'-Im',
'pip',
'install',
'--use-pep517',
'--no-warn-script-location',
'-r',
]
@pytest.mark.skipif(IS_PYPY3, reason='PyPy3 uses get path to create and provision venv')
@pytest.mark.skipif(sys.platform != 'darwin', reason='workaround for Apple Python')
def test_can_get_venv_paths_with_conflicting_default_scheme(mocker):
get_scheme_names = mocker.patch('sysconfig.get_scheme_names', return_value=('osx_framework_library',))
with build.env.IsolatedEnvBuilder():
pass
assert get_scheme_names.call_count == 1
@pytest.mark.skipif('posix_local' not in sysconfig.get_scheme_names(), reason='workaround for Debian/Ubuntu Python')
def test_can_get_venv_paths_with_posix_local_default_scheme(mocker):
get_paths = mocker.spy(sysconfig, 'get_paths')
# We should never call this, but we patch it to ensure failure if we do
get_default_scheme = mocker.patch('sysconfig.get_default_scheme', return_value='posix_local')
with build.env.IsolatedEnvBuilder():
pass
get_paths.assert_called_once_with(scheme='posix_prefix', vars=mocker.ANY)
assert get_default_scheme.call_count == 0
def test_executable_missing_post_creation(mocker):
venv_create = mocker.patch('venv.EnvBuilder.create')
with pytest.raises(RuntimeError, match='Virtual environment creation failed, executable .* missing'):
with build.env.IsolatedEnvBuilder():
pass
assert venv_create.call_count == 1
def test_isolated_env_abstract():
with pytest.raises(TypeError):
build.env.IsolatedEnv()
def test_isolated_env_has_executable_still_abstract():
class Env(build.env.IsolatedEnv):
@property
def executable(self):
raise NotImplementedError
with pytest.raises(TypeError):
Env()
def test_isolated_env_has_install_still_abstract():
class Env(build.env.IsolatedEnv):
def install(self, requirements):
raise NotImplementedError
with pytest.raises(TypeError):
Env()
@pytest.mark.pypy3323bug
def test_isolated_env_log(mocker, caplog, package_test_flit):
mocker.patch('build.env._subprocess')
caplog.set_level(logging.DEBUG)
builder = build.env.IsolatedEnvBuilder()
frameinfo = inspect.getframeinfo(inspect.currentframe())
builder.log('something') # line number 106
with builder as env:
env.install(['something'])
assert [(record.levelname, record.message) for record in caplog.records] == [
('INFO', 'something'),
('INFO', 'Creating venv isolated environment...'),
('INFO', 'Installing packages in isolated environment... (something)'),
]
if sys.version_info >= (3, 8): # stacklevel
assert [(record.lineno) for record in caplog.records] == [
frameinfo.lineno + 1,
frameinfo.lineno - 6,
frameinfo.lineno + 85,
]
@pytest.mark.isolated
def test_default_pip_is_never_too_old():
with build.env.IsolatedEnvBuilder() as env:
version = subprocess.check_output(
[env.executable, '-c', 'import pip; print(pip.__version__)'], universal_newlines=True
).strip()
assert Version(version) >= Version('19.1')
@pytest.mark.isolated
@pytest.mark.parametrize('pip_version', ['20.2.0', '20.3.0', '21.0.0', '21.0.1'])
@pytest.mark.parametrize('arch', ['x86_64', 'arm64'])
def test_pip_needs_upgrade_mac_os_11(mocker, pip_version, arch):
SimpleNamespace = collections.namedtuple('SimpleNamespace', 'version')
_subprocess = mocker.patch('build.env._subprocess')
mocker.patch('platform.system', return_value='Darwin')
mocker.patch('platform.machine', return_value=arch)
mocker.patch('platform.mac_ver', return_value=('11.0', ('', '', ''), ''))
metadata_name = 'importlib_metadata' if sys.version_info < (3, 8) else 'importlib.metadata'
mocker.patch(metadata_name + '.distributions', return_value=(SimpleNamespace(version=pip_version),))
min_version = Version('20.3' if arch == 'x86_64' else '21.0.1')
with build.env.IsolatedEnvBuilder():
if Version(pip_version) < min_version:
print(_subprocess.call_args_list)
upgrade_call, uninstall_call = _subprocess.call_args_list
answer = 'pip>=20.3.0' if arch == 'x86_64' else 'pip>=21.0.1'
assert upgrade_call[0][0][1:] == ['-m', 'pip', 'install', answer]
assert uninstall_call[0][0][1:] == ['-m', 'pip', 'uninstall', 'setuptools', '-y']
else:
(uninstall_call,) = _subprocess.call_args_list
assert uninstall_call[0][0][1:] == ['-m', 'pip', 'uninstall', 'setuptools', '-y']
@pytest.mark.isolated
@pytest.mark.skipif(IS_PYPY3 and sys.platform.startswith('win'), reason='Isolated tests not supported on PyPy3 + Windows')
@pytest.mark.parametrize('has_symlink', [True, False] if sys.platform.startswith('win') else [True])
def test_venv_symlink(mocker, has_symlink):
if has_symlink:
mocker.patch('os.symlink')
mocker.patch('os.unlink')
else:
mocker.patch('os.symlink', side_effect=OSError())
# Cache must be cleared to rerun
build.env._fs_supports_symlink.cache_clear()
supports_symlink = build.env._fs_supports_symlink()
build.env._fs_supports_symlink.cache_clear()
assert supports_symlink is has_symlink
|