Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/changelog/3600.bugfix.rst
Original file line number Diff line number Diff line change
@@ -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`
12 changes: 8 additions & 4 deletions src/tox/provision.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
4 changes: 3 additions & 1 deletion src/tox/tox_env/package.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down
63 changes: 55 additions & 8 deletions tests/test_provision.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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()