Skip to content

Commit 2ce35c0

Browse files
jurko-gospodneticgaborbernat
authored andcommitted
allow defining negative conditional factors with a leading ! (tox-dev#694)
* allow defining negative conditional factors with a leading ! * add negated factor conditions changelog entry * touch up the `package_formats` idea feature draft docs - moved `package_formats` related config settings together in the example - simplified the envlist declaration - added missing expected output line * touch up factor condition config to env matching docs * add negated factor conditions docs * add Jurko to the CONTRIBUTORS file
1 parent 4898491 commit 2ce35c0

File tree

6 files changed

+107
-36
lines changed

6 files changed

+107
-36
lines changed

CONTRIBUTORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ Jannis Leidel
2727
Johannes Christ
2828
Josh Smeaton
2929
Julian Krause
30+
Jurko Gospodnetić
3031
Krisztian Fekete
3132
Laszlo Vasko
3233
Lukasz Balcerzak

changelog/292.feature.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
add support for negated factor conditions, e.g. ``!dev: production_log`` - by @jurko-gospodnetic

doc/config.rst

Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -636,34 +636,42 @@ special case for a combination of factors. Here is how you do it::
636636

637637
[testenv]
638638
deps =
639-
py34-mysql: PyMySQL ; use if both py34 and mysql are in an env name
640-
py27,py36: urllib3 ; use if any of py36 or py27 are in an env name
641-
py{27,36}-sqlite: mock ; mocking sqlite in python 2.x
642-
643-
Take a look at first ``deps`` line. It shows how you can special case something
644-
for a combination of factors, you just join combining factors with a hyphen.
645-
This particular line states that ``PyMySQL`` will be loaded for python 3.3,
646-
mysql environments, e.g. ``py34-django15-mysql`` and ``py34-django16-mysql``.
647-
648-
The second line shows how you use same line for several factors - by listing
649-
them delimited by commas. It's possible to list not only simple factors, but
650-
also their combinations like ``py27-sqlite,py36-sqlite``.
651-
652-
Finally, factor expressions are expanded the same way as envlist, so last
653-
example could be rewritten as ``py{27,36}-sqlite``.
639+
py34-mysql: PyMySQL ; use if both py34 and mysql are in the env name
640+
py27,py36: urllib3 ; use if either py36 or py27 are in the env name
641+
py{27,36}-sqlite: mock ; mocking sqlite in python 2.x & 3.6
642+
!py34-sqlite: mock ; mocking sqlite, except in python 3.4
643+
sqlite-!py34: mock ; (same as the line above)
644+
645+
Take a look at the first ``deps`` line. It shows how you can special case
646+
something for a combination of factors, by just hyphenating the combining
647+
factors together. This particular line states that ``PyMySQL`` will be loaded
648+
for python 3.3, mysql environments, e.g. ``py34-django15-mysql`` and
649+
``py34-django16-mysql``.
650+
651+
The second line shows how you use the same setting for several factors - by
652+
listing them delimited by commas. It's possible to list not only simple factors,
653+
but also their combinations like ``py27-sqlite,py36-sqlite``.
654+
655+
The remaining lines all have the same effect and use conditions equivalent to
656+
``py27-sqlite,py36-sqlite``. They have all been added only to help demonstrate
657+
the following:
658+
659+
- how factor expressions get expanded the same way as in envlist
660+
- how to use negated factor conditions by prefixing negated factors with ``!``
661+
- that the order in which factors are hyphenated together does not matter
654662

655663
.. note::
656664

657665
Factors don't do substring matching against env name, instead every
658-
hyphenated expression is split by ``-`` and if ALL the factors in an
659-
expression are also factors of an env then that condition is considered
660-
hold.
666+
hyphenated expression is split by ``-`` and if ALL of its non-negated
667+
factors and NONE of its negated ones are also factors of an env then that
668+
condition is considered to hold for that env.
661669

662-
For example, environment ``py36-mysql``:
670+
For example, environment ``py36-mysql-!dev``:
663671

664-
- could be matched with expressions ``py36``, ``py36-mysql``,
672+
- would be matched by expressions ``py36``, ``py36-mysql`` or
665673
``mysql-py36``,
666-
- but not with ``py2`` or ``py36-sql``.
674+
- but not ``py2``, ``py36-sql`` or ``py36-mysql-dev``.
667675

668676

669677
Other Rules and notes

doc/drafts/extend-envs-and-packagebuilds.md

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ Goal: drive building of packages and the environments needed to test them, exerc
1818

1919
It should be possible to build other kinds of packages than just the standard sdist and it should also be possible to create different kinds of builds that can be used from different environments. To make this possible there has to be some concept of factorized package definitions and a way to match these factorized builds to environments with a similar way of matching like what is in place already to generate environments. sdist would for example would match to a "sdist" factor to only be matched against virtualenvs as the default.
2020

21-
This could then be used to hae virtualenv, conda, nixos, docker, pyenv, rpm, deb, etc. builds and tie them to concrete test environments.
21+
This could then be used to have virtualenv, conda, nixos, docker, pyenv, rpm, deb, etc. builds and tie them to concrete test environments.
2222

2323
To summarize - we would need a:
2424

@@ -114,19 +114,35 @@ Illustrate how to exclude a certain package format for a factor:
114114
```ini
115115
[tox]
116116
plugins=conda
117-
envlist={py27,py35}, py27-xdist
117+
envlist=py27,py35,py27-xdist
118118

119119
[testenv]
120-
package_formats=sdist wheel conda
121120
commands = py.test
121+
package_formats=sdist wheel conda
122122
exclude_package_formats= # new option which filters out packages
123123
py27-xdist: wheel
124124
```
125125

126+
or possibly using the negated factor condition support:
127+
128+
```ini
129+
[tox]
130+
plugins=conda
131+
envlist=py27,py35,py27-xdist
132+
133+
[testenv]
134+
commands = py.test
135+
package_formats=
136+
sdist
137+
!py27,!xdist: wheel
138+
conda
139+
```
140+
126141
Output of `tox --list`:
127142

128143
```
129144
(sdist) py27
145+
(wheel) py27
130146
(conda) py27
131147
(sdist) py35
132148
(wheel) py35

tests/test_config.py

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1376,11 +1376,20 @@ def test_factors(self, newconfig):
13761376
a: dep-a
13771377
b: dep-b
13781378
x: dep-x
1379+
!a: dep-!a
1380+
!b: dep-!b
1381+
!x: dep-!x
13791382
"""
13801383
conf = newconfig([], inisource)
13811384
configs = conf.envconfigs
1382-
assert [dep.name for dep in configs['a-x'].deps] == ["dep-all", "dep-a", "dep-x"]
1383-
assert [dep.name for dep in configs['b'].deps] == ["dep-all", "dep-b"]
1385+
expected = ["dep-all", "dep-a", "dep-x", "dep-!b"]
1386+
assert [dep.name for dep in configs['a-x'].deps] == expected
1387+
expected = ["dep-all", "dep-b", "dep-!a", "dep-!x"]
1388+
assert [dep.name for dep in configs['b'].deps] == expected
1389+
expected = ["dep-all", "dep-a", "dep-x", "dep-!b"]
1390+
assert [dep.name for dep in configs['a-x'].deps] == expected
1391+
expected = ["dep-all", "dep-b", "dep-!a", "dep-!x"]
1392+
assert [dep.name for dep in configs['b'].deps] == expected
13841393

13851394
def test_factor_ops(self, newconfig):
13861395
inisource = """
@@ -1392,16 +1401,25 @@ def test_factor_ops(self, newconfig):
13921401
a,b: dep-a-or-b
13931402
a-x: dep-a-and-x
13941403
{a,b}-y: dep-ab-and-y
1404+
a-!x: dep-a-and-!x
1405+
a,!x: dep-a-or-!x
1406+
!a-!x: dep-!a-and-!x
1407+
!a,!x: dep-!a-or-!x
1408+
!a-!b: dep-!a-and-!b
1409+
!a-!b-!x-!y: dep-!a-and-!b-and-!x-and-!y
13951410
"""
13961411
configs = newconfig([], inisource).envconfigs
13971412

13981413
def get_deps(env):
13991414
return [dep.name for dep in configs[env].deps]
14001415

1401-
assert get_deps("a-x") == ["dep-a-or-b", "dep-a-and-x"]
1402-
assert get_deps("a-y") == ["dep-a-or-b", "dep-ab-and-y"]
1403-
assert get_deps("b-x") == ["dep-a-or-b"]
1404-
assert get_deps("b-y") == ["dep-a-or-b", "dep-ab-and-y"]
1416+
assert get_deps("a-x") == ["dep-a-or-b", "dep-a-and-x", "dep-a-or-!x"]
1417+
assert get_deps("a-y") == ["dep-a-or-b", "dep-ab-and-y",
1418+
"dep-a-and-!x", "dep-a-or-!x",
1419+
"dep-!a-or-!x"]
1420+
assert get_deps("b-x") == ["dep-a-or-b", "dep-!a-or-!x"]
1421+
assert get_deps("b-y") == ["dep-a-or-b", "dep-ab-and-y", "dep-a-or-!x",
1422+
"dep-!a-and-!x", "dep-!a-or-!x"]
14051423

14061424
def test_envconfigs_based_on_factors(self, newconfig):
14071425
inisource = """
@@ -1410,6 +1428,9 @@ def test_envconfigs_based_on_factors(self, newconfig):
14101428
a: something
14111429
b,c: something
14121430
d-e: something
1431+
!f: something
1432+
!g,!h: something
1433+
!i-!j: something
14131434
14141435
[unknown-section]
14151436
some-setting=
@@ -1424,7 +1445,7 @@ def test_envconfigs_based_on_factors(self, newconfig):
14241445
config = newconfig(["-e py3-spam"], inisource)
14251446
assert not config.envconfigs
14261447
assert config.envlist == ["py3-spam"]
1427-
for x in "abcde":
1448+
for x in "abcdefghij":
14281449
env = "py3-{}".format(x)
14291450
config = newconfig(["-e {}".format(env)], inisource)
14301451
assert sorted(config.envconfigs) == [env]

tox/config.py

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -848,8 +848,8 @@ def _list_section_factors(self, section):
848848
factors = set()
849849
if section in self._cfg:
850850
for _, value in self._cfg[section].items():
851-
exprs = re.findall(r'^([\w{}\.,-]+)\:\s+', value, re.M)
852-
factors.update(*mapcat(_split_factor_expr, exprs))
851+
exprs = re.findall(r'^([\w{}\.!,-]+)\:\s+', value, re.M)
852+
factors.update(*mapcat(_split_factor_expr_all, exprs))
853853
return factors
854854

855855
def make_envconfig(self, name, section, subs, config, replace=True):
@@ -912,9 +912,31 @@ def _split_env(env):
912912
return mapcat(_expand_envstr, env)
913913

914914

915+
def _is_negated_factor(factor):
916+
return factor.startswith('!')
917+
918+
919+
def _base_factor_name(factor):
920+
return factor[1:] if _is_negated_factor(factor) else factor
921+
922+
915923
def _split_factor_expr(expr):
924+
def split_single(e):
925+
raw = e.split('-')
926+
included = {_base_factor_name(factor) for factor in raw
927+
if not _is_negated_factor(factor)}
928+
excluded = {_base_factor_name(factor) for factor in raw
929+
if _is_negated_factor(factor)}
930+
return included, excluded
931+
932+
partial_envs = _expand_envstr(expr)
933+
return [split_single(e) for e in partial_envs]
934+
935+
936+
def _split_factor_expr_all(expr):
916937
partial_envs = _expand_envstr(expr)
917-
return [set(e.split('-')) for e in partial_envs]
938+
return [{_base_factor_name(factor) for factor in e.split('-')}
939+
for e in partial_envs]
918940

919941

920942
def _expand_envstr(envstr):
@@ -1064,12 +1086,14 @@ def getstring(self, name, default=None, replace=True, crossonly=False):
10641086

10651087
def _apply_factors(self, s):
10661088
def factor_line(line):
1067-
m = re.search(r'^([\w{}\.,-]+)\:\s+(.+)', line)
1089+
m = re.search(r'^([\w{}\.!,-]+)\:\s+(.+)', line)
10681090
if not m:
10691091
return line
10701092

10711093
expr, line = m.groups()
1072-
if any(fs <= self.factors for fs in _split_factor_expr(expr)):
1094+
if any(included <= self.factors
1095+
and not any(x in self.factors for x in excluded)
1096+
for included, excluded in _split_factor_expr(expr)):
10731097
return line
10741098

10751099
lines = s.strip().splitlines()

0 commit comments

Comments
 (0)