Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provision: ignore other test environments #2865

Merged
merged 7 commits into from
Jan 16, 2023
4 changes: 4 additions & 0 deletions docs/changelog/2862.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
The provision environment (``.tox``) will never inherit from ``testenv``.
During provisioning, other test environments are not processed, allowing the
use of keys and values that may be registered by later tox version or
provisioned plugins - by :user:`masenf`.
7 changes: 7 additions & 0 deletions docs/upgrading.rst
Original file line number Diff line number Diff line change
Expand Up @@ -317,3 +317,10 @@ version is above the version this feature was added to it, for example for setup
[testenv:dev]
deps = setuptools>=64
package = editable

Provisioning environment
------------------------

The provisioning environment is triggered when ``minversion`` or ``requires`` are specified and the current environment
does not satisfy the requirement. In tox 4, the provisioning environment (``.tox`` by default) must be explicitly
configured and will not inherit values from ``[testenv]`` section.
3 changes: 2 additions & 1 deletion src/tox/provision.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ def add_tox_requires_min_version(reqs: list[Requirement]) -> list[Requirement]:
recreate=state.conf.options.recreate and not state.conf.options.no_recreate_provision,
)
provision_tox_env: str = state.conf.core["provision_tox_env"]
state.envs._mark_provision(bool(missing), provision_tox_env, loader)
state.conf.memory_seed_loaders[provision_tox_env].append(loader)
state.envs._mark_provision(bool(missing), provision_tox_env)

from tox.plugin.manager import MANAGER

Expand Down
13 changes: 7 additions & 6 deletions src/tox/session/env_select.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
from tox.tox_env.register import REGISTER
from tox.tox_env.runner import RunToxEnv

from ..config.loader.memory import MemoryLoader
from ..config.types import EnvList
from ..report import HandledError
from ..tox_env.errors import Skip
Expand Down Expand Up @@ -134,7 +133,7 @@ def __init__(self, state: State) -> None:
self._manager = MANAGER
self._log_handler = self._state._options.log_handler
self._journal = self._state._journal
self._provision: None | tuple[bool, str, MemoryLoader] = None
self._provision: None | tuple[bool, str] = None

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

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

def _mark_provision(self, on: bool, provision_tox_env: str, loader: MemoryLoader) -> None:
self._provision = on, provision_tox_env, loader
def _mark_provision(self, on: bool, provision_tox_env: str) -> None:
self._provision = on, provision_tox_env


__all__ = [
Expand Down
27 changes: 27 additions & 0 deletions tests/demo_pkg_inline/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,38 @@
version = "1.0.0"
dist_info = "{}-{}.dist-info".format(name, version)
logic = "{}/__init__.py".format(name)
plugin = "{}/example_plugin.py".format(name)
entry_points = "{}/entry_points.txt".format(dist_info)
metadata = "{}/METADATA".format(dist_info)
wheel = "{}/WHEEL".format(dist_info)
record = "{}/RECORD".format(dist_info)
content = {
logic: "def do():\n print('greetings from {}')".format(name),
plugin: dedent(
"""
try:
from tox.plugin import impl
from tox.tox_env.python.virtual_env.runner import VirtualEnvRunner
from tox.tox_env.register import ToxEnvRegister
except ImportError:
pass
else:
class ExampleVirtualEnvRunner(VirtualEnvRunner):
@staticmethod
def id() -> str:
return "example"
@impl
def tox_register_tox_env(register: ToxEnvRegister) -> None:
register.add_run_env(ExampleVirtualEnvRunner)
""",
),
entry_points: dedent(
"""
[tox]
example = {}.example_plugin""".format(
name,
),
),
metadata: """
Metadata-Version: 2.1
Name: {}
Expand Down
25 changes: 25 additions & 0 deletions tests/test_provision.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,3 +187,28 @@ def test_provision_no_recreate_json(tox_project: ToxProjectCreator) -> None:
with (project.path / "out.json").open() as file_handler:
requires = json.load(file_handler)
assert requires == {"minversion": None, "requires": ["p", "tox"]}


@pytest.mark.integration()
@pytest.mark.usefixtures("_pypi_index_self")
@pytest.mark.parametrize("plugin_testenv", ["testenv", "testenv:a"])
def test_provision_plugin_runner(tox_project: ToxProjectCreator, tmp_path: Path, plugin_testenv: str) -> None:
"""Ensure that testenv runner doesn't affect the provision env."""
log = tmp_path / "out.log"
proj = tox_project({"tox.ini": f"[tox]\nrequires=demo-pkg-inline\n[{plugin_testenv}]\nrunner=example"})
result_first = proj.run("r", "-e", "py", "--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"
)
assert prov_msg in result_first.out


@pytest.mark.integration()
def test_provision_plugin_runner_in_provision(tox_project: ToxProjectCreator, tmp_path: Path) -> None:
"""Ensure that provision environment can be explicitly configured."""
log = tmp_path / "out.log"
proj = tox_project({"tox.ini": "[tox]\nrequires=somepkg123xyz\n[testenv:.tox]\nrunner=example"})
with pytest.raises(KeyError, match="example"):
proj.run("r", "-e", "py", "--result-json", str(log))