Skip to content

Commit 5436e42

Browse files
committed
Use pytest.fail(..., pytrace=False) when treating user errors
This prevents an enormous and often useless stack trace from showing to end users. Fix #3867 Fix #2293
1 parent 67f40e1 commit 5436e42

File tree

11 files changed

+124
-99
lines changed

11 files changed

+124
-99
lines changed

changelog/2293.feature.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Improve usage errors messages by hiding internal details which can be distracting and noisy.
2+
3+
This has the side effect that some error conditions that previously raised generic errors (such as
4+
``ValueError`` for unregistered marks) are now raising ``Failed`` exceptions.

changelog/2293.trivial.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
The internal ``MarkerError`` exception has been removed.

src/_pytest/fixtures.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -579,7 +579,7 @@ def _compute_fixture_value(self, fixturedef):
579579
nodeid=funcitem.nodeid,
580580
typename=type(funcitem).__name__,
581581
)
582-
fail(msg)
582+
fail(msg, pytrace=False)
583583
if has_params:
584584
frame = inspect.stack()[3]
585585
frameinfo = inspect.getframeinfo(frame[0])
@@ -600,7 +600,7 @@ def _compute_fixture_value(self, fixturedef):
600600
source_lineno,
601601
)
602602
)
603-
fail(msg)
603+
fail(msg, pytrace=False)
604604
else:
605605
# indices might not be set if old-style metafunc.addcall() was used
606606
param_index = funcitem.callspec.indices.get(argname, 0)
@@ -718,10 +718,11 @@ def scope2index(scope, descr, where=None):
718718
try:
719719
return scopes.index(scope)
720720
except ValueError:
721-
raise ValueError(
722-
"{} {}has an unsupported scope value '{}'".format(
721+
fail(
722+
"{} {}got an unexpected scope value '{}'".format(
723723
descr, "from {} ".format(where) if where else "", scope
724-
)
724+
),
725+
pytrace=False,
725726
)
726727

727728

@@ -854,7 +855,9 @@ def __init__(
854855
self.argname = argname
855856
self.scope = scope
856857
self.scopenum = scope2index(
857-
scope or "function", descr="fixture {}".format(func.__name__), where=baseid
858+
scope or "function",
859+
descr="Fixture '{}'".format(func.__name__),
860+
where=baseid,
858861
)
859862
self.params = params
860863
self.argnames = getfuncargnames(func, is_method=unittest)

src/_pytest/mark/__init__.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,6 @@
2424
]
2525

2626

27-
class MarkerError(Exception):
28-
29-
"""Error in use of a pytest marker/attribute."""
30-
31-
3227
def param(*values, **kw):
3328
"""Specify a parameter in `pytest.mark.parametrize`_ calls or
3429
:ref:`parametrized fixtures <fixture-parametrize-marks>`.

src/_pytest/mark/structures.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import attr
88

9+
from _pytest.outcomes import fail
910
from ..deprecated import MARK_PARAMETERSET_UNPACKING, MARK_INFO_ATTRIBUTE
1011
from ..compat import NOTSET, getfslineno, MappingMixin
1112
from six.moves import map
@@ -393,7 +394,7 @@ def _check(self, name):
393394
x = marker.split("(", 1)[0]
394395
values.add(x)
395396
if name not in self._markers:
396-
raise AttributeError("%r not a registered marker" % (name,))
397+
fail("{!r} not a registered marker".format(name), pytrace=False)
397398

398399

399400
MARK_GEN = MarkGenerator()

src/_pytest/nodes.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import _pytest
1010
import _pytest._code
1111
from _pytest.compat import getfslineno
12+
from _pytest.outcomes import fail
1213

1314
from _pytest.mark.structures import NodeKeywords, MarkInfo
1415

@@ -346,6 +347,9 @@ def _prunetraceback(self, excinfo):
346347
pass
347348

348349
def _repr_failure_py(self, excinfo, style=None):
350+
if excinfo.errisinstance(fail.Exception):
351+
if not excinfo.value.pytrace:
352+
return six.text_type(excinfo.value)
349353
fm = self.session._fixturemanager
350354
if excinfo.errisinstance(fm.FixtureLookupError):
351355
return excinfo.value.formatrepr()

src/_pytest/python.py

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
import py
1414
import six
1515
from _pytest.main import FSHookProxy
16-
from _pytest.mark import MarkerError
1716
from _pytest.config import hookimpl
1817

1918
import _pytest
@@ -159,8 +158,8 @@ def pytest_generate_tests(metafunc):
159158
alt_spellings = ["parameterize", "parametrise", "parameterise"]
160159
for attr in alt_spellings:
161160
if hasattr(metafunc.function, attr):
162-
msg = "{0} has '{1}', spelling should be 'parametrize'"
163-
raise MarkerError(msg.format(metafunc.function.__name__, attr))
161+
msg = "{0} has '{1}' mark, spelling should be 'parametrize'"
162+
fail(msg.format(metafunc.function.__name__, attr), pytrace=False)
164163
for marker in metafunc.definition.iter_markers(name="parametrize"):
165164
metafunc.parametrize(*marker.args, **marker.kwargs)
166165

@@ -760,12 +759,6 @@ def _prunetraceback(self, excinfo):
760759
for entry in excinfo.traceback[1:-1]:
761760
entry.set_repr_style("short")
762761

763-
def _repr_failure_py(self, excinfo, style="long"):
764-
if excinfo.errisinstance(fail.Exception):
765-
if not excinfo.value.pytrace:
766-
return six.text_type(excinfo.value)
767-
return super(FunctionMixin, self)._repr_failure_py(excinfo, style=style)
768-
769762
def repr_failure(self, excinfo, outerr=None):
770763
assert outerr is None, "XXX outerr usage is deprecated"
771764
style = self.config.option.tbstyle
@@ -987,7 +980,9 @@ def parametrize(self, argnames, argvalues, indirect=False, ids=None, scope=None)
987980

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

990-
scopenum = scope2index(scope, descr="call to {}".format(self.parametrize))
983+
scopenum = scope2index(
984+
scope, descr="parametrize() call in {}".format(self.function.__name__)
985+
)
991986

992987
# create the new calls: if we are parametrize() multiple times (by applying the decorator
993988
# more than once) then we accumulate those calls generating the cartesian product
@@ -1026,15 +1021,16 @@ def _resolve_arg_ids(self, argnames, ids, parameters, item):
10261021
idfn = ids
10271022
ids = None
10281023
if ids:
1024+
func_name = self.function.__name__
10291025
if len(ids) != len(parameters):
1030-
raise ValueError(
1031-
"%d tests specified with %d ids" % (len(parameters), len(ids))
1032-
)
1026+
msg = "In {}: {} parameter sets specified, with different number of ids: {}"
1027+
fail(msg.format(func_name, len(parameters), len(ids)), pytrace=False)
10331028
for id_value in ids:
10341029
if id_value is not None and not isinstance(id_value, six.string_types):
1035-
msg = "ids must be list of strings, found: %s (type: %s)"
1036-
raise ValueError(
1037-
msg % (saferepr(id_value), type(id_value).__name__)
1030+
msg = "In {}: ids must be list of strings, found: {} (type: {!r})"
1031+
fail(
1032+
msg.format(func_name, saferepr(id_value), type(id_value)),
1033+
pytrace=False,
10381034
)
10391035
ids = idmaker(argnames, parameters, idfn, ids, self.config, item=item)
10401036
return ids
@@ -1059,9 +1055,11 @@ def _resolve_arg_value_types(self, argnames, indirect):
10591055
valtypes = dict.fromkeys(argnames, "funcargs")
10601056
for arg in indirect:
10611057
if arg not in argnames:
1062-
raise ValueError(
1063-
"indirect given to %r: fixture %r doesn't exist"
1064-
% (self.function, arg)
1058+
fail(
1059+
"In {}: indirect fixture '{}' doesn't exist".format(
1060+
self.function.__name__, arg
1061+
),
1062+
pytrace=False,
10651063
)
10661064
valtypes[arg] = "params"
10671065
return valtypes
@@ -1075,19 +1073,25 @@ def _validate_if_using_arg_names(self, argnames, indirect):
10751073
:raise ValueError: if validation fails.
10761074
"""
10771075
default_arg_names = set(get_default_arg_names(self.function))
1076+
func_name = self.function.__name__
10781077
for arg in argnames:
10791078
if arg not in self.fixturenames:
10801079
if arg in default_arg_names:
1081-
raise ValueError(
1082-
"%r already takes an argument %r with a default value"
1083-
% (self.function, arg)
1080+
fail(
1081+
"In {}: function already takes an argument '{}' with a default value".format(
1082+
func_name, arg
1083+
),
1084+
pytrace=False,
10841085
)
10851086
else:
10861087
if isinstance(indirect, (tuple, list)):
10871088
name = "fixture" if arg in indirect else "argument"
10881089
else:
10891090
name = "fixture" if indirect else "argument"
1090-
raise ValueError("%r uses no %s %r" % (self.function, name, arg))
1091+
fail(
1092+
"In {}: function uses no {} '{}'".format(func_name, name, arg),
1093+
pytrace=False,
1094+
)
10911095

10921096
def addcall(self, funcargs=None, id=NOTSET, param=NOTSET):
10931097
""" Add a new call to the underlying test function during the collection phase of a test run.

testing/python/fixture.py

Lines changed: 39 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1217,8 +1217,7 @@ def test_nothing(badscope):
12171217
result = testdir.runpytest_inprocess()
12181218
result.stdout.fnmatch_lines(
12191219
(
1220-
"*ValueError: fixture badscope from test_invalid_scope.py has an unsupported"
1221-
" scope value 'functions'"
1220+
"*Fixture 'badscope' from test_invalid_scope.py got an unexpected scope value 'functions'"
12221221
)
12231222
)
12241223

@@ -3607,16 +3606,15 @@ def test_foo(request, get_named_fixture):
36073606
)
36083607
result = testdir.runpytest()
36093608
result.stdout.fnmatch_lines(
3610-
"""
3611-
E*Failed: The requested fixture has no parameter defined for test:
3612-
E* test_call_from_fixture.py::test_foo
3613-
E*
3614-
E*Requested fixture 'fix_with_param' defined in:
3615-
E*test_call_from_fixture.py:4
3616-
E*Requested here:
3617-
E*test_call_from_fixture.py:9
3618-
*1 error*
3619-
"""
3609+
[
3610+
"The requested fixture has no parameter defined for test:",
3611+
" test_call_from_fixture.py::test_foo",
3612+
"Requested fixture 'fix_with_param' defined in:",
3613+
"test_call_from_fixture.py:4",
3614+
"Requested here:",
3615+
"test_call_from_fixture.py:9",
3616+
"*1 error in*",
3617+
]
36203618
)
36213619

36223620
def test_call_from_test(self, testdir):
@@ -3634,16 +3632,15 @@ def test_foo(request):
36343632
)
36353633
result = testdir.runpytest()
36363634
result.stdout.fnmatch_lines(
3637-
"""
3638-
E*Failed: The requested fixture has no parameter defined for test:
3639-
E* test_call_from_test.py::test_foo
3640-
E*
3641-
E*Requested fixture 'fix_with_param' defined in:
3642-
E*test_call_from_test.py:4
3643-
E*Requested here:
3644-
E*test_call_from_test.py:8
3645-
*1 failed*
3646-
"""
3635+
[
3636+
"The requested fixture has no parameter defined for test:",
3637+
" test_call_from_test.py::test_foo",
3638+
"Requested fixture 'fix_with_param' defined in:",
3639+
"test_call_from_test.py:4",
3640+
"Requested here:",
3641+
"test_call_from_test.py:8",
3642+
"*1 failed*",
3643+
]
36473644
)
36483645

36493646
def test_external_fixture(self, testdir):
@@ -3665,16 +3662,16 @@ def test_foo(request):
36653662
)
36663663
result = testdir.runpytest()
36673664
result.stdout.fnmatch_lines(
3668-
"""
3669-
E*Failed: The requested fixture has no parameter defined for test:
3670-
E* test_external_fixture.py::test_foo
3671-
E*
3672-
E*Requested fixture 'fix_with_param' defined in:
3673-
E*conftest.py:4
3674-
E*Requested here:
3675-
E*test_external_fixture.py:2
3676-
*1 failed*
3677-
"""
3665+
[
3666+
"The requested fixture has no parameter defined for test:",
3667+
" test_external_fixture.py::test_foo",
3668+
"",
3669+
"Requested fixture 'fix_with_param' defined in:",
3670+
"conftest.py:4",
3671+
"Requested here:",
3672+
"test_external_fixture.py:2",
3673+
"*1 failed*",
3674+
]
36783675
)
36793676

36803677
def test_non_relative_path(self, testdir):
@@ -3709,16 +3706,16 @@ def test_foo(request):
37093706
testdir.syspathinsert(fixdir)
37103707
result = testdir.runpytest()
37113708
result.stdout.fnmatch_lines(
3712-
"""
3713-
E*Failed: The requested fixture has no parameter defined for test:
3714-
E* test_foos.py::test_foo
3715-
E*
3716-
E*Requested fixture 'fix_with_param' defined in:
3717-
E*fix.py:4
3718-
E*Requested here:
3719-
E*test_foos.py:4
3720-
*1 failed*
3721-
"""
3709+
[
3710+
"The requested fixture has no parameter defined for test:",
3711+
" test_foos.py::test_foo",
3712+
"",
3713+
"Requested fixture 'fix_with_param' defined in:",
3714+
"*fix.py:4",
3715+
"Requested here:",
3716+
"test_foos.py:4",
3717+
"*1 failed*",
3718+
]
37223719
)
37233720

37243721

0 commit comments

Comments
 (0)