-
-
Notifications
You must be signed in to change notification settings - Fork 1k
/
conftest.py
390 lines (311 loc) · 11.8 KB
/
conftest.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
# -*- coding: utf-8 -*-
from __future__ import absolute_import, unicode_literals
import logging
import os
import shutil
import sys
from contextlib import contextmanager
from functools import partial
import pytest
import six
from virtualenv.app_data import AppDataDiskFolder
from virtualenv.discovery.builtin import get_interpreter
from virtualenv.discovery.py_info import PythonInfo
from virtualenv.info import IS_PYPY, IS_WIN, fs_supports_symlink
from virtualenv.report import LOGGER
from virtualenv.util.path import Path
from virtualenv.util.six import ensure_str, ensure_text
def pytest_addoption(parser):
parser.addoption("--int", action="store_true", default=False, help="run integration tests")
def pytest_configure(config):
"""Ensure randomly is called before we re-order"""
manager = config.pluginmanager
# noinspection PyProtectedMember
order = manager.hook.pytest_collection_modifyitems._nonwrappers
dest = next((i for i, p in enumerate(order) if p.plugin is manager.getplugin("randomly")), None)
if dest is not None:
from_pos = next(i for i, p in enumerate(order) if p.plugin is manager.getplugin(__file__))
temp = order[dest]
order[dest] = order[from_pos]
order[from_pos] = temp
def pytest_collection_modifyitems(config, items):
int_location = os.path.join("tests", "integration", "").rstrip()
if len(items) == 1:
return
items.sort(key=lambda i: 2 if i.location[0].startswith(int_location) else (1 if "slow" in i.keywords else 0))
if not config.getoption("--int"):
for item in items:
if item.location[0].startswith(int_location):
item.add_marker(pytest.mark.skip(reason="need --int option to run"))
@pytest.fixture(scope="session")
def has_symlink_support(tmp_path_factory):
return fs_supports_symlink()
@pytest.fixture(scope="session")
def link_folder(has_symlink_support):
if has_symlink_support:
return os.symlink
elif sys.platform == "win32" and sys.version_info[0:2] > (3, 4):
# on Windows junctions may be used instead
import _winapi # Cpython3.5 has builtin implementation for junctions
return getattr(_winapi, "CreateJunction", None)
else:
return None
@pytest.fixture(scope="session")
def link_file(has_symlink_support):
if has_symlink_support:
return os.symlink
else:
return None
@pytest.fixture(scope="session")
def link(link_folder, link_file):
def _link(src, dest):
clean = dest.unlink
s_dest = str(dest)
s_src = str(src)
if src.is_dir():
if link_folder:
link_folder(s_src, s_dest)
else:
shutil.copytree(s_src, s_dest)
clean = partial(shutil.rmtree, str(dest))
else:
if link_file:
link_file(s_src, s_dest)
else:
shutil.copy2(s_src, s_dest)
return clean
return _link
@pytest.fixture(autouse=True)
def ensure_logging_stable():
logger_level = LOGGER.level
handlers = [i for i in LOGGER.handlers]
filelock_logger = logging.getLogger("filelock")
fl_level = filelock_logger.level
yield
filelock_logger.setLevel(fl_level)
for handler in LOGGER.handlers:
LOGGER.removeHandler(handler)
for handler in handlers:
LOGGER.addHandler(handler)
LOGGER.setLevel(logger_level)
@pytest.fixture(autouse=True)
def check_cwd_not_changed_by_test():
old = os.getcwd()
yield
new = os.getcwd()
if old != new:
pytest.fail("tests changed cwd: {!r} => {!r}".format(old, new))
@pytest.fixture(autouse=True)
def ensure_py_info_cache_empty(session_app_data):
PythonInfo.clear_cache(session_app_data)
yield
PythonInfo.clear_cache(session_app_data)
@contextmanager
def change_os_environ(key, value):
env_var = key
previous = os.environ[env_var] if env_var in os.environ else None
os.environ[env_var] = value
try:
yield
finally:
if previous is not None:
os.environ[env_var] = previous
@pytest.fixture(autouse=True, scope="session")
def ignore_global_config(tmp_path_factory):
filename = str(tmp_path_factory.mktemp("folder") / "virtualenv-test-suite.ini")
with change_os_environ(ensure_str("VIRTUALENV_CONFIG_FILE"), filename):
yield
@pytest.fixture(autouse=True, scope="session")
def pip_cert(tmp_path_factory):
# workaround for https://github.com/pypa/pip/issues/8984 - if the certificate is explicitly set no error can happen
key = ensure_str("PIP_CERT")
if key in os.environ:
yield
else:
cert = tmp_path_factory.mktemp("folder") / "cert"
import pkgutil
cert_data = pkgutil.get_data("pip._vendor.certifi", "cacert.pem")
cert.write_bytes(cert_data)
with change_os_environ(key, str(cert)):
yield
@pytest.fixture(autouse=True)
def check_os_environ_stable():
old = os.environ.copy()
# ensure we don't inherit parent env variables
to_clean = {
k
for k in os.environ.keys()
if k.startswith(str("VIRTUALENV_")) or str("VIRTUAL_ENV") in k or k.startswith(str("TOX_"))
}
cleaned = {k: os.environ[k] for k, v in os.environ.items()}
override = {
"VIRTUALENV_NO_PERIODIC_UPDATE": "1",
"VIRTUALENV_NO_DOWNLOAD": "1",
}
for key, value in override.items():
os.environ[str(key)] = str(value)
is_exception = False
try:
yield
except BaseException:
is_exception = True
raise
finally:
try:
for key in override.keys():
del os.environ[str(key)]
if is_exception is False:
new = os.environ
extra = {k: new[k] for k in set(new) - set(old)}
miss = {k: old[k] for k in set(old) - set(new) - to_clean}
diff = {
"{} = {} vs {}".format(k, old[k], new[k])
for k in set(old) & set(new)
if old[k] != new[k] and not k.startswith(str("PYTEST_"))
}
if extra or miss or diff:
msg = "tests changed environ"
if extra:
msg += " extra {}".format(extra)
if miss:
msg += " miss {}".format(miss)
if diff:
msg += " diff {}".format(diff)
pytest.fail(msg)
finally:
os.environ.update(cleaned)
COV_ENV_VAR = "COVERAGE_PROCESS_START"
COVERAGE_RUN = os.environ.get(str(COV_ENV_VAR))
@pytest.fixture(autouse=True)
def coverage_env(monkeypatch, link, request):
"""
Enable coverage report collection on the created virtual environments by injecting the coverage project
"""
if COVERAGE_RUN and "no_coverage" not in request.fixturenames:
# we inject right after creation, we cannot collect coverage on site.py - used for helper scripts, such as debug
from virtualenv import run
def _session_via_cli(args, options, setup_logging, env=None):
session = prev_run(args, options, setup_logging, env)
old_run = session.creator.run
def create_run():
result = old_run()
obj["cov"] = EnableCoverage(link)
obj["cov"].__enter__(session.creator)
return result
monkeypatch.setattr(session.creator, "run", create_run)
return session
obj = {"cov": None}
prev_run = run.session_via_cli
monkeypatch.setattr(run, "session_via_cli", _session_via_cli)
def finish():
cov = obj["cov"]
obj["cov"] = None
cov.__exit__(None, None, None)
yield finish
if obj["cov"]:
finish()
else:
def finish():
pass
yield finish
# no_coverage tells coverage_env to disable coverage injection for no_coverage user.
@pytest.fixture
def no_coverage():
pass
if COVERAGE_RUN:
import coverage
class EnableCoverage(object):
_COV_FILE = Path(coverage.__file__)
_ROOT_COV_FILES_AND_FOLDERS = [i for i in _COV_FILE.parents[1].iterdir() if i.name.startswith("coverage")]
def __init__(self, link):
self.link = link
self.targets = []
def __enter__(self, creator):
site_packages = creator.purelib
for entry in self._ROOT_COV_FILES_AND_FOLDERS:
target = site_packages / entry.name
if not target.exists():
clean = self.link(entry, target)
self.targets.append((target, clean))
return self
def __exit__(self, exc_type, exc_val, exc_tb):
for target, clean in self.targets:
if target.exists():
clean()
assert self._COV_FILE.exists()
@pytest.fixture(scope="session")
def is_inside_ci():
yield bool(os.environ.get(str("CI_RUN")))
@pytest.fixture(scope="session")
def special_char_name():
base = "e-$ èрт🚒♞中片-j"
# workaround for pypy3 https://bitbucket.org/pypy/pypy/issues/3147/venv-non-ascii-support-windows
encoding = "ascii" if IS_WIN else sys.getfilesystemencoding()
# let's not include characters that the file system cannot encode)
result = ""
for char in base:
try:
trip = char.encode(encoding, errors="strict").decode(encoding)
if char == trip:
result += char
except ValueError:
continue
assert result
return result
@pytest.fixture()
def special_name_dir(tmp_path, special_char_name):
dest = Path(str(tmp_path)) / special_char_name
yield dest
if six.PY2 and sys.platform == "win32" and not IS_PYPY: # pytest python2 windows does not support unicode delete
shutil.rmtree(ensure_text(str(dest)))
@pytest.fixture(scope="session")
def current_creators(session_app_data):
return PythonInfo.current_system(session_app_data).creators()
@pytest.fixture(scope="session")
def current_fastest(current_creators):
return "builtin" if "builtin" in current_creators.key_to_class else next(iter(current_creators.key_to_class))
@pytest.fixture(scope="session")
def session_app_data(tmp_path_factory):
temp_folder = tmp_path_factory.mktemp("session-app-data")
app_data = AppDataDiskFolder(folder=str(temp_folder))
with change_env_var(str("VIRTUALENV_OVERRIDE_APP_DATA"), str(app_data.lock.path)):
yield app_data
@contextmanager
def change_env_var(key, value):
"""Temporarily change an environment variable.
:param key: the key of the env var
:param value: the value of the env var
"""
already_set = key in os.environ
prev_value = os.environ.get(key)
os.environ[key] = value
try:
yield
finally:
if already_set:
os.environ[key] = prev_value # type: ignore
else:
del os.environ[key] # pragma: no cover
@pytest.fixture()
def temp_app_data(monkeypatch, tmp_path):
app_data = tmp_path / "app-data"
monkeypatch.setenv(str("VIRTUALENV_OVERRIDE_APP_DATA"), str(app_data))
return app_data
@pytest.fixture(scope="session")
def cross_python(is_inside_ci, session_app_data):
spec = str(2 if sys.version_info[0] == 3 else 3)
interpreter = get_interpreter(spec, [], session_app_data)
if interpreter is None:
msg = "could not find {}".format(spec)
if is_inside_ci:
raise RuntimeError(msg)
pytest.skip(msg=msg)
yield interpreter
@pytest.fixture(scope="session")
def for_py_version():
return "{}.{}".format(*sys.version_info[0:2])
@pytest.fixture()
def skip_if_test_in_system(session_app_data):
current = PythonInfo.current(session_app_data)
if current.system_executable is not None:
pytest.skip("test not valid if run under system")