2022-02-10 10:58:59 +01:00
|
|
|
import configparser
|
|
|
|
import contextlib
|
|
|
|
import functools
|
|
|
|
import os
|
|
|
|
import pathlib
|
|
|
|
import re
|
|
|
|
import subprocess
|
|
|
|
import sys
|
|
|
|
from configparser import ConfigParser
|
|
|
|
|
|
|
|
import pytest
|
|
|
|
from packaging.version import parse as ver
|
|
|
|
|
|
|
|
PYTHON_VERSION_DOT = f"{sys.version_info[0]}.{sys.version_info[1]}"
|
|
|
|
PYTHON_VERSION_NODOT = f"{sys.version_info[0]}{sys.version_info[1]}"
|
|
|
|
NATIVE_TOXENV = f"py{PYTHON_VERSION_NODOT}"
|
|
|
|
NATIVE_SITE_PACKAGES = f"lib/python{PYTHON_VERSION_DOT}/site-packages"
|
|
|
|
NATIVE_EXECUTABLE = str(pathlib.Path(sys.executable).resolve())
|
|
|
|
FIXTURES_DIR = pathlib.Path(__file__).parent / "fixtures"
|
|
|
|
DOT_TOX = pathlib.Path("./.tox")
|
|
|
|
|
|
|
|
|
|
|
|
def _exec_prefix(executable):
|
|
|
|
"""Returns sys.exec_prefix for the given executable"""
|
|
|
|
cmd = (executable, "-c", "import sys; print(sys.exec_prefix)")
|
|
|
|
return subprocess.check_output(cmd, encoding="utf-8").strip()
|
|
|
|
|
|
|
|
|
|
|
|
NATIVE_EXEC_PREFIX = _exec_prefix(NATIVE_EXECUTABLE)
|
|
|
|
NATIVE_EXEC_PREFIX_MSG = f"{NATIVE_EXEC_PREFIX} is the exec_prefix"
|
|
|
|
|
|
|
|
|
|
|
|
def tox(*args, quiet=True, **kwargs):
|
|
|
|
kwargs.setdefault("encoding", "utf-8")
|
|
|
|
kwargs.setdefault("stdout", subprocess.PIPE)
|
|
|
|
kwargs.setdefault("stderr", subprocess.PIPE)
|
|
|
|
kwargs.setdefault("check", True)
|
|
|
|
kwargs.setdefault("cwd", os.getcwd())
|
|
|
|
q = ("-q",) if quiet else ()
|
|
|
|
env = dict(os.environ)
|
2022-12-14 11:47:07 +01:00
|
|
|
env.pop("TOX_WORK_DIR", None)
|
2022-12-14 13:48:10 +01:00
|
|
|
kwargs["env"] = {**env, **kwargs.get("env", {})}
|
2022-02-10 10:58:59 +01:00
|
|
|
try:
|
|
|
|
print("current", os.getcwd(), "running in", kwargs["cwd"])
|
|
|
|
cp = subprocess.run((sys.executable, "-m", "tox") + q + args, **kwargs)
|
|
|
|
except subprocess.CalledProcessError as e:
|
|
|
|
print(e.stdout, file=sys.stdout)
|
|
|
|
print(e.stderr, file=sys.stderr)
|
|
|
|
raise
|
|
|
|
print(cp.stdout, file=sys.stdout)
|
|
|
|
print(cp.stderr, file=sys.stderr)
|
|
|
|
return cp
|
|
|
|
|
|
|
|
|
2022-12-12 12:38:11 +01:00
|
|
|
TOX_VERSION = ver(tox("--version").stdout.split(" ")[0].split("+")[0])
|
2022-02-10 10:58:59 +01:00
|
|
|
TOX4 = TOX_VERSION.major == 4
|
|
|
|
|
|
|
|
|
|
|
|
@contextlib.contextmanager
|
|
|
|
def modify_config(tox_ini_path):
|
|
|
|
"""Context manager that allows modifying the given Tox config file
|
|
|
|
|
|
|
|
A statement like::
|
|
|
|
|
|
|
|
with prepare_config(projdir) as config:
|
|
|
|
|
|
|
|
will make `config` a ConfigParser instance that is saved at the end
|
|
|
|
of the `with` block.
|
|
|
|
"""
|
|
|
|
config = configparser.ConfigParser()
|
|
|
|
config.read(tox_ini_path)
|
|
|
|
yield config
|
|
|
|
with open(tox_ini_path, "w") as tox_ini_file:
|
|
|
|
config.write(tox_ini_file)
|
|
|
|
|
|
|
|
|
|
|
|
@functools.lru_cache(maxsize=8)
|
|
|
|
def is_available(python):
|
|
|
|
try:
|
|
|
|
subprocess.run((python, "--version"))
|
|
|
|
except FileNotFoundError:
|
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
2024-10-22 17:35:13 +02:00
|
|
|
@functools.lru_cache()
|
|
|
|
def drop_unsupported_pythons(envlist):
|
|
|
|
return envlist[len("py36,py37,"):] if TOX4 else envlist
|
|
|
|
|
|
|
|
|
2022-02-10 10:58:59 +01:00
|
|
|
@functools.lru_cache()
|
|
|
|
def envs_from_tox_ini():
|
|
|
|
cp = ConfigParser()
|
|
|
|
cp.read(FIXTURES_DIR / "tox.ini")
|
2024-10-22 17:35:13 +02:00
|
|
|
envlist = drop_unsupported_pythons(cp["tox"]["envlist"])
|
|
|
|
return envlist.split(",")
|
2022-02-10 10:58:59 +01:00
|
|
|
|
|
|
|
|
|
|
|
def tox_footer(envs=None, spaces=8):
|
|
|
|
if envs is None:
|
|
|
|
envs = envs_from_tox_ini()
|
|
|
|
elif isinstance(envs, str):
|
|
|
|
envs = [envs]
|
|
|
|
|
|
|
|
default_indent = " " * spaces
|
|
|
|
|
|
|
|
if TOX4:
|
|
|
|
result = ""
|
|
|
|
else:
|
|
|
|
result = "___________________________________ summary ____________________________________\n"
|
|
|
|
|
|
|
|
for i, env in enumerate(envs):
|
|
|
|
if TOX4:
|
|
|
|
# Skip indentation for the first line
|
|
|
|
indent = default_indent if i > 0 else ""
|
|
|
|
result += f"{indent} {env}: OK\n"
|
|
|
|
else:
|
|
|
|
result += f"{default_indent} {env}: commands succeeded\n"
|
|
|
|
|
|
|
|
result += f"{default_indent} congratulations :)"
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
|
def prep_tox_output(output):
|
|
|
|
"""Remove time info from tox output"""
|
|
|
|
result = re.sub(r" \((\d+\.\d+|\d+) seconds\)", "", output)
|
|
|
|
result = re.sub(r" ✔ in (\d+\.\d+|\d+) seconds", "", result)
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
|
needs_all_pythons = pytest.mark.skipif(
|
|
|
|
not all((is_available(f"python3.{x}") for x in range(6, 12))),
|
|
|
|
reason="This test needs all pythons from 3.6 to 3.11 available in $PATH",
|
|
|
|
)
|