Skip to content

Commit e4c65bb

Browse files
authored
Provision: ignore other test environments (#2865)
Fix #2862
1 parent a2222e9 commit e4c65bb

File tree

6 files changed

+72
-7
lines changed

6 files changed

+72
-7
lines changed

docs/changelog/2862.bugfix.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
The provision environment (``.tox``) will never inherit from ``testenv``.
2+
During provisioning, other test environments are not processed, allowing the
3+
use of keys and values that may be registered by later tox version or
4+
provisioned plugins - by :user:`masenf`.

docs/upgrading.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,3 +317,10 @@ version is above the version this feature was added to it, for example for setup
317317
[testenv:dev]
318318
deps = setuptools>=64
319319
package = editable
320+
321+
Provisioning environment
322+
------------------------
323+
324+
The provisioning environment is triggered when ``minversion`` or ``requires`` are specified and the current environment
325+
does not satisfy the requirement. In tox 4, the provisioning environment (``.tox`` by default) must be explicitly
326+
configured and will not inherit values from ``[testenv]`` section.

src/tox/provision.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,8 @@ def add_tox_requires_min_version(reqs: list[Requirement]) -> list[Requirement]:
9797
recreate=state.conf.options.recreate and not state.conf.options.no_recreate_provision,
9898
)
9999
provision_tox_env: str = state.conf.core["provision_tox_env"]
100-
state.envs._mark_provision(bool(missing), provision_tox_env, loader)
100+
state.conf.memory_seed_loaders[provision_tox_env].append(loader)
101+
state.envs._mark_provision(bool(missing), provision_tox_env)
101102

102103
from tox.plugin.manager import MANAGER
103104

src/tox/session/env_select.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
from tox.tox_env.register import REGISTER
1414
from tox.tox_env.runner import RunToxEnv
1515

16-
from ..config.loader.memory import MemoryLoader
1716
from ..config.types import EnvList
1817
from ..report import HandledError
1918
from ..tox_env.errors import Skip
@@ -134,7 +133,7 @@ def __init__(self, state: State) -> None:
134133
self._manager = MANAGER
135134
self._log_handler = self._state._options.log_handler
136135
self._journal = self._state._journal
137-
self._provision: None | tuple[bool, str, MemoryLoader] = None
136+
self._provision: None | tuple[bool, str] = None
138137

139138
self._state.conf.core.add_config("labels", Dict[str, EnvList], {}, "core labels")
140139
tox_env_filter_regex = getattr(state.conf.options, "skip_env", "").strip()
@@ -242,10 +241,12 @@ def _finalize_config(self) -> None:
242241

243242
def _build_run_env(self, name: str) -> RunToxEnv | None:
244243
if self._provision is not None and self._provision[0] is False and name == self._provision[1]:
244+
# ignore provision env unless this is a provision run
245+
return None
246+
if self._provision is not None and self._provision[0] and name != self._provision[1]:
247+
# ignore other envs when this is a provision run
245248
return None
246249
env_conf = self._state.conf.get_env(name, package=False)
247-
if self._provision is not None and self._provision[1] == name:
248-
env_conf.loaders.insert(0, self._provision[2])
249250
desc = "the tox execute used to evaluate this environment"
250251
env_conf.add_config(keys="runner", desc=desc, of_type=str, default=self._state.conf.options.default_runner)
251252
runner = REGISTER.runner(cast(str, env_conf["runner"]))
@@ -367,8 +368,8 @@ def ensure_only_run_env_is_active(self) -> None:
367368
if invalid:
368369
raise HandledError(f"cannot run packaging environment(s) {','.join(invalid)}")
369370

370-
def _mark_provision(self, on: bool, provision_tox_env: str, loader: MemoryLoader) -> None:
371-
self._provision = on, provision_tox_env, loader
371+
def _mark_provision(self, on: bool, provision_tox_env: str) -> None:
372+
self._provision = on, provision_tox_env
372373

373374

374375
__all__ = [

tests/demo_pkg_inline/build.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,38 @@
1414
version = "1.0.0"
1515
dist_info = "{}-{}.dist-info".format(name, version)
1616
logic = "{}/__init__.py".format(name)
17+
plugin = "{}/example_plugin.py".format(name)
18+
entry_points = "{}/entry_points.txt".format(dist_info)
1719
metadata = "{}/METADATA".format(dist_info)
1820
wheel = "{}/WHEEL".format(dist_info)
1921
record = "{}/RECORD".format(dist_info)
2022
content = {
2123
logic: "def do():\n print('greetings from {}')".format(name),
24+
plugin: dedent(
25+
"""
26+
try:
27+
from tox.plugin import impl
28+
from tox.tox_env.python.virtual_env.runner import VirtualEnvRunner
29+
from tox.tox_env.register import ToxEnvRegister
30+
except ImportError:
31+
pass
32+
else:
33+
class ExampleVirtualEnvRunner(VirtualEnvRunner):
34+
@staticmethod
35+
def id() -> str:
36+
return "example"
37+
@impl
38+
def tox_register_tox_env(register: ToxEnvRegister) -> None:
39+
register.add_run_env(ExampleVirtualEnvRunner)
40+
""",
41+
),
42+
entry_points: dedent(
43+
"""
44+
[tox]
45+
example = {}.example_plugin""".format(
46+
name,
47+
),
48+
),
2249
metadata: """
2350
Metadata-Version: 2.1
2451
Name: {}

tests/test_provision.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,3 +187,28 @@ def test_provision_no_recreate_json(tox_project: ToxProjectCreator) -> None:
187187
with (project.path / "out.json").open() as file_handler:
188188
requires = json.load(file_handler)
189189
assert requires == {"minversion": None, "requires": ["p", "tox"]}
190+
191+
192+
@pytest.mark.integration()
193+
@pytest.mark.usefixtures("_pypi_index_self")
194+
@pytest.mark.parametrize("plugin_testenv", ["testenv", "testenv:a"])
195+
def test_provision_plugin_runner(tox_project: ToxProjectCreator, tmp_path: Path, plugin_testenv: str) -> None:
196+
"""Ensure that testenv runner doesn't affect the provision env."""
197+
log = tmp_path / "out.log"
198+
proj = tox_project({"tox.ini": f"[tox]\nrequires=demo-pkg-inline\n[{plugin_testenv}]\nrunner=example"})
199+
result_first = proj.run("r", "-e", "py", "--result-json", str(log))
200+
result_first.assert_success()
201+
prov_msg = (
202+
f"ROOT: will run in automatically provisioned tox, host {sys.executable} is missing"
203+
f" [requires (has)]: demo-pkg-inline"
204+
)
205+
assert prov_msg in result_first.out
206+
207+
208+
@pytest.mark.integration()
209+
def test_provision_plugin_runner_in_provision(tox_project: ToxProjectCreator, tmp_path: Path) -> None:
210+
"""Ensure that provision environment can be explicitly configured."""
211+
log = tmp_path / "out.log"
212+
proj = tox_project({"tox.ini": "[tox]\nrequires=somepkg123xyz\n[testenv:.tox]\nrunner=example"})
213+
with pytest.raises(KeyError, match="example"):
214+
proj.run("r", "-e", "py", "--result-json", str(log))

0 commit comments

Comments
 (0)