diff options
-rw-r--r-- | docs/changelog/1793.bugfix.rst | 2 | ||||
-rw-r--r-- | src/tox/provision.py | 9 | ||||
-rw-r--r-- | src/tox/session/cmd/run/common.py | 7 | ||||
-rw-r--r-- | tests/config/cli/test_cli_env_var.py | 2 | ||||
-rw-r--r-- | tests/config/cli/test_cli_ini.py | 4 | ||||
-rw-r--r-- | tests/test_provision.py | 105 |
6 files changed, 83 insertions, 46 deletions
diff --git a/docs/changelog/1793.bugfix.rst b/docs/changelog/1793.bugfix.rst new file mode 100644 index 00000000..d85a7091 --- /dev/null +++ b/docs/changelog/1793.bugfix.rst @@ -0,0 +1,2 @@ +When using a provisioned tox environment requesting ``--recreate`` failed with ``AttributeError`` - +by :user:`gaborbernat`. diff --git a/src/tox/provision.py b/src/tox/provision.py index 9996d248..2a00acdb 100644 --- a/src/tox/provision.py +++ b/src/tox/provision.py @@ -31,10 +31,17 @@ else: # pragma: no cover (py38+) def tox_add_option(parser: ArgumentParser) -> None: parser.add_argument( "--no-recreate-provision", - dest="recreate", + dest="no_recreate_provision", help="if recreate is set do not recreate provision tox environment", action="store_true", ) + parser.add_argument( + "-r", + "--recreate", + dest="recreate", + help="recreate the tox environments", + action="store_true", + ) @impl diff --git a/src/tox/session/cmd/run/common.py b/src/tox/session/cmd/run/common.py index 5557ba18..3a260084 100644 --- a/src/tox/session/cmd/run/common.py +++ b/src/tox/session/cmd/run/common.py @@ -56,13 +56,6 @@ def env_run_create_flags(parser: ArgumentParser) -> None: help="don't fail tests for missing interpreters: {config,true,false} choice", ) parser.add_argument( - "-r", - "--recreate", - dest="recreate", - help="recreate the tox environments", - action="store_true", - ) - parser.add_argument( "-n", "--notest", dest="no_test", diff --git a/tests/config/cli/test_cli_env_var.py b/tests/config/cli/test_cli_env_var.py index cf05c653..9cd79d17 100644 --- a/tests/config/cli/test_cli_env_var.py +++ b/tests/config/cli/test_cli_env_var.py @@ -44,6 +44,7 @@ def test_verbose_no_test(monkeypatch: MonkeyPatch) -> None: "skip_missing_interpreters": "config", "skip_pkg_install": False, "recreate": False, + "no_recreate_provision": False, "no_test": True, "package_only": False, "installpkg": None, @@ -98,6 +99,7 @@ def test_env_var_exhaustive_parallel_values( "pre": False, "quiet": 1, "recreate": True, + "no_recreate_provision": False, "result_json": None, "show_config": False, "sitepackages": False, diff --git a/tests/config/cli/test_cli_ini.py b/tests/config/cli/test_cli_ini.py index 4e7f190f..d8dd75a4 100644 --- a/tests/config/cli/test_cli_ini.py +++ b/tests/config/cli/test_cli_ini.py @@ -86,11 +86,12 @@ def default_options(tmp_path: Path) -> Dict[str, Any]: "package_only": False, "quiet": 0, "recreate": False, + "no_recreate_provision": False, + "no_recreate_pkg": False, "result_json": None, "skip_missing_interpreters": "config", "skip_pkg_install": False, "verbose": 2, - "no_recreate_pkg": False, "work_dir": None, "root_dir": None, "config_file": (tmp_path / "tox.ini").absolute(), @@ -117,6 +118,7 @@ def test_ini_exhaustive_parallel_values(exhaustive_ini: Path, core_handlers: Dic "parallel_no_spinner": False, "quiet": 1, "recreate": True, + "no_recreate_provision": False, "result_json": None, "skip_missing_interpreters": "config", "skip_pkg_install": False, diff --git a/tests/test_provision.py b/tests/test_provision.py index 58bc447e..c08024ae 100644 --- a/tests/test_provision.py +++ b/tests/test_provision.py @@ -1,9 +1,11 @@ import json import os import sys +import time +from contextlib import contextmanager from pathlib import Path from subprocess import check_call -from typing import List, Optional +from typing import Iterator, List, Optional from zipfile import ZipFile import pytest @@ -19,41 +21,50 @@ else: # pragma: no cover (<py38) ROOT = Path(__file__).parents[1] +@contextmanager +def elapsed(msg: str) -> Iterator[None]: + start = time.monotonic() + try: + yield + finally: + print(f"done in {time.monotonic() - start}s {msg}") + + @pytest.fixture(scope="session") def tox_wheel(tmp_path_factory: TempPathFactory) -> Path: - # takes around 3.2s - package: Optional[Path] = None - if "TOX_PACKAGE" in os.environ: - env_tox_pkg = Path(os.environ["TOX_PACKAGE"]) - if env_tox_pkg.exists() and env_tox_pkg.suffix == ".whl": - package = env_tox_pkg - if package is None: # pragma: no cover - # when we don't get a wheel path injected, build it (for example when running from an IDE) - package = build_wheel(tmp_path_factory.mktemp("dist"), Path(__file__).parents[1]) - return package + with elapsed("acquire current tox wheel"): # takes around 3.2s on build + package: Optional[Path] = None + if "TOX_PACKAGE" in os.environ: + env_tox_pkg = Path(os.environ["TOX_PACKAGE"]) # pragma: no cover + if env_tox_pkg.exists() and env_tox_pkg.suffix == ".whl": # pragma: no cover + package = env_tox_pkg # pragma: no cover + if package is None: + # when we don't get a wheel path injected, build it (for example when running from an IDE) + package = build_wheel(tmp_path_factory.mktemp("dist"), Path(__file__).parents[1]) # pragma: no cover + return package @pytest.fixture(scope="session") def tox_wheels(tox_wheel: Path, tmp_path_factory: TempPathFactory) -> List[Path]: - # takes around 1.5s if already cached - result: List[Path] = [tox_wheel] - info = tmp_path_factory.mktemp("info") - with ZipFile(str(tox_wheel), "r") as zip_file: - zip_file.extractall(path=info) - dist_info = next((i for i in info.iterdir() if i.suffix == ".dist-info"), None) - if dist_info is None: # pragma: no cover - raise RuntimeError(f"no tox.dist-info inside {tox_wheel}") - distribution = Distribution.at(dist_info) - wheel_cache = ROOT / ".wheel_cache" / f"{sys.version_info.major}.{sys.version_info.minor}" - wheel_cache.mkdir(parents=True, exist_ok=True) - cmd = [sys.executable, "-I", "-m", "pip", "download", "-d", str(wheel_cache)] - for req in distribution.requires: - requirement = Requirement(req) - if not requirement.extras: # pragma: no branch # we don't need to install any extras (tests/docs/etc) - cmd.append(req) - check_call(cmd) - result.extend(wheel_cache.iterdir()) - return result + with elapsed("acquire dependencies for current tox"): # takes around 1.5s if already cached + result: List[Path] = [tox_wheel] + info = tmp_path_factory.mktemp("info") + with ZipFile(str(tox_wheel), "r") as zip_file: + zip_file.extractall(path=info) + dist_info = next((i for i in info.iterdir() if i.suffix == ".dist-info"), None) + if dist_info is None: # pragma: no cover + raise RuntimeError(f"no tox.dist-info inside {tox_wheel}") + distribution = Distribution.at(dist_info) + wheel_cache = ROOT / ".wheel_cache" / f"{sys.version_info.major}.{sys.version_info.minor}" + wheel_cache.mkdir(parents=True, exist_ok=True) + cmd = [sys.executable, "-I", "-m", "pip", "download", "-d", str(wheel_cache)] + for req in distribution.requires: + requirement = Requirement(req) + if not requirement.extras: # pragma: no branch # we don't need to install any extras (tests/docs/etc) + cmd.append(req) + check_call(cmd) + result.extend(wheel_cache.iterdir()) + return result @pytest.fixture(scope="session") @@ -71,9 +82,10 @@ def build_wheel(dist_dir: Path, of: Path) -> Path: @pytest.fixture(scope="session") def pypi_index_self(pypi_server: IndexServer, tox_wheels: List[Path], demo_pkg_inline_wheel: Path) -> Index: - # takes around 1s - self_index = pypi_server.create_index("self", "volatile=False") - self_index.upload(tox_wheels + [demo_pkg_inline_wheel]) + with elapsed("start devpi and create index"): # takes around 1s + self_index = pypi_server.create_index("self", "volatile=False") + with elapsed("upload tox and its wheels to devpi"): # takes around 3.2s on build + self_index.upload(tox_wheels + [demo_pkg_inline_wheel]) return self_index @@ -94,13 +106,32 @@ def test_provision_requires_nok(tox_project: ToxProjectCreator) -> None: def test_provision_requires_ok( tox_project: ToxProjectCreator, pypi_index_self: Index, monkeypatch: MonkeyPatch, tmp_path: Path ) -> None: - log = tmp_path / "out.log" pypi_index_self.use(monkeypatch) - ini = "[tox]\nrequires = demo-pkg-inline\n setuptools \n[testenv]\npackage=skip" + proj = tox_project({"tox.ini": "[tox]\nrequires=demo-pkg-inline\n[testenv]\npackage=skip"}) + log = tmp_path / "out.log" - outcome = tox_project({"tox.ini": ini}).run("r", "-e", "py", "--result-json", str(log)) + # initial run + result_first = proj.run("r", "--result-json", str(log)) + result_first.assert_success() + prov_msg = ( + f"ROOT: will run in automatically provisioned tox, host {sys.executable} is missing" + f" [requires (has)]: demo-pkg-inline (N/A)" + ) + assert prov_msg in result_first.out - outcome.assert_success() with log.open("rt") as file_handler: log_report = json.load(file_handler) assert "py" in log_report["testenvs"] + + # recreate without recreating the provisioned env + provision_env = result_first.state.tox_env(".tox").conf["env_dir"] + result_recreate_no_pr = proj.run("r", "--recreate", "--no-recreate-provision") + result_recreate_no_pr.assert_success() + assert prov_msg in result_recreate_no_pr.out + assert f"ROOT: remove tox env folder {provision_env}" not in result_recreate_no_pr.out, result_recreate_no_pr.out + + # recreate with recreating the provisioned env + result_recreate = proj.run("r", "--recreate") + result_recreate.assert_success() + assert prov_msg in result_recreate.out + assert f"ROOT: remove tox env folder {provision_env}" in result_recreate.out, result_recreate.out |