-
-
Notifications
You must be signed in to change notification settings - Fork 1k
/
test_virtualenv.py
520 lines (432 loc) · 20.3 KB
/
test_virtualenv.py
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
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
from __future__ import absolute_import, unicode_literals
import inspect
import optparse
import os
import shutil
import subprocess
import sys
import tempfile
import textwrap
import zipfile
import pypiserver
import pytest
import pytest_localserver.http
import six
import virtualenv
try:
from pathlib import Path
from unittest.mock import NonCallableMock, call, patch
except ImportError:
from mock import NonCallableMock, call, patch
from pathlib2 import Path
def test_version():
"""Should have a version string"""
assert virtualenv.virtualenv_version, "Should have version"
class TestGetInstalledPythons:
key_local_machine = "key-local-machine"
key_current_user = "key-current-user"
@classmethod
def mock_virtualenv_winreg(cls, monkeypatch, data):
def enum_key(key, index):
try:
return data.get(key, [])[index]
except IndexError:
raise WindowsError
def query_value(key, path):
installed_version_tags = data.get(key, [])
suffix = "\\InstallPath"
if path.endswith(suffix):
version_tag = path[: -len(suffix)]
if version_tag in installed_version_tags:
return "{}-{}-path".format(key, version_tag)
raise WindowsError
mock_winreg = NonCallableMock(
spec_set=["HKEY_LOCAL_MACHINE", "HKEY_CURRENT_USER", "CreateKey", "EnumKey", "QueryValue", "CloseKey"]
)
mock_winreg.HKEY_LOCAL_MACHINE = "HKEY_LOCAL_MACHINE"
mock_winreg.HKEY_CURRENT_USER = "HKEY_CURRENT_USER"
mock_winreg.CreateKey.side_effect = [cls.key_local_machine, cls.key_current_user]
mock_winreg.EnumKey.side_effect = enum_key
mock_winreg.QueryValue.side_effect = query_value
mock_winreg.CloseKey.return_value = None
monkeypatch.setattr(virtualenv, "winreg", mock_winreg)
return mock_winreg
@pytest.mark.skipif(sys.platform == "win32", reason="non-windows specific test")
def test_on_non_windows(self, monkeypatch):
assert not virtualenv.IS_WIN
assert not hasattr(virtualenv, "winreg")
assert virtualenv.get_installed_pythons() == {}
@pytest.mark.skipif(sys.platform != "win32", reason="windows specific test")
def test_on_windows(self, monkeypatch):
assert virtualenv.IS_WIN
mock_winreg = self.mock_virtualenv_winreg(
monkeypatch,
{
self.key_local_machine: (
"2.4",
"2.7",
"3.2",
"3.4",
"3.5", # 64-bit only
"3.6-32", # 32-bit only
"3.7",
"3.7-32", # both 32 & 64-bit with a 64-bit user install
"3.8",
), # 64-bit with a 32-bit user install
self.key_current_user: ("2.5", "2.7", "3.7", "3.8-32"),
},
)
monkeypatch.setattr(virtualenv, "join", "{}\\{}".format)
installed_pythons = virtualenv.get_installed_pythons()
assert installed_pythons == {
"2": self.key_current_user + "-2.7-path\\python.exe",
"2.4": self.key_local_machine + "-2.4-path\\python.exe",
"2.5": self.key_current_user + "-2.5-path\\python.exe",
"2.7": self.key_current_user + "-2.7-path\\python.exe",
"3": self.key_local_machine + "-3.8-path\\python.exe",
"3.2": self.key_local_machine + "-3.2-path\\python.exe",
"3.4": self.key_local_machine + "-3.4-path\\python.exe",
"3.5": self.key_local_machine + "-3.5-path\\python.exe",
"3.5-64": self.key_local_machine + "-3.5-path\\python.exe",
"3.6": self.key_local_machine + "-3.6-32-path\\python.exe",
"3.6-32": self.key_local_machine + "-3.6-32-path\\python.exe",
"3.7": self.key_current_user + "-3.7-path\\python.exe",
"3.7-32": self.key_local_machine + "-3.7-32-path\\python.exe",
"3.7-64": self.key_current_user + "-3.7-path\\python.exe",
"3.8": self.key_local_machine + "-3.8-path\\python.exe",
"3.8-32": self.key_current_user + "-3.8-32-path\\python.exe",
"3.8-64": self.key_local_machine + "-3.8-path\\python.exe",
}
assert mock_winreg.mock_calls == [
call.CreateKey(mock_winreg.HKEY_LOCAL_MACHINE, "Software\\Python\\PythonCore"),
call.EnumKey(self.key_local_machine, 0),
call.QueryValue(self.key_local_machine, "2.4\\InstallPath"),
call.EnumKey(self.key_local_machine, 1),
call.QueryValue(self.key_local_machine, "2.7\\InstallPath"),
call.EnumKey(self.key_local_machine, 2),
call.QueryValue(self.key_local_machine, "3.2\\InstallPath"),
call.EnumKey(self.key_local_machine, 3),
call.QueryValue(self.key_local_machine, "3.4\\InstallPath"),
call.EnumKey(self.key_local_machine, 4),
call.QueryValue(self.key_local_machine, "3.5\\InstallPath"),
call.EnumKey(self.key_local_machine, 5),
call.QueryValue(self.key_local_machine, "3.6-32\\InstallPath"),
call.EnumKey(self.key_local_machine, 6),
call.QueryValue(self.key_local_machine, "3.7\\InstallPath"),
call.EnumKey(self.key_local_machine, 7),
call.QueryValue(self.key_local_machine, "3.7-32\\InstallPath"),
call.EnumKey(self.key_local_machine, 8),
call.QueryValue(self.key_local_machine, "3.8\\InstallPath"),
call.EnumKey(self.key_local_machine, 9),
call.CloseKey(self.key_local_machine),
call.CreateKey(mock_winreg.HKEY_CURRENT_USER, "Software\\Python\\PythonCore"),
call.EnumKey(self.key_current_user, 0),
call.QueryValue(self.key_current_user, "2.5\\InstallPath"),
call.EnumKey(self.key_current_user, 1),
call.QueryValue(self.key_current_user, "2.7\\InstallPath"),
call.EnumKey(self.key_current_user, 2),
call.QueryValue(self.key_current_user, "3.7\\InstallPath"),
call.EnumKey(self.key_current_user, 3),
call.QueryValue(self.key_current_user, "3.8-32\\InstallPath"),
call.EnumKey(self.key_current_user, 4),
call.CloseKey(self.key_current_user),
]
@pytest.mark.skipif(sys.platform != "win32", reason="windows specific test")
def test_on_windows_with_no_installations(self, monkeypatch):
assert virtualenv.IS_WIN
mock_winreg = self.mock_virtualenv_winreg(monkeypatch, {})
installed_pythons = virtualenv.get_installed_pythons()
assert installed_pythons == {}
assert mock_winreg.mock_calls == [
call.CreateKey(mock_winreg.HKEY_LOCAL_MACHINE, "Software\\Python\\PythonCore"),
call.EnumKey(self.key_local_machine, 0),
call.CloseKey(self.key_local_machine),
call.CreateKey(mock_winreg.HKEY_CURRENT_USER, "Software\\Python\\PythonCore"),
call.EnumKey(self.key_current_user, 0),
call.CloseKey(self.key_current_user),
]
@patch("distutils.spawn.find_executable")
@patch("virtualenv.is_executable", return_value=True)
@patch("virtualenv.get_installed_pythons")
@patch("os.path.exists", return_value=True)
@patch("os.path.abspath")
def test_resolve_interpreter_with_installed_python(
mock_abspath, mock_exists, mock_get_installed_pythons, mock_is_executable, mock_find_executable
):
test_tag = "foo"
test_path = "/path/to/foo/python.exe"
test_abs_path = "some-abs-path"
test_found_path = "some-found-path"
mock_get_installed_pythons.return_value = {test_tag: test_path, test_tag + "2": test_path + "2"}
mock_abspath.return_value = test_abs_path
mock_find_executable.return_value = test_found_path
exe = virtualenv.resolve_interpreter("foo")
assert exe == test_found_path, "installed python should be accessible by key"
mock_get_installed_pythons.assert_called_once_with()
mock_abspath.assert_called_once_with(test_path)
mock_find_executable.assert_called_once_with(test_path)
mock_exists.assert_called_once_with(test_found_path)
mock_is_executable.assert_called_once_with(test_found_path)
@patch("virtualenv.is_executable", return_value=True)
@patch("virtualenv.get_installed_pythons", return_value={"foo": "bar"})
@patch("os.path.exists", return_value=True)
def test_resolve_interpreter_with_absolute_path(mock_exists, mock_get_installed_pythons, mock_is_executable):
"""Should return absolute path if given and exists"""
test_abs_path = os.path.abspath("/usr/bin/python53")
exe = virtualenv.resolve_interpreter(test_abs_path)
assert exe == test_abs_path, "Absolute path should return as is"
mock_exists.assert_called_with(test_abs_path)
mock_is_executable.assert_called_with(test_abs_path)
@patch("virtualenv.get_installed_pythons", return_value={"foo": "bar"})
@patch("os.path.exists", return_value=False)
def test_resolve_interpreter_with_nonexistent_interpreter(mock_exists, mock_get_installed_pythons):
"""Should SystemExit with an nonexistent python interpreter path"""
with pytest.raises(SystemExit):
virtualenv.resolve_interpreter("/usr/bin/python53")
mock_exists.assert_called_with("/usr/bin/python53")
@patch("virtualenv.is_executable", return_value=False)
@patch("os.path.exists", return_value=True)
def test_resolve_interpreter_with_invalid_interpreter(mock_exists, mock_is_executable):
"""Should exit when with absolute path if not exists"""
invalid = os.path.abspath("/usr/bin/pyt_hon53")
with pytest.raises(SystemExit):
virtualenv.resolve_interpreter(invalid)
mock_exists.assert_called_with(invalid)
mock_is_executable.assert_called_with(invalid)
def test_activate_after_future_statements():
"""Should insert activation line after last future statement"""
script = [
"#!/usr/bin/env python",
"from __future__ import with_statement",
"from __future__ import print_function",
'print("Hello, world!")',
]
out = virtualenv.relative_script(script)
assert out == [
"#!/usr/bin/env python",
"from __future__ import with_statement",
"from __future__ import print_function",
"",
"import os; "
"activate_this=os.path.join(os.path.dirname(os.path.realpath(__file__)), 'activate_this.py'); "
"exec(compile(open(activate_this).read(), activate_this, 'exec'), { '__file__': activate_this}); "
"del os, activate_this",
"",
'print("Hello, world!")',
], out
def test_cop_update_defaults_with_store_false():
"""store_false options need reverted logic"""
class MyConfigOptionParser(virtualenv.ConfigOptionParser):
def __init__(self, *args, **kwargs):
self.config = virtualenv.ConfigParser.RawConfigParser()
self.files = []
optparse.OptionParser.__init__(self, *args, **kwargs)
def get_environ_vars(self, prefix="VIRTUALENV_"):
yield ("no_site_packages", "1")
cop = MyConfigOptionParser()
cop.add_option(
"--no-site-packages",
dest="system_site_packages",
action="store_false",
help="Don't give access to the global site-packages dir to the " "virtual environment (default)",
)
defaults = {}
cop.update_defaults(defaults)
assert defaults == {"system_site_packages": 0}
def test_install_python_bin():
"""Should create the right python executables and links"""
tmp_virtualenv = tempfile.mkdtemp()
try:
home_dir, lib_dir, inc_dir, bin_dir = virtualenv.path_locations(tmp_virtualenv)
virtualenv.install_python(home_dir, lib_dir, inc_dir, bin_dir, False, False)
if virtualenv.IS_WIN:
required_executables = ["python.exe", "pythonw.exe"]
else:
py_exe_no_version = "python"
py_exe_version_major = "python%s" % sys.version_info[0]
py_exe_version_major_minor = "python{}.{}".format(sys.version_info[0], sys.version_info[1])
required_executables = [py_exe_no_version, py_exe_version_major, py_exe_version_major_minor]
for pth in required_executables:
assert os.path.exists(os.path.join(bin_dir, pth)), "%s should exist in bin_dir" % pth
finally:
shutil.rmtree(tmp_virtualenv)
@pytest.mark.skipif("platform.python_implementation() == 'PyPy'")
def test_always_copy_option():
"""Should be no symlinks in directory tree"""
tmp_virtualenv = tempfile.mkdtemp()
ve_path = os.path.join(tmp_virtualenv, "venv")
try:
virtualenv.create_environment(ve_path, symlink=False)
for root, dirs, files in os.walk(tmp_virtualenv):
for f in files + dirs:
full_name = os.path.join(root, f)
assert not os.path.islink(full_name), "%s should not be a" " symlink (to %s)" % (
full_name,
os.readlink(full_name),
)
finally:
shutil.rmtree(tmp_virtualenv)
@pytest.mark.skipif(not hasattr(os, "symlink"), reason="requires working symlink implementation")
def test_relative_symlink(tmpdir):
""" Test if a virtualenv works correctly if it was created via a symlink and this symlink is removed """
tmpdir = str(tmpdir)
ve_path = os.path.join(tmpdir, "venv")
os.mkdir(ve_path)
workdir = os.path.join(tmpdir, "work")
os.mkdir(workdir)
ve_path_linked = os.path.join(workdir, "venv")
os.symlink(ve_path, ve_path_linked)
lib64 = os.path.join(ve_path, "lib64")
virtualenv.create_environment(ve_path_linked, symlink=True)
if not os.path.lexists(lib64):
# no lib 64 on this platform
return
assert os.path.exists(lib64)
shutil.rmtree(workdir)
assert os.path.exists(lib64)
@pytest.mark.skipif(not hasattr(os, "symlink"), reason="requires working symlink implementation")
def test_copyfile_from_symlink(tmp_path):
"""Test that copyfile works correctly when the source is a symlink with a
relative target, and a symlink to a symlink. (This can occur when creating
an environment if Python was installed using stow or homebrew.)"""
# Set up src/link2 -> ../src/link1 -> file.
# We will copy to a different directory, so misinterpreting either symlink
# will be detected.
src_dir = tmp_path / "src"
src_dir.mkdir()
with open(str(src_dir / "file"), "w") as f:
f.write("contents")
os.symlink("file", str(src_dir / "link1"))
os.symlink(str(Path("..") / "src" / "link1"), str(src_dir / "link2"))
# Check that copyfile works on link2.
# This may produce a symlink or a regular file depending on the platform --
# which doesn't matter as long as it has the right contents.
copy_path = tmp_path / "copy"
virtualenv.copyfile(str(src_dir / "link2"), str(copy_path))
with open(str(copy_path), "r") as f:
assert f.read() == "contents"
shutil.rmtree(str(src_dir))
os.remove(str(copy_path))
def test_missing_certifi_pem(tmp_path):
"""Make sure that we can still create virtual environment if pip is
patched to not use certifi's cacert.pem and the file is removed.
This can happen if pip is packaged by Linux distributions."""
proj_dir = Path(__file__).parent.parent
support_original = proj_dir / "virtualenv_support"
pip_wheel = sorted(support_original.glob("pip*whl"))[0]
whl_name = pip_wheel.name
wheeldir = tmp_path / "wheels"
wheeldir.mkdir()
tmpcert = tmp_path / "tmpcert.pem"
cacert = "pip/_vendor/certifi/cacert.pem"
certifi = "pip/_vendor/certifi/core.py"
oldpath = b"os.path.join(f, 'cacert.pem')"
newpath = "r'{}'".format(tmpcert).encode()
removed = False
replaced = False
with zipfile.ZipFile(str(pip_wheel), "r") as whlin:
with zipfile.ZipFile(str(wheeldir / whl_name), "w") as whlout:
for item in whlin.infolist():
buff = whlin.read(item.filename)
if item.filename == cacert:
tmpcert.write_bytes(buff)
removed = True
continue
if item.filename == certifi:
nbuff = buff.replace(oldpath, newpath)
assert nbuff != buff
buff = nbuff
replaced = True
whlout.writestr(item, buff)
assert removed and replaced
venvdir = tmp_path / "venv"
search_dirs = [str(wheeldir), str(support_original)]
virtualenv.create_environment(str(venvdir), search_dirs=search_dirs)
def test_create_environment_from_dir_with_spaces(tmpdir):
"""Should work with wheel sources read from a dir with spaces."""
ve_path = str(tmpdir / "venv")
spaced_support_dir = str(tmpdir / "support with spaces")
from virtualenv_support import __file__ as support_dir
support_dir = os.path.dirname(os.path.abspath(support_dir))
shutil.copytree(support_dir, spaced_support_dir)
virtualenv.create_environment(ve_path, search_dirs=[spaced_support_dir])
def test_create_environment_in_dir_with_spaces(tmpdir):
"""Should work with environment path containing spaces."""
ve_path = str(tmpdir / "venv with spaces")
virtualenv.create_environment(ve_path)
def test_create_environment_with_local_https_pypi(tmpdir):
"""Create virtual environment using local PyPI listening https with
certificate signed with custom certificate authority
"""
test_dir = Path(__file__).parent
ssl_dir = test_dir / "ssl"
proj_dir = test_dir.parent
support_dir = proj_dir / "virtualenv_support"
local_pypi_app = pypiserver.app(root=str(support_dir))
local_pypi = pytest_localserver.http.WSGIServer(
host="localhost",
port=0,
application=local_pypi_app,
ssl_context=(str(ssl_dir / "server.crt"), str(ssl_dir / "server.key")),
)
local_pypi.start()
local_pypi_url = "https://localhost:{}/".format(local_pypi.server_address[1])
venvdir = tmpdir / "venv"
pip_log = tmpdir / "pip.log"
env_addition = {
"PIP_CERT": str(ssl_dir / "rootCA.pem"),
"PIP_INDEX_URL": local_pypi_url,
"PIP_LOG": str(pip_log),
"PIP_RETRIES": "0",
}
if six.PY2:
env_addition = {key.encode("utf-8"): value.encode("utf-8") for key, value in env_addition.items()}
env_backup = {}
for key, value in env_addition.items():
if key in os.environ:
env_backup[key] = os.environ[key]
os.environ[key] = value
try:
virtualenv.create_environment(str(venvdir), download=True)
with pip_log.open("rb") as f:
assert b"SSLError" not in f.read()
finally:
local_pypi.stop()
for key in env_addition.keys():
os.environ.pop(key)
if key in env_backup:
os.environ[key] = env_backup[key]
def check_pypy_pre_import():
import sys
# These modules(module_name, optional) are taken from PyPy's site.py:
# https://bitbucket.org/pypy/pypy/src/d0187cf2f1b70ec4b60f10673ff081bdd91e9a17/lib-python/2.7/site.py#lines-532:539
modules = [
("encodings", False),
("exceptions", True), # "exceptions" module does not exist in Python3
("zipimport", True),
]
for module, optional in modules:
if not optional or module in sys.builtin_module_names:
assert module in sys.modules, "missing {!r} in sys.modules".format(module)
@pytest.mark.skipif("platform.python_implementation() != 'PyPy'")
def test_pypy_pre_import(tmp_path):
"""For PyPy, some built-in modules should be pre-imported because
some programs expect them to be in sys.modules on startup.
"""
check_code = inspect.getsource(check_pypy_pre_import)
check_code = textwrap.dedent(check_code[check_code.index("\n") + 1 :])
if six.PY2:
check_code = check_code.decode()
check_prog = tmp_path / "check-pre-import.py"
check_prog.write_text(check_code)
ve_path = str(tmp_path / "venv")
virtualenv.create_environment(ve_path)
bin_dir = virtualenv.path_locations(ve_path)[-1]
try:
cmd = [
os.path.join(bin_dir, "{}{}".format(virtualenv.EXPECTED_EXE, ".exe" if virtualenv.IS_WIN else "")),
str(check_prog),
]
subprocess.check_output(cmd, universal_newlines=True, stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as exception:
assert not exception.returncode, exception.output