diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 3cb28ed..a48da34 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -37,6 +37,9 @@ jobs: - py310-toxrelease - py310-toxmaster - py310-tox315 - + - py37-tox4 + - py38-tox4 + - py39-tox4 + - py310-tox4 # Use GitHub's Linux Docker host runs-on: ubuntu-latest diff --git a/README.rst b/README.rst index 2ccec46..35a85e0 100644 --- a/README.rst +++ b/README.rst @@ -157,6 +157,17 @@ To get a list of names of extras, run: 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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/setup.py b/setup.py index a976155..9f18b8f 100644 --- a/setup.py +++ b/setup.py @@ -19,7 +19,7 @@ setup( packages=find_packages("src"), entry_points={"tox": ["current-env = tox_current_env.hooks"]}, install_requires=[ - "tox>=3.15,<4", + "tox>=3.15", "importlib_metadata; python_version < '3.8'" ], extras_require={ diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..b33178f --- /dev/null +++ b/tests/conftest.py @@ -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 diff --git a/tests/test_integration.py b/tests/test_integration.py index f802403..5dcf3ba 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -1,120 +1,36 @@ -import functools import os -import pathlib import re import shutil import subprocess import sys import textwrap -import warnings -import configparser -import contextlib - -from packaging.version import parse as ver import pytest +from packaging.version import parse as ver - -NATIVE_TOXENV = f"py{sys.version_info[0]}{sys.version_info[1]}" -NATIVE_SITE_PACKAGES = f"lib/python{sys.version_info[0]}.{sys.version_info[1]}/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" - - -@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", +from utils import ( + DOT_TOX, + NATIVE_EXEC_PREFIX_MSG, + NATIVE_EXECUTABLE, + NATIVE_SITE_PACKAGES, + NATIVE_TOXENV, + TOX_VERSION, + envs_from_tox_ini, + is_available, + modify_config, + needs_all_pythons, + tox, + tox_footer, ) - 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_py3678910 +@needs_all_pythons def test_all_toxenv_current_env(): result = tox("--current-env", check=False) assert NATIVE_EXEC_PREFIX_MSG in result.stdout.splitlines() @@ -133,7 +49,7 @@ def test_missing_toxenv_current_env(python): assert result.returncode > 0 -@needs_py3678910 +@needs_all_pythons def test_all_toxenv_current_env_skip_missing(): result = tox("--current-env", "--skip-missing-interpreters", check=False) assert "InterpreterMismatch:" in result.stdout @@ -141,22 +57,20 @@ def test_all_toxenv_current_env_skip_missing(): 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): result = tox("-e", toxenv, print_deps_stdout_arg) expected = textwrap.dedent( f""" six py - ___________________________________ summary ____________________________________ - {toxenv}: commands succeeded - congratulations :) + {tox_footer(toxenv)} """ ).lstrip() 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"]) def test_print_deps_with_commands_pre_post(projdir, toxenv, pre_post, print_deps_stdout_arg): 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""" six py - ___________________________________ summary ____________________________________ - {toxenv}: commands succeeded - congratulations :) + {tox_footer(toxenv)} """ ).lstrip() assert result.stdout == expected 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): - with modify_config(projdir / 'tox.ini') as config: + with modify_config(projdir / "tox.ini") as config: config["tox"]["minversion"] = "3.13" result = tox("-e", toxenv, print_deps_stdout_arg) expected = textwrap.dedent( @@ -189,18 +101,16 @@ def test_print_deps_with_tox_minversion(projdir, toxenv, print_deps_stdout_arg): tox >= 3.13 six py - ___________________________________ summary ____________________________________ - {toxenv}: commands succeeded - congratulations :) + {tox_footer(toxenv)} """ ).lstrip() assert result.stdout == expected @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): - with modify_config(projdir / 'tox.ini') as config: + with modify_config(projdir / "tox.ini") as config: config["tox"]["requires"] = "\n setuptools > 30\n pluggy" result = tox("-e", toxenv, print_deps_stdout_arg) expected = textwrap.dedent( @@ -209,18 +119,18 @@ def test_print_deps_with_tox_requires(projdir, toxenv, print_deps_stdout_arg): pluggy six py - ___________________________________ summary ____________________________________ - {toxenv}: commands succeeded - congratulations :) + {tox_footer(toxenv)} """ ).lstrip() assert result.stdout == expected @pytest.mark.xfail(TOX_VERSION < ver("3.22"), reason="No support in old tox") -@pytest.mark.parametrize("toxenv", ["py36", "py37", "py38", "py39"]) -def test_print_deps_with_tox_minversion_and_requires(projdir, toxenv, print_deps_stdout_arg): - with modify_config(projdir / 'tox.ini') as config: +@pytest.mark.parametrize("toxenv", envs_from_tox_ini()) +def test_print_deps_with_tox_minversion_and_requires( + projdir, toxenv, print_deps_stdout_arg +): + with modify_config(projdir / "tox.ini") as config: config["tox"]["minversion"] = "3.13" config["tox"]["requires"] = "\n setuptools > 30\n pluggy" 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 six py - ___________________________________ summary ____________________________________ - {toxenv}: commands succeeded - congratulations :) + {tox_footer(toxenv)} """ ).lstrip() 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): result = tox("-e", toxenv, print_extras_stdout_arg) expected = textwrap.dedent( f""" dev full - ___________________________________ summary ____________________________________ - {toxenv}: commands succeeded - congratulations :) + {tox_footer(toxenv)} """ ).lstrip() 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"]) def test_print_extras_with_commands_pre_post(projdir, toxenv, pre_post, print_extras_stdout_arg): 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""" dev full - ___________________________________ summary ____________________________________ - {toxenv}: commands succeeded - congratulations :) + {tox_footer(toxenv)} """ ).lstrip() assert result.stdout == expected 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): result = tox( - "-e", toxenv, '--print-deps-only', - env={**os.environ, 'PYTHONWARNINGS': 'always'}, + "-e", + toxenv, + "--print-deps-only", + env={**os.environ, "PYTHONWARNINGS": "always"}, ) waring_text = ( "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): result = tox(print_deps_stdout_arg) - expected = textwrap.dedent( - """ - six - py - 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() + expected = "" + for env in envs_from_tox_ini(): + expected += "six\npy\n" + expected += tox_footer(spaces=0) + "\n" 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 - dev - full - ___________________________________ summary ____________________________________ - py36: commands succeeded - py37: commands succeeded - py38: commands succeeded - py39: commands succeeded - py310: commands succeeded - congratulations :) - """ - ).lstrip() + expected = "" + for env in envs_from_tox_ini(): + expected += "dev\nfull\n" + expected += tox_footer(spaces=0) + "\n" 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): 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 :) + {tox_footer(toxenv)} """ ).lstrip() 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): 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 :) + {tox_footer(toxenv)} """ ).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): depspath = tmp_path / "deps" result = tox(option, str(depspath)) assert depspath.read_text().splitlines() == ["six", "py"] * 5 expected = textwrap.dedent( - """ - ___________________________________ summary ____________________________________ - py36: commands succeeded - py37: commands succeeded - py38: commands succeeded - py39: commands succeeded - py310: commands succeeded - congratulations :) + f""" + {tox_footer()} """ ).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): extraspath = tmp_path / "extras" result = tox(option, str(extraspath)) assert extraspath.read_text().splitlines() == ["dev", "full"] * 5 expected = textwrap.dedent( - """ - ___________________________________ summary ____________________________________ - py36: commands succeeded - py37: commands succeeded - py38: commands succeeded - py39: commands succeeded - py310: commands succeeded - congratulations :) + f""" + {tox_footer()} """ ).lstrip() 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): depspath = tmp_path / "deps" depspath.write_text("nada") - result = tox("--print-deps-to", str(depspath)) + _ = tox("--print-deps-to", str(depspath)) lines = depspath.read_text().splitlines() assert "nada" not 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): extraspath = tmp_path / "extras" extraspath.write_text("nada") - result = tox("--print-extras-to", str(extraspath)) + _ = tox("--print-extras-to", str(extraspath)) lines = extraspath.read_text().splitlines() assert "nada" not 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("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: 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)) + result = tox("--print-deps-to", str(depspath), "--print-extras-to", str(extraspath)) if deps_stdout: depslines = result.stdout.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( "-e", NATIVE_TOXENV, - "--print-deps-to", str(depsextraspath), - "--print-extras-to", str(depsextraspath), + "--print-deps-to", + str(depsextraspath), + "--print-extras-to", + str(depsextraspath), check=False, ) assert result.returncode > 0 @@ -475,9 +334,10 @@ def test_print_deps_extras_to_same_file_is_not_possible(tmp_path): def test_print_deps_extras_to_stdout_is_not_possible( - tmp_path, - print_deps_stdout_arg, - print_extras_stdout_arg,): + tmp_path, + print_deps_stdout_arg, + print_extras_stdout_arg, +): result = tox( "-e", 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 -@needs_py3678910 +@needs_all_pythons def test_regular_run(): result = tox() lines = result.stdout.splitlines()[:5] - 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 "/.tox/py310 is the exec_prefix" in lines[4] + for line, env in zip(lines, envs_from_tox_ini()): + assert f"/.tox/{env} is the exec_prefix" in line 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": - 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 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 "congratulations" in result.stdout for pkg in "py", "six", "test": - sitelib = ( - DOT_TOX / f"{NATIVE_TOXENV}/{NATIVE_SITE_PACKAGES}" - ) + sitelib = DOT_TOX / f"{NATIVE_TOXENV}/{NATIVE_SITE_PACKAGES}" assert sitelib.is_dir() 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""" six py - ___________________________________ summary ____________________________________ - {NATIVE_TOXENV}: commands succeeded - congratulations :) + {tox_footer(NATIVE_TOXENV)} """ ).lstrip() 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) -@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]) def test_self_is_not_installed(projdir, flag, usedevelop): - with modify_config(projdir / 'tox.ini') as config: - config['testenv']['usedevelop'] = str(usedevelop) + with modify_config(projdir / "tox.ini") as config: + config["testenv"]["usedevelop"] = str(usedevelop) result = tox("-e", NATIVE_TOXENV, flag, quiet=False) - assert 'test==0.0.0' not in result.stdout - assert 'test @ file://' not in result.stdout + assert "test==0.0.0" not in result.stdout + assert "test @ file://" not in result.stdout @pytest.mark.parametrize("externals", [None, "allowlist_externals", "whitelist_externals"]) @@ -705,8 +561,7 @@ def test_externals(projdir, externals): @pytest.mark.parametrize("usedevelop", [True, False]) def test_self_is_installed_with_regular_tox(projdir, usedevelop): - with modify_config(projdir / 'tox.ini') as config: - config['testenv']['usedevelop'] = str(usedevelop) + with modify_config(projdir / "tox.ini") as config: + config["testenv"]["usedevelop"] = str(usedevelop) result = tox("-e", NATIVE_TOXENV, quiet=False) - assert ('test==0.0.0' in result.stdout or - 'test @ file://' in result.stdout) + assert "test==0.0.0" in result.stdout or "test @ file://" in result.stdout diff --git a/tests/test_integration_tox4.py b/tests/test_integration_tox4.py index 7660e5d..ccca154 100644 --- a/tests/test_integration_tox4.py +++ b/tests/test_integration_tox4.py @@ -1,381 +1,192 @@ -import functools import os -import pathlib import re import shutil -import subprocess -import sys import textwrap -import warnings -import configparser -import contextlib - -from packaging.version import parse as ver import pytest +from packaging.version import parse as ver - -NATIVE_TOXENV = f"py{sys.version_info[0]}{sys.version_info[1]}" -NATIVE_SITE_PACKAGES = f"lib/python{sys.version_info[0]}.{sys.version_info[1]}/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" - - -@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", +from utils import ( + DOT_TOX, + NATIVE_EXEC_PREFIX_MSG, + NATIVE_SITE_PACKAGES, + NATIVE_TOXENV, + TOX_VERSION, + envs_from_tox_ini, + modify_config, + needs_all_pythons, + prep_tox_output, + tox, + tox_footer, ) - 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_py367891011 -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"]) +@pytest.mark.parametrize("toxenv", envs_from_tox_ini()) def test_print_deps(toxenv, print_deps_stdout_arg): result = tox("-e", toxenv, print_deps_stdout_arg) expected = textwrap.dedent( f""" + tox>={TOX_VERSION} six py - ___________________________________ summary ____________________________________ - {toxenv}: commands succeeded - congratulations :) + {tox_footer(toxenv)} """ ).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): - with modify_config(projdir / 'tox.ini') as config: + with modify_config(projdir / "tox.ini") as config: config["tox"]["minversion"] = "3.13" result = tox("-e", toxenv, print_deps_stdout_arg) expected = textwrap.dedent( f""" - tox >= 3.13 + tox>=3.13 six py - ___________________________________ summary ____________________________________ - {toxenv}: commands succeeded - congratulations :) + {tox_footer(toxenv)} """ ).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.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): - with modify_config(projdir / 'tox.ini') as config: + with modify_config(projdir / "tox.ini") as config: config["tox"]["requires"] = "\n setuptools > 30\n pluggy" result = tox("-e", toxenv, print_deps_stdout_arg) expected = textwrap.dedent( f""" - setuptools > 30 + setuptools>30 pluggy + tox>={TOX_VERSION} six py - ___________________________________ summary ____________________________________ - {toxenv}: commands succeeded - congratulations :) + {tox_footer(toxenv)} """ ).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.parametrize("toxenv", ["py36", "py37", "py38", "py39"]) -def test_print_deps_with_tox_minversion_and_requires(projdir, toxenv, print_deps_stdout_arg): - with modify_config(projdir / 'tox.ini') as config: +@pytest.mark.parametrize("toxenv", envs_from_tox_ini()) +def test_print_deps_with_tox_minversion_and_requires( + projdir, toxenv, print_deps_stdout_arg +): + with modify_config(projdir / "tox.ini") as config: config["tox"]["minversion"] = "3.13" config["tox"]["requires"] = "\n setuptools > 30\n pluggy" result = tox("-e", toxenv, print_deps_stdout_arg) expected = textwrap.dedent( f""" - tox >= 3.13 - setuptools > 30 + setuptools>30 pluggy + tox>=3.13 six py - ___________________________________ summary ____________________________________ - {toxenv}: commands succeeded - congratulations :) + {tox_footer(toxenv)} """ ).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): result = tox("-e", toxenv, print_extras_stdout_arg) expected = textwrap.dedent( f""" dev full - ___________________________________ summary ____________________________________ - {toxenv}: commands succeeded - congratulations :) + {tox_footer(toxenv)} """ ).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'}, + assert sorted(prep_tox_output(result.stdout).splitlines()) == sorted( + expected.splitlines() ) - 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 - 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 + expected = [] + for env in envs_from_tox_ini(): + expected.extend((f"tox>={TOX_VERSION}", "six", "py", f"{env}: OK")) + expected.pop() # The last "py310: OK" is not there + expected.append(tox_footer(spaces=0)) + expected = ("\n".join(expected)).splitlines() + assert sorted(prep_tox_output(result.stdout).splitlines()) == sorted(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 - 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 + expected = [] + for env in envs_from_tox_ini(): + expected.extend(("dev", "full", f"{env}: OK")) + expected.pop() # The last "py310: OK" is not there + expected.append(tox_footer(spaces=0)) + expected = ("\n".join(expected)).splitlines() + assert sorted(prep_tox_output(result.stdout).splitlines()) == sorted(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): 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 + assert sorted(depspath.read_text().splitlines()) == sorted( + [f"tox>={TOX_VERSION}", "six", "py"] + ) + expected = tox_footer(toxenv, spaces=0) + "\n" + assert prep_tox_output(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): 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 + assert sorted(extraspath.read_text().splitlines()) == sorted(["dev", "full"]) + expected = tox_footer(toxenv, spaces=0) + "\n" + assert prep_tox_output(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): depspath = tmp_path / "deps" result = tox(option, str(depspath)) - assert depspath.read_text().splitlines() == ["six", "py"] * 6 - expected = textwrap.dedent( - """ - ___________________________________ 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 + assert sorted(depspath.read_text().splitlines()) == sorted( + [f"tox>={TOX_VERSION}", "six", "py"] * len(envs_from_tox_ini()) + ) + expected = "" + for env in envs_from_tox_ini()[:-1]: + expected += f"{env}: OK\n" + expected += tox_footer(spaces=0) + "\n" + assert prep_tox_output(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): extraspath = tmp_path / "extras" result = tox(option, str(extraspath)) - assert extraspath.read_text().splitlines() == ["dev", "full"] * 6 - expected = textwrap.dedent( - """ - ___________________________________ 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 + assert sorted(extraspath.read_text().splitlines()) == sorted( + ["dev", "full"] * len(envs_from_tox_ini()) + ) + expected = "" + for env in envs_from_tox_ini()[:-1]: + expected += f"{env}: OK\n" + expected += tox_footer(spaces=0) + "\n" + assert prep_tox_output(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)) + _ = tox("--print-deps-to", str(depspath)) lines = depspath.read_text().splitlines() assert "nada" not 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): extraspath = tmp_path / "extras" extraspath.write_text("nada") - result = tox("--print-extras-to", str(extraspath)) + _ = tox("--print-extras-to", str(extraspath)) lines = extraspath.read_text().splitlines() assert "nada" not 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("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: 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)) + result = tox("--print-deps-to", str(depspath), "--print-extras-to", str(extraspath)) if deps_stdout: depslines = result.stdout.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( "-e", NATIVE_TOXENV, - "--print-deps-to", str(depsextraspath), - "--print-extras-to", str(depsextraspath), + "--print-deps-to", + str(depsextraspath), + "--print-extras-to", + str(depsextraspath), check=False, ) assert result.returncode > 0 @@ -437,9 +251,10 @@ def test_print_deps_extras_to_same_file_is_not_possible(tmp_path): def test_print_deps_extras_to_stdout_is_not_possible( - tmp_path, - print_deps_stdout_arg, - print_extras_stdout_arg,): + tmp_path, + print_deps_stdout_arg, + print_extras_stdout_arg, +): result = tox( "-e", NATIVE_TOXENV, @@ -451,33 +266,17 @@ def test_print_deps_extras_to_stdout_is_not_possible( 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_py367891011 +@needs_all_pythons def test_regular_run(): result = tox() - lines = result.stdout.splitlines()[:6] - 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 "/.tox/py310 is the exec_prefix" in lines[4] - assert "/.tox/py311 is the exec_prefix" in lines[5] + lines = result.stdout.splitlines()[:5] + for line, env in zip(lines, envs_from_tox_ini()): + assert f"/.tox/{env} is the exec_prefix" in line 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": - 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 len(list(sitelib.glob(f"{pkg}-*.dist-info"))) == 1 @@ -488,111 +287,11 @@ def test_regular_run_native_toxenv(): 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}/{NATIVE_SITE_PACKAGES}" - ) + 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}" - 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() @@ -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) expected = textwrap.dedent( f""" + tox>={TOX_VERSION} six py - ___________________________________ summary ____________________________________ - {NATIVE_TOXENV}: commands succeeded - congratulations :) + {tox_footer(NATIVE_TOXENV)} """ ).lstrip() - assert result.stdout == expected + assert prep_tox_output(result.stdout) == expected -@pytest.mark.parametrize("flag", [None, "--print-deps-to=-", "--current-env"]) -def test_noquiet_installed_packages(flag): +@pytest.mark.parametrize("flag", ["--print-deps-to=-", "--current-env"]) +def test_recreate_environment(flag): flags = (flag,) if flag else () + _ = tox("-e", NATIVE_TOXENV, check=False) 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() + assert f"{NATIVE_TOXENV}: recreate env because env type changed" in prep_tox_output( + result.stdout ) - # 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) - - -@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]) def test_self_is_not_installed(projdir, flag, usedevelop): - with modify_config(projdir / 'tox.ini') as config: - config['testenv']['usedevelop'] = str(usedevelop) - result = tox("-e", NATIVE_TOXENV, flag, quiet=False) - assert 'test==0.0.0' not in result.stdout - assert 'test @ file://' not in result.stdout + with modify_config(projdir / "tox.ini") as config: + config["testenv"]["usedevelop"] = str(usedevelop) + _ = tox("-e", NATIVE_TOXENV, flag, quiet=False) + egg_link = DOT_TOX / f"{NATIVE_TOXENV}/{NATIVE_SITE_PACKAGES}" / "test.egg-link" + 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]) def test_self_is_installed_with_regular_tox(projdir, usedevelop): - with modify_config(projdir / 'tox.ini') as config: - config['testenv']['usedevelop'] = str(usedevelop) - result = tox("-e", NATIVE_TOXENV, quiet=False) - assert ('test==0.0.0' in result.stdout or - 'test @ file://' in result.stdout) + with modify_config(projdir / "tox.ini") as config: + config["testenv"]["usedevelop"] = str(usedevelop) + _ = tox("-e", NATIVE_TOXENV, "-v", quiet=False) + egg_link = DOT_TOX / f"{NATIVE_TOXENV}/{NATIVE_SITE_PACKAGES}" / "test.egg-link" + 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() diff --git a/tests/utils.py b/tests/utils.py new file mode 100644 index 0000000..b7a948f --- /dev/null +++ b/tests/utils.py @@ -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", +) diff --git a/tox.ini b/tox.ini index ac9136d..9ed86b7 100644 --- a/tox.ini +++ b/tox.ini @@ -2,7 +2,7 @@ # This information is repeated in .github/workflows/main.yaml # (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] extras = @@ -11,8 +11,10 @@ deps= tox315: tox >=3.15,<3.16 toxrelease: tox < 4 toxmaster: git+https://github.com/tox-dev/tox.git@legacy + tox4: git+https://github.com/tox-dev/tox.git@rewrite commands = - pytest -v {posargs} tests + !tox4: pytest -v {posargs} tests/test_integration.py + tox4: pytest -v {posargs} tests/test_integration_tox4.py [pytest] addopts = -nauto