Repository: tox-dev/tox

Issue:
tox.ini parsing regression with globbed modules
Our CI suddenly stopped working with `tox==4.0.13`.  `4.0.12` works as expected.  The command is `tox -l`

config file has globbed submodules which look like this: ` [mypy-rest_framework.compat.*]`. Tox seems to choke here.

https://github.com/tfranzel/drf-spectacular/blob/7bfcfb16fe63556f0c6044a4fda4d41cca058051/tox.ini#L97

```
File "/opt/hostedtoolcache/Python/3.10.9/x64/bin/tox", line 8, in <module>
    sys.exit(run())
  File "/opt/hostedtoolcache/Python/3.10.9/x64/lib/python3.10/site-packages/tox/run.py", line 19, in run
    result = main(sys.argv[1:] if args is None else args)
  File "/opt/hostedtoolcache/Python/3.10.9/x64/lib/python3.10/site-packages/tox/run.py", line 45, in main
    result = handler(state)
  File "/opt/hostedtoolcache/Python/3.10.9/x64/lib/python3.10/site-packages/tox/session/cmd/legacy.py", line 105, in legacy
    return list_env(state)
  File "/opt/hostedtoolcache/Python/3.10.9/x64/lib/python3.10/site-packages/tox/session/cmd/list_env.py", line 27, in list_env
    active = dict.fromkeys(state.envs.iter())
  File "/opt/hostedtoolcache/Python/3.10.9/x64/lib/python3.10/site-packages/tox/session/env_select.py", line 333, in iter
    for name, env_info in self._defined_envs.items():
  File "/opt/hostedtoolcache/Python/3.10.9/x64/lib/python3.10/site-packages/tox/session/env_select.py", line 176, in _defined_envs
    env_name_to_active = self._env_name_to_active()
  File "/opt/hostedtoolcache/Python/3.10.9/x64/lib/python3.10/site-packages/tox/session/env_select.py", line [15](https://github.com/tfranzel/drf-spectacular/actions/runs/3721512771/jobs/6316917714#step:5:16)1, in _env_name_to_active
    for name in a_collection:
  File "/opt/hostedtoolcache/Python/3.10.9/x64/lib/python3.10/site-packages/tox/config/source/ini.py", line 70, in envs
    for name in self._discover_tox_envs(core_config):
  File "/opt/hostedtoolcache/Python/3.10.9/x64/lib/python3.10/site-packages/tox/config/source/ini.py", line 86, in _discover_tox_envs
    register_factors(section.names)
  File "/opt/hostedtoolcache/Python/3.10.9/x64/lib/python3.10/site-packages/tox/config/source/ini_section.py", line [18](https://github.com/tfranzel/drf-spectacular/actions/runs/3721512771/jobs/6316917714#step:5:19), in names
    elements = list(extend_factors(self.name))
  File "/opt/hostedtoolcache/Python/3.10.9/x64/lib/python3.10/site-packages/tox/config/loader/ini/factor.py", line 41, in extend_factors
    for group in find_factor_groups(value):
  File "/opt/hostedtoolcache/Python/3.10.9/x64/lib/python3.10/site-packages/tox/config/loader/ini/factor.py", line 65, in find_factor_groups
    for env in expand_env_with_negation(value):
  File "/opt/hostedtoolcache/Python/3.10.9/x64/lib/python3.10/site-packages/tox/config/loader/ini/factor.py", line 80, in expand_env_with_negation
    raise ValueError(variant_str)
ValueError: mypy-rest_framework.compat.*
```

Discussion:

Patch:
diff --git a/docs/changelog/2746.bugfix.rst b/docs/changelog/2746.bugfix.rst
new file mode 100644
index 00000000..074ca2a1
--- /dev/null
+++ b/docs/changelog/2746.bugfix.rst
@@ -0,0 +1,2 @@
+Do not include non test environment sections or factor filters in INI configuration to factor discovery - by
+:user:`gaborbernat`.
diff --git a/src/tox/config/loader/ini/factor.py b/src/tox/config/loader/ini/factor.py
index 731fe610..bc121a65 100644
--- a/src/tox/config/loader/ini/factor.py
+++ b/src/tox/config/loader/ini/factor.py
@@ -73,7 +73,7 @@ def expand_env_with_negation(value: str) -> Iterator[str]:
         if key:
             group_str = "".join(group).strip()
             elements = re.split(r"{([^}]+)}", group_str)
-            parts = [re.sub(r"\s+", "", elem).split(",") for elem in elements]
+            parts = [[i.strip() for i in elem.split(",")] for elem in elements]
             for variant in product(*parts):
                 variant_str = "".join(variant)
                 if not re.fullmatch(r"!?[\w._][\w._-]*", variant_str):
diff --git a/src/tox/config/source/ini.py b/src/tox/config/source/ini.py
index ee3ed77c..f3e0c083 100644
--- a/src/tox/config/source/ini.py
+++ b/src/tox/config/source/ini.py
@@ -83,9 +83,9 @@ class IniSource(Source):
 
         # discover all additional defined environments, including generative section headers
         for section in self.sections():
-            register_factors(section.names)
-            for name in section.names:
-                if section.is_test_env:
+            if section.is_test_env:
+                register_factors(section.names)
+                for name in section.names:
                     self._section_mapping[name].append(section.key)
                     yield name
         # add all conditional markers that are not part of the explicitly defined sections


Test patch:
diff --git a/tests/config/source/test_source_ini.py b/tests/config/source/test_source_ini.py
index c3162486..5d61290b 100644
--- a/tests/config/source/test_source_ini.py
+++ b/tests/config/source/test_source_ini.py
@@ -10,3 +10,15 @@ def test_source_ini_with_interpolated(tmp_path: Path) -> None:
     loader = IniSource(tmp_path, content="[tox]\na = %(c)s").get_loader(Section(None, "tox"), {})
     assert loader is not None
     loader.load_raw("a", None, None)
+
+
+def test_source_ini_ignore_non_testenv_sections(tmp_path: Path) -> None:
+    loader = IniSource(tmp_path, content="[mypy-rest_framework.compat.*]")
+    res = list(loader.envs({"env_list": []}))  # type: ignore
+    assert not res
+
+
+def test_source_ini_ignore_invalid_factor_filters(tmp_path: Path) -> None:
+    loader = IniSource(tmp_path, content="[a]\nb= if c: d")
+    res = list(loader.envs({"env_list": []}))  # type: ignore
+    assert not res