1
0
mirror of https://github.com/fedora-python/tox-current-env.git synced 2025-01-11 08:56:14 +01:00

Improved tests and testing with tox 4

This commit is contained in:
Lumir Balhar 2022-02-10 10:58:59 +01:00 committed by Miro Hrončok
parent b9030ccc0f
commit b0923ba7ea
8 changed files with 412 additions and 684 deletions

View File

@ -37,6 +37,9 @@ jobs:
- py310-toxrelease - py310-toxrelease
- py310-toxmaster - py310-toxmaster
- py310-tox315 - py310-tox315
- py37-tox4
- py38-tox4
- py39-tox4
- py310-tox4
# Use GitHub's Linux Docker host # Use GitHub's Linux Docker host
runs-on: ubuntu-latest runs-on: ubuntu-latest

View File

@ -157,6 +157,17 @@ To get a list of names of extras, run:
Caveats, warnings and limitations Caveats, warnings and limitations
--------------------------------- ---------------------------------
tox 4
~~~~~
The plugin is available also for tox 4. Differences in behavior between tox 3 and 4 are these:
- ``--recreate`` is no longer needed when you switch from the plugin back to standard tox. Tox
detects it and handles the recreation automatically.
- The plugin does not check the requested Python version nor the environment name. If you let
it run for multiple environments they'll all use the same Python.
- Deprecated ``--print-deps-only`` option is no longer available.
Use an isolated environment Use an isolated environment
~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -19,7 +19,7 @@ setup(
packages=find_packages("src"), packages=find_packages("src"),
entry_points={"tox": ["current-env = tox_current_env.hooks"]}, entry_points={"tox": ["current-env = tox_current_env.hooks"]},
install_requires=[ install_requires=[
"tox>=3.15,<4", "tox>=3.15",
"importlib_metadata; python_version < '3.8'" "importlib_metadata; python_version < '3.8'"
], ],
extras_require={ extras_require={

36
tests/conftest.py Normal file
View File

@ -0,0 +1,36 @@
import shutil
import pytest
from utils import FIXTURES_DIR, TOX4
@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)
return pwd
if TOX4:
available_options = ("--print-deps-to-file=-", "--print-deps-to=-")
else:
available_options = (
"--print-deps-only",
"--print-deps-to-file=-",
"--print-deps-to=-",
)
@pytest.fixture(params=available_options)
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

View File

@ -1,120 +1,36 @@
import functools
import os import os
import pathlib
import re import re
import shutil import shutil
import subprocess import subprocess
import sys import sys
import textwrap import textwrap
import warnings
import configparser
import contextlib
from packaging.version import parse as ver
import pytest import pytest
from packaging.version import parse as ver
from utils import (
NATIVE_TOXENV = f"py{sys.version_info[0]}{sys.version_info[1]}" DOT_TOX,
NATIVE_SITE_PACKAGES = f"lib/python{sys.version_info[0]}.{sys.version_info[1]}/site-packages" NATIVE_EXEC_PREFIX_MSG,
NATIVE_EXECUTABLE = str(pathlib.Path(sys.executable).resolve()) NATIVE_EXECUTABLE,
FIXTURES_DIR = pathlib.Path(__file__).parent / "fixtures" NATIVE_SITE_PACKAGES,
DOT_TOX = pathlib.Path("./.tox") NATIVE_TOXENV,
TOX_VERSION,
envs_from_tox_ini,
def _exec_prefix(executable): is_available,
"""Returns sys.exec_prefix for the given executable""" modify_config,
cmd = (executable, "-c", "import sys; print(sys.exec_prefix)") needs_all_pythons,
return subprocess.check_output(cmd, encoding="utf-8").strip() tox,
tox_footer,
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)
return 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
@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)
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
TOX_VERSION = ver(tox("--version").stdout.split(" ")[0])
@functools.lru_cache(maxsize=8)
def is_available(python):
try:
subprocess.run((python, "--version"))
except FileNotFoundError:
return False
return True
needs_py3678910 = pytest.mark.skipif(
not all((is_available(f"python3.{x}") for x in range(6, 12))),
reason="This test needs python3.6, 3.7, 3.8, 3.9 and 3.10 available in $PATH",
) )
def test_native_toxenv_current_env(): def test_native_toxenv_current_env():
result = tox("-e", NATIVE_TOXENV, "--current-env") result = tox("-e", NATIVE_TOXENV, "--current-env")
assert result.stdout.splitlines()[0] == NATIVE_EXEC_PREFIX_MSG assert result.stdout.splitlines()[0] == NATIVE_EXEC_PREFIX_MSG
assert not (DOT_TOX / NATIVE_TOXENV / "lib").is_dir() assert not (DOT_TOX / NATIVE_TOXENV / "lib").is_dir()
@needs_py3678910 @needs_all_pythons
def test_all_toxenv_current_env(): def test_all_toxenv_current_env():
result = tox("--current-env", check=False) result = tox("--current-env", check=False)
assert NATIVE_EXEC_PREFIX_MSG in result.stdout.splitlines() assert NATIVE_EXEC_PREFIX_MSG in result.stdout.splitlines()
@ -133,7 +49,7 @@ def test_missing_toxenv_current_env(python):
assert result.returncode > 0 assert result.returncode > 0
@needs_py3678910 @needs_all_pythons
def test_all_toxenv_current_env_skip_missing(): def test_all_toxenv_current_env_skip_missing():
result = tox("--current-env", "--skip-missing-interpreters", check=False) result = tox("--current-env", "--skip-missing-interpreters", check=False)
assert "InterpreterMismatch:" in result.stdout assert "InterpreterMismatch:" in result.stdout
@ -141,22 +57,20 @@ def test_all_toxenv_current_env_skip_missing():
assert result.returncode == 0 assert result.returncode == 0
@pytest.mark.parametrize("toxenv", ["py36", "py37", "py38", "py39"]) @pytest.mark.parametrize("toxenv", envs_from_tox_ini())
def test_print_deps(toxenv, print_deps_stdout_arg): def test_print_deps(toxenv, print_deps_stdout_arg):
result = tox("-e", toxenv, print_deps_stdout_arg) result = tox("-e", toxenv, print_deps_stdout_arg)
expected = textwrap.dedent( expected = textwrap.dedent(
f""" f"""
six six
py py
___________________________________ summary ____________________________________ {tox_footer(toxenv)}
{toxenv}: commands succeeded
congratulations :)
""" """
).lstrip() ).lstrip()
assert result.stdout == expected assert result.stdout == expected
@pytest.mark.parametrize("toxenv", ["py36", "py37", "py38", "py39"]) @pytest.mark.parametrize("toxenv", envs_from_tox_ini())
@pytest.mark.parametrize("pre_post", ["pre", "post", "both"]) @pytest.mark.parametrize("pre_post", ["pre", "post", "both"])
def test_print_deps_with_commands_pre_post(projdir, toxenv, pre_post, print_deps_stdout_arg): def test_print_deps_with_commands_pre_post(projdir, toxenv, pre_post, print_deps_stdout_arg):
with modify_config(projdir / 'tox.ini') as config: with modify_config(projdir / 'tox.ini') as config:
@ -170,18 +84,16 @@ def test_print_deps_with_commands_pre_post(projdir, toxenv, pre_post, print_deps
f""" f"""
six six
py py
___________________________________ summary ____________________________________ {tox_footer(toxenv)}
{toxenv}: commands succeeded
congratulations :)
""" """
).lstrip() ).lstrip()
assert result.stdout == expected assert result.stdout == expected
assert result.stderr == "" assert result.stderr == ""
@pytest.mark.parametrize("toxenv", ["py36", "py37", "py38", "py39"]) @pytest.mark.parametrize("toxenv", envs_from_tox_ini())
def test_print_deps_with_tox_minversion(projdir, toxenv, print_deps_stdout_arg): def test_print_deps_with_tox_minversion(projdir, toxenv, print_deps_stdout_arg):
with modify_config(projdir / 'tox.ini') as config: with modify_config(projdir / "tox.ini") as config:
config["tox"]["minversion"] = "3.13" config["tox"]["minversion"] = "3.13"
result = tox("-e", toxenv, print_deps_stdout_arg) result = tox("-e", toxenv, print_deps_stdout_arg)
expected = textwrap.dedent( expected = textwrap.dedent(
@ -189,18 +101,16 @@ def test_print_deps_with_tox_minversion(projdir, toxenv, print_deps_stdout_arg):
tox >= 3.13 tox >= 3.13
six six
py py
___________________________________ summary ____________________________________ {tox_footer(toxenv)}
{toxenv}: commands succeeded
congratulations :)
""" """
).lstrip() ).lstrip()
assert result.stdout == expected assert result.stdout == expected
@pytest.mark.xfail(TOX_VERSION < ver("3.22"), reason="No support in old tox") @pytest.mark.xfail(TOX_VERSION < ver("3.22"), reason="No support in old tox")
@pytest.mark.parametrize("toxenv", ["py36", "py37", "py38", "py39"]) @pytest.mark.parametrize("toxenv", envs_from_tox_ini())
def test_print_deps_with_tox_requires(projdir, toxenv, print_deps_stdout_arg): def test_print_deps_with_tox_requires(projdir, toxenv, print_deps_stdout_arg):
with modify_config(projdir / 'tox.ini') as config: with modify_config(projdir / "tox.ini") as config:
config["tox"]["requires"] = "\n setuptools > 30\n pluggy" config["tox"]["requires"] = "\n setuptools > 30\n pluggy"
result = tox("-e", toxenv, print_deps_stdout_arg) result = tox("-e", toxenv, print_deps_stdout_arg)
expected = textwrap.dedent( expected = textwrap.dedent(
@ -209,18 +119,18 @@ def test_print_deps_with_tox_requires(projdir, toxenv, print_deps_stdout_arg):
pluggy pluggy
six six
py py
___________________________________ summary ____________________________________ {tox_footer(toxenv)}
{toxenv}: commands succeeded
congratulations :)
""" """
).lstrip() ).lstrip()
assert result.stdout == expected assert result.stdout == expected
@pytest.mark.xfail(TOX_VERSION < ver("3.22"), reason="No support in old tox") @pytest.mark.xfail(TOX_VERSION < ver("3.22"), reason="No support in old tox")
@pytest.mark.parametrize("toxenv", ["py36", "py37", "py38", "py39"]) @pytest.mark.parametrize("toxenv", envs_from_tox_ini())
def test_print_deps_with_tox_minversion_and_requires(projdir, toxenv, print_deps_stdout_arg): def test_print_deps_with_tox_minversion_and_requires(
with modify_config(projdir / 'tox.ini') as config: projdir, toxenv, print_deps_stdout_arg
):
with modify_config(projdir / "tox.ini") as config:
config["tox"]["minversion"] = "3.13" config["tox"]["minversion"] = "3.13"
config["tox"]["requires"] = "\n setuptools > 30\n pluggy" config["tox"]["requires"] = "\n setuptools > 30\n pluggy"
result = tox("-e", toxenv, print_deps_stdout_arg) result = tox("-e", toxenv, print_deps_stdout_arg)
@ -231,30 +141,26 @@ def test_print_deps_with_tox_minversion_and_requires(projdir, toxenv, print_deps
pluggy pluggy
six six
py py
___________________________________ summary ____________________________________ {tox_footer(toxenv)}
{toxenv}: commands succeeded
congratulations :)
""" """
).lstrip() ).lstrip()
assert result.stdout == expected assert result.stdout == expected
@pytest.mark.parametrize("toxenv", ["py36", "py37", "py38", "py39"]) @pytest.mark.parametrize("toxenv", envs_from_tox_ini())
def test_print_extras(toxenv, print_extras_stdout_arg): def test_print_extras(toxenv, print_extras_stdout_arg):
result = tox("-e", toxenv, print_extras_stdout_arg) result = tox("-e", toxenv, print_extras_stdout_arg)
expected = textwrap.dedent( expected = textwrap.dedent(
f""" f"""
dev dev
full full
___________________________________ summary ____________________________________ {tox_footer(toxenv)}
{toxenv}: commands succeeded
congratulations :)
""" """
).lstrip() ).lstrip()
assert result.stdout == expected assert result.stdout == expected
@pytest.mark.parametrize("toxenv", ["py36", "py37", "py38", "py39"]) @pytest.mark.parametrize("toxenv", envs_from_tox_ini())
@pytest.mark.parametrize("pre_post", ["pre", "post", "both"]) @pytest.mark.parametrize("pre_post", ["pre", "post", "both"])
def test_print_extras_with_commands_pre_post(projdir, toxenv, pre_post, print_extras_stdout_arg): def test_print_extras_with_commands_pre_post(projdir, toxenv, pre_post, print_extras_stdout_arg):
with modify_config(projdir / 'tox.ini') as config: with modify_config(projdir / 'tox.ini') as config:
@ -268,20 +174,20 @@ def test_print_extras_with_commands_pre_post(projdir, toxenv, pre_post, print_ex
f""" f"""
dev dev
full full
___________________________________ summary ____________________________________ {tox_footer(toxenv)}
{toxenv}: commands succeeded
congratulations :)
""" """
).lstrip() ).lstrip()
assert result.stdout == expected assert result.stdout == expected
assert result.stderr == "" assert result.stderr == ""
@pytest.mark.parametrize("toxenv", ["py36", "py37", "py38", "py39"]) @pytest.mark.parametrize("toxenv", envs_from_tox_ini())
def test_print_deps_only_deprecated(toxenv): def test_print_deps_only_deprecated(toxenv):
result = tox( result = tox(
"-e", toxenv, '--print-deps-only', "-e",
env={**os.environ, 'PYTHONWARNINGS': 'always'}, toxenv,
"--print-deps-only",
env={**os.environ, "PYTHONWARNINGS": "always"},
) )
waring_text = ( waring_text = (
"DeprecationWarning: --print-deps-only is deprecated; " "DeprecationWarning: --print-deps-only is deprecated; "
@ -292,119 +198,69 @@ def test_print_deps_only_deprecated(toxenv):
def test_allenvs_print_deps(print_deps_stdout_arg): def test_allenvs_print_deps(print_deps_stdout_arg):
result = tox(print_deps_stdout_arg) result = tox(print_deps_stdout_arg)
expected = textwrap.dedent( expected = ""
""" for env in envs_from_tox_ini():
six expected += "six\npy\n"
py expected += tox_footer(spaces=0) + "\n"
six
py
six
py
six
py
six
py
___________________________________ summary ____________________________________
py36: commands succeeded
py37: commands succeeded
py38: commands succeeded
py39: commands succeeded
py310: commands succeeded
congratulations :)
"""
).lstrip()
assert result.stdout == expected assert result.stdout == expected
def test_allenvs_print_extras(print_extras_stdout_arg): def test_allenvs_print_extras(print_extras_stdout_arg):
result = tox(print_extras_stdout_arg) result = tox(print_extras_stdout_arg)
expected = textwrap.dedent( expected = ""
""" for env in envs_from_tox_ini():
dev expected += "dev\nfull\n"
full expected += tox_footer(spaces=0) + "\n"
dev
full
dev
full
dev
full
dev
full
___________________________________ summary ____________________________________
py36: commands succeeded
py37: commands succeeded
py38: commands succeeded
py39: commands succeeded
py310: commands succeeded
congratulations :)
"""
).lstrip()
assert result.stdout == expected assert result.stdout == expected
@pytest.mark.parametrize("toxenv", ["py36", "py37", "py38", "py39", "py310"]) @pytest.mark.parametrize("toxenv", envs_from_tox_ini())
def test_print_deps_to_file(toxenv, tmp_path): def test_print_deps_to_file(toxenv, tmp_path):
depspath = tmp_path / "deps" depspath = tmp_path / "deps"
result = tox("-e", toxenv, "--print-deps-to", str(depspath)) result = tox("-e", toxenv, "--print-deps-to", str(depspath))
assert depspath.read_text().splitlines() == ["six", "py"] assert depspath.read_text().splitlines() == ["six", "py"]
expected = textwrap.dedent( expected = textwrap.dedent(
f""" f"""
___________________________________ summary ____________________________________ {tox_footer(toxenv)}
{toxenv}: commands succeeded
congratulations :)
""" """
).lstrip() ).lstrip()
assert result.stdout == expected assert result.stdout == expected
@pytest.mark.parametrize("toxenv", ["py36", "py37", "py38", "py39", "py310"]) @pytest.mark.parametrize("toxenv", envs_from_tox_ini())
def test_print_extras_to_file(toxenv, tmp_path): def test_print_extras_to_file(toxenv, tmp_path):
extraspath = tmp_path / "extras" extraspath = tmp_path / "extras"
result = tox("-e", toxenv, "--print-extras-to", str(extraspath)) result = tox("-e", toxenv, "--print-extras-to", str(extraspath))
assert extraspath.read_text().splitlines() == ["dev", "full"] assert extraspath.read_text().splitlines() == ["dev", "full"]
expected = textwrap.dedent( expected = textwrap.dedent(
f""" f"""
___________________________________ summary ____________________________________ {tox_footer(toxenv)}
{toxenv}: commands succeeded
congratulations :)
""" """
).lstrip() ).lstrip()
assert result.stdout == expected assert result.stdout == expected
@pytest.mark.parametrize('option', ('--print-deps-to', '--print-deps-to-file')) @pytest.mark.parametrize("option", ("--print-deps-to", "--print-deps-to-file"))
def test_allenvs_print_deps_to_file(tmp_path, option): def test_allenvs_print_deps_to_file(tmp_path, option):
depspath = tmp_path / "deps" depspath = tmp_path / "deps"
result = tox(option, str(depspath)) result = tox(option, str(depspath))
assert depspath.read_text().splitlines() == ["six", "py"] * 5 assert depspath.read_text().splitlines() == ["six", "py"] * 5
expected = textwrap.dedent( expected = textwrap.dedent(
""" f"""
___________________________________ summary ____________________________________ {tox_footer()}
py36: commands succeeded
py37: commands succeeded
py38: commands succeeded
py39: commands succeeded
py310: commands succeeded
congratulations :)
""" """
).lstrip() ).lstrip()
assert result.stdout == expected assert result.stdout == expected
@pytest.mark.parametrize('option', ('--print-extras-to', '--print-extras-to-file')) @pytest.mark.parametrize("option", ("--print-extras-to", "--print-extras-to-file"))
def test_allenvs_print_extras_to_file(tmp_path, option): def test_allenvs_print_extras_to_file(tmp_path, option):
extraspath = tmp_path / "extras" extraspath = tmp_path / "extras"
result = tox(option, str(extraspath)) result = tox(option, str(extraspath))
assert extraspath.read_text().splitlines() == ["dev", "full"] * 5 assert extraspath.read_text().splitlines() == ["dev", "full"] * 5
expected = textwrap.dedent( expected = textwrap.dedent(
""" f"""
___________________________________ summary ____________________________________ {tox_footer()}
py36: commands succeeded
py37: commands succeeded
py38: commands succeeded
py39: commands succeeded
py310: commands succeeded
congratulations :)
""" """
).lstrip() ).lstrip()
assert result.stdout == expected assert result.stdout == expected
@ -413,7 +269,7 @@ def test_allenvs_print_extras_to_file(tmp_path, option):
def test_allenvs_print_deps_to_existing_file(tmp_path): def test_allenvs_print_deps_to_existing_file(tmp_path):
depspath = tmp_path / "deps" depspath = tmp_path / "deps"
depspath.write_text("nada") depspath.write_text("nada")
result = tox("--print-deps-to", str(depspath)) _ = tox("--print-deps-to", str(depspath))
lines = depspath.read_text().splitlines() lines = depspath.read_text().splitlines()
assert "nada" not in lines assert "nada" not in lines
assert "six" in lines assert "six" in lines
@ -423,7 +279,7 @@ def test_allenvs_print_deps_to_existing_file(tmp_path):
def test_allenvs_print_extras_to_existing_file(tmp_path): def test_allenvs_print_extras_to_existing_file(tmp_path):
extraspath = tmp_path / "extras" extraspath = tmp_path / "extras"
extraspath.write_text("nada") extraspath.write_text("nada")
result = tox("--print-extras-to", str(extraspath)) _ = tox("--print-extras-to", str(extraspath))
lines = extraspath.read_text().splitlines() lines = extraspath.read_text().splitlines()
assert "nada" not in lines assert "nada" not in lines
assert "dev" in lines assert "dev" in lines
@ -432,14 +288,15 @@ def test_allenvs_print_extras_to_existing_file(tmp_path):
@pytest.mark.parametrize("deps_stdout", [True, False]) @pytest.mark.parametrize("deps_stdout", [True, False])
@pytest.mark.parametrize("extras_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): def test_allenvs_print_deps_to_file_print_extras_to_other_file(
tmp_path, deps_stdout, extras_stdout
):
if deps_stdout and extras_stdout: if deps_stdout and extras_stdout:
pytest.xfail("Unsupported combination of parameters") pytest.xfail("Unsupported combination of parameters")
depspath = "-" if deps_stdout else tmp_path / "deps" depspath = "-" if deps_stdout else tmp_path / "deps"
extraspath = "-" if extras_stdout else tmp_path / "extras" extraspath = "-" if extras_stdout else tmp_path / "extras"
result = tox("--print-deps-to", str(depspath), result = tox("--print-deps-to", str(depspath), "--print-extras-to", str(extraspath))
"--print-extras-to", str(extraspath))
if deps_stdout: if deps_stdout:
depslines = result.stdout.splitlines() depslines = result.stdout.splitlines()
extraslines = extraspath.read_text().splitlines() extraslines = extraspath.read_text().splitlines()
@ -466,8 +323,10 @@ def test_print_deps_extras_to_same_file_is_not_possible(tmp_path):
result = tox( result = tox(
"-e", "-e",
NATIVE_TOXENV, NATIVE_TOXENV,
"--print-deps-to", str(depsextraspath), "--print-deps-to",
"--print-extras-to", str(depsextraspath), str(depsextraspath),
"--print-extras-to",
str(depsextraspath),
check=False, check=False,
) )
assert result.returncode > 0 assert result.returncode > 0
@ -477,7 +336,8 @@ def test_print_deps_extras_to_same_file_is_not_possible(tmp_path):
def test_print_deps_extras_to_stdout_is_not_possible( def test_print_deps_extras_to_stdout_is_not_possible(
tmp_path, tmp_path,
print_deps_stdout_arg, print_deps_stdout_arg,
print_extras_stdout_arg,): print_extras_stdout_arg,
):
result = tox( result = tox(
"-e", "-e",
NATIVE_TOXENV, NATIVE_TOXENV,
@ -502,19 +362,17 @@ def test_print_deps_only_print_deps_to_file_are_mutually_exclusive():
assert "cannot be used together" in result.stderr assert "cannot be used together" in result.stderr
@needs_py3678910 @needs_all_pythons
def test_regular_run(): def test_regular_run():
result = tox() result = tox()
lines = result.stdout.splitlines()[:5] lines = result.stdout.splitlines()[:5]
assert "/.tox/py36 is the exec_prefix" in lines[0] for line, env in zip(lines, envs_from_tox_ini()):
assert "/.tox/py37 is the exec_prefix" in lines[1] assert f"/.tox/{env} is the exec_prefix" in line
assert "/.tox/py38 is the exec_prefix" in lines[2]
assert "/.tox/py39 is the exec_prefix" in lines[3]
assert "/.tox/py310 is the exec_prefix" in lines[4]
assert "congratulations" in result.stdout assert "congratulations" in result.stdout
for y in 6, 7, 8, 9, 10: for env in envs_from_tox_ini():
major, minor = re.match(r"py(\d)(\d+)", env).groups()
for pkg in "py", "six", "test": for pkg in "py", "six", "test":
sitelib = DOT_TOX / f"py3{y}/lib/python3.{y}/site-packages" sitelib = DOT_TOX / f"{env}/lib/python{major}.{minor}/site-packages"
assert sitelib.is_dir() assert sitelib.is_dir()
assert len(list(sitelib.glob(f"{pkg}-*.dist-info"))) == 1 assert len(list(sitelib.glob(f"{pkg}-*.dist-info"))) == 1
@ -525,9 +383,7 @@ def test_regular_run_native_toxenv():
assert f"/.tox/{NATIVE_TOXENV} is the exec_prefix" in lines[0] assert f"/.tox/{NATIVE_TOXENV} is the exec_prefix" in lines[0]
assert "congratulations" in result.stdout assert "congratulations" in result.stdout
for pkg in "py", "six", "test": for pkg in "py", "six", "test":
sitelib = ( sitelib = DOT_TOX / f"{NATIVE_TOXENV}/{NATIVE_SITE_PACKAGES}"
DOT_TOX / f"{NATIVE_TOXENV}/{NATIVE_SITE_PACKAGES}"
)
assert sitelib.is_dir() assert sitelib.is_dir()
assert len(list(sitelib.glob(f"{pkg}-*.dist-info"))) == 1 assert len(list(sitelib.glob(f"{pkg}-*.dist-info"))) == 1
@ -643,9 +499,7 @@ def test_print_deps_without_python_command(tmp_path, print_deps_stdout_arg):
f""" f"""
six six
py py
___________________________________ summary ____________________________________ {tox_footer(NATIVE_TOXENV)}
{NATIVE_TOXENV}: commands succeeded
congratulations :)
""" """
).lstrip() ).lstrip()
assert result.stdout == expected assert result.stdout == expected
@ -679,14 +533,16 @@ def test_noquiet_installed_packages(flag):
assert all(re.match(r"\S+==\S+", p) for p in packages) assert all(re.match(r"\S+==\S+", p) for p in packages)
@pytest.mark.parametrize("flag", ["--print-deps-to=-", "--print-extras-to=-", "--current-env"]) @pytest.mark.parametrize(
"flag", ["--print-deps-to=-", "--print-extras-to=-", "--current-env"]
)
@pytest.mark.parametrize("usedevelop", [True, False]) @pytest.mark.parametrize("usedevelop", [True, False])
def test_self_is_not_installed(projdir, flag, usedevelop): def test_self_is_not_installed(projdir, flag, usedevelop):
with modify_config(projdir / 'tox.ini') as config: with modify_config(projdir / "tox.ini") as config:
config['testenv']['usedevelop'] = str(usedevelop) config["testenv"]["usedevelop"] = str(usedevelop)
result = tox("-e", NATIVE_TOXENV, flag, quiet=False) result = tox("-e", NATIVE_TOXENV, flag, quiet=False)
assert 'test==0.0.0' not in result.stdout assert "test==0.0.0" not in result.stdout
assert 'test @ file://' not in result.stdout assert "test @ file://" not in result.stdout
@pytest.mark.parametrize("externals", [None, "allowlist_externals", "whitelist_externals"]) @pytest.mark.parametrize("externals", [None, "allowlist_externals", "whitelist_externals"])
@ -705,8 +561,7 @@ def test_externals(projdir, externals):
@pytest.mark.parametrize("usedevelop", [True, False]) @pytest.mark.parametrize("usedevelop", [True, False])
def test_self_is_installed_with_regular_tox(projdir, usedevelop): def test_self_is_installed_with_regular_tox(projdir, usedevelop):
with modify_config(projdir / 'tox.ini') as config: with modify_config(projdir / "tox.ini") as config:
config['testenv']['usedevelop'] = str(usedevelop) config["testenv"]["usedevelop"] = str(usedevelop)
result = tox("-e", NATIVE_TOXENV, quiet=False) result = tox("-e", NATIVE_TOXENV, quiet=False)
assert ('test==0.0.0' in result.stdout or assert "test==0.0.0" in result.stdout or "test @ file://" in result.stdout
'test @ file://' in result.stdout)

View File

@ -1,164 +1,49 @@
import functools
import os import os
import pathlib
import re import re
import shutil import shutil
import subprocess
import sys
import textwrap import textwrap
import warnings
import configparser
import contextlib
from packaging.version import parse as ver
import pytest import pytest
from packaging.version import parse as ver
from utils import (
NATIVE_TOXENV = f"py{sys.version_info[0]}{sys.version_info[1]}" DOT_TOX,
NATIVE_SITE_PACKAGES = f"lib/python{sys.version_info[0]}.{sys.version_info[1]}/site-packages" NATIVE_EXEC_PREFIX_MSG,
NATIVE_EXECUTABLE = str(pathlib.Path(sys.executable).resolve()) NATIVE_SITE_PACKAGES,
FIXTURES_DIR = pathlib.Path(__file__).parent / "fixtures" NATIVE_TOXENV,
DOT_TOX = pathlib.Path("./.tox") TOX_VERSION,
envs_from_tox_ini,
modify_config,
def _exec_prefix(executable): needs_all_pythons,
"""Returns sys.exec_prefix for the given executable""" prep_tox_output,
cmd = (executable, "-c", "import sys; print(sys.exec_prefix)") tox,
return subprocess.check_output(cmd, encoding="utf-8").strip() tox_footer,
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)
return 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
@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)
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
TOX_VERSION = ver(tox("--version").stdout.split(" ")[0])
@functools.lru_cache(maxsize=8)
def is_available(python):
try:
subprocess.run((python, "--version"))
except FileNotFoundError:
return False
return True
needs_py367891011 = pytest.mark.skipif(
not all((is_available(f"python3.{x}") for x in range(6, 12))),
reason="This test needs python3.6, 3.7, 3.8, 3.9, 3.10 and 3.11 available in $PATH",
) )
def test_native_toxenv_current_env(): def test_native_toxenv_current_env():
result = tox("-e", NATIVE_TOXENV, "--current-env") result = tox("-e", NATIVE_TOXENV, "--current-env")
assert result.stdout.splitlines()[0] == NATIVE_EXEC_PREFIX_MSG assert result.stdout.splitlines()[0] == NATIVE_EXEC_PREFIX_MSG
assert not (DOT_TOX / NATIVE_TOXENV / "lib").is_dir() assert not (DOT_TOX / NATIVE_TOXENV / "lib").is_dir()
@needs_py367891011 @pytest.mark.parametrize("toxenv", envs_from_tox_ini())
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_py367891011
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): def test_print_deps(toxenv, print_deps_stdout_arg):
result = tox("-e", toxenv, print_deps_stdout_arg) result = tox("-e", toxenv, print_deps_stdout_arg)
expected = textwrap.dedent( expected = textwrap.dedent(
f""" f"""
tox>={TOX_VERSION}
six six
py py
___________________________________ summary ____________________________________ {tox_footer(toxenv)}
{toxenv}: commands succeeded
congratulations :)
""" """
).lstrip() ).lstrip()
assert result.stdout == expected assert prep_tox_output(result.stdout) == expected
@pytest.mark.parametrize("toxenv", ["py36", "py37", "py38", "py39"]) @pytest.mark.parametrize("toxenv", envs_from_tox_ini())
def test_print_deps_with_tox_minversion(projdir, toxenv, print_deps_stdout_arg): def test_print_deps_with_tox_minversion(projdir, toxenv, print_deps_stdout_arg):
with modify_config(projdir / 'tox.ini') as config: with modify_config(projdir / "tox.ini") as config:
config["tox"]["minversion"] = "3.13" config["tox"]["minversion"] = "3.13"
result = tox("-e", toxenv, print_deps_stdout_arg) result = tox("-e", toxenv, print_deps_stdout_arg)
expected = textwrap.dedent( expected = textwrap.dedent(
@ -166,216 +51,142 @@ def test_print_deps_with_tox_minversion(projdir, toxenv, print_deps_stdout_arg):
tox>=3.13 tox>=3.13
six six
py py
___________________________________ summary ____________________________________ {tox_footer(toxenv)}
{toxenv}: commands succeeded
congratulations :)
""" """
).lstrip() ).lstrip()
assert result.stdout == expected assert prep_tox_output(result.stdout) == expected
@pytest.mark.xfail(TOX_VERSION < ver("3.22"), reason="No support in old tox") @pytest.mark.xfail(TOX_VERSION < ver("3.22"), reason="No support in old tox")
@pytest.mark.parametrize("toxenv", ["py36", "py37", "py38", "py39"]) @pytest.mark.parametrize("toxenv", envs_from_tox_ini())
def test_print_deps_with_tox_requires(projdir, toxenv, print_deps_stdout_arg): def test_print_deps_with_tox_requires(projdir, toxenv, print_deps_stdout_arg):
with modify_config(projdir / 'tox.ini') as config: with modify_config(projdir / "tox.ini") as config:
config["tox"]["requires"] = "\n setuptools > 30\n pluggy" config["tox"]["requires"] = "\n setuptools > 30\n pluggy"
result = tox("-e", toxenv, print_deps_stdout_arg) result = tox("-e", toxenv, print_deps_stdout_arg)
expected = textwrap.dedent( expected = textwrap.dedent(
f""" f"""
setuptools>30 setuptools>30
pluggy pluggy
tox>={TOX_VERSION}
six six
py py
___________________________________ summary ____________________________________ {tox_footer(toxenv)}
{toxenv}: commands succeeded
congratulations :)
""" """
).lstrip() ).lstrip()
assert result.stdout == expected assert prep_tox_output(result.stdout) == expected
@pytest.mark.xfail(TOX_VERSION < ver("3.22"), reason="No support in old tox") @pytest.mark.xfail(TOX_VERSION < ver("3.22"), reason="No support in old tox")
@pytest.mark.parametrize("toxenv", ["py36", "py37", "py38", "py39"]) @pytest.mark.parametrize("toxenv", envs_from_tox_ini())
def test_print_deps_with_tox_minversion_and_requires(projdir, toxenv, print_deps_stdout_arg): def test_print_deps_with_tox_minversion_and_requires(
with modify_config(projdir / 'tox.ini') as config: projdir, toxenv, print_deps_stdout_arg
):
with modify_config(projdir / "tox.ini") as config:
config["tox"]["minversion"] = "3.13" config["tox"]["minversion"] = "3.13"
config["tox"]["requires"] = "\n setuptools > 30\n pluggy" config["tox"]["requires"] = "\n setuptools > 30\n pluggy"
result = tox("-e", toxenv, print_deps_stdout_arg) result = tox("-e", toxenv, print_deps_stdout_arg)
expected = textwrap.dedent( expected = textwrap.dedent(
f""" f"""
tox >= 3.13
setuptools>30 setuptools>30
pluggy pluggy
tox>=3.13
six six
py py
___________________________________ summary ____________________________________ {tox_footer(toxenv)}
{toxenv}: commands succeeded
congratulations :)
""" """
).lstrip() ).lstrip()
assert result.stdout == expected assert prep_tox_output(result.stdout) == expected
@pytest.mark.parametrize("toxenv", ["py36", "py37", "py38", "py39"]) @pytest.mark.parametrize("toxenv", envs_from_tox_ini())
def test_print_extras(toxenv, print_extras_stdout_arg): def test_print_extras(toxenv, print_extras_stdout_arg):
result = tox("-e", toxenv, print_extras_stdout_arg) result = tox("-e", toxenv, print_extras_stdout_arg)
expected = textwrap.dedent( expected = textwrap.dedent(
f""" f"""
dev dev
full full
___________________________________ summary ____________________________________ {tox_footer(toxenv)}
{toxenv}: commands succeeded
congratulations :)
""" """
).lstrip() ).lstrip()
assert result.stdout == expected assert sorted(prep_tox_output(result.stdout).splitlines()) == sorted(
expected.splitlines()
@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): def test_allenvs_print_deps(print_deps_stdout_arg):
result = tox(print_deps_stdout_arg) result = tox(print_deps_stdout_arg)
expected = textwrap.dedent( expected = []
""" for env in envs_from_tox_ini():
six expected.extend((f"tox>={TOX_VERSION}", "six", "py", f"{env}: OK"))
py expected.pop() # The last "py310: OK" is not there
six expected.append(tox_footer(spaces=0))
py expected = ("\n".join(expected)).splitlines()
six assert sorted(prep_tox_output(result.stdout).splitlines()) == sorted(expected)
py
six
py
six
py
six
py
___________________________________ summary ____________________________________
py36: commands succeeded
py37: commands succeeded
py38: commands succeeded
py39: commands succeeded
py310: commands succeeded
py311: commands succeeded
congratulations :)
"""
).lstrip()
assert result.stdout == expected
def test_allenvs_print_extras(print_extras_stdout_arg): def test_allenvs_print_extras(print_extras_stdout_arg):
result = tox(print_extras_stdout_arg) result = tox(print_extras_stdout_arg)
expected = textwrap.dedent( expected = []
""" for env in envs_from_tox_ini():
dev expected.extend(("dev", "full", f"{env}: OK"))
full expected.pop() # The last "py310: OK" is not there
dev expected.append(tox_footer(spaces=0))
full expected = ("\n".join(expected)).splitlines()
dev assert sorted(prep_tox_output(result.stdout).splitlines()) == sorted(expected)
full
dev
full
dev
full
dev
full
___________________________________ summary ____________________________________
py36: commands succeeded
py37: commands succeeded
py38: commands succeeded
py39: commands succeeded
py310: commands succeeded
py311: commands succeeded
congratulations :)
"""
).lstrip()
assert result.stdout == expected
@pytest.mark.parametrize("toxenv", ["py36", "py37", "py38", "py39", "py310", "py311"]) @pytest.mark.parametrize("toxenv", envs_from_tox_ini())
def test_print_deps_to_file(toxenv, tmp_path): def test_print_deps_to_file(toxenv, tmp_path):
depspath = tmp_path / "deps" depspath = tmp_path / "deps"
result = tox("-e", toxenv, "--print-deps-to", str(depspath)) result = tox("-e", toxenv, "--print-deps-to", str(depspath))
assert depspath.read_text().splitlines() == ["six", "py"] assert sorted(depspath.read_text().splitlines()) == sorted(
expected = textwrap.dedent( [f"tox>={TOX_VERSION}", "six", "py"]
f""" )
___________________________________ summary ____________________________________ expected = tox_footer(toxenv, spaces=0) + "\n"
{toxenv}: commands succeeded assert prep_tox_output(result.stdout) == expected
congratulations :)
"""
).lstrip()
assert result.stdout == expected
@pytest.mark.parametrize("toxenv", ["py36", "py37", "py38", "py39", "py310", "py311"]) @pytest.mark.parametrize("toxenv", envs_from_tox_ini())
def test_print_extras_to_file(toxenv, tmp_path): def test_print_extras_to_file(toxenv, tmp_path):
extraspath = tmp_path / "extras" extraspath = tmp_path / "extras"
result = tox("-e", toxenv, "--print-extras-to", str(extraspath)) result = tox("-e", toxenv, "--print-extras-to", str(extraspath))
assert extraspath.read_text().splitlines() == ["dev", "full"] assert sorted(extraspath.read_text().splitlines()) == sorted(["dev", "full"])
expected = textwrap.dedent( expected = tox_footer(toxenv, spaces=0) + "\n"
f""" assert prep_tox_output(result.stdout) == expected
___________________________________ summary ____________________________________
{toxenv}: commands succeeded
congratulations :)
"""
).lstrip()
assert result.stdout == expected
@pytest.mark.parametrize('option', ('--print-deps-to', '--print-deps-to-file')) @pytest.mark.parametrize("option", ("--print-deps-to", "--print-deps-to-file"))
def test_allenvs_print_deps_to_file(tmp_path, option): def test_allenvs_print_deps_to_file(tmp_path, option):
depspath = tmp_path / "deps" depspath = tmp_path / "deps"
result = tox(option, str(depspath)) result = tox(option, str(depspath))
assert depspath.read_text().splitlines() == ["six", "py"] * 6 assert sorted(depspath.read_text().splitlines()) == sorted(
expected = textwrap.dedent( [f"tox>={TOX_VERSION}", "six", "py"] * len(envs_from_tox_ini())
""" )
___________________________________ summary ____________________________________ expected = ""
py36: commands succeeded for env in envs_from_tox_ini()[:-1]:
py37: commands succeeded expected += f"{env}: OK\n"
py38: commands succeeded expected += tox_footer(spaces=0) + "\n"
py39: commands succeeded assert prep_tox_output(result.stdout) == expected
py310: commands succeeded
py311: commands succeeded
congratulations :)
"""
).lstrip()
assert result.stdout == expected
@pytest.mark.parametrize('option', ('--print-extras-to', '--print-extras-to-file')) @pytest.mark.parametrize("option", ("--print-extras-to", "--print-extras-to-file"))
def test_allenvs_print_extras_to_file(tmp_path, option): def test_allenvs_print_extras_to_file(tmp_path, option):
extraspath = tmp_path / "extras" extraspath = tmp_path / "extras"
result = tox(option, str(extraspath)) result = tox(option, str(extraspath))
assert extraspath.read_text().splitlines() == ["dev", "full"] * 6 assert sorted(extraspath.read_text().splitlines()) == sorted(
expected = textwrap.dedent( ["dev", "full"] * len(envs_from_tox_ini())
""" )
___________________________________ summary ____________________________________ expected = ""
py36: commands succeeded for env in envs_from_tox_ini()[:-1]:
py37: commands succeeded expected += f"{env}: OK\n"
py38: commands succeeded expected += tox_footer(spaces=0) + "\n"
py39: commands succeeded assert prep_tox_output(result.stdout) == expected
py310: commands succeeded
py311: commands succeeded
congratulations :)
"""
).lstrip()
assert result.stdout == expected
def test_allenvs_print_deps_to_existing_file(tmp_path): def test_allenvs_print_deps_to_existing_file(tmp_path):
depspath = tmp_path / "deps" depspath = tmp_path / "deps"
depspath.write_text("nada") depspath.write_text("nada")
result = tox("--print-deps-to", str(depspath)) _ = tox("--print-deps-to", str(depspath))
lines = depspath.read_text().splitlines() lines = depspath.read_text().splitlines()
assert "nada" not in lines assert "nada" not in lines
assert "six" in lines assert "six" in lines
@ -385,7 +196,7 @@ def test_allenvs_print_deps_to_existing_file(tmp_path):
def test_allenvs_print_extras_to_existing_file(tmp_path): def test_allenvs_print_extras_to_existing_file(tmp_path):
extraspath = tmp_path / "extras" extraspath = tmp_path / "extras"
extraspath.write_text("nada") extraspath.write_text("nada")
result = tox("--print-extras-to", str(extraspath)) _ = tox("--print-extras-to", str(extraspath))
lines = extraspath.read_text().splitlines() lines = extraspath.read_text().splitlines()
assert "nada" not in lines assert "nada" not in lines
assert "dev" in lines assert "dev" in lines
@ -394,14 +205,15 @@ def test_allenvs_print_extras_to_existing_file(tmp_path):
@pytest.mark.parametrize("deps_stdout", [True, False]) @pytest.mark.parametrize("deps_stdout", [True, False])
@pytest.mark.parametrize("extras_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): def test_allenvs_print_deps_to_file_print_extras_to_other_file(
tmp_path, deps_stdout, extras_stdout
):
if deps_stdout and extras_stdout: if deps_stdout and extras_stdout:
pytest.xfail("Unsupported combination of parameters") pytest.xfail("Unsupported combination of parameters")
depspath = "-" if deps_stdout else tmp_path / "deps" depspath = "-" if deps_stdout else tmp_path / "deps"
extraspath = "-" if extras_stdout else tmp_path / "extras" extraspath = "-" if extras_stdout else tmp_path / "extras"
result = tox("--print-deps-to", str(depspath), result = tox("--print-deps-to", str(depspath), "--print-extras-to", str(extraspath))
"--print-extras-to", str(extraspath))
if deps_stdout: if deps_stdout:
depslines = result.stdout.splitlines() depslines = result.stdout.splitlines()
extraslines = extraspath.read_text().splitlines() extraslines = extraspath.read_text().splitlines()
@ -428,8 +240,10 @@ def test_print_deps_extras_to_same_file_is_not_possible(tmp_path):
result = tox( result = tox(
"-e", "-e",
NATIVE_TOXENV, NATIVE_TOXENV,
"--print-deps-to", str(depsextraspath), "--print-deps-to",
"--print-extras-to", str(depsextraspath), str(depsextraspath),
"--print-extras-to",
str(depsextraspath),
check=False, check=False,
) )
assert result.returncode > 0 assert result.returncode > 0
@ -439,7 +253,8 @@ def test_print_deps_extras_to_same_file_is_not_possible(tmp_path):
def test_print_deps_extras_to_stdout_is_not_possible( def test_print_deps_extras_to_stdout_is_not_possible(
tmp_path, tmp_path,
print_deps_stdout_arg, print_deps_stdout_arg,
print_extras_stdout_arg,): print_extras_stdout_arg,
):
result = tox( result = tox(
"-e", "-e",
NATIVE_TOXENV, NATIVE_TOXENV,
@ -451,33 +266,17 @@ def test_print_deps_extras_to_stdout_is_not_possible(
assert "cannot be identical" in result.stderr assert "cannot be identical" in result.stderr
def test_print_deps_only_print_deps_to_file_are_mutually_exclusive(): @needs_all_pythons
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_py367891011
def test_regular_run(): def test_regular_run():
result = tox() result = tox()
lines = result.stdout.splitlines()[:6] lines = result.stdout.splitlines()[:5]
assert "/.tox/py36 is the exec_prefix" in lines[0] for line, env in zip(lines, envs_from_tox_ini()):
assert "/.tox/py37 is the exec_prefix" in lines[1] assert f"/.tox/{env} is the exec_prefix" in line
assert "/.tox/py38 is the exec_prefix" in lines[2]
assert "/.tox/py39 is the exec_prefix" in lines[3]
assert "/.tox/py310 is the exec_prefix" in lines[4]
assert "/.tox/py311 is the exec_prefix" in lines[5]
assert "congratulations" in result.stdout assert "congratulations" in result.stdout
for y in 6, 7, 8, 9, 10, 11: for env in envs_from_tox_ini():
major, minor = re.match(r"py(\d)(\d+)", env).groups()
for pkg in "py", "six", "test": for pkg in "py", "six", "test":
sitelib = DOT_TOX / f"py3{y}/lib/python3.{y}/site-packages" sitelib = DOT_TOX / f"{env}/lib/python{major}.{minor}/site-packages"
assert sitelib.is_dir() assert sitelib.is_dir()
assert len(list(sitelib.glob(f"{pkg}-*.dist-info"))) == 1 assert len(list(sitelib.glob(f"{pkg}-*.dist-info"))) == 1
@ -488,109 +287,9 @@ def test_regular_run_native_toxenv():
assert f"/.tox/{NATIVE_TOXENV} is the exec_prefix" in lines[0] assert f"/.tox/{NATIVE_TOXENV} is the exec_prefix" in lines[0]
assert "congratulations" in result.stdout assert "congratulations" in result.stdout
for pkg in "py", "six", "test": for pkg in "py", "six", "test":
sitelib = (
DOT_TOX / f"{NATIVE_TOXENV}/{NATIVE_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}/{NATIVE_SITE_PACKAGES}" sitelib = DOT_TOX / f"{NATIVE_TOXENV}/{NATIVE_SITE_PACKAGES}"
assert sitelib.is_dir() assert sitelib.is_dir()
assert len(list(sitelib.glob("test-*.dist-info"))) == 1 assert len(list(sitelib.glob(f"{pkg}-*.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): def test_print_deps_without_python_command(tmp_path, print_deps_stdout_arg):
@ -604,58 +303,49 @@ def test_print_deps_without_python_command(tmp_path, print_deps_stdout_arg):
result = tox("-e", NATIVE_TOXENV, print_deps_stdout_arg, env=env) result = tox("-e", NATIVE_TOXENV, print_deps_stdout_arg, env=env)
expected = textwrap.dedent( expected = textwrap.dedent(
f""" f"""
tox>={TOX_VERSION}
six six
py py
___________________________________ summary ____________________________________ {tox_footer(NATIVE_TOXENV)}
{NATIVE_TOXENV}: commands succeeded
congratulations :)
""" """
).lstrip() ).lstrip()
assert result.stdout == expected assert prep_tox_output(result.stdout) == expected
@pytest.mark.parametrize("flag", [None, "--print-deps-to=-", "--current-env"]) @pytest.mark.parametrize("flag", ["--print-deps-to=-", "--current-env"])
def test_noquiet_installed_packages(flag): def test_recreate_environment(flag):
flags = (flag,) if flag else () flags = (flag,) if flag else ()
_ = tox("-e", NATIVE_TOXENV, check=False)
result = tox("-e", NATIVE_TOXENV, *flags, quiet=False, check=False) result = tox("-e", NATIVE_TOXENV, *flags, quiet=False, check=False)
assert f"\n{NATIVE_TOXENV} installed: " in result.stdout assert f"{NATIVE_TOXENV}: recreate env because env type changed" in prep_tox_output(
for line in result.stdout.splitlines(): result.stdout
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 @pytest.mark.parametrize(
else: "flag", ["--print-deps-to=-", "--print-extras-to=-", "--current-env"]
assert len([p for p in packages if p.startswith("tox==")]) == 1 )
assert all(re.match(r"\S+==\S+", p) for p in packages)
@pytest.mark.parametrize("flag", ["--print-deps-to=-", "--print-extras-to=-", "--current-env"])
@pytest.mark.parametrize("usedevelop", [True, False]) @pytest.mark.parametrize("usedevelop", [True, False])
def test_self_is_not_installed(projdir, flag, usedevelop): def test_self_is_not_installed(projdir, flag, usedevelop):
with modify_config(projdir / 'tox.ini') as config: with modify_config(projdir / "tox.ini") as config:
config['testenv']['usedevelop'] = str(usedevelop) config["testenv"]["usedevelop"] = str(usedevelop)
result = tox("-e", NATIVE_TOXENV, flag, quiet=False) _ = tox("-e", NATIVE_TOXENV, flag, quiet=False)
assert 'test==0.0.0' not in result.stdout egg_link = DOT_TOX / f"{NATIVE_TOXENV}/{NATIVE_SITE_PACKAGES}" / "test.egg-link"
assert 'test @ file://' not in result.stdout dist_info = (
DOT_TOX / f"{NATIVE_TOXENV}/{NATIVE_SITE_PACKAGES}" / "test-0.0.0.dist-info"
)
assert not egg_link.exists()
assert not dist_info.exists()
@pytest.mark.parametrize("usedevelop", [True, False]) @pytest.mark.parametrize("usedevelop", [True, False])
def test_self_is_installed_with_regular_tox(projdir, usedevelop): def test_self_is_installed_with_regular_tox(projdir, usedevelop):
with modify_config(projdir / 'tox.ini') as config: with modify_config(projdir / "tox.ini") as config:
config['testenv']['usedevelop'] = str(usedevelop) config["testenv"]["usedevelop"] = str(usedevelop)
result = tox("-e", NATIVE_TOXENV, quiet=False) _ = tox("-e", NATIVE_TOXENV, "-v", quiet=False)
assert ('test==0.0.0' in result.stdout or egg_link = DOT_TOX / f"{NATIVE_TOXENV}/{NATIVE_SITE_PACKAGES}" / "test.egg-link"
'test @ file://' in result.stdout) dist_info = (
DOT_TOX / f"{NATIVE_TOXENV}/{NATIVE_SITE_PACKAGES}" / "test-0.0.0.dist-info"
)
to_test = egg_link if usedevelop else dist_info
assert to_test.exists()

131
tests/utils.py Normal file
View File

@ -0,0 +1,131 @@
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)
env.pop("TOX_WORK_DIR")
kwargs.setdefault("env", env)
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
TOX_VERSION = ver(tox("--version").stdout.split(" ")[0].split("+")[0])
TOX4 = TOX_VERSION.major == 4
# if TOX4:
# DOT_TOX /= "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
@functools.lru_cache()
def envs_from_tox_ini():
cp = ConfigParser()
cp.read(FIXTURES_DIR / "tox.ini")
return cp["tox"]["envlist"].split(",")
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",
)

View File

@ -2,7 +2,7 @@
# This information is repeated in .github/workflows/main.yaml # This information is repeated in .github/workflows/main.yaml
# (see https://github.com/fedora-python/tox-github-action/issues/8) # (see https://github.com/fedora-python/tox-github-action/issues/8)
envlist = {py36,py37,py38,py39,py310}-tox{release,master,315} envlist = {py36,py37,py38,py39,py310}-tox{release,master,315},{py37,py38,py39,py310}-tox4
[testenv] [testenv]
extras = extras =
@ -11,8 +11,10 @@ deps=
tox315: tox >=3.15,<3.16 tox315: tox >=3.15,<3.16
toxrelease: tox < 4 toxrelease: tox < 4
toxmaster: git+https://github.com/tox-dev/tox.git@legacy toxmaster: git+https://github.com/tox-dev/tox.git@legacy
tox4: git+https://github.com/tox-dev/tox.git@rewrite
commands = commands =
pytest -v {posargs} tests !tox4: pytest -v {posargs} tests/test_integration.py
tox4: pytest -v {posargs} tests/test_integration_tox4.py
[pytest] [pytest]
addopts = -nauto addopts = -nauto