summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/changelog/1545.feature.rst1
-rw-r--r--docs/config.rst21
-rw-r--r--docs/example/basic.rst23
-rw-r--r--src/tox/config/__init__.py24
-rw-r--r--tests/unit/config/test_config.py9
5 files changed, 78 insertions, 0 deletions
diff --git a/docs/changelog/1545.feature.rst b/docs/changelog/1545.feature.rst
new file mode 100644
index 00000000..755145e9
--- /dev/null
+++ b/docs/changelog/1545.feature.rst
@@ -0,0 +1 @@
+Allow generative section name expansion. - by :user:`bruchar1`
diff --git a/docs/config.rst b/docs/config.rst
index 2f207321..c159a508 100644
--- a/docs/config.rst
+++ b/docs/config.rst
@@ -830,6 +830,27 @@ are stripped, so the following line defines the same environment names::
flake
+.. _generative-sections:
+
+Generative section names
+++++++++++++++++++++++++
+
+.. versionadded:: 3.15
+
+Using similar syntax, it is possible to generate sections::
+
+ [testenv:py{27,36}-flake]
+
+This is equivalent to defining distinct sections::
+
+ $ tox -a
+ py27-flake
+ py36-flake
+
+It is useful when you need an environment different from the default one,
+but still want to take advantage of factor-conditional settings.
+
+
.. _factors:
Factors and factor-conditional settings
diff --git a/docs/example/basic.rst b/docs/example/basic.rst
index 6030bc17..6e6edd0e 100644
--- a/docs/example/basic.rst
+++ b/docs/example/basic.rst
@@ -372,6 +372,29 @@ use :ref:`generative-envlist` and :ref:`conditional settings <factors>` to expre
# mocking sqlite on 2.7 and 3.6 if factor "sqlite" is present
py{27,36}-sqlite: mock
+
+Using generative section names
+------------------------------
+
+Suppose you have some binary packages, and need to run tests both in 32 and 64 bits.
+You also want an environment to create your virtual env for the developers.
+
+.. code-block:: ini
+
+ [testenv]
+ basepython =
+ py38-x86: python3.8-32
+ py38-x64: python3.8-64
+ commands = pytest
+
+ [testenv:py38-{x86,x64}-venv]
+ usedevelop = true
+ envdir =
+ x86: .venv-x86
+ x64: .venv-x64
+ commands =
+
+
Prevent symbolic links in virtualenv
------------------------------------
By default virtualenv will use symlinks to point to the system's python files, modules, etc.
diff --git a/src/tox/config/__init__.py b/src/tox/config/__init__.py
index 85e99185..b33dbc39 100644
--- a/src/tox/config/__init__.py
+++ b/src/tox/config/__init__.py
@@ -1015,6 +1015,8 @@ class ParseIni(object):
self._cfg = py.iniconfig.IniConfig(config.toxinipath, ini_data)
previous_line_of = self._cfg.lineof
+ self.expand_section_names(self._cfg)
+
def line_of_default_to_zero(section, name=None):
at = previous_line_of(section, name=name)
if at is None:
@@ -1353,6 +1355,28 @@ class ParseIni(object):
raise tox.exception.ConfigError(msg)
return env_list, all_envs, _split_env(from_config), envlist_explicit
+ @staticmethod
+ def expand_section_names(config):
+ """Generative section names.
+
+ Allow writing section as [testenv:py{36,37}-cov]
+ The parser will see it as two different sections: [testenv:py36-cov], [testenv:py37-cov]
+
+ """
+ factor_re = re.compile(r"\{\s*([\w\s,]+)\s*\}")
+ split_re = re.compile(r"\s*,\s*")
+ to_remove = set()
+ for section in list(config.sections):
+ split_section = factor_re.split(section)
+ for parts in itertools.product(*map(split_re.split, split_section)):
+ section_name = "".join(parts)
+ if section_name not in config.sections:
+ config.sections[section_name] = config.sections[section]
+ to_remove.add(section)
+
+ for section in to_remove:
+ del config.sections[section]
+
def _split_env(env):
"""if handed a list, action="append" was used for -e """
diff --git a/tests/unit/config/test_config.py b/tests/unit/config/test_config.py
index 56e849e6..ba21a25b 100644
--- a/tests/unit/config/test_config.py
+++ b/tests/unit/config/test_config.py
@@ -859,6 +859,15 @@ class TestIniParser:
(msg,) = excinfo.value.args
assert msg == "key5: boolean value 'yes' needs to be 'True' or 'False'"
+ def test_expand_section_name(self, newconfig):
+ config = newconfig(
+ """
+ [testenv:custom-{one,two,three}-{four,five}-six]
+ """
+ )
+ assert "testenv:custom-one-five-six" in config._cfg.sections
+ assert "testenv:custom-{one,two,three}-{four,five}-six" not in config._cfg.sections
+
class TestIniParserPrefix:
def test_basic_section_access(self, newconfig):