diff --git a/docs/changelog/3600.bugfix.rst b/docs/changelog/3600.bugfix.rst new file mode 100644 index 0000000000..4fa82025bd --- /dev/null +++ b/docs/changelog/3600.bugfix.rst @@ -0,0 +1,4 @@ +Previously, when tox ran in an automatically provisioned environment, it could hang waiting for a PEP 517 build backend +if used in conjunction with the ``--installpkg`` option. This has been fixed by properly tearing down the automatically +provisioned environment after the tests. +- by :user:`vytas7` diff --git a/src/tox/provision.py b/src/tox/provision.py index c989c39ddd..86fedf9122 100644 --- a/src/tox/provision.py +++ b/src/tox/provision.py @@ -146,10 +146,14 @@ def run_provision(name: str, state: State) -> int: logging.info("will run in a automatically provisioned python environment under %s", env_python) try: tox_env.setup() + args: list[str] = [str(env_python), "-m", "tox"] + args.extend(state.args) + outcome = tox_env.execute( + cmd=args, stdin=StdinSource.user_only(), show=True, run_id="provision", cwd=Path.cwd() + ) + return cast("int", outcome.exit_code) except Skip as exception: msg = f"cannot provision tox environment {tox_env.conf['env_name']} because {exception}" raise HandledError(msg) from exception - args: list[str] = [str(env_python), "-m", "tox"] - args.extend(state.args) - outcome = tox_env.execute(cmd=args, stdin=StdinSource.user_only(), show=True, run_id="provision", cwd=Path.cwd()) - return cast("int", outcome.exit_code) + finally: + tox_env.teardown() diff --git a/src/tox/tox_env/package.py b/src/tox/tox_env/package.py index a870d3649b..a097b5a67d 100644 --- a/src/tox/tox_env/package.py +++ b/src/tox/tox_env/package.py @@ -97,7 +97,9 @@ def mark_active_run_env(self, run_env: RunToxEnv) -> None: self._envs.add(run_env.conf.name) def teardown_env(self, conf: EnvConfigSet) -> None: - self._envs.remove(conf.name) + if conf.name in self._envs: + # conf.name (".tox") may be missing in self._envs in the case of an automatically provisioned environment + self._envs.remove(conf.name) if len(self._envs) == 0: self._teardown() diff --git a/tests/test_provision.py b/tests/test_provision.py index 0cf4de5898..2cde4f68bd 100644 --- a/tests/test_provision.py +++ b/tests/test_provision.py @@ -7,7 +7,7 @@ from contextlib import contextmanager from pathlib import Path from subprocess import check_call -from typing import TYPE_CHECKING, Callable, Iterator +from typing import TYPE_CHECKING, Callable, Iterator, Sequence from unittest import mock from zipfile import ZipFile @@ -16,6 +16,7 @@ from packaging.requirements import Requirement if TYPE_CHECKING: + from build import DistributionType from devpi_process import Index, IndexServer from tox.pytest import MonkeyPatch, TempPathFactory, ToxProjectCreator @@ -102,22 +103,38 @@ def tox_wheels(tox_wheel: Path, tmp_path_factory: TempPathFactory) -> list[Path] @pytest.fixture(scope="session") -def pypi_index_self(pypi_server: IndexServer, tox_wheels: list[Path], demo_pkg_inline_wheel: Path) -> Index: - with elapsed("start devpi and create index"): # takes around 1s +def local_pypi_indexes( + pypi_server: IndexServer, tox_wheels: list[Path], demo_pkg_inline_wheel: Path +) -> tuple[Index, Index]: + with elapsed("start devpi and create indexes"): # takes around 1s + pypi_server.create_index("mirror", "type=mirror", "mirror_url=https://pypi.org/simple/") + mirrored_index = pypi_server.create_index("magic", f"bases={pypi_server.user}/mirror") self_index = pypi_server.create_index("self", "volatile=False") with elapsed("upload tox and its wheels to devpi"): # takes around 3.2s on build + mirrored_index.upload(*tox_wheels, demo_pkg_inline_wheel) self_index.upload(*tox_wheels, demo_pkg_inline_wheel) - return self_index + return mirrored_index, self_index -@pytest.fixture -def _pypi_index_self(pypi_index_self: Index, monkeypatch: MonkeyPatch) -> None: - pypi_index_self.use() - monkeypatch.setenv("PIP_INDEX_URL", pypi_index_self.url) +def _use_pypi_index(pypi_index: Index, monkeypatch: MonkeyPatch) -> None: + pypi_index.use() + monkeypatch.setenv("PIP_INDEX_URL", pypi_index.url) monkeypatch.setenv("PIP_RETRIES", str(2)) monkeypatch.setenv("PIP_TIMEOUT", str(5)) +@pytest.fixture +def _pypi_index_mirrored(local_pypi_indexes: tuple[Index, Index], monkeypatch: MonkeyPatch) -> None: + pypi_index_mirrored, _ = local_pypi_indexes + _use_pypi_index(pypi_index_mirrored, monkeypatch) + + +@pytest.fixture +def _pypi_index_self(local_pypi_indexes: tuple[Index, Index], monkeypatch: MonkeyPatch) -> None: + _, pypi_index_self = local_pypi_indexes + _use_pypi_index(pypi_index_self, monkeypatch) + + def test_provision_requires_nok(tox_project: ToxProjectCreator) -> None: ini = "[tox]\nrequires = pkg-does-not-exist\n setuptools==1\nskipsdist=true\n" outcome = tox_project({"tox.ini": ini}).run("c", "-e", "py") @@ -254,3 +271,33 @@ def test_provision_default_arguments_exists(tox_project: ToxProjectCreator, subc outcome = project.run(subcommand) for argument in ["result_json", "hash_seed", "discover", "list_dependencies"]: assert hasattr(outcome.state.conf.options, argument) + + +@pytest.mark.integration +@pytest.mark.usefixtures("_pypi_index_mirrored") +def test_provision_install_pkg_pep517( + tmp_path_factory: TempPathFactory, + tox_project: ToxProjectCreator, + pkg_builder: Callable[[Path, Path, Sequence[DistributionType], bool], Path], +) -> None: + example = tmp_path_factory.mktemp("example") + skeleton = """ + [build-system] + requires = ["setuptools"] + build-backend = "setuptools.build_meta" + [project] + name = "skeleton" + version = "0.1.1337" + """ + (example / "pyproject.toml").write_text(skeleton) + sdist = pkg_builder(example / "dist", example, ["sdist"], False) + + tox_ini = r""" + [tox] + requires = demo-pkg-inline + [testenv] + commands = python -c "print(42)" + """ + project = tox_project({"tox.ini": tox_ini}, base=example) + result = project.run("r", "-e", "py", "--installpkg", str(sdist), "--notest") + result.assert_success()