summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/changelog/1793.bugfix.rst2
-rw-r--r--src/tox/provision.py9
-rw-r--r--src/tox/session/cmd/run/common.py7
-rw-r--r--tests/config/cli/test_cli_env_var.py2
-rw-r--r--tests/config/cli/test_cli_ini.py4
-rw-r--r--tests/test_provision.py105
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