Skip to content

Commit 6cdd99c

Browse files
authored
Improved factor selection to allow multiple uses of -f for "OR" and to allow hyphenated factors (#2786)
* Enable multiple uses of '-f' meaning 'OR' Previously, when `-f` was passed, it overwrote the last value. The result was that `-f foo -f bar` was equivalent to only passing `-f bar`. Under the new behavior, `-f foo -f bar` combines `foo` and `bar` as selection criteria, using OR-semantics. Envs matching `foo OR bar` will be selected. The existing multi-value argument behavior for `-f` is retained, in which `-f foo bar` means `foo AND bar`. The behaviors can be combined to express a variety of environment selections which were not previously possible in a single invocation. e.g. `-f foo bar -f baz` meaning `(foo AND bar) OR baz`. No existing tests fail, and the new behavior is checked by a new test. The help message for `-f` is updated. * Allow factors to be passed hyphenated The existing parsing of factors allows multiple factors to be selected by passing them as multiple arguments to the `-f` flag. For example, `-f foo bar` to pass both `foo` and `bar` as factors. This can now be passed equivalently using `-f foo-bar`. The meaning of this usage is identical to `-f foo bar`. A new test checks the behavior, and very closely mirrors the existing `-f` selection test so that their outputs are exactly equivalent. * Make factor tests parametrized & apply pre-commit These three tests are nearly identical in structure, and rely upon the same project configuration. Convert from three distinct test cases to a single parametrized test. Also apply pre-commit, which does some mild reformatting. * Add changelog entry for #2766 * Fix missing annotation in tests * Fix changelog entry for #2766 * Improve env selection with factors: perf and types - use tuple instead of list for immutable data - use `continue` and `break` to skip unnecessary loop iterations * Cleanup factor selection tests - convert args from list[str] to tuple[str, ...] - reformat str concat into a `.format()` usage * Remove unreachable factor selection check This check cannot be reached because it relies on an impossible combination of factors and labels.
1 parent 6f056ca commit 6cdd99c

File tree

3 files changed

+54
-9
lines changed

3 files changed

+54
-9
lines changed

docs/changelog/2766.feature.rst

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
``-f`` can be used multiple times and on hyphenated factors (e.g. ``-f py311-django -f py39``) - by :user:`sirosen`.

src/tox/session/env_select.py

+27-6
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,19 @@ def register_env_select_flags(
9191
if multiple:
9292
help_msg = "labels to evaluate"
9393
add_to.add_argument("-m", dest="labels", metavar="label", help=help_msg, default=[], type=str, nargs="+")
94-
help_msg = "factors to evaluate"
95-
add_to.add_argument("-f", dest="factors", metavar="factor", help=help_msg, default=[], type=str, nargs="+")
94+
help_msg = (
95+
"factors to evaluate (passing multiple factors means 'AND', passing this option multiple times means 'OR')"
96+
)
97+
add_to.add_argument(
98+
"-f",
99+
dest="factors",
100+
metavar="factor",
101+
help=help_msg,
102+
default=[],
103+
type=str,
104+
nargs="+",
105+
action="append",
106+
)
96107
help_msg = "exclude all environments selected that match this regular expression"
97108
add_to.add_argument("--skip-env", dest="skip_env", metavar="re", help=help_msg, default="", type=str)
98109
return add_to
@@ -288,9 +299,17 @@ def _get_package_env(self, packager: str, name: str, is_active: bool) -> Package
288299
self._manager.tox_add_env_config(pkg_conf, self._state)
289300
return pkg_env
290301

302+
def _parse_factors(self) -> tuple[set[str], ...]:
303+
# factors is a list of lists, from the combination of nargs="+" and action="append"
304+
# also parse hyphenated factors into lists of factors
305+
# so that `-f foo-bar` and `-f foo bar` are treated equivalently
306+
raw_factors = getattr(self._state.conf.options, "factors", [])
307+
return tuple({f for factor in factor_list for f in factor.split("-")} for factor_list in raw_factors)
308+
291309
def _mark_active(self) -> None:
292310
labels = set(getattr(self._state.conf.options, "labels", []))
293-
factors = set(getattr(self._state.conf.options, "factors", []))
311+
factors = self._parse_factors()
312+
294313
assert self._defined_envs_ is not None
295314
if labels or factors:
296315
for env_info in self._defined_envs_.values():
@@ -302,10 +321,12 @@ def _mark_active(self) -> None:
302321
for env_info in self._defined_envs_.values():
303322
if labels.intersection(env_info.env.conf["labels"]):
304323
env_info.is_active = True
305-
if self._state.conf.options.factors: # if matches mark it active
324+
if factors: # if matches mark it active
306325
for name, env_info in self._defined_envs_.items():
307-
if factors.issubset(set(name.split("-"))):
308-
env_info.is_active = True
326+
for factor_set in factors:
327+
if factor_set.issubset(set(name.split("-"))):
328+
env_info.is_active = True
329+
break
309330

310331
def __getitem__(self, item: str) -> RunToxEnv | PackageToxEnv:
311332
"""

tests/session/test_env_select.py

+26-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from __future__ import annotations
22

3+
import pytest
4+
35
from tox.pytest import MonkeyPatch, ToxProjectCreator
46

57

@@ -61,15 +63,36 @@ def test_label_core_and_trait(tox_project: ToxProjectCreator) -> None:
6163
outcome.assert_out_err("py310\npy39\nflake8\ntype\n", "")
6264

6365

64-
def test_factor_select(tox_project: ToxProjectCreator) -> None:
66+
@pytest.mark.parametrize(
67+
("selection_arguments", "expect_envs"),
68+
[
69+
(
70+
("-f", "cov", "django20"),
71+
("py310-django20-cov", "py39-django20-cov"),
72+
),
73+
(
74+
("-f", "cov-django20"),
75+
("py310-django20-cov", "py39-django20-cov"),
76+
),
77+
(
78+
("-f", "py39", "django20", "-f", "py310", "django21"),
79+
("py310-django21-cov", "py310-django21", "py39-django20-cov", "py39-django20"),
80+
),
81+
],
82+
)
83+
def test_factor_select(
84+
tox_project: ToxProjectCreator,
85+
selection_arguments: tuple[str, ...],
86+
expect_envs: tuple[str, ...],
87+
) -> None:
6588
ini = """
6689
[tox]
6790
env_list = py3{10,9}-{django20,django21}{-cov,}
6891
"""
6992
project = tox_project({"tox.ini": ini})
70-
outcome = project.run("l", "--no-desc", "-f", "cov", "django20")
93+
outcome = project.run("l", "--no-desc", *selection_arguments)
7194
outcome.assert_success()
72-
outcome.assert_out_err("py310-django20-cov\npy39-django20-cov\n", "")
95+
outcome.assert_out_err("{}\n".format("\n".join(expect_envs)), "")
7396

7497

7598
def test_tox_skip_env(tox_project: ToxProjectCreator, monkeypatch: MonkeyPatch) -> None:

0 commit comments

Comments
 (0)