Repository: tox-dev/tox

Issue:
option to skip a factor level
For example if ``{py27,py36}-{nocov,cov,diffcov}``. @warsaw

In this case the user might want to not run all non diffcov envs.

Discussion:

Patch:
diff --git a/changelog/824.feature.rst b/changelog/824.feature.rst
new file mode 100644
index 00000000..aae99136
--- /dev/null
+++ b/changelog/824.feature.rst
@@ -0,0 +1,1 @@
+use the os environment variable ``TOX_SKIP_ENV`` to filter out tox environment names from the run list (set by :confval:`envlist`)  - by :user:`gaborbernat`
diff --git a/doc/config.rst b/doc/config.rst
index 89ce7a3a..7c33646a 100644
--- a/doc/config.rst
+++ b/doc/config.rst
@@ -76,6 +76,13 @@ and will first lookup global tox settings in this section:
     * environment variable ``TOXENV``
     * ``tox.ini`` file's ``envlist``
 
+    .. versionadded:: 3.4.0
+
+        What tox environments are ran during the tox invocation can be further filtered
+        via the operating system environment variable ``TOX_SKIP_ENV`` regular expression
+        (e.g. ``py27.*`` means **don't** evaluate environments that start with the key ``py27``).
+        Skipped environments will be logged at level two verbosity level.
+
 .. confval:: ignore_basepython_conflict=True|False(default)
 
     .. versionadded:: 3.1.0
diff --git a/src/tox/session.py b/src/tox/session.py
index 7da1efcd..dc25a277 100644
--- a/src/tox/session.py
+++ b/src/tox/session.py
@@ -381,7 +381,7 @@ class Session:
         self._spec2pkg = {}
         self._name2venv = {}
         try:
-            self.venvlist = [self.getvenv(x) for x in self.config.envlist]
+            self.venvlist = [self.getvenv(x) for x in self.evaluated_env_list()]
         except LookupError:
             raise SystemExit(1)
         except tox.exception.ConfigError as e:
@@ -389,6 +389,18 @@ class Session:
             raise SystemExit(1)
         self._actions = []
 
+    def evaluated_env_list(self):
+        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 in self.config.envlist:
+            if tox_env_filter_re is not None and tox_env_filter_re.match(name):
+                msg = "skip environment {}, matches filter {!r}".format(
+                    name, tox_env_filter_re.pattern
+                )
+                self.report.verbosity1(msg)
+                continue
+            yield name
+
     @property
     def hook(self):
         return self.config.pluginmanager.hook


Test patch:
diff --git a/tests/unit/session/test_session.py b/tests/unit/session/test_session.py
index 90b1b992..0e93f588 100644
--- a/tests/unit/session/test_session.py
+++ b/tests/unit/session/test_session.py
@@ -140,3 +140,73 @@ def test_skip_install_skip_package(cmd, initproj, mock_venv):
     )
     result = cmd("--notest")
     assert result.ret == 0
+
+
+@pytest.fixture()
+def venv_filter_project(initproj, cmd):
+    def func(*args):
+        initproj(
+            "pkg123-0.7",
+            filedefs={
+                "tox.ini": """
+                    [tox]
+                    envlist = {py27,py36}-{nocov,cov,diffcov}{,-extra}
+                    skipsdist = true
+
+                    [testenv]
+                    skip_install = true
+                    commands = python -c 'print("{envname}")'
+                """
+            },
+        )
+        result = cmd(*args)
+        assert result.ret == 0
+        active = [i.name for i in result.session.venvlist]
+        return active, result
+
+    yield func
+
+
+def test_venv_filter_empty_all_active(venv_filter_project, monkeypatch):
+    monkeypatch.delenv("TOX_SKIP_ENV", raising=False)
+    active, result = venv_filter_project("-a")
+    assert result.outlines == [
+        "py27-nocov",
+        "py27-nocov-extra",
+        "py27-cov",
+        "py27-cov-extra",
+        "py27-diffcov",
+        "py27-diffcov-extra",
+        "py36-nocov",
+        "py36-nocov-extra",
+        "py36-cov",
+        "py36-cov-extra",
+        "py36-diffcov",
+        "py36-diffcov-extra",
+    ]
+    assert active == result.outlines
+
+
+def test_venv_filter_match_all_none_active(venv_filter_project, monkeypatch):
+    monkeypatch.setenv("TOX_SKIP_ENV", ".*")
+    active, result = venv_filter_project("-a")
+    assert not active
+    existing_envs = result.outlines
+
+    _, result = venv_filter_project("-avv")
+    for name in existing_envs:
+        msg = "skip environment {}, matches filter '.*'".format(name)
+        assert msg in result.outlines
+
+
+def test_venv_filter_match_some_some_active(venv_filter_project, monkeypatch):
+    monkeypatch.setenv("TOX_SKIP_ENV", "py27.*")
+    active, result = venv_filter_project("-avvv")
+    assert active == [
+        "py36-nocov",
+        "py36-nocov-extra",
+        "py36-cov",
+        "py36-cov-extra",
+        "py36-diffcov",
+        "py36-diffcov-extra",
+    ]