Skip to content

Commit

Permalink
feat: allow setting tags on parametrized sessions
Browse files Browse the repository at this point in the history
To allow more fine-grained session selection, allow tags to be set on
individual parametrized sessions via either a tags argument to the
@nox.parametrize() decorator, or a tags argument to nox.param() (similar
to how parametrized session IDs can be specified).  Any tags specified
this way will be added to any tags passed to the @nox.session()
decorator.
  • Loading branch information
living180 committed May 29, 2024
1 parent cf82e6d commit 3e9584c
Show file tree
Hide file tree
Showing 6 changed files with 88 additions and 13 deletions.
2 changes: 1 addition & 1 deletion nox/_decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ def __init__(self, func: Func, param_spec: Param) -> None:
func.venv_backend,
func.venv_params,
func.should_warn,
func.tags,
func.tags + param_spec.tags,
default=func.default,
)
self.call_spec = call_spec
Expand Down
28 changes: 25 additions & 3 deletions nox/_parametrize.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,16 @@ class Param:
arg_names (Sequence[str]): The names of the args.
id (str): An optional ID for this set of parameters. If unspecified,
it will be generated from the parameters.
tags (Sequence[str]): Optional tags to associate with this set of
parameters.
"""

def __init__(
self,
*args: Any,
arg_names: Sequence[str] | None = None,
id: str | None = None,
tags: Sequence[str] | None = None,
) -> None:
self.args = args
self.id = id
Expand All @@ -45,6 +48,11 @@ def __init__(

self.arg_names = tuple(arg_names)

if tags is None:
tags = []

self.tags = list(tags)

@property
def call_spec(self) -> dict[str, Any]:
return dict(zip(self.arg_names, self.args))
Expand All @@ -60,20 +68,24 @@ def __str__(self) -> str:
__repr__ = __str__

def copy(self) -> Param:
new = self.__class__(*self.args, arg_names=self.arg_names, id=self.id)
new = self.__class__(
*self.args, arg_names=self.arg_names, id=self.id, tags=self.tags
)
return new

def update(self, other: Param) -> None:
self.id = ", ".join([str(self), str(other)])
self.args = self.args + other.args
self.arg_names = self.arg_names + other.arg_names
self.tags = self.tags + other.tags

def __eq__(self, other: object) -> bool:
if isinstance(other, self.__class__):
return (
self.args == other.args
and self.arg_names == other.arg_names
and self.id == other.id
and self.tags == other.tags
)
elif isinstance(other, dict):
return dict(zip(self.arg_names, self.args)) == other
Expand All @@ -95,6 +107,7 @@ def parametrize_decorator(
arg_names: str | Sequence[str],
arg_values_list: Iterable[ArgValue] | ArgValue,
ids: Iterable[str | None] | None = None,
tags: Iterable[Sequence[str] | None] | None = None,
) -> Callable[[Any], Any]:
"""Parametrize a session.
Expand All @@ -114,6 +127,8 @@ def parametrize_decorator(
argument name, for example ``[(1, 'a'), (2, 'b')]``.
ids (Sequence[str]): Optional sequence of test IDs to use for the
parametrized arguments.
tags (Iterable[Sequence[str] | None]): Optional iterable of tags to
associate with the parametrized arguments.
"""

# Allow args names to be specified as any of 'arg', 'arg,arg2' or ('arg', 'arg2')
Expand Down Expand Up @@ -143,14 +158,21 @@ def parametrize_decorator(
if not ids:
ids = []

if tags is None:
tags = []

# Generate params for each item in the param_args_values list.
param_specs: list[Param] = []
for param_arg_values, param_id in itertools.zip_longest(_arg_values_list, ids):
for param_arg_values, param_id, param_tags in itertools.zip_longest(
_arg_values_list, ids, tags
):
if isinstance(param_arg_values, Param):
param_spec = param_arg_values
param_spec.arg_names = tuple(arg_names)
else:
param_spec = Param(*param_arg_values, arg_names=arg_names, id=param_id)
param_spec = Param(
*param_arg_values, arg_names=arg_names, id=param_id, tags=param_tags
)

param_specs.append(param_spec)

Expand Down
7 changes: 7 additions & 0 deletions tests/resources/noxfile_tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,10 @@ def one_tag(unused_session):
@nox.session(tags=["tag1", "tag2", "tag3"])
def moar_tags(unused_session):
print("Some more tags here.")


@nox.session(tags=["tag4"])
@nox.parametrize("foo", [nox.param(1, tags=["tag5", "tag6"])])
@nox.parametrize("bar", [2, 3], tags=[["tag7"]])
def parametrized_tags(unused_session):
print("Parametrized tags here.")
2 changes: 1 addition & 1 deletion tests/test__option_set.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,5 +127,5 @@ def test_tag_completer(self):
prefix=None, parsed_args=parsed_args
)

expected_tags = {"tag1", "tag2", "tag3"}
expected_tags = {f"tag{n}" for n in range(1, 8)}
assert expected_tags == set(actual_tags_from_file)
40 changes: 39 additions & 1 deletion tests/test__parametrize.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ def test_generate_calls_simple():


def test_generate_calls_multiple_args():
f = mock.Mock(should_warn=None, tags=None)
f = mock.Mock(should_warn=None, tags=[])
f.__name__ = "f"

arg_names = ("foo", "abc")
Expand Down Expand Up @@ -244,6 +244,44 @@ def test_generate_calls_ids():
f.assert_called_with(foo=2)


def test_generate_calls_tags():
f = mock.Mock(should_warn={}, tags=[])
f.__name__ = "f"

arg_names = ("foo",)
call_specs = [
_parametrize.Param(1, arg_names=arg_names, tags=["tag3"]),
_parametrize.Param(1, arg_names=arg_names),
_parametrize.Param(2, arg_names=arg_names, tags=["tag4", "tag5"]),
]

calls = _decorators.Call.generate_calls(f, call_specs)

assert len(calls) == 3
assert calls[0].tags == ["tag3"]
assert calls[1].tags == []
assert calls[2].tags == ["tag4", "tag5"]


def test_generate_calls_merge_tags():
f = mock.Mock(should_warn={}, tags=["tag1", "tag2"])
f.__name__ = "f"

arg_names = ("foo",)
call_specs = [
_parametrize.Param(1, arg_names=arg_names, tags=["tag3"]),
_parametrize.Param(1, arg_names=arg_names),
_parametrize.Param(2, arg_names=arg_names, tags=["tag4", "tag5"]),
]

calls = _decorators.Call.generate_calls(f, call_specs)

assert len(calls) == 3
assert calls[0].tags == ["tag1", "tag2", "tag3"]
assert calls[1].tags == ["tag1", "tag2"]
assert calls[2].tags == ["tag1", "tag2", "tag4", "tag5"]


def test_generate_calls_session_python():
called_with = []

Expand Down
22 changes: 15 additions & 7 deletions tests/test_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,13 +254,14 @@ def test_filter_manifest_keywords_syntax_error():
@pytest.mark.parametrize(
"tags,session_count",
[
(None, 4),
(["foo"], 3),
(["bar"], 3),
(["baz"], 1),
(["foo", "bar"], 4),
(["foo", "baz"], 3),
(["foo", "bar", "baz"], 4),
(None, 8),
(["foo"], 7),
(["bar"], 5),
(["baz"], 3),
(["foo", "bar"], 8),
(["foo", "baz"], 7),
(["bar", "baz"], 6),
(["foo", "bar", "baz"], 8),
],
)
def test_filter_manifest_tags(tags, session_count):
Expand All @@ -280,6 +281,12 @@ def quuz():
def corge():
pass

@nox.session(tags=["foo"])
@nox.parametrize("a", [1, nox.param(2, tags=["bar"])])
@nox.parametrize("b", [3, 4], tags=[["baz"]])
def grault():
pass

config = _options.options.namespace(
sessions=None, pythons=(), posargs=[], tags=tags
)
Expand All @@ -289,6 +296,7 @@ def corge():
"quux": quux,
"quuz": quuz,
"corge": corge,
"grault": grault,
},
config,
)
Expand Down

0 comments on commit 3e9584c

Please sign in to comment.