Skip to content

Commit 3c22636

Browse files
committed
python: use nodeid in error messages instead of function name
The function name is ambiguous, full nodeid is more helpful.
1 parent 7743720 commit 3c22636

File tree

2 files changed

+35
-52
lines changed

2 files changed

+35
-52
lines changed

src/_pytest/python.py

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -870,7 +870,6 @@ class IdMaker:
870870
__slots__ = (
871871
"argnames",
872872
"config",
873-
"func_name",
874873
"idfn",
875874
"ids",
876875
"nodeid",
@@ -893,9 +892,6 @@ class IdMaker:
893892
# Optionally, the ID of the node being parametrized.
894893
# Used only for clearer error messages.
895894
nodeid: str | None
896-
# Optionally, the ID of the function being parametrized.
897-
# Used only for clearer error messages.
898-
func_name: str | None
899895

900896
def make_unique_parameterset_ids(self) -> list[str | _HiddenParam]:
901897
"""Make a unique identifier for each ParameterSet, that may be used to
@@ -1083,9 +1079,7 @@ def _complain_multiple_hidden_parameter_sets(self) -> NoReturn:
10831079
)
10841080

10851081
def _make_error_prefix(self) -> str:
1086-
if self.func_name is not None:
1087-
return f"In {self.func_name}: "
1088-
elif self.nodeid is not None:
1082+
if self.nodeid is not None:
10891083
return f"In {self.nodeid}: "
10901084
else:
10911085
return ""
@@ -1435,23 +1429,21 @@ def _resolve_parameter_set_ids(
14351429
ids_ = None
14361430
else:
14371431
idfn = None
1438-
ids_ = self._validate_ids(ids, parametersets, self.function.__name__)
1432+
ids_ = self._validate_ids(ids, parametersets)
14391433
id_maker = IdMaker(
14401434
argnames,
14411435
parametersets,
14421436
idfn,
14431437
ids_,
14441438
self.config,
14451439
nodeid=nodeid,
1446-
func_name=self.function.__name__,
14471440
)
14481441
return id_maker.make_unique_parameterset_ids()
14491442

14501443
def _validate_ids(
14511444
self,
14521445
ids: Iterable[object | None],
14531446
parametersets: Sequence[ParameterSet],
1454-
func_name: str,
14551447
) -> list[object | None]:
14561448
try:
14571449
num_ids = len(ids) # type: ignore[arg-type]
@@ -1464,8 +1456,11 @@ def _validate_ids(
14641456

14651457
# num_ids == 0 is a special case: https://github.com/pytest-dev/pytest/issues/1849
14661458
if num_ids != len(parametersets) and num_ids != 0:
1467-
msg = "In {}: {} parameter sets specified, with different number of ids: {}"
1468-
fail(msg.format(func_name, len(parametersets), num_ids), pytrace=False)
1459+
nodeid = self.definition.nodeid
1460+
fail(
1461+
f"In {nodeid}: {len(parametersets)} parameter sets specified, with different number of ids: {num_ids}",
1462+
pytrace=False,
1463+
)
14691464

14701465
return list(itertools.islice(ids, num_ids))
14711466

@@ -1486,6 +1481,7 @@ def _resolve_args_directness(
14861481
:returns
14871482
A dict mapping each arg name to either "indirect" or "direct".
14881483
"""
1484+
nodeid = self.definition.nodeid
14891485
arg_directness: dict[str, Literal["indirect", "direct"]]
14901486
if isinstance(indirect, bool):
14911487
arg_directness = dict.fromkeys(
@@ -1496,14 +1492,13 @@ def _resolve_args_directness(
14961492
for arg in indirect:
14971493
if arg not in argnames:
14981494
fail(
1499-
f"In {self.function.__name__}: indirect fixture '{arg}' doesn't exist",
1495+
f"In {nodeid}: indirect fixture '{arg}' doesn't exist",
15001496
pytrace=False,
15011497
)
15021498
arg_directness[arg] = "indirect"
15031499
else:
15041500
fail(
1505-
f"In {self.function.__name__}: expected Sequence or boolean"
1506-
f" for indirect, got {type(indirect).__name__}",
1501+
f"In {nodeid}: expected Sequence or boolean for indirect, got {type(indirect).__name__}",
15071502
pytrace=False,
15081503
)
15091504
return arg_directness
@@ -1520,12 +1515,12 @@ def _validate_if_using_arg_names(
15201515
:raises ValueError: If validation fails.
15211516
"""
15221517
default_arg_names = set(get_default_arg_names(self.function))
1523-
func_name = self.function.__name__
1518+
nodeid = self.definition.nodeid
15241519
for arg in argnames:
15251520
if arg not in self.fixturenames:
15261521
if arg in default_arg_names:
15271522
fail(
1528-
f"In {func_name}: function already takes an argument '{arg}' with a default value",
1523+
f"In {nodeid}: function already takes an argument '{arg}' with a default value",
15291524
pytrace=False,
15301525
)
15311526
else:
@@ -1534,7 +1529,7 @@ def _validate_if_using_arg_names(
15341529
else:
15351530
name = "fixture" if indirect else "argument"
15361531
fail(
1537-
f"In {func_name}: function uses no {name} '{arg}'",
1532+
f"In {nodeid}: function uses no {name} '{arg}'",
15381533
pytrace=False,
15391534
)
15401535

testing/python/metafunc.py

Lines changed: 22 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ def gen() -> Iterator[int | None | Exc]:
119119
with pytest.raises(
120120
fail.Exception,
121121
match=(
122-
r"In func: ids contains unsupported value Exc\(from_gen\) \(type: <class .*Exc'>\) at index 2. "
122+
r"In mock::nodeid: ids contains unsupported value Exc\(from_gen\) \(type: <class .*Exc'>\) at index 2. "
123123
r"Supported types are: .*"
124124
),
125125
):
@@ -300,7 +300,7 @@ class A:
300300
deadline=400.0
301301
) # very close to std deadline and CI boxes are not reliable in CPU power
302302
def test_idval_hypothesis(self, value) -> None:
303-
escaped = IdMaker([], [], None, None, None, None, None)._idval(value, "a", 6)
303+
escaped = IdMaker([], [], None, None, None, None)._idval(value, "a", 6)
304304
assert isinstance(escaped, str)
305305
escaped.encode("ascii")
306306

@@ -323,8 +323,7 @@ def test_unicode_idval(self) -> None:
323323
]
324324
for val, expected in values:
325325
assert (
326-
IdMaker([], [], None, None, None, None, None)._idval(val, "a", 6)
327-
== expected
326+
IdMaker([], [], None, None, None, None)._idval(val, "a", 6) == expected
328327
)
329328

330329
def test_unicode_idval_with_config(self) -> None:
@@ -353,7 +352,7 @@ def getini(self, name):
353352
("ação", MockConfig({option: False}), "a\\xe7\\xe3o"),
354353
]
355354
for val, config, expected in values:
356-
actual = IdMaker([], [], None, None, config, None, None)._idval(val, "a", 6)
355+
actual = IdMaker([], [], None, None, config, None)._idval(val, "a", 6)
357356
assert actual == expected
358357

359358
def test_bytes_idval(self) -> None:
@@ -367,8 +366,7 @@ def test_bytes_idval(self) -> None:
367366
]
368367
for val, expected in values:
369368
assert (
370-
IdMaker([], [], None, None, None, None, None)._idval(val, "a", 6)
371-
== expected
369+
IdMaker([], [], None, None, None, None)._idval(val, "a", 6) == expected
372370
)
373371

374372
def test_class_or_function_idval(self) -> None:
@@ -384,8 +382,7 @@ def test_function():
384382
values = [(TestClass, "TestClass"), (test_function, "test_function")]
385383
for val, expected in values:
386384
assert (
387-
IdMaker([], [], None, None, None, None, None)._idval(val, "a", 6)
388-
== expected
385+
IdMaker([], [], None, None, None, None)._idval(val, "a", 6) == expected
389386
)
390387

391388
def test_notset_idval(self) -> None:
@@ -394,9 +391,7 @@ def test_notset_idval(self) -> None:
394391
395392
Regression test for #7686.
396393
"""
397-
assert (
398-
IdMaker([], [], None, None, None, None, None)._idval(NOTSET, "a", 0) == "a0"
399-
)
394+
assert IdMaker([], [], None, None, None, None)._idval(NOTSET, "a", 0) == "a0"
400395

401396
def test_idmaker_autoname(self) -> None:
402397
"""#250"""
@@ -407,7 +402,6 @@ def test_idmaker_autoname(self) -> None:
407402
None,
408403
None,
409404
None,
410-
None,
411405
).make_unique_parameterset_ids()
412406
assert result == ["string-1.0", "st-ring-2.0"]
413407

@@ -418,18 +412,17 @@ def test_idmaker_autoname(self) -> None:
418412
None,
419413
None,
420414
None,
421-
None,
422415
).make_unique_parameterset_ids()
423416
assert result == ["a0-1.0", "a1-b1"]
424417
# unicode mixing, issue250
425418
result = IdMaker(
426-
("a", "b"), [pytest.param({}, b"\xc3\xb4")], None, None, None, None, None
419+
("a", "b"), [pytest.param({}, b"\xc3\xb4")], None, None, None, None
427420
).make_unique_parameterset_ids()
428421
assert result == ["a0-\\xc3\\xb4"]
429422

430423
def test_idmaker_with_bytes_regex(self) -> None:
431424
result = IdMaker(
432-
("a"), [pytest.param(re.compile(b"foo"))], None, None, None, None, None
425+
("a"), [pytest.param(re.compile(b"foo"))], None, None, None, None
433426
).make_unique_parameterset_ids()
434427
assert result == ["foo"]
435428

@@ -455,7 +448,6 @@ def test_idmaker_native_strings(self) -> None:
455448
None,
456449
None,
457450
None,
458-
None,
459451
).make_unique_parameterset_ids()
460452
assert result == [
461453
"1.0--1.1",
@@ -488,7 +480,6 @@ def test_idmaker_non_printable_characters(self) -> None:
488480
None,
489481
None,
490482
None,
491-
None,
492483
).make_unique_parameterset_ids()
493484
assert result == ["\\x00-1", "\\x05-2", "\\x00-3", "\\x05-4", "\\t-5", "\\t-6"]
494485

@@ -503,15 +494,14 @@ def test_idmaker_manual_ids_must_be_printable(self) -> None:
503494
None,
504495
None,
505496
None,
506-
None,
507497
).make_unique_parameterset_ids()
508498
assert result == ["hello \\x00", "hello \\x05"]
509499

510500
def test_idmaker_enum(self) -> None:
511501
enum = pytest.importorskip("enum")
512502
e = enum.Enum("Foo", "one, two")
513503
result = IdMaker(
514-
("a", "b"), [pytest.param(e.one, e.two)], None, None, None, None, None
504+
("a", "b"), [pytest.param(e.one, e.two)], None, None, None, None
515505
).make_unique_parameterset_ids()
516506
assert result == ["Foo.one-Foo.two"]
517507

@@ -534,7 +524,6 @@ def ids(val: object) -> str | None:
534524
None,
535525
None,
536526
None,
537-
None,
538527
).make_unique_parameterset_ids()
539528
assert result == ["10.0-IndexError()", "20-KeyError()", "three-b2"]
540529

@@ -555,7 +544,6 @@ def ids(val: object) -> str:
555544
None,
556545
None,
557546
None,
558-
None,
559547
).make_unique_parameterset_ids()
560548
assert result == ["a-a0", "a-a1", "a-a2"]
561549

@@ -593,7 +581,6 @@ def getini(self, name):
593581
None,
594582
config,
595583
None,
596-
None,
597584
).make_unique_parameterset_ids()
598585
assert result == [expected]
599586

@@ -625,7 +612,7 @@ def getini(self, name):
625612
]
626613
for config, expected in values:
627614
result = IdMaker(
628-
("a",), [pytest.param("string")], None, ["ação"], config, None, None
615+
("a",), [pytest.param("string")], None, ["ação"], config, None
629616
).make_unique_parameterset_ids()
630617
assert result == [expected]
631618

@@ -656,14 +643,13 @@ def getini(self, name):
656643
None,
657644
config,
658645
None,
659-
None,
660646
).make_unique_parameterset_ids()
661647
assert result == [expected]
662648

663649
def test_idmaker_duplicated_empty_str(self) -> None:
664650
"""Regression test for empty strings parametrized more than once (#11563)."""
665651
result = IdMaker(
666-
("a",), [pytest.param(""), pytest.param("")], None, None, None, None, None
652+
("a",), [pytest.param(""), pytest.param("")], None, None, None, None
667653
).make_unique_parameterset_ids()
668654
assert result == ["0", "1"]
669655

@@ -728,7 +714,6 @@ def test_idmaker_with_ids(self) -> None:
728714
["a", None],
729715
None,
730716
None,
731-
None,
732717
).make_unique_parameterset_ids()
733718
assert result == ["a", "3-4"]
734719

@@ -740,7 +725,6 @@ def test_idmaker_with_paramset_id(self) -> None:
740725
["a", None],
741726
None,
742727
None,
743-
None,
744728
).make_unique_parameterset_ids()
745729
assert result == ["me", "you"]
746730

@@ -752,7 +736,6 @@ def test_idmaker_with_ids_unique_names(self) -> None:
752736
["a", "a", "b", "c", "b"],
753737
None,
754738
None,
755-
None,
756739
).make_unique_parameterset_ids()
757740
assert result == ["a0", "a1", "b0", "c", "b1"]
758741

@@ -811,7 +794,7 @@ def func(x, y):
811794
metafunc = self.Metafunc(func)
812795
with pytest.raises(
813796
fail.Exception,
814-
match="In func: expected Sequence or boolean for indirect, got dict",
797+
match="In mock::nodeid: expected Sequence or boolean for indirect, got dict",
815798
):
816799
metafunc.parametrize("x, y", [("a", "b")], indirect={}) # type: ignore[arg-type]
817800

@@ -1431,7 +1414,8 @@ def test_ids_numbers(x,expected):
14311414
result = pytester.runpytest()
14321415
result.stdout.fnmatch_lines(
14331416
[
1434-
"In test_ids_numbers: ids contains unsupported value OSError() (type: <class 'OSError'>) at index 2. "
1417+
"In test_parametrized_ids_invalid_type.py::test_ids_numbers: ids contains unsupported value "
1418+
"OSError() (type: <class 'OSError'>) at index 2. "
14351419
"Supported types are: str, bytes, int, float, complex, bool, enum, regex or anything with a __name__."
14361420
]
14371421
)
@@ -2303,8 +2287,8 @@ def test_func(foo, bar):
23032287
"",
23042288
"*= ERRORS =*",
23052289
"*_ ERROR collecting test_multiple_hidden_param_is_forbidden.py _*",
2306-
"E Failed: In test_func: multiple instances of HIDDEN_PARAM cannot be used "
2307-
"in the same parametrize call, because the tests names need to be unique.",
2290+
"E Failed: In test_multiple_hidden_param_is_forbidden.py::test_func: multiple instances of "
2291+
"HIDDEN_PARAM cannot be used in the same parametrize call, because the tests names need to be unique.",
23082292
"*! Interrupted: 1 error during collection !*",
23092293
"*= no tests collected, 1 error in *",
23102294
]
@@ -2318,12 +2302,16 @@ def test_multiple_hidden_param_is_forbidden_idmaker(self) -> None:
23182302
[pytest.HIDDEN_PARAM, pytest.HIDDEN_PARAM],
23192303
None,
23202304
"some_node_id",
2321-
None,
23222305
)
23232306
expected = "In some_node_id: multiple instances of HIDDEN_PARAM"
23242307
with pytest.raises(Failed, match=expected):
23252308
id_maker.make_unique_parameterset_ids()
23262309

2310+
def test_idmaker_error_without_nodeid(self) -> None:
2311+
id_maker = IdMaker(["a"], [pytest.param("a")], None, [object()], None, None)
2312+
with pytest.raises(Failed, match="ids contains unsupported value"):
2313+
id_maker.make_unique_parameterset_ids()
2314+
23272315
def test_multiple_parametrize(self, pytester: Pytester) -> None:
23282316
items = pytester.getitems(
23292317
"""

0 commit comments

Comments
 (0)