9 new commits in tox:
https://bitbucket.org/hpk42/tox/commits/de361f0a345c/
Changeset: de361f0a345c
User: suor
Date: 2014-06-20 12:10:04
Summary: First stab at multidimensional config
Affected #: 1 file
diff -r cefc0fd28dda72ac76a9170b4c586e0eb3f1d124 -r
de361f0a345ce1843425fa3128eab4ef8e437977 tox/_config.py
--- a/tox/_config.py
+++ b/tox/_config.py
@@ -303,10 +303,18 @@
config.skipsdist = reader.getbool(toxsection, "skipsdist", all_develop)
- def _makeenvconfig(self, name, section, subs, config):
+ # interpolate missing configs
+ for name in config.envlist:
+ if name not in config.envconfigs:
+ config.envconfigs[name] = \
+ self._makeenvconfig(name, "testenv", reader._subs, config,
+ factors=name.split('-'))
+
+ def _makeenvconfig(self, name, section, subs, config, factors=()):
vc = VenvConfig(envname=name)
vc.config = config
- reader = IniReader(self._cfg, fallbacksections=["testenv"])
+ reader = IniReader(self._cfg, fallbacksections=["testenv"],
+ factors=factors)
reader.addsubstitutions(**subs)
vc.develop = not config.option.installpkg and \
reader.getbool(section, "usedevelop", config.option.develop)
@@ -391,16 +399,33 @@
if not env:
env = os.environ.get("TOXENV", None)
if not env:
- envlist = reader.getlist(toxsection, "envlist", sep=",")
+ envstr = reader.getdefault(toxsection, "envlist", default="",
+ replace=False)
+ envlist = self._expand_envstr(envstr)
if not envlist:
envlist = self.config.envconfigs.keys()
return envlist
+ # TODO: move envsplit magic to _split_env()
envlist = _split_env(env)
if "ALL" in envlist:
envlist = list(self.config.envconfigs)
envlist.sort()
return envlist
+ def _expand_envstr(self, envstr):
+ from itertools import groupby, product, chain
+
+ # split by commas not in groups
+ tokens = re.split(r'(\{[^}]+\})|,', envstr)
+ envlist = [''.join(g).strip() for k, g in groupby(tokens, key=bool) if
k]
+
+ def expand(env):
+ tokens = re.split(r'\{([^}]+)\}', env)
+ parts = [token.split(',') for token in tokens]
+ return [''.join(variant) for variant in product(*parts)]
+
+ return list(chain(*map(expand, envlist)))
+
def _replace_forced_dep(self, name, config):
"""
Override the given dependency config name taking --force-dep-version
@@ -468,9 +493,10 @@
class IniReader:
- def __init__(self, cfgparser, fallbacksections=None):
+ def __init__(self, cfgparser, fallbacksections=None, factors=()):
self._cfg = cfgparser
self.fallbacksections = fallbacksections or []
+ self.factors = factors
self._subs = {}
self._subststack = []
@@ -586,18 +612,19 @@
return s
def getdefault(self, section, name, default=None, replace=True):
- try:
- x = self._cfg[section][name]
- except KeyError:
- for fallbacksection in self.fallbacksections:
- try:
- x = self._cfg[fallbacksection][name]
- except KeyError:
- pass
- else:
- break
- else:
- x = default
+ x = None
+ for s in [section] + self.fallbacksections:
+ try:
+ x = self._cfg[s][name]
+ break
+ except KeyError:
+ continue
+
+ if x is None:
+ x = default
+ else:
+ x = self._apply_factors(x)
+
if replace and x and hasattr(x, 'replace'):
self._subststack.append((section, name))
try:
@@ -607,6 +634,19 @@
#print "getdefault", section, name, "returned", repr(x)
return x
+ def _apply_factors(self, s):
+ def factor_line(line):
+ m = re.search(r'^(!)?(\w+)?\:\s*(.+)', line)
+ if not m:
+ return line
+
+ negate, factor, line = m.groups()
+ if bool(negate) ^ (factor in self.factors):
+ return line
+
+ lines = s.strip().splitlines()
+ return '\n'.join(filter(None, map(factor_line, lines)))
+
def _replace_env(self, match):
match_value = match.group('substitution_value')
if not match_value:
https://bitbucket.org/hpk42/tox/commits/26612b04a9fd/
Changeset: 26612b04a9fd
User: suor
Date: 2014-06-28 16:17:46
Summary: Fix regressions after adding factors
Affected #: 1 file
diff -r de361f0a345ce1843425fa3128eab4ef8e437977 -r
26612b04a9fd86702309469182b16f47ae56fc8d tox/_config.py
--- a/tox/_config.py
+++ b/tox/_config.py
@@ -636,7 +636,7 @@
def _apply_factors(self, s):
def factor_line(line):
- m = re.search(r'^(!)?(\w+)?\:\s*(.+)', line)
+ m = re.search(r'^(!)?(\w+)\:\s+(.+)', line)
if not m:
return line
https://bitbucket.org/hpk42/tox/commits/c3c0b8cf5e00/
Changeset: c3c0b8cf5e00
User: suor
Date: 2014-06-29 11:01:28
Summary: Parse env and args for factors, detect undefined envs
Affected #: 1 file
diff -r 26612b04a9fd86702309469182b16f47ae56fc8d -r
c3c0b8cf5e00e99761e892c113cbfe95150de3cb tox/_config.py
--- a/tox/_config.py
+++ b/tox/_config.py
@@ -6,6 +6,7 @@
import shlex
import string
import pkg_resources
+import itertools
from tox.interpreters import Interpreters
@@ -280,22 +281,19 @@
config.sdistsrc = reader.getpath(toxsection, "sdistsrc", None)
config.setupdir = reader.getpath(toxsection, "setupdir", "{toxinidir}")
config.logdir = config.toxworkdir.join("log")
- for sectionwrapper in self._cfg:
- section = sectionwrapper.name
- if section.startswith(testenvprefix):
- name = section[len(testenvprefix):]
- envconfig = self._makeenvconfig(name, section, reader._subs,
- config)
- config.envconfigs[name] = envconfig
- if not config.envconfigs:
- config.envconfigs['python'] = \
- self._makeenvconfig("python", "_xz_9", reader._subs, config)
- config.envlist = self._getenvlist(reader, toxsection)
- for name in config.envlist:
- if name not in config.envconfigs:
- if name in defaultenvs:
- config.envconfigs[name] = \
- self._makeenvconfig(name, "_xz_9", reader._subs, config)
+
+ config.envlist, all_envs = self._getenvdata(reader, toxsection)
+
+ # configure testenvs
+ known_factors = self._list_section_factors("testenv")
+ known_factors.update(defaultenvs)
+ known_factors.add("python")
+ for name in all_envs:
+ section = testenvprefix + name
+ factors = set(name.split('-'))
+ if section in self._cfg or factors & known_factors:
+ config.envconfigs[name] = \
+ self._makeenvconfig(name, section, reader._subs, config)
all_develop = all(name in config.envconfigs
and config.envconfigs[name].develop
@@ -303,18 +301,18 @@
config.skipsdist = reader.getbool(toxsection, "skipsdist", all_develop)
- # interpolate missing configs
- for name in config.envlist:
- if name not in config.envconfigs:
- config.envconfigs[name] = \
- self._makeenvconfig(name, "testenv", reader._subs, config,
- factors=name.split('-'))
+ def _list_section_factors(self, section):
+ factors = set()
+ if section in self._cfg:
+ for _, value in self._cfg[section].items():
+ factors.update(re.findall(r'^(!)?(\w+)\:\s+(.+)', value))
+ return factors
- def _makeenvconfig(self, name, section, subs, config, factors=()):
+ def _makeenvconfig(self, name, section, subs, config):
vc = VenvConfig(envname=name)
vc.config = config
reader = IniReader(self._cfg, fallbacksections=["testenv"],
- factors=factors)
+ factors=name.split('-'))
reader.addsubstitutions(**subs)
vc.develop = not config.option.installpkg and \
reader.getbool(section, "usedevelop", config.option.develop)
@@ -394,37 +392,25 @@
"'install_command' must contain '{packages}' substitution")
return vc
- def _getenvlist(self, reader, toxsection):
- env = self.config.option.env
- if not env:
- env = os.environ.get("TOXENV", None)
- if not env:
- envstr = reader.getdefault(toxsection, "envlist", default="",
- replace=False)
- envlist = self._expand_envstr(envstr)
- if not envlist:
- envlist = self.config.envconfigs.keys()
- return envlist
- # TODO: move envsplit magic to _split_env()
- envlist = _split_env(env)
- if "ALL" in envlist:
- envlist = list(self.config.envconfigs)
- envlist.sort()
- return envlist
+ def _getenvdata(self, reader, toxsection):
+ envstr = self.config.option.env \
+ or os.environ.get("TOXENV") \
+ or reader.getdefault(toxsection, "envlist", replace=False) \
+ or []
+ envlist = _split_env(envstr)
- def _expand_envstr(self, envstr):
- from itertools import groupby, product, chain
+ # collect section envs
+ all_envs = set(envlist) - set(["ALL"])
+ for section in self._cfg:
+ if section.name.startswith(testenvprefix):
+ all_envs.add(section.name[len(testenvprefix):])
+ if not all_envs:
+ all_envs.add("python")
- # split by commas not in groups
- tokens = re.split(r'(\{[^}]+\})|,', envstr)
- envlist = [''.join(g).strip() for k, g in groupby(tokens, key=bool) if
k]
+ if not envlist or "ALL" in envlist:
+ envlist = sorted(all_envs)
- def expand(env):
- tokens = re.split(r'\{([^}]+)\}', env)
- parts = [token.split(',') for token in tokens]
- return [''.join(variant) for variant in product(*parts)]
-
- return list(chain(*map(expand, envlist)))
+ return envlist, all_envs
def _replace_forced_dep(self, name, config):
"""
@@ -454,15 +440,25 @@
def _split_env(env):
"""if handed a list, action="append" was used for -e """
- envlist = []
if not isinstance(env, list):
env = [env]
- for to_split in env:
- for single_env in to_split.split(","):
- # "remove True or", if not allowing multiple same runs, update
tests
- if True or single_env not in envlist:
- envlist.append(single_env)
- return envlist
+ return mapcat(_expand_envstr, env)
+
+def _expand_envstr(envstr):
+ # split by commas not in groups
+ tokens = re.split(r'(\{[^}]+\})|,', envstr)
+ envlist = [''.join(g).strip()
+ for k, g in itertools.groupby(tokens, key=bool) if k]
+
+ def expand(env):
+ tokens = re.split(r'\{([^}]+)\}', env)
+ parts = [token.split(',') for token in tokens]
+ return [''.join(variant) for variant in itertools.product(*parts)]
+
+ return mapcat(expand, envlist)
+
+def mapcat(f, seq):
+ return list(itertools.chain.from_iterable(map(f, seq)))
class DepConfig:
def __init__(self, name, indexserver=None):
https://bitbucket.org/hpk42/tox/commits/7a0dd904a4fb/
Changeset: 7a0dd904a4fb
User: suor
Date: 2014-07-03 12:22:28
Summary: Test factors and envlist expansion
Affected #: 1 file
diff -r c3c0b8cf5e00e99761e892c113cbfe95150de3cb -r
7a0dd904a4fbacfe93c223f4eec746ef9f9b4f07 tests/test_config.py
--- a/tests/test_config.py
+++ b/tests/test_config.py
@@ -832,6 +832,24 @@
assert conf.changedir.basename == 'testing'
assert conf.changedir.dirpath().realpath() == tmpdir.realpath()
+ def test_factors(self, newconfig):
+ inisource="""
+ [tox]
+ envlist = a,b
+
+ [testenv]
+ deps=
+ dep-all
+ a: dep-a
+ b: dep-b
+ !a: dep-not-a
+ """
+ conf = newconfig([], inisource)
+ configs = conf.envconfigs
+ assert [dep.name for dep in configs['a'].deps] == ["dep-all", "dep-a"]
+ assert [dep.name for dep in configs['b'].deps] == \
+ ["dep-all", "dep-b", "dep-not-a"]
+
class TestGlobalOptions:
def test_notest(self, newconfig):
config = newconfig([], "")
@@ -935,6 +953,23 @@
bp = "python%s.%s" %(name[2], name[3])
assert env.basepython == bp
+ def test_envlist_expansion(self, newconfig):
+ inisource = """
+ [tox]
+ envlist = py{26,27},docs
+ """
+ config = newconfig([], inisource)
+ assert config.envlist == ["py26", "py27", "docs"]
+
+ def test_envlist_cross_product(self, newconfig):
+ inisource = """
+ [tox]
+ envlist = py{26,27}-dep{1,2}
+ """
+ config = newconfig([], inisource)
+ assert config.envlist == \
+ ["py26-dep1", "py26-dep2", "py27-dep1", "py27-dep2"]
+
def test_minversion(self, tmpdir, newconfig, monkeypatch):
inisource = """
[tox]
https://bitbucket.org/hpk42/tox/commits/a6ef74cfe446/
Changeset: a6ef74cfe446
User: suor
Date: 2014-07-03 12:34:23
Summary: Fix undefined env check
Affected #: 1 file
diff -r 7a0dd904a4fbacfe93c223f4eec746ef9f9b4f07 -r
a6ef74cfe44662feff5bda94bdaf83c362e505d3 tox/_config.py
--- a/tox/_config.py
+++ b/tox/_config.py
@@ -291,7 +291,7 @@
for name in all_envs:
section = testenvprefix + name
factors = set(name.split('-'))
- if section in self._cfg or factors & known_factors:
+ if section in self._cfg or factors <= known_factors:
config.envconfigs[name] = \
self._makeenvconfig(name, section, reader._subs, config)
@@ -305,7 +305,7 @@
factors = set()
if section in self._cfg:
for _, value in self._cfg[section].items():
- factors.update(re.findall(r'^(!)?(\w+)\:\s+(.+)', value))
+ factors.update(re.findall(r'^!?(\w+)\:\s+', value, re.M))
return factors
def _makeenvconfig(self, name, section, subs, config):
https://bitbucket.org/hpk42/tox/commits/7130bc740587/
Changeset: 7130bc740587
User: suor
Date: 2014-07-17 09:12:02
Summary: Docs on factors and envlist expansion
Affected #: 1 file
diff -r a6ef74cfe44662feff5bda94bdaf83c362e505d3 -r
7130bc7405879fa2254cfcb618d07ff7aec9a7d5 doc/config.txt
--- a/doc/config.txt
+++ b/doc/config.txt
@@ -382,6 +382,115 @@
{[base]deps}
+Generating environments and selecting factors
+---------------------------------------------
+
+.. versionadded:: 1.8
+
+Suppose you want to test your package against python2.6, python2.7 and against
+several versions of a dependency, say Django 1.5 and Django 1.6. You can
+accomplish that by writing down 2*2 = 4 ``[testenv:*]`` sections and then
+listing all of them in ``envlist``.
+
+However, a better approach would be generating ``envlist`` and then selecting
+dependencies this way::
+
+ [tox]
+ envlist = {py26,py27}-django{15,16}
+
+ [testenv]
+ basepython =
+ py26: python2.6
+ py27: python2.7
+ deps =
+ pytest
+ django15: Django>=1.5,<1.6
+ django16: Django>=1.6,<1.7
+ !py27: unittest2
+ commands = py.test
+
+Let's go through this step by step.
+
+
+Generating environments
++++++++++++++++++++++++
+
+::
+
+ envlist = {py26,py27}-django{15,16}
+
+This is bash-style syntax and will create ``2*2=4`` environment names
+like this::
+
+ py26-django15
+ py26-django16
+ py27-django15
+ py27-django16
+
+You can still list explicit environments along with generated ones::
+
+ envlist = {py26,py27}-django{15,16}, docs, flake
+
+
+Factors
++++++++
+
+A parts of environment names delimited by hyphens are called factors and could
+be used to alter values of ``[testenv]`` settings::
+
+ basepython =
+ py26: python2.6
+ py27: python2.7
+
+This conditional setting will lead to either ``python2.6`` or
+``python2.7`` used as base python, e.g. ``python2.6`` is selected if current
+environment contains ``py26`` factor.
+
+In list settings such as ``deps`` or ``commands`` you can freely intermix
+optional lines with unconditional ones::
+
+ deps =
+ pytest
+ django15: Django>=1.5,<1.6
+ django16: Django>=1.6,<1.7
+ !py27: unittest2
+
+A last line here uses negation of a factor, this means ``unittest2`` will be
+in ``deps`` for all pythons except python2.7. The whole effect of this setting
+definition could be described with a table:
+
+=============== ==================================
+environment deps
+=============== ==================================
+py26-django15 pytest, Django>=1.5,<1.6, unitest2
+py26-django16 pytest, Django>=1.6,<1.7, unitest2
+py27-django15 pytest, Django>=1.5,<1.6
+py27-django16 pytest, Django>=1.6,<1.7
+=============== ==================================
+
+And this table can significantly grow as you have more dependencies and other
+factors such as platform, python version and/or database.
+
+.. note::
+
+ Tox provides good defaults for basepython setting, so the above ini-file
can be
+ further reduced by omitting it.
+
+
+Showing all expanded sections
++++++++++++++++++++++++++++++
+
+To help with understanding how the variants will produce section values,
+you can ask tox to show their expansion with a new option::
+
+ $ tox -l
+ py26-django15
+ py26-django16
+ py27-django15
+ py27-django16
+ docs
+ flake
+
Other Rules and notes
=====================
https://bitbucket.org/hpk42/tox/commits/34e8cf7abd61/
Changeset: 34e8cf7abd61
User: suor
Date: 2014-07-17 10:27:47
Summary: Reimplement defaultenvs as default factors
Affected #: 2 files
diff -r 7130bc7405879fa2254cfcb618d07ff7aec9a7d5 -r
34e8cf7abd61ea9b7c6cee4d4a02918a03880c09 tests/test_config.py
--- a/tests/test_config.py
+++ b/tests/test_config.py
@@ -850,6 +850,20 @@
assert [dep.name for dep in configs['b'].deps] == \
["dep-all", "dep-b", "dep-not-a"]
+ def test_default_factors(self, newconfig):
+ inisource="""
+ [tox]
+ envlist = py{26,27,33,34}-dep
+
+ [testenv]
+ deps=
+ dep: dep
+ """
+ conf = newconfig([], inisource)
+ configs = conf.envconfigs
+ for name, config in configs.items():
+ assert config.basepython == 'python%s.%s' % (name[2], name[3])
+
class TestGlobalOptions:
def test_notest(self, newconfig):
config = newconfig([], "")
diff -r 7130bc7405879fa2254cfcb618d07ff7aec9a7d5 -r
34e8cf7abd61ea9b7c6cee4d4a02918a03880c09 tox/_config.py
--- a/tox/_config.py
+++ b/tox/_config.py
@@ -16,13 +16,9 @@
iswin32 = sys.platform == "win32"
-defaultenvs = {'jython': 'jython', 'pypy': 'pypy'}
-for _name in "py,py24,py25,py26,py27,py30,py31,py32,py33,py34".split(","):
- if _name == "py":
- basepython = sys.executable
- else:
- basepython = "python" + ".".join(_name[2:4])
- defaultenvs[_name] = basepython
+default_factors = {'jython': 'jython', 'pypy': 'pypy', 'py': sys.executable}
+for version in '24,25,26,27,30,31,32,33,34'.split(','):
+ default_factors['py' + version] = 'python%s.%s' % tuple(version)
def parseconfig(args=None, pkg=None):
if args is None:
@@ -286,7 +282,7 @@
# configure testenvs
known_factors = self._list_section_factors("testenv")
- known_factors.update(defaultenvs)
+ known_factors.update(default_factors)
known_factors.add("python")
for name in all_envs:
section = testenvprefix + name
@@ -311,8 +307,9 @@
def _makeenvconfig(self, name, section, subs, config):
vc = VenvConfig(envname=name)
vc.config = config
+ factors = set(name.split('-'))
reader = IniReader(self._cfg, fallbacksections=["testenv"],
- factors=name.split('-'))
+ factors=factors)
reader.addsubstitutions(**subs)
vc.develop = not config.option.installpkg and \
reader.getbool(section, "usedevelop", config.option.develop)
@@ -321,10 +318,8 @@
if reader.getdefault(section, "python", None):
raise tox.exception.ConfigError(
"'python=' key was renamed to 'basepython='")
- if name in defaultenvs:
- bp = defaultenvs[name]
- else:
- bp = sys.executable
+ bp = next((default_factors[f] for f in factors if f in
default_factors),
+ sys.executable)
vc.basepython = reader.getdefault(section, "basepython", bp)
vc._basepython_info = config.interpreters.get_info(vc.basepython)
reader.addsubstitutions(envdir=vc.envdir, envname=vc.envname,
https://bitbucket.org/hpk42/tox/commits/ffec51fcde95/
Changeset: ffec51fcde95
User: hpk42
Date: 2014-07-20 11:27:59
Summary: merge Alexander's "multi-dimensional" PR.
Affected #: 5 files
diff -r 27b38ca7904a514f5b9e51e3570fac218b522737 -r
ffec51fcde950f4f662b4d8528570f17e047ed8d CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,10 @@
+1.8.0.dev1
+-----------
+
+- new multi-dimensional configuration support. Many thanks to
+ Alexander Schepanovski for the complete PR with docs.
+
+
1.7.2
-----------
diff -r 27b38ca7904a514f5b9e51e3570fac218b522737 -r
ffec51fcde950f4f662b4d8528570f17e047ed8d CONTRIBUTORS
--- a/CONTRIBUTORS
+++ b/CONTRIBUTORS
@@ -3,6 +3,7 @@
Krisztian Fekete
Marc Abramowitz
+Aleaxner Schepanovski
Sridhar Ratnakumar
Barry Warsaw
Chris Rose
diff -r 27b38ca7904a514f5b9e51e3570fac218b522737 -r
ffec51fcde950f4f662b4d8528570f17e047ed8d doc/config.txt
--- a/doc/config.txt
+++ b/doc/config.txt
@@ -382,6 +382,115 @@
{[base]deps}
+Generating environments and selecting factors
+---------------------------------------------
+
+.. versionadded:: 1.8
+
+Suppose you want to test your package against python2.6, python2.7 and against
+several versions of a dependency, say Django 1.5 and Django 1.6. You can
+accomplish that by writing down 2*2 = 4 ``[testenv:*]`` sections and then
+listing all of them in ``envlist``.
+
+However, a better approach would be generating ``envlist`` and then selecting
+dependencies this way::
+
+ [tox]
+ envlist = {py26,py27}-django{15,16}
+
+ [testenv]
+ basepython =
+ py26: python2.6
+ py27: python2.7
+ deps =
+ pytest
+ django15: Django>=1.5,<1.6
+ django16: Django>=1.6,<1.7
+ !py27: unittest2
+ commands = py.test
+
+Let's go through this step by step.
+
+
+Generating environments
++++++++++++++++++++++++
+
+::
+
+ envlist = {py26,py27}-django{15,16}
+
+This is bash-style syntax and will create ``2*2=4`` environment names
+like this::
+
+ py26-django15
+ py26-django16
+ py27-django15
+ py27-django16
+
+You can still list explicit environments along with generated ones::
+
+ envlist = {py26,py27}-django{15,16}, docs, flake
+
+
+Factors
++++++++
+
+A parts of environment names delimited by hyphens are called factors and could
+be used to alter values of ``[testenv]`` settings::
+
+ basepython =
+ py26: python2.6
+ py27: python2.7
+
+This conditional setting will lead to either ``python2.6`` or
+``python2.7`` used as base python, e.g. ``python2.6`` is selected if current
+environment contains ``py26`` factor.
+
+In list settings such as ``deps`` or ``commands`` you can freely intermix
+optional lines with unconditional ones::
+
+ deps =
+ pytest
+ django15: Django>=1.5,<1.6
+ django16: Django>=1.6,<1.7
+ !py27: unittest2
+
+A last line here uses negation of a factor, this means ``unittest2`` will be
+in ``deps`` for all pythons except python2.7. The whole effect of this setting
+definition could be described with a table:
+
+=============== ==================================
+environment deps
+=============== ==================================
+py26-django15 pytest, Django>=1.5,<1.6, unitest2
+py26-django16 pytest, Django>=1.6,<1.7, unitest2
+py27-django15 pytest, Django>=1.5,<1.6
+py27-django16 pytest, Django>=1.6,<1.7
+=============== ==================================
+
+And this table can significantly grow as you have more dependencies and other
+factors such as platform, python version and/or database.
+
+.. note::
+
+ Tox provides good defaults for basepython setting, so the above ini-file
can be
+ further reduced by omitting it.
+
+
+Showing all expanded sections
++++++++++++++++++++++++++++++
+
+To help with understanding how the variants will produce section values,
+you can ask tox to show their expansion with a new option::
+
+ $ tox -l
+ py26-django15
+ py26-django16
+ py27-django15
+ py27-django16
+ docs
+ flake
+
Other Rules and notes
=====================
diff -r 27b38ca7904a514f5b9e51e3570fac218b522737 -r
ffec51fcde950f4f662b4d8528570f17e047ed8d tests/test_config.py
--- a/tests/test_config.py
+++ b/tests/test_config.py
@@ -832,6 +832,38 @@
assert conf.changedir.basename == 'testing'
assert conf.changedir.dirpath().realpath() == tmpdir.realpath()
+ def test_factors(self, newconfig):
+ inisource="""
+ [tox]
+ envlist = a,b
+
+ [testenv]
+ deps=
+ dep-all
+ a: dep-a
+ b: dep-b
+ !a: dep-not-a
+ """
+ conf = newconfig([], inisource)
+ configs = conf.envconfigs
+ assert [dep.name for dep in configs['a'].deps] == ["dep-all", "dep-a"]
+ assert [dep.name for dep in configs['b'].deps] == \
+ ["dep-all", "dep-b", "dep-not-a"]
+
+ def test_default_factors(self, newconfig):
+ inisource="""
+ [tox]
+ envlist = py{26,27,33,34}-dep
+
+ [testenv]
+ deps=
+ dep: dep
+ """
+ conf = newconfig([], inisource)
+ configs = conf.envconfigs
+ for name, config in configs.items():
+ assert config.basepython == 'python%s.%s' % (name[2], name[3])
+
class TestGlobalOptions:
def test_notest(self, newconfig):
config = newconfig([], "")
@@ -935,6 +967,23 @@
bp = "python%s.%s" %(name[2], name[3])
assert env.basepython == bp
+ def test_envlist_expansion(self, newconfig):
+ inisource = """
+ [tox]
+ envlist = py{26,27},docs
+ """
+ config = newconfig([], inisource)
+ assert config.envlist == ["py26", "py27", "docs"]
+
+ def test_envlist_cross_product(self, newconfig):
+ inisource = """
+ [tox]
+ envlist = py{26,27}-dep{1,2}
+ """
+ config = newconfig([], inisource)
+ assert config.envlist == \
+ ["py26-dep1", "py26-dep2", "py27-dep1", "py27-dep2"]
+
def test_minversion(self, tmpdir, newconfig, monkeypatch):
inisource = """
[tox]
diff -r 27b38ca7904a514f5b9e51e3570fac218b522737 -r
ffec51fcde950f4f662b4d8528570f17e047ed8d tox/_config.py
--- a/tox/_config.py
+++ b/tox/_config.py
@@ -6,6 +6,7 @@
import shlex
import string
import pkg_resources
+import itertools
from tox.interpreters import Interpreters
@@ -15,13 +16,10 @@
iswin32 = sys.platform == "win32"
-defaultenvs = {'jython': 'jython', 'pypy': 'pypy', 'pypy3': 'pypy3'}
-for _name in "py,py24,py25,py26,py27,py30,py31,py32,py33,py34".split(","):
- if _name == "py":
- basepython = sys.executable
- else:
- basepython = "python" + ".".join(_name[2:4])
- defaultenvs[_name] = basepython
+default_factors = {'jython': 'jython', 'pypy': 'pypy', 'pypy3': 'pypy3',
+ 'py': sys.executable}
+for version in '24,25,26,27,30,31,32,33,34'.split(','):
+ default_factors['py' + version] = 'python%s.%s' % tuple(version)
def parseconfig(args=None, pkg=None):
if args is None:
@@ -280,22 +278,19 @@
config.sdistsrc = reader.getpath(toxsection, "sdistsrc", None)
config.setupdir = reader.getpath(toxsection, "setupdir", "{toxinidir}")
config.logdir = config.toxworkdir.join("log")
- for sectionwrapper in self._cfg:
- section = sectionwrapper.name
- if section.startswith(testenvprefix):
- name = section[len(testenvprefix):]
- envconfig = self._makeenvconfig(name, section, reader._subs,
- config)
- config.envconfigs[name] = envconfig
- if not config.envconfigs:
- config.envconfigs['python'] = \
- self._makeenvconfig("python", "_xz_9", reader._subs, config)
- config.envlist = self._getenvlist(reader, toxsection)
- for name in config.envlist:
- if name not in config.envconfigs:
- if name in defaultenvs:
- config.envconfigs[name] = \
- self._makeenvconfig(name, "_xz_9", reader._subs, config)
+
+ config.envlist, all_envs = self._getenvdata(reader, toxsection)
+
+ # configure testenvs
+ known_factors = self._list_section_factors("testenv")
+ known_factors.update(default_factors)
+ known_factors.add("python")
+ for name in all_envs:
+ section = testenvprefix + name
+ factors = set(name.split('-'))
+ if section in self._cfg or factors <= known_factors:
+ config.envconfigs[name] = \
+ self._makeenvconfig(name, section, reader._subs, config)
all_develop = all(name in config.envconfigs
and config.envconfigs[name].develop
@@ -303,10 +298,19 @@
config.skipsdist = reader.getbool(toxsection, "skipsdist", all_develop)
+ def _list_section_factors(self, section):
+ factors = set()
+ if section in self._cfg:
+ for _, value in self._cfg[section].items():
+ factors.update(re.findall(r'^!?(\w+)\:\s+', value, re.M))
+ return factors
+
def _makeenvconfig(self, name, section, subs, config):
vc = VenvConfig(envname=name)
vc.config = config
- reader = IniReader(self._cfg, fallbacksections=["testenv"])
+ factors = set(name.split('-'))
+ reader = IniReader(self._cfg, fallbacksections=["testenv"],
+ factors=factors)
reader.addsubstitutions(**subs)
vc.develop = not config.option.installpkg and \
reader.getbool(section, "usedevelop", config.option.develop)
@@ -315,10 +319,8 @@
if reader.getdefault(section, "python", None):
raise tox.exception.ConfigError(
"'python=' key was renamed to 'basepython='")
- if name in defaultenvs:
- bp = defaultenvs[name]
- else:
- bp = sys.executable
+ bp = next((default_factors[f] for f in factors if f in
default_factors),
+ sys.executable)
vc.basepython = reader.getdefault(section, "basepython", bp)
vc._basepython_info = config.interpreters.get_info(vc.basepython)
reader.addsubstitutions(envdir=vc.envdir, envname=vc.envname,
@@ -386,20 +388,25 @@
"'install_command' must contain '{packages}' substitution")
return vc
- def _getenvlist(self, reader, toxsection):
- env = self.config.option.env
- if not env:
- env = os.environ.get("TOXENV", None)
- if not env:
- envlist = reader.getlist(toxsection, "envlist", sep=",")
- if not envlist:
- envlist = self.config.envconfigs.keys()
- return envlist
- envlist = _split_env(env)
- if "ALL" in envlist:
- envlist = list(self.config.envconfigs)
- envlist.sort()
- return envlist
+ def _getenvdata(self, reader, toxsection):
+ envstr = self.config.option.env \
+ or os.environ.get("TOXENV") \
+ or reader.getdefault(toxsection, "envlist", replace=False) \
+ or []
+ envlist = _split_env(envstr)
+
+ # collect section envs
+ all_envs = set(envlist) - set(["ALL"])
+ for section in self._cfg:
+ if section.name.startswith(testenvprefix):
+ all_envs.add(section.name[len(testenvprefix):])
+ if not all_envs:
+ all_envs.add("python")
+
+ if not envlist or "ALL" in envlist:
+ envlist = sorted(all_envs)
+
+ return envlist, all_envs
def _replace_forced_dep(self, name, config):
"""
@@ -429,15 +436,25 @@
def _split_env(env):
"""if handed a list, action="append" was used for -e """
- envlist = []
if not isinstance(env, list):
env = [env]
- for to_split in env:
- for single_env in to_split.split(","):
- # "remove True or", if not allowing multiple same runs, update
tests
- if True or single_env not in envlist:
- envlist.append(single_env)
- return envlist
+ return mapcat(_expand_envstr, env)
+
+def _expand_envstr(envstr):
+ # split by commas not in groups
+ tokens = re.split(r'(\{[^}]+\})|,', envstr)
+ envlist = [''.join(g).strip()
+ for k, g in itertools.groupby(tokens, key=bool) if k]
+
+ def expand(env):
+ tokens = re.split(r'\{([^}]+)\}', env)
+ parts = [token.split(',') for token in tokens]
+ return [''.join(variant) for variant in itertools.product(*parts)]
+
+ return mapcat(expand, envlist)
+
+def mapcat(f, seq):
+ return list(itertools.chain.from_iterable(map(f, seq)))
class DepConfig:
def __init__(self, name, indexserver=None):
@@ -468,9 +485,10 @@
class IniReader:
- def __init__(self, cfgparser, fallbacksections=None):
+ def __init__(self, cfgparser, fallbacksections=None, factors=()):
self._cfg = cfgparser
self.fallbacksections = fallbacksections or []
+ self.factors = factors
self._subs = {}
self._subststack = []
@@ -586,18 +604,19 @@
return s
def getdefault(self, section, name, default=None, replace=True):
- try:
- x = self._cfg[section][name]
- except KeyError:
- for fallbacksection in self.fallbacksections:
- try:
- x = self._cfg[fallbacksection][name]
- except KeyError:
- pass
- else:
- break
- else:
- x = default
+ x = None
+ for s in [section] + self.fallbacksections:
+ try:
+ x = self._cfg[s][name]
+ break
+ except KeyError:
+ continue
+
+ if x is None:
+ x = default
+ else:
+ x = self._apply_factors(x)
+
if replace and x and hasattr(x, 'replace'):
self._subststack.append((section, name))
try:
@@ -607,6 +626,19 @@
#print "getdefault", section, name, "returned", repr(x)
return x
+ def _apply_factors(self, s):
+ def factor_line(line):
+ m = re.search(r'^(!)?(\w+)\:\s+(.+)', line)
+ if not m:
+ return line
+
+ negate, factor, line = m.groups()
+ if bool(negate) ^ (factor in self.factors):
+ return line
+
+ lines = s.strip().splitlines()
+ return '\n'.join(filter(None, map(factor_line, lines)))
+
def _replace_env(self, match):
match_value = match.group('substitution_value')
if not match_value:
https://bitbucket.org/hpk42/tox/commits/30fcc8529698/
Changeset: 30fcc8529698
User: hpk42
Date: 2014-07-20 17:36:10
Summary: some streamlining of the docs
Affected #: 1 file
diff -r ffec51fcde950f4f662b4d8528570f17e047ed8d -r
30fcc85296981a9fc3f5b8782c91817d5a569357 doc/config.txt
--- a/doc/config.txt
+++ b/doc/config.txt
@@ -382,7 +382,7 @@
{[base]deps}
-Generating environments and selecting factors
+Generating environments, conditional settings
---------------------------------------------
.. versionadded:: 1.8
@@ -392,8 +392,7 @@
accomplish that by writing down 2*2 = 4 ``[testenv:*]`` sections and then
listing all of them in ``envlist``.
-However, a better approach would be generating ``envlist`` and then selecting
-dependencies this way::
+However, a better approach looks like this::
[tox]
envlist = {py26,py27}-django{15,16}
@@ -409,10 +408,16 @@
!py27: unittest2
commands = py.test
+This uses two new facilities of tox-1.8:
+
+- generative envlist declarations where each envname
+ consists of environment parts or "factors"
+
+- "factor" specific settings
+
Let's go through this step by step.
-
-Generating environments
+Generative envlist
+++++++++++++++++++++++
::
@@ -427,16 +432,29 @@
py27-django15
py27-django16
-You can still list explicit environments along with generated ones::
+You can still list environments explicitely along with generated ones::
envlist = {py26,py27}-django{15,16}, docs, flake
+.. note::
-Factors
-+++++++
+ To help with understanding how the variants will produce section values,
+ you can ask tox to show their expansion with a new option::
-A parts of environment names delimited by hyphens are called factors and could
-be used to alter values of ``[testenv]`` settings::
+ $ tox -l
+ py26-django15
+ py26-django16
+ py27-django15
+ py27-django16
+ docs
+ flake
+
+
+Factors and factor-conditional settings
+++++++++++++++++++++++++++++++++++++++++
+
+Parts of an environment name delimited by hyphens are called factors and can
+be used to set values conditionally::
basepython =
py26: python2.6
@@ -455,7 +473,7 @@
django16: Django>=1.6,<1.7
!py27: unittest2
-A last line here uses negation of a factor, this means ``unittest2`` will be
+The last line here uses negation of a factor, this means ``unittest2`` will be
in ``deps`` for all pythons except python2.7. The whole effect of this setting
definition could be described with a table:
@@ -473,23 +491,9 @@
.. note::
- Tox provides good defaults for basepython setting, so the above ini-file
can be
- further reduced by omitting it.
-
-
-Showing all expanded sections
-+++++++++++++++++++++++++++++
-
-To help with understanding how the variants will produce section values,
-you can ask tox to show their expansion with a new option::
-
- $ tox -l
- py26-django15
- py26-django16
- py27-django15
- py27-django16
- docs
- flake
+ Tox provides good defaults for basepython setting, so the above
+ ini-file can be further reduced by omitting the ``basepython``
+ setting.
Other Rules and notes
Repository URL: https://bitbucket.org/hpk42/tox/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
_______________________________________________
pytest-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pytest-commit