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
6 changes: 6 additions & 0 deletions docs/changelog/3598.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
The :func:`tox_extend_envs() hook <tox.plugin.spec.tox_extend_envs>`
recently added in :pull:`3591` turned out to not work well with
``tox run``. It was fixed internally, not to exhaust the underlying
iterator on the first use.

-- by :user:`webknjaz`
8 changes: 6 additions & 2 deletions src/tox/config/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import os
from collections import OrderedDict, defaultdict
from itertools import chain
from itertools import chain, tee
from pathlib import Path
from typing import TYPE_CHECKING, Any, Iterable, Iterator, Sequence, TypeVar

Expand Down Expand Up @@ -81,7 +81,11 @@ def src_path(self) -> Path:

def __iter__(self) -> Iterator[str]:
""":return: an iterator that goes through existing environments"""
return chain(self._src.envs(self.core), self._extra_envs)
# NOTE: `tee(self._extra_envs)[1]` is necessary for compatibility with
# NOTE: Python 3.11 and older versions. Once Python 3.12 is the lowest
# NOTE: supported version, it can be changed to
# NOTE: `chain.from_iterable(tee(self._extra_envs, 1))`.
return chain(self._src.envs(self.core), tee(self._extra_envs)[1])

def sections(self) -> Iterator[Section]:
yield from self._src.sections()
Expand Down
4 changes: 2 additions & 2 deletions src/tox/session/state.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from __future__ import annotations

import sys
from itertools import chain
from itertools import chain, tee
from typing import TYPE_CHECKING, Sequence

from tox.config.main import Config
Expand All @@ -20,7 +20,7 @@ class State:
"""Runtime state holder."""

def __init__(self, options: Options, args: Sequence[str]) -> None:
extended_envs = chain.from_iterable(MANAGER.tox_extend_envs())
(extended_envs,) = tee(chain.from_iterable(MANAGER.tox_extend_envs()), 1)
self.conf = Config.make(options.parsed, options.pos_args, options.source, extended_envs)
self.conf.core.add_constant(
keys=["on_platform"],
Expand Down
14 changes: 14 additions & 0 deletions tests/plugin/test_inline.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import sys
from typing import TYPE_CHECKING

if TYPE_CHECKING:
Expand Down Expand Up @@ -43,6 +44,7 @@ def tox_extend_envs() -> tuple[str]:
def tox_add_core_config(core_conf: ConfigSet, state: State) -> None: # noqa: ARG001
in_memory_config_loader = MemoryLoader(
base=["sentinel-base"],
commands_pre=["sentinel-cmd"],
description="sentinel-description",
)
state.conf.memory_seed_loaders[env_name].append(
Expand All @@ -59,3 +61,15 @@ def tox_add_core_config(core_conf: ConfigSet, state: State) -> None: # noqa: AR
tox_config_result = project.run("config", "-e", "sentinel-env-name", "-qq")
tox_config_result.assert_success()
assert "base = sentinel-base" in tox_config_result.out

tox_run_result = project.run("run", "-e", "sentinel-env-name", "-q")
tox_run_result.assert_failed()
underlying_expected_oserror_msg = (
"[WinError 2] The system cannot find the file specified"
if sys.platform == "win32"
else "[Errno 2] No such file or directory: 'sentinel-cmd'"
)
expected_cmd_lookup_error_txt = (
f"sentinel-env-name: Exception running subprocess {underlying_expected_oserror_msg!s}\n"
)
assert expected_cmd_lookup_error_txt in tox_run_result.out