Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions src/_pytest/mark/structures.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,6 @@
from _pytest.outcomes import fail
from _pytest.warning_types import PytestUnknownMarkWarning

if TYPE_CHECKING:
from _pytest.python import FunctionDefinition


EMPTY_PARAMETERSET_OPTION = "empty_parameter_set_mark"

Expand Down Expand Up @@ -159,7 +156,7 @@ def _for_parametrize(
argvalues: Iterable[Union["ParameterSet", Sequence[object], object]],
func,
config: Config,
function_definition: "FunctionDefinition",
nodeid: str,
) -> Tuple[Union[List[str], Tuple[str, ...]], List["ParameterSet"]]:
argnames, force_tuple = cls._parse_parametrize_args(argnames, argvalues)
parameters = cls._parse_parametrize_parameters(argvalues, force_tuple)
Expand All @@ -177,7 +174,7 @@ def _for_parametrize(
)
fail(
msg.format(
nodeid=function_definition.nodeid,
nodeid=nodeid,
values=param.values,
names=argnames,
names_len=len(argnames),
Expand Down
44 changes: 22 additions & 22 deletions src/_pytest/python.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,16 +209,12 @@ def pytest_pycollect_makemodule(path: py.path.local, parent) -> "Module":
return mod


@hookimpl(hookwrapper=True)
def pytest_pycollect_makeitem(collector: "PyCollector", name: str, obj):
outcome = yield
res = outcome.get_result()
if res is not None:
return
@hookimpl(trylast=True)
def pytest_pycollect_makeitem(collector: "PyCollector", name: str, obj: object):
# nothing was collected elsewhere, let's do it here
if safe_isclass(obj):
if collector.istestclass(obj, name):
outcome.force_result(Class.from_parent(collector, name=name, obj=obj))
return Class.from_parent(collector, name=name, obj=obj)
elif collector.istestfunction(obj, name):
# mock seems to store unbound methods (issue473), normalize it
obj = getattr(obj, "__func__", obj)
Expand All @@ -245,7 +241,7 @@ def pytest_pycollect_makeitem(collector: "PyCollector", name: str, obj):
res.warn(PytestCollectionWarning(reason))
else:
res = list(collector._genfunctions(name, obj))
outcome.force_result(res)
return res


class PyobjMixin:
Expand Down Expand Up @@ -980,7 +976,7 @@ def parametrize(
argvalues,
self.function,
self.config,
function_definition=self.definition,
nodeid=self.definition.nodeid,
)
del argvalues

Expand All @@ -1003,7 +999,9 @@ def parametrize(
if generated_ids is not None:
ids = generated_ids

ids = self._resolve_arg_ids(argnames, ids, parameters, item=self.definition)
ids = self._resolve_arg_ids(
argnames, ids, parameters, nodeid=self.definition.nodeid
)

# Store used (possibly generated) ids with parametrize Marks.
if _param_mark and _param_mark._param_ids_from and generated_ids is None:
Expand Down Expand Up @@ -1042,15 +1040,15 @@ def _resolve_arg_ids(
]
],
parameters: typing.Sequence[ParameterSet],
item,
nodeid: str,
) -> List[str]:
"""Resolves the actual ids for the given argnames, based on the ``ids`` parameter given
to ``parametrize``.

:param List[str] argnames: list of argument names passed to ``parametrize()``.
:param ids: the ids parameter of the parametrized call (see docs).
:param List[ParameterSet] parameters: the list of parameter values, same size as ``argnames``.
:param Item item: the item that generated this parametrized call.
:param str str: the nodeid of the item that generated this parametrized call.
:rtype: List[str]
:return: the list of ids for each argname given
"""
Expand All @@ -1063,7 +1061,7 @@ def _resolve_arg_ids(
else:
idfn = None
ids_ = self._validate_ids(ids, parameters, self.function.__name__)
return idmaker(argnames, parameters, idfn, ids_, self.config, item=item)
return idmaker(argnames, parameters, idfn, ids_, self.config, nodeid=nodeid)

def _validate_ids(
self,
Expand Down Expand Up @@ -1223,7 +1221,7 @@ def _idval(
argname: str,
idx: int,
idfn: Optional[Callable[[object], Optional[object]]],
item,
nodeid: Optional[str],
config: Optional[Config],
) -> str:
if idfn:
Expand All @@ -1232,8 +1230,9 @@ def _idval(
if generated_id is not None:
val = generated_id
except Exception as e:
msg = "{}: error raised while trying to determine id of parameter '{}' at position {}"
msg = msg.format(item.nodeid, argname, idx)
prefix = "{}: ".format(nodeid) if nodeid is not None else ""
msg = "error raised while trying to determine id of parameter '{}' at position {}"
msg = prefix + msg.format(argname, idx)
raise ValueError(msg) from e
elif config:
hook_id = config.hook.pytest_make_parametrize_id(
Expand Down Expand Up @@ -1263,15 +1262,15 @@ def _idvalset(
argnames: Iterable[str],
idfn: Optional[Callable[[object], Optional[object]]],
ids: Optional[List[Union[None, str]]],
item,
nodeid: Optional[str],
config: Optional[Config],
):
if parameterset.id is not None:
return parameterset.id
id = None if ids is None or idx >= len(ids) else ids[idx]
if id is None:
this_id = [
_idval(val, argname, idx, idfn, item=item, config=config)
_idval(val, argname, idx, idfn, nodeid=nodeid, config=config)
for val, argname in zip(parameterset.values, argnames)
]
return "-".join(this_id)
Expand All @@ -1285,10 +1284,12 @@ def idmaker(
idfn: Optional[Callable[[object], Optional[object]]] = None,
ids: Optional[List[Union[None, str]]] = None,
config: Optional[Config] = None,
item=None,
nodeid: Optional[str] = None,
) -> List[str]:
resolved_ids = [
_idvalset(valindex, parameterset, argnames, idfn, ids, config=config, item=item)
_idvalset(
valindex, parameterset, argnames, idfn, ids, config=config, nodeid=nodeid
)
for valindex, parameterset in enumerate(parametersets)
]

Expand Down Expand Up @@ -1589,9 +1590,8 @@ def _prunetraceback(self, excinfo: ExceptionInfo) -> None:

# TODO: Type ignored -- breaks Liskov Substitution.
def repr_failure( # type: ignore[override] # noqa: F821
self, excinfo: ExceptionInfo[BaseException], outerr: None = None
self, excinfo: ExceptionInfo[BaseException],
) -> Union[str, TerminalRepr]:
assert outerr is None, "XXX outerr usage is deprecated"
style = self.config.getoption("tbstyle", "auto")
if style == "auto":
style = "long"
Expand Down
36 changes: 8 additions & 28 deletions testing/python/metafunc.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from _pytest.outcomes import fail
from _pytest.pytester import Testdir
from _pytest.python import _idval
from _pytest.python import idmaker


class TestMetafunc:
Expand All @@ -35,10 +36,11 @@ def __init__(self, names):
@attr.s
class DefinitionMock(python.FunctionDefinition):
obj = attr.ib()
_nodeid = attr.ib()

names = fixtures.getfuncargnames(func)
fixtureinfo = FuncFixtureInfoMock(names) # type: Any
definition = DefinitionMock._create(func) # type: Any
definition = DefinitionMock._create(func, "mock::nodeid") # type: Any
return python.Metafunc(definition, fixtureinfo, config)

def test_no_funcargs(self) -> None:
Expand Down Expand Up @@ -270,7 +272,7 @@ class A:
deadline=400.0
) # very close to std deadline and CI boxes are not reliable in CPU power
def test_idval_hypothesis(self, value) -> None:
escaped = _idval(value, "a", 6, None, item=None, config=None)
escaped = _idval(value, "a", 6, None, nodeid=None, config=None)
assert isinstance(escaped, str)
escaped.encode("ascii")

Expand All @@ -292,7 +294,7 @@ def test_unicode_idval(self) -> None:
),
]
for val, expected in values:
assert _idval(val, "a", 6, None, item=None, config=None) == expected
assert _idval(val, "a", 6, None, nodeid=None, config=None) == expected

def test_unicode_idval_with_config(self) -> None:
"""unittest for expected behavior to obtain ids with
Expand Down Expand Up @@ -321,7 +323,7 @@ def getini(self, name):
("ação", MockConfig({option: False}), "a\\xe7\\xe3o"),
] # type: List[Tuple[str, Any, str]]
for val, config, expected in values:
actual = _idval(val, "a", 6, None, item=None, config=config)
actual = _idval(val, "a", 6, None, nodeid=None, config=config)
assert actual == expected

def test_bytes_idval(self) -> None:
Expand All @@ -338,7 +340,7 @@ def test_bytes_idval(self) -> None:
("αρά".encode(), "\\xce\\xb1\\xcf\\x81\\xce\\xac"),
]
for val, expected in values:
assert _idval(val, "a", 6, idfn=None, item=None, config=None) == expected
assert _idval(val, "a", 6, idfn=None, nodeid=None, config=None) == expected

def test_class_or_function_idval(self) -> None:
"""unittest for the expected behavior to obtain ids for parametrized
Expand All @@ -353,12 +355,10 @@ def test_function():

values = [(TestClass, "TestClass"), (test_function, "test_function")]
for val, expected in values:
assert _idval(val, "a", 6, None, item=None, config=None) == expected
assert _idval(val, "a", 6, None, nodeid=None, config=None) == expected

def test_idmaker_autoname(self) -> None:
"""#250"""
from _pytest.python import idmaker

result = idmaker(
("a", "b"), [pytest.param("string", 1.0), pytest.param("st-ring", 2.0)]
)
Expand All @@ -373,14 +373,10 @@ def test_idmaker_autoname(self) -> None:
assert result == ["a0-\\xc3\\xb4"]

def test_idmaker_with_bytes_regex(self) -> None:
from _pytest.python import idmaker

result = idmaker(("a"), [pytest.param(re.compile(b"foo"), 1.0)])
assert result == ["foo"]

def test_idmaker_native_strings(self) -> None:
from _pytest.python import idmaker

result = idmaker(
("a", "b"),
[
Expand Down Expand Up @@ -414,8 +410,6 @@ def test_idmaker_native_strings(self) -> None:
]

def test_idmaker_non_printable_characters(self) -> None:
from _pytest.python import idmaker

result = idmaker(
("s", "n"),
[
Expand All @@ -430,8 +424,6 @@ def test_idmaker_non_printable_characters(self) -> None:
assert result == ["\\x00-1", "\\x05-2", "\\x00-3", "\\x05-4", "\\t-5", "\\t-6"]

def test_idmaker_manual_ids_must_be_printable(self) -> None:
from _pytest.python import idmaker

result = idmaker(
("s",),
[
Expand All @@ -442,16 +434,13 @@ def test_idmaker_manual_ids_must_be_printable(self) -> None:
assert result == ["hello \\x00", "hello \\x05"]

def test_idmaker_enum(self) -> None:
from _pytest.python import idmaker

enum = pytest.importorskip("enum")
e = enum.Enum("Foo", "one, two")
result = idmaker(("a", "b"), [pytest.param(e.one, e.two)])
assert result == ["Foo.one-Foo.two"]

def test_idmaker_idfn(self) -> None:
"""#351"""
from _pytest.python import idmaker

def ids(val: object) -> Optional[str]:
if isinstance(val, Exception):
Expand All @@ -471,7 +460,6 @@ def ids(val: object) -> Optional[str]:

def test_idmaker_idfn_unique_names(self) -> None:
"""#351"""
from _pytest.python import idmaker

def ids(val: object) -> str:
return "a"
Expand All @@ -492,7 +480,6 @@ def test_idmaker_with_idfn_and_config(self) -> None:
disable_test_id_escaping_and_forfeit_all_rights_to_community_support
option. (#5294)
"""
from _pytest.python import idmaker

class MockConfig:
def __init__(self, config):
Expand Down Expand Up @@ -525,7 +512,6 @@ def test_idmaker_with_ids_and_config(self) -> None:
disable_test_id_escaping_and_forfeit_all_rights_to_community_support
option. (#5294)
"""
from _pytest.python import idmaker

class MockConfig:
def __init__(self, config):
Expand Down Expand Up @@ -607,16 +593,12 @@ def test_int(arg):
)

def test_idmaker_with_ids(self) -> None:
from _pytest.python import idmaker

result = idmaker(
("a", "b"), [pytest.param(1, 2), pytest.param(3, 4)], ids=["a", None]
)
assert result == ["a", "3-4"]

def test_idmaker_with_paramset_id(self) -> None:
from _pytest.python import idmaker

result = idmaker(
("a", "b"),
[pytest.param(1, 2, id="me"), pytest.param(3, 4, id="you")],
Expand All @@ -625,8 +607,6 @@ def test_idmaker_with_paramset_id(self) -> None:
assert result == ["me", "you"]

def test_idmaker_with_ids_unique_names(self) -> None:
from _pytest.python import idmaker

result = idmaker(
("a"), map(pytest.param, [1, 2, 3, 4, 5]), ids=["a", "a", "b", "c", "b"]
)
Expand Down