mirror of
https://github.com/fedora-python/tox-current-env.git
synced 2024-12-25 09:36:13 +01:00
338d03ac22
The previous version (resolving the exec_prefix) no longer works for me. Also, apparently my current environment is different in tests (it is the "absolutely current" now), so I've adapted test_noquiet_installed_packages. Not very sure about what has changed, but this has been always fragile.
549 lines
18 KiB
Python
549 lines
18 KiB
Python
import functools
|
|
import os
|
|
import pathlib
|
|
import re
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import textwrap
|
|
import warnings
|
|
|
|
from packaging import version
|
|
|
|
import pytest
|
|
|
|
|
|
NATIVE_TOXENV = f"py{sys.version_info[0]}{sys.version_info[1]}"
|
|
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"
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def projdir(tmp_path, monkeypatch):
|
|
pwd = tmp_path / "projdir"
|
|
pwd.mkdir()
|
|
for fname in "tox.ini", "setup.py":
|
|
shutil.copy(FIXTURES_DIR / fname, pwd)
|
|
monkeypatch.chdir(pwd)
|
|
|
|
|
|
@pytest.fixture(params=('--print-deps-only', '--print-deps-to-file=-', '--print-deps-to=-'))
|
|
def print_deps_stdout_arg(request):
|
|
"""Argument for printing deps to stdout"""
|
|
return request.param
|
|
|
|
|
|
@pytest.fixture(params=('--print-extras-to-file=-', '--print-extras-to=-'))
|
|
def print_extras_stdout_arg(request):
|
|
"""Argument for printing extras to stdout"""
|
|
return request.param
|
|
|
|
|
|
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)
|
|
q = ("-q",) if quiet else ()
|
|
try:
|
|
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
|
|
|
|
|
|
@functools.lru_cache(maxsize=8)
|
|
def is_available(python):
|
|
try:
|
|
subprocess.run((python, "--version"))
|
|
except FileNotFoundError:
|
|
return False
|
|
return True
|
|
|
|
|
|
TOX_VERSION = version.parse(tox("--version").stdout.split(" ")[0])
|
|
TOX313 = TOX_VERSION < version.parse("3.14")
|
|
|
|
|
|
needs_py36789 = pytest.mark.skipif(
|
|
not all((is_available(f"python3.{x}") for x in range(6, 10))),
|
|
reason="This test needs python3.6, 3.7, 3.8 and 3.9 available in $PATH",
|
|
)
|
|
|
|
|
|
|
|
def test_native_toxenv_current_env():
|
|
result = tox("-e", NATIVE_TOXENV, "--current-env")
|
|
assert result.stdout.splitlines()[0] == NATIVE_EXEC_PREFIX_MSG
|
|
assert not (DOT_TOX / NATIVE_TOXENV / "lib").is_dir()
|
|
|
|
|
|
@needs_py36789
|
|
def test_all_toxenv_current_env():
|
|
result = tox("--current-env", check=False)
|
|
assert NATIVE_EXEC_PREFIX_MSG in result.stdout.splitlines()
|
|
assert result.stdout.count("InterpreterMismatch:") >= 2
|
|
assert result.returncode > 0
|
|
|
|
|
|
@pytest.mark.parametrize("python", ["python3.4", "python3.5"])
|
|
def test_missing_toxenv_current_env(python):
|
|
if is_available(python):
|
|
pytest.skip(f"Only works if {python} is not available in $PATH")
|
|
env = python.replace("python", "py").replace(".", "")
|
|
result = tox("-e", env, "--current-env", check=False)
|
|
assert f"InterpreterNotFound: {python}" in result.stdout
|
|
assert "Traceback" not in (result.stderr + result.stdout)
|
|
assert result.returncode > 0
|
|
|
|
|
|
@needs_py36789
|
|
def test_all_toxenv_current_env_skip_missing():
|
|
result = tox("--current-env", "--skip-missing-interpreters", check=False)
|
|
assert "InterpreterMismatch:" in result.stdout
|
|
assert "congratulations" in result.stdout
|
|
assert result.returncode == 0
|
|
|
|
|
|
@pytest.mark.parametrize("toxenv", ["py36", "py37", "py38", "py39"])
|
|
def test_print_deps(toxenv, print_deps_stdout_arg):
|
|
result = tox("-e", toxenv, print_deps_stdout_arg)
|
|
expected = textwrap.dedent(
|
|
f"""
|
|
six
|
|
py
|
|
___________________________________ summary ____________________________________
|
|
{toxenv}: commands succeeded
|
|
congratulations :)
|
|
"""
|
|
).lstrip()
|
|
assert result.stdout == expected
|
|
|
|
|
|
@pytest.mark.parametrize("toxenv", ["py36", "py37", "py38", "py39"])
|
|
def test_print_extras(toxenv, print_extras_stdout_arg):
|
|
result = tox("-e", toxenv, print_extras_stdout_arg)
|
|
expected = textwrap.dedent(
|
|
f"""
|
|
dev
|
|
full
|
|
___________________________________ summary ____________________________________
|
|
{toxenv}: commands succeeded
|
|
congratulations :)
|
|
"""
|
|
).lstrip()
|
|
assert result.stdout == expected
|
|
|
|
|
|
@pytest.mark.parametrize("toxenv", ["py36", "py37", "py38", "py39"])
|
|
def test_print_deps_only_deprecated(toxenv):
|
|
result = tox(
|
|
"-e", toxenv, '--print-deps-only',
|
|
env={**os.environ, 'PYTHONWARNINGS': 'always'},
|
|
)
|
|
waring_text = (
|
|
"DeprecationWarning: --print-deps-only is deprecated; "
|
|
+ "use `--print-deps-to -`"
|
|
)
|
|
assert waring_text in result.stderr
|
|
|
|
|
|
def test_allenvs_print_deps(print_deps_stdout_arg):
|
|
result = tox(print_deps_stdout_arg)
|
|
expected = textwrap.dedent(
|
|
"""
|
|
six
|
|
py
|
|
six
|
|
py
|
|
six
|
|
py
|
|
six
|
|
py
|
|
___________________________________ summary ____________________________________
|
|
py36: commands succeeded
|
|
py37: commands succeeded
|
|
py38: commands succeeded
|
|
py39: commands succeeded
|
|
congratulations :)
|
|
"""
|
|
).lstrip()
|
|
assert result.stdout == expected
|
|
|
|
|
|
def test_allenvs_print_extras(print_extras_stdout_arg):
|
|
result = tox(print_extras_stdout_arg)
|
|
expected = textwrap.dedent(
|
|
"""
|
|
dev
|
|
full
|
|
dev
|
|
full
|
|
dev
|
|
full
|
|
dev
|
|
full
|
|
___________________________________ summary ____________________________________
|
|
py36: commands succeeded
|
|
py37: commands succeeded
|
|
py38: commands succeeded
|
|
py39: commands succeeded
|
|
congratulations :)
|
|
"""
|
|
).lstrip()
|
|
assert result.stdout == expected
|
|
|
|
|
|
@pytest.mark.parametrize("toxenv", ["py36", "py37", "py38", "py39"])
|
|
def test_print_deps_to_file(toxenv, tmp_path):
|
|
depspath = tmp_path / "deps"
|
|
result = tox("-e", toxenv, "--print-deps-to", str(depspath))
|
|
assert depspath.read_text().splitlines() == ["six", "py"]
|
|
expected = textwrap.dedent(
|
|
f"""
|
|
___________________________________ summary ____________________________________
|
|
{toxenv}: commands succeeded
|
|
congratulations :)
|
|
"""
|
|
).lstrip()
|
|
assert result.stdout == expected
|
|
|
|
|
|
@pytest.mark.parametrize("toxenv", ["py36", "py37", "py38", "py39"])
|
|
def test_print_extras_to_file(toxenv, tmp_path):
|
|
extraspath = tmp_path / "extras"
|
|
result = tox("-e", toxenv, "--print-extras-to", str(extraspath))
|
|
assert extraspath.read_text().splitlines() == ["dev", "full"]
|
|
expected = textwrap.dedent(
|
|
f"""
|
|
___________________________________ summary ____________________________________
|
|
{toxenv}: commands succeeded
|
|
congratulations :)
|
|
"""
|
|
).lstrip()
|
|
assert result.stdout == expected
|
|
|
|
|
|
@pytest.mark.parametrize('option', ('--print-deps-to', '--print-deps-to-file'))
|
|
def test_allenvs_print_deps_to_file(tmp_path, option):
|
|
depspath = tmp_path / "deps"
|
|
result = tox(option, str(depspath))
|
|
assert depspath.read_text().splitlines() == ["six", "py"] * 4
|
|
expected = textwrap.dedent(
|
|
"""
|
|
___________________________________ summary ____________________________________
|
|
py36: commands succeeded
|
|
py37: commands succeeded
|
|
py38: commands succeeded
|
|
py39: commands succeeded
|
|
congratulations :)
|
|
"""
|
|
).lstrip()
|
|
assert result.stdout == expected
|
|
|
|
|
|
@pytest.mark.parametrize('option', ('--print-extras-to', '--print-extras-to-file'))
|
|
def test_allenvs_print_extras_to_file(tmp_path, option):
|
|
extraspath = tmp_path / "extras"
|
|
result = tox(option, str(extraspath))
|
|
assert extraspath.read_text().splitlines() == ["dev", "full"] * 4
|
|
expected = textwrap.dedent(
|
|
"""
|
|
___________________________________ summary ____________________________________
|
|
py36: commands succeeded
|
|
py37: commands succeeded
|
|
py38: commands succeeded
|
|
py39: commands succeeded
|
|
congratulations :)
|
|
"""
|
|
).lstrip()
|
|
assert result.stdout == expected
|
|
|
|
|
|
def test_allenvs_print_deps_to_existing_file(tmp_path):
|
|
depspath = tmp_path / "deps"
|
|
depspath.write_text("nada")
|
|
result = tox("--print-deps-to", str(depspath))
|
|
lines = depspath.read_text().splitlines()
|
|
assert "nada" not in lines
|
|
assert "six" in lines
|
|
assert "py" in lines
|
|
|
|
|
|
def test_allenvs_print_extras_to_existing_file(tmp_path):
|
|
extraspath = tmp_path / "extras"
|
|
extraspath.write_text("nada")
|
|
result = tox("--print-extras-to", str(extraspath))
|
|
lines = extraspath.read_text().splitlines()
|
|
assert "nada" not in lines
|
|
assert "dev" in lines
|
|
assert "full" in lines
|
|
|
|
|
|
@pytest.mark.parametrize("deps_stdout", [True, False])
|
|
@pytest.mark.parametrize("extras_stdout", [True, False])
|
|
def test_allenvs_print_deps_to_file_print_extras_to_other_file(tmp_path, deps_stdout, extras_stdout):
|
|
if deps_stdout and extras_stdout:
|
|
pytest.xfail("Unsupported combination of parameters")
|
|
|
|
depspath = "-" if deps_stdout else tmp_path / "deps"
|
|
extraspath = "-" if extras_stdout else tmp_path / "extras"
|
|
result = tox("--print-deps-to", str(depspath),
|
|
"--print-extras-to", str(extraspath))
|
|
if deps_stdout:
|
|
depslines = result.stdout.splitlines()
|
|
extraslines = extraspath.read_text().splitlines()
|
|
elif extras_stdout:
|
|
depslines = depspath.read_text().splitlines()
|
|
extraslines = result.stdout.splitlines()
|
|
else:
|
|
extraslines = extraspath.read_text().splitlines()
|
|
depslines = depspath.read_text().splitlines()
|
|
|
|
assert "six" in depslines
|
|
assert "py" in depslines
|
|
assert "full" in extraslines
|
|
assert "dev" in extraslines
|
|
|
|
assert "six" not in extraslines
|
|
assert "py" not in extraslines
|
|
assert "full" not in depslines
|
|
assert "dev" not in depslines
|
|
|
|
|
|
def test_print_deps_extras_to_same_file_is_not_possible(tmp_path):
|
|
depsextraspath = tmp_path / "depsextras"
|
|
result = tox(
|
|
"-e",
|
|
NATIVE_TOXENV,
|
|
"--print-deps-to", str(depsextraspath),
|
|
"--print-extras-to", str(depsextraspath),
|
|
check=False,
|
|
)
|
|
assert result.returncode > 0
|
|
assert "cannot be identical" in result.stderr
|
|
|
|
|
|
def test_print_deps_extras_to_stdout_is_not_possible(
|
|
tmp_path,
|
|
print_deps_stdout_arg,
|
|
print_extras_stdout_arg,):
|
|
result = tox(
|
|
"-e",
|
|
NATIVE_TOXENV,
|
|
print_deps_stdout_arg,
|
|
print_extras_stdout_arg,
|
|
check=False,
|
|
)
|
|
assert result.returncode > 0
|
|
assert "cannot be identical" in result.stderr
|
|
|
|
|
|
def test_print_deps_only_print_deps_to_file_are_mutually_exclusive():
|
|
result = tox(
|
|
"-e",
|
|
NATIVE_TOXENV,
|
|
"--print-deps-only",
|
|
"--print-deps-to",
|
|
"foobar",
|
|
check=False,
|
|
)
|
|
assert result.returncode > 0
|
|
assert "cannot be used together" in result.stderr
|
|
|
|
|
|
@needs_py36789
|
|
def test_regular_run():
|
|
result = tox()
|
|
lines = sorted(result.stdout.splitlines()[:4])
|
|
assert "/.tox/py36 is the exec_prefix" in lines[0]
|
|
assert "/.tox/py37 is the exec_prefix" in lines[1]
|
|
assert "/.tox/py38 is the exec_prefix" in lines[2]
|
|
assert "/.tox/py39 is the exec_prefix" in lines[3]
|
|
assert "congratulations" in result.stdout
|
|
for y in 6, 7, 8, 9:
|
|
if TOX313 and y > 8:
|
|
# there is a bug in tox < 3.14,
|
|
# it creates venv with /usr/bin/python3 if the version is unknown
|
|
# See https://src.fedoraproject.org/rpms/python-tox/pull-request/15
|
|
continue
|
|
for pkg in "py", "six", "test":
|
|
sitelib = DOT_TOX / f"py3{y}/lib/python3.{y}/site-packages"
|
|
assert sitelib.is_dir()
|
|
assert len(list(sitelib.glob(f"{pkg}-*.dist-info"))) == 1
|
|
|
|
|
|
def test_regular_run_native_toxenv():
|
|
result = tox("-e", NATIVE_TOXENV)
|
|
lines = sorted(result.stdout.splitlines()[:1])
|
|
assert f"/.tox/{NATIVE_TOXENV} is the exec_prefix" in lines[0]
|
|
assert "congratulations" in result.stdout
|
|
for pkg in "py", "six", "test":
|
|
sitelib = (
|
|
DOT_TOX / f"{NATIVE_TOXENV}/lib/python3.{NATIVE_TOXENV[-1]}/site-packages"
|
|
)
|
|
assert sitelib.is_dir()
|
|
assert len(list(sitelib.glob(f"{pkg}-*.dist-info"))) == 1
|
|
|
|
|
|
def test_regular_after_current_is_supported():
|
|
result = tox("-e", NATIVE_TOXENV, "--current-env")
|
|
assert result.stdout.splitlines()[0] == NATIVE_EXEC_PREFIX_MSG
|
|
result = tox("-e", NATIVE_TOXENV)
|
|
assert f"/.tox/{NATIVE_TOXENV} is the exec_prefix" in result.stdout
|
|
assert "--recreate" not in result.stderr
|
|
|
|
|
|
def test_regular_after_killed_current_is_not_supported():
|
|
# fake broken tox run
|
|
shutil.rmtree(DOT_TOX, ignore_errors=True)
|
|
(DOT_TOX / NATIVE_TOXENV / "bin").mkdir(parents=True)
|
|
(DOT_TOX / NATIVE_TOXENV / "bin" / "python").symlink_to(NATIVE_EXECUTABLE)
|
|
|
|
result = tox("-e", NATIVE_TOXENV, check=False)
|
|
assert result.returncode > 0
|
|
assert "--recreate" in result.stderr
|
|
|
|
|
|
def test_regular_after_first_print_deps_is_supported(print_deps_stdout_arg):
|
|
result = tox("-e", NATIVE_TOXENV, print_deps_stdout_arg)
|
|
assert result.stdout.splitlines()[0] == "six"
|
|
result = tox("-e", NATIVE_TOXENV)
|
|
lines = sorted(result.stdout.splitlines()[:1])
|
|
assert "--recreate" not in result.stderr
|
|
assert f"/.tox/{NATIVE_TOXENV} is the exec_prefix" in lines[0]
|
|
|
|
# check that "test" was not installed to current environment
|
|
shutil.rmtree("./test.egg-info")
|
|
pip_freeze = subprocess.run(
|
|
(sys.executable, "-m", "pip", "freeze"),
|
|
encoding="utf-8",
|
|
stdout=subprocess.PIPE,
|
|
).stdout.splitlines()
|
|
# XXX when this fails, recreate your current environment
|
|
assert "test==0.0.0" not in pip_freeze
|
|
|
|
|
|
def test_regular_recreate_after_current():
|
|
result = tox("-e", NATIVE_TOXENV, "--current-env")
|
|
assert result.stdout.splitlines()[0] == NATIVE_EXEC_PREFIX_MSG
|
|
result = tox("-re", NATIVE_TOXENV)
|
|
assert f"/.tox/{NATIVE_TOXENV} is the exec_prefix" in result.stdout
|
|
assert "not supported" not in result.stderr
|
|
assert "--recreate" not in result.stderr
|
|
|
|
|
|
def test_current_after_regular_is_not_supported():
|
|
result = tox("-e", NATIVE_TOXENV)
|
|
assert f"/.tox/{NATIVE_TOXENV} is the exec_prefix" in result.stdout
|
|
result = tox("-e", NATIVE_TOXENV, "--current-env", check=False)
|
|
assert result.returncode > 0
|
|
assert "not supported" in result.stderr
|
|
|
|
|
|
def test_current_recreate_after_regular():
|
|
result = tox("-e", NATIVE_TOXENV)
|
|
assert f"/.tox/{NATIVE_TOXENV} is the exec_prefix" in result.stdout
|
|
result = tox("-re", NATIVE_TOXENV, "--current-env")
|
|
assert result.stdout.splitlines()[0] == NATIVE_EXEC_PREFIX_MSG
|
|
|
|
|
|
def test_current_after_print_deps(print_deps_stdout_arg):
|
|
# this is quite fast, so we can do it several times
|
|
for _ in range(3):
|
|
result = tox("-e", NATIVE_TOXENV, print_deps_stdout_arg)
|
|
assert "bin/python" not in result.stdout
|
|
assert "six" in result.stdout
|
|
result = tox("-re", NATIVE_TOXENV, "--current-env")
|
|
assert result.stdout.splitlines()[0] == NATIVE_EXEC_PREFIX_MSG
|
|
|
|
|
|
def test_current_after_print_extras(print_extras_stdout_arg):
|
|
# this is quite fast, so we can do it several times
|
|
for _ in range(3):
|
|
result = tox("-e", NATIVE_TOXENV, print_extras_stdout_arg)
|
|
assert "bin/python" not in result.stdout
|
|
assert "full" in result.stdout
|
|
result = tox("-re", NATIVE_TOXENV, "--current-env")
|
|
assert result.stdout.splitlines()[0] == NATIVE_EXEC_PREFIX_MSG
|
|
|
|
|
|
def test_regular_recreate_after_print_deps(print_deps_stdout_arg):
|
|
result = tox("-e", NATIVE_TOXENV, print_deps_stdout_arg)
|
|
assert "bin/python" not in result.stdout
|
|
assert "six" in result.stdout
|
|
|
|
result = tox("-re", NATIVE_TOXENV)
|
|
assert result.stdout.splitlines()[0] != NATIVE_EXEC_PREFIX_MSG
|
|
sitelib = DOT_TOX / f"{NATIVE_TOXENV}/lib/python3.{NATIVE_TOXENV[-1]}/site-packages"
|
|
assert sitelib.is_dir()
|
|
assert len(list(sitelib.glob("test-*.dist-info"))) == 1
|
|
|
|
result = tox("-e", NATIVE_TOXENV, print_deps_stdout_arg)
|
|
assert "bin/python" not in result.stdout
|
|
assert "six" in result.stdout
|
|
|
|
|
|
def test_print_deps_without_python_command(tmp_path, print_deps_stdout_arg):
|
|
bin = tmp_path / "bin"
|
|
bin.mkdir()
|
|
tox_link = bin / "tox"
|
|
tox_path = shutil.which("tox")
|
|
tox_link.symlink_to(tox_path)
|
|
env = {**os.environ, "PATH": str(bin)}
|
|
|
|
result = tox("-e", NATIVE_TOXENV, print_deps_stdout_arg, env=env)
|
|
expected = textwrap.dedent(
|
|
f"""
|
|
six
|
|
py
|
|
___________________________________ summary ____________________________________
|
|
{NATIVE_TOXENV}: commands succeeded
|
|
congratulations :)
|
|
"""
|
|
).lstrip()
|
|
assert result.stdout == expected
|
|
|
|
|
|
@pytest.mark.parametrize("flag", [None, "--print-deps-to=-", "--current-env"])
|
|
def test_noquiet_installed_packages(flag):
|
|
flags = (flag,) if flag else ()
|
|
result = tox("-e", NATIVE_TOXENV, *flags, quiet=False, check=False)
|
|
assert f"\n{NATIVE_TOXENV} installed: " in result.stdout
|
|
for line in result.stdout.splitlines():
|
|
if line.startswith(f"{NATIVE_TOXENV} installed: "):
|
|
packages = line.rpartition(" installed: ")[-1].split(",")
|
|
break
|
|
|
|
# default tox produces output sorted by package names
|
|
assert packages == sorted(
|
|
packages, key=lambda p: p.partition("==")[0].partition(" @ ")[0].lower()
|
|
)
|
|
|
|
# without a flag, the output must match tox defaults
|
|
if not flag:
|
|
assert len(packages) == 3
|
|
assert packages[0].startswith("py==")
|
|
assert packages[1].startswith("six==")
|
|
assert packages[2].startswith(("test==", "test @ ")) # old and new pip
|
|
|
|
# with our flags, uses the absolutely current environment by default, hence has tox
|
|
else:
|
|
assert len([p for p in packages if p.startswith("tox==")]) == 1
|
|
assert all(re.match(r"\S+==\S+", p) for p in packages)
|