diff --git a/docs/changelog/2698.bugfix.rst b/docs/changelog/2698.bugfix.rst new file mode 100644 index 000000000..71e656513 --- /dev/null +++ b/docs/changelog/2698.bugfix.rst @@ -0,0 +1,2 @@ +``TOX_SKIP_ENV`` environment variable now works again, and can also be set via the CLI argument ``--skip-env`` +for any command where ``-e`` can be set - by :user:`mgedmin`. diff --git a/src/tox/session/env_select.py b/src/tox/session/env_select.py index 15d41c43b..fcf288939 100644 --- a/src/tox/session/env_select.py +++ b/src/tox/session/env_select.py @@ -1,5 +1,7 @@ from __future__ import annotations +import logging +import re from argparse import ArgumentParser from collections import Counter from dataclasses import dataclass @@ -21,6 +23,9 @@ from tox.session.state import State +LOGGER = logging.getLogger(__name__) + + class CliEnv: """CLI tox env selection""" @@ -88,6 +93,8 @@ def register_env_select_flags( add_to.add_argument("-m", dest="labels", metavar="label", help=help_msg, default=[], type=str, nargs="+") help_msg = "factors to evaluate" add_to.add_argument("-f", dest="factors", metavar="factor", help=help_msg, default=[], type=str, nargs="+") + help_msg = "exclude all environments selected that match this regular expression" + add_to.add_argument("--skip-env", dest="skip_env", metavar="re", help=help_msg, default="", type=str) return add_to @@ -106,6 +113,7 @@ def __init__(self, state: State) -> None: # to load the package environments of a run environments we need the run environment builder # to load labels we need core + the run environment self.on_empty_fallback_py = True + self._warned_about: set[str] = set() #: shared set of skipped environments that were already warned about self._state = state self._cli_envs: CliEnv | None = getattr(self._state.conf.options, "env", None) self._defined_envs_: None | dict[str, _ToxEnvInfo] = None @@ -118,6 +126,8 @@ def __init__(self, state: State) -> None: self._provision: None | tuple[bool, str, MemoryLoader] = None self._state.conf.core.add_config("labels", Dict[str, EnvList], {}, "core labels") + tox_env_filter_regex = getattr(state.conf.options, "skip_env", "").strip() + self._filter_re = re.compile(tox_env_filter_regex) if tox_env_filter_regex else None def _collect_names(self) -> Iterator[tuple[Iterable[str], bool]]: """:return: sources of tox environments defined with name and if is marked as target to run""" @@ -320,14 +330,17 @@ def iter( :return: an iteration of tox environments """ - ignore_envs: set[str] = set() for name, env_info in self._defined_envs.items(): if only_active and not env_info.is_active: continue if not package and not isinstance(env_info.env, RunToxEnv): continue + if self._filter_re is not None and self._filter_re.match(name): + if name not in self._warned_about: + self._warned_about.add(name) + LOGGER.warning("skip environment %s, matches filter %r", name, self._filter_re.pattern) + continue yield name - ignore_envs.add(name) def ensure_only_run_env_is_active(self) -> None: envs, active = self._defined_envs, self._env_name_to_active() diff --git a/tests/config/cli/test_cli_env_var.py b/tests/config/cli/test_cli_env_var.py index 89f156b29..831f9daeb 100644 --- a/tests/config/cli/test_cli_env_var.py +++ b/tests/config/cli/test_cli_env_var.py @@ -62,6 +62,7 @@ def test_verbose_no_test() -> None: "index_url": [], "factors": [], "labels": [], + "skip_env": "", } @@ -120,6 +121,7 @@ def test_env_var_exhaustive_parallel_values( "factors": [], "labels": [], "exit_and_dump_after": 0, + "skip_env": "", } assert options.parsed.verbosity == 4 assert options.cmd_handlers == core_handlers diff --git a/tests/config/cli/test_cli_ini.py b/tests/config/cli/test_cli_ini.py index cfb8a2835..6d7e245b9 100644 --- a/tests/config/cli/test_cli_ini.py +++ b/tests/config/cli/test_cli_ini.py @@ -99,6 +99,7 @@ def default_options(tmp_path: Path) -> dict[str, Any]: "factors": [], "labels": [], "exit_and_dump_after": 0, + "skip_env": "", } @@ -134,6 +135,7 @@ def test_ini_exhaustive_parallel_values(exhaustive_ini: Path, core_handlers: dic "factors": [], "labels": [], "exit_and_dump_after": 0, + "skip_env": "", } assert options.parsed.verbosity == 4 assert options.cmd_handlers == core_handlers diff --git a/tests/session/test_env_select.py b/tests/session/test_env_select.py index 90d72a430..b6683541f 100644 --- a/tests/session/test_env_select.py +++ b/tests/session/test_env_select.py @@ -1,6 +1,6 @@ from __future__ import annotations -from tox.pytest import ToxProjectCreator +from tox.pytest import MonkeyPatch, ToxProjectCreator def test_label_core_can_define(tox_project: ToxProjectCreator) -> None: @@ -70,3 +70,27 @@ def test_factor_select(tox_project: ToxProjectCreator) -> None: outcome = project.run("l", "--no-desc", "-f", "cov", "django20") outcome.assert_success() outcome.assert_out_err("py310-django20-cov\npy39-django20-cov\n", "") + + +def test_tox_skip_env(tox_project: ToxProjectCreator, monkeypatch: MonkeyPatch) -> None: + monkeypatch.setenv("TOX_SKIP_ENV", "m[y]py") + project = tox_project({"tox.ini": "[tox]\nenv_list = py3{10,9},mypy"}) + outcome = project.run("l", "--no-desc", "-q") + outcome.assert_success() + outcome.assert_out_err("py310\npy39\n", "") + + +def test_tox_skip_env_cli(tox_project: ToxProjectCreator, monkeypatch: MonkeyPatch) -> None: + monkeypatch.delenv("TOX_SKIP_ENV", raising=False) + project = tox_project({"tox.ini": "[tox]\nenv_list = py3{10,9},mypy"}) + outcome = project.run("l", "--no-desc", "-q", "--skip-env", "m[y]py") + outcome.assert_success() + outcome.assert_out_err("py310\npy39\n", "") + + +def test_tox_skip_env_logs(tox_project: ToxProjectCreator, monkeypatch: MonkeyPatch) -> None: + monkeypatch.setenv("TOX_SKIP_ENV", "m[y]py") + project = tox_project({"tox.ini": "[tox]\nenv_list = py3{10,9},mypy"}) + outcome = project.run("l", "--no-desc") + outcome.assert_success() + outcome.assert_out_err("ROOT: skip environment mypy, matches filter 'm[y]py'\npy310\npy39\n", "")