From 8a28e88be122eaf0c9f3e737b6555637241399e0 Mon Sep 17 00:00:00 2001 From: Marius Gedminas Date: Wed, 14 Dec 2022 11:11:23 +0200 Subject: [PATCH 1/9] Restore TOX_SKIP_ENV filtering This is still documented in docs/config.rst. The only missing thing is the reporting at verbosity level 2. Fixes #2698. --- src/tox/session/env_select.py | 8 ++++++-- tests/session/test_env_select.py | 12 ++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/tox/session/env_select.py b/src/tox/session/env_select.py index 15d41c43b..2ef9c2032 100644 --- a/src/tox/session/env_select.py +++ b/src/tox/session/env_select.py @@ -1,5 +1,7 @@ from __future__ import annotations +import os +import re from argparse import ArgumentParser from collections import Counter from dataclasses import dataclass @@ -320,14 +322,16 @@ def iter( :return: an iteration of tox environments """ - ignore_envs: set[str] = set() + tox_env_filter = os.environ.get("TOX_SKIP_ENV") + tox_env_filter_re = re.compile(tox_env_filter) if tox_env_filter is not None else None 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 tox_env_filter_re is not None and tox_env_filter_re.match(name): + 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/session/test_env_select.py b/tests/session/test_env_select.py index 90d72a430..e494038b5 100644 --- a/tests/session/test_env_select.py +++ b/tests/session/test_env_select.py @@ -70,3 +70,15 @@ 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) -> None: + monkeypatch.setenv("TOX_SKIP_ENV", "m[y]py") + ini = """ + [tox] + env_list = py3{10,9},mypy + """ + project = tox_project({"tox.ini": ini}) + outcome = project.run("l", "--no-desc") + outcome.assert_success() + outcome.assert_out_err("py310\npy39\n", "") From a6887fafdc941a493f66686386671f49f6a1b447 Mon Sep 17 00:00:00 2001 From: Marius Gedminas Date: Wed, 14 Dec 2022 11:15:52 +0200 Subject: [PATCH 2/9] Add a news fragment --- docs/changelog/2698.bugfix.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 docs/changelog/2698.bugfix.rst diff --git a/docs/changelog/2698.bugfix.rst b/docs/changelog/2698.bugfix.rst new file mode 100644 index 000000000..55534c50e --- /dev/null +++ b/docs/changelog/2698.bugfix.rst @@ -0,0 +1,2 @@ +``TOX_SKIP_ENV`` environment variable now works again (it was unintentionally +broken in the tox 4.0.0 release) From fe2d258432357332fe79830eabe085c938e137b9 Mon Sep 17 00:00:00 2001 From: Marius Gedminas Date: Wed, 14 Dec 2022 11:34:55 +0200 Subject: [PATCH 3/9] Log about skipped environments Logging this at DEBUG level (tox -vv) felt wrong to me, so I went with INFO (tox -v). --- src/tox/session/env_select.py | 10 ++++++++++ tests/session/test_env_select.py | 12 ++++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/tox/session/env_select.py b/src/tox/session/env_select.py index 2ef9c2032..cc18dc455 100644 --- a/src/tox/session/env_select.py +++ b/src/tox/session/env_select.py @@ -1,5 +1,6 @@ from __future__ import annotations +import logging import os import re from argparse import ArgumentParser @@ -23,6 +24,9 @@ from tox.session.state import State +LOGGER = logging.getLogger(__name__) + + class CliEnv: """CLI tox env selection""" @@ -103,6 +107,9 @@ class _ToxEnvInfo: class EnvSelector: + + _warned_about: set[str] = set() #: shared set of skipped environments that were already warned about + def __init__(self, state: State) -> None: # needs core to load the default tox environment list # to load the package environments of a run environments we need the run environment builder @@ -330,6 +337,9 @@ def iter( if not package and not isinstance(env_info.env, RunToxEnv): continue if tox_env_filter_re is not None and tox_env_filter_re.match(name): + if name not in self._warned_about: + LOGGER.info("skip environment %s, matches filter %r", name, tox_env_filter_re.pattern) + self._warned_about.add(name) continue yield name diff --git a/tests/session/test_env_select.py b/tests/session/test_env_select.py index e494038b5..70c4b411c 100644 --- a/tests/session/test_env_select.py +++ b/tests/session/test_env_select.py @@ -82,3 +82,15 @@ def test_tox_skip_env(tox_project: ToxProjectCreator, monkeypatch) -> None: outcome = project.run("l", "--no-desc") outcome.assert_success() outcome.assert_out_err("py310\npy39\n", "") + + +def test_tox_skip_env_logs(tox_project: ToxProjectCreator, monkeypatch) -> None: + monkeypatch.setenv("TOX_SKIP_ENV", "m[y]py") + ini = """ + [tox] + env_list = py3{10,9},mypy + """ + project = tox_project({"tox.ini": ini}) + outcome = project.run("l", "--no-desc", "-v") + outcome.assert_success() + outcome.assert_out_err("ROOT: skip environment mypy, matches filter 'm[y]py'\npy310\npy39\n", "") From c101da7de759f24ec8355a736cc335c80cb45026 Mon Sep 17 00:00:00 2001 From: Marius Gedminas Date: Wed, 14 Dec 2022 11:39:03 +0200 Subject: [PATCH 4/9] Add missing test fixture type annotations --- tests/session/test_env_select.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/session/test_env_select.py b/tests/session/test_env_select.py index 70c4b411c..1143222a6 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: @@ -72,7 +72,7 @@ def test_factor_select(tox_project: ToxProjectCreator) -> None: outcome.assert_out_err("py310-django20-cov\npy39-django20-cov\n", "") -def test_tox_skip_env(tox_project: ToxProjectCreator, monkeypatch) -> None: +def test_tox_skip_env(tox_project: ToxProjectCreator, monkeypatch: MonkeyPatch) -> None: monkeypatch.setenv("TOX_SKIP_ENV", "m[y]py") ini = """ [tox] @@ -84,7 +84,7 @@ def test_tox_skip_env(tox_project: ToxProjectCreator, monkeypatch) -> None: outcome.assert_out_err("py310\npy39\n", "") -def test_tox_skip_env_logs(tox_project: ToxProjectCreator, monkeypatch) -> None: +def test_tox_skip_env_logs(tox_project: ToxProjectCreator, monkeypatch: MonkeyPatch) -> None: monkeypatch.setenv("TOX_SKIP_ENV", "m[y]py") ini = """ [tox] From 89572aaeae4cb576916b0e1c31d1b44a4224996a Mon Sep 17 00:00:00 2001 From: Marius Gedminas Date: Wed, 14 Dec 2022 11:43:25 +0200 Subject: [PATCH 5/9] Change the log level to WARNING to match the documentation tox --help says verbosity level 2 is WARNING. --- src/tox/session/env_select.py | 2 +- tests/session/test_env_select.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tox/session/env_select.py b/src/tox/session/env_select.py index cc18dc455..40254c75a 100644 --- a/src/tox/session/env_select.py +++ b/src/tox/session/env_select.py @@ -338,7 +338,7 @@ def iter( continue if tox_env_filter_re is not None and tox_env_filter_re.match(name): if name not in self._warned_about: - LOGGER.info("skip environment %s, matches filter %r", name, tox_env_filter_re.pattern) + LOGGER.warning("skip environment %s, matches filter %r", name, tox_env_filter_re.pattern) self._warned_about.add(name) continue yield name diff --git a/tests/session/test_env_select.py b/tests/session/test_env_select.py index 1143222a6..c500909a2 100644 --- a/tests/session/test_env_select.py +++ b/tests/session/test_env_select.py @@ -79,7 +79,7 @@ def test_tox_skip_env(tox_project: ToxProjectCreator, monkeypatch: MonkeyPatch) env_list = py3{10,9},mypy """ project = tox_project({"tox.ini": ini}) - outcome = project.run("l", "--no-desc") + outcome = project.run("l", "--no-desc", "-q") outcome.assert_success() outcome.assert_out_err("py310\npy39\n", "") @@ -91,6 +91,6 @@ def test_tox_skip_env_logs(tox_project: ToxProjectCreator, monkeypatch: MonkeyPa env_list = py3{10,9},mypy """ project = tox_project({"tox.ini": ini}) - outcome = project.run("l", "--no-desc", "-v") + 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", "") From c13c9dbd42a3cfb9c5847b3732e022b829341439 Mon Sep 17 00:00:00 2001 From: Marius Gedminas Date: Wed, 14 Dec 2022 12:00:45 +0200 Subject: [PATCH 6/9] Use an instance attribute As far as I can tell, during runtime there's only one EnvSelector instance, so there's no danger of repeatedly warning about the same skipped environment when .iter() gets called several times. Fixes failing unit tests. (Oops. I only ran each test in isolation, because the entire test suite is a bit slow.) --- src/tox/session/env_select.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tox/session/env_select.py b/src/tox/session/env_select.py index 40254c75a..0ed76515d 100644 --- a/src/tox/session/env_select.py +++ b/src/tox/session/env_select.py @@ -108,13 +108,14 @@ class _ToxEnvInfo: class EnvSelector: - _warned_about: set[str] = set() #: shared set of skipped environments that were already warned about + _warned_about: set[str] #: shared set of skipped environments that were already warned about def __init__(self, state: State) -> None: # needs core to load the default tox environment list # 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() self._state = state self._cli_envs: CliEnv | None = getattr(self._state.conf.options, "env", None) self._defined_envs_: None | dict[str, _ToxEnvInfo] = None From 924e32c66a8452c1c67c07365f596f235206e35b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bern=C3=A1t=20G=C3=A1bor?= Date: Wed, 14 Dec 2022 18:44:28 -0800 Subject: [PATCH 7/9] PR feedback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Bernát Gábor --- docs/changelog/2698.bugfix.rst | 3 +-- src/tox/session/env_select.py | 13 +++++-------- tests/session/test_env_select.py | 12 ++---------- 3 files changed, 8 insertions(+), 20 deletions(-) diff --git a/docs/changelog/2698.bugfix.rst b/docs/changelog/2698.bugfix.rst index 55534c50e..9a54940dc 100644 --- a/docs/changelog/2698.bugfix.rst +++ b/docs/changelog/2698.bugfix.rst @@ -1,2 +1 @@ -``TOX_SKIP_ENV`` environment variable now works again (it was unintentionally -broken in the tox 4.0.0 release) +``TOX_SKIP_ENV`` environment variable now works again - by :user:`mgedmin`. diff --git a/src/tox/session/env_select.py b/src/tox/session/env_select.py index 0ed76515d..22f0507e3 100644 --- a/src/tox/session/env_select.py +++ b/src/tox/session/env_select.py @@ -107,15 +107,12 @@ class _ToxEnvInfo: class EnvSelector: - - _warned_about: set[str] #: shared set of skipped environments that were already warned about - def __init__(self, state: State) -> None: # needs core to load the default tox environment list # 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() + 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 @@ -128,6 +125,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 = os.environ.get("TOX_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""" @@ -330,17 +329,15 @@ def iter( :return: an iteration of tox environments """ - tox_env_filter = os.environ.get("TOX_SKIP_ENV") - tox_env_filter_re = re.compile(tox_env_filter) if tox_env_filter is not None else None 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 tox_env_filter_re is not None and tox_env_filter_re.match(name): + if self._filter_re is not None and self._filter_re.match(name): if name not in self._warned_about: - LOGGER.warning("skip environment %s, matches filter %r", name, tox_env_filter_re.pattern) self._warned_about.add(name) + LOGGER.warning("skip environment %s, matches filter %r", name, self._filter_re.pattern) continue yield name diff --git a/tests/session/test_env_select.py b/tests/session/test_env_select.py index c500909a2..d565fca96 100644 --- a/tests/session/test_env_select.py +++ b/tests/session/test_env_select.py @@ -74,11 +74,7 @@ def test_factor_select(tox_project: ToxProjectCreator) -> None: def test_tox_skip_env(tox_project: ToxProjectCreator, monkeypatch: MonkeyPatch) -> None: monkeypatch.setenv("TOX_SKIP_ENV", "m[y]py") - ini = """ - [tox] - env_list = py3{10,9},mypy - """ - project = tox_project({"tox.ini": ini}) + 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", "") @@ -86,11 +82,7 @@ def test_tox_skip_env(tox_project: ToxProjectCreator, monkeypatch: MonkeyPatch) def test_tox_skip_env_logs(tox_project: ToxProjectCreator, monkeypatch: MonkeyPatch) -> None: monkeypatch.setenv("TOX_SKIP_ENV", "m[y]py") - ini = """ - [tox] - env_list = py3{10,9},mypy - """ - project = tox_project({"tox.ini": ini}) + 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", "") From ad3e3ed0cc2ba5f2ef1d512fc7467de2c3d5a569 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bern=C3=A1t=20G=C3=A1bor?= Date: Wed, 14 Dec 2022 18:52:34 -0800 Subject: [PATCH 8/9] Add CLI flag too MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Bernát Gábor --- docs/changelog/2698.bugfix.rst | 3 ++- src/tox/session/env_select.py | 5 +++-- tests/session/test_env_select.py | 8 ++++++++ 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/docs/changelog/2698.bugfix.rst b/docs/changelog/2698.bugfix.rst index 9a54940dc..71e656513 100644 --- a/docs/changelog/2698.bugfix.rst +++ b/docs/changelog/2698.bugfix.rst @@ -1 +1,2 @@ -``TOX_SKIP_ENV`` environment variable now works again - by :user:`mgedmin`. +``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 22f0507e3..fcf288939 100644 --- a/src/tox/session/env_select.py +++ b/src/tox/session/env_select.py @@ -1,7 +1,6 @@ from __future__ import annotations import logging -import os import re from argparse import ArgumentParser from collections import Counter @@ -94,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 @@ -125,7 +126,7 @@ 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 = os.environ.get("TOX_SKIP_ENV", "").strip() + 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]]: diff --git a/tests/session/test_env_select.py b/tests/session/test_env_select.py index d565fca96..b6683541f 100644 --- a/tests/session/test_env_select.py +++ b/tests/session/test_env_select.py @@ -80,6 +80,14 @@ def test_tox_skip_env(tox_project: ToxProjectCreator, monkeypatch: MonkeyPatch) 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"}) From 449d884df2cfb3796aac9217528076cd49fa34f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bern=C3=A1t=20G=C3=A1bor?= Date: Wed, 14 Dec 2022 18:56:06 -0800 Subject: [PATCH 9/9] fix tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Bernát Gábor --- tests/config/cli/test_cli_env_var.py | 2 ++ tests/config/cli/test_cli_ini.py | 2 ++ 2 files changed, 4 insertions(+) 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