Skip to content

Commit e1aed8c

Browse files
authored
Merge pull request #2490 from RonnyPfannschmidt/fix-580
Test Outcomes as BaseException - fix #580
2 parents 713f763 + be401bc commit e1aed8c

13 files changed

+175
-160
lines changed

_pytest/fixtures.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
getlocation, getfuncargnames,
1717
safe_getattr,
1818
)
19-
from _pytest.runner import fail
19+
from _pytest.outcomes import fail, TEST_OUTCOME
2020
from _pytest.compat import FuncargnamesCompatAttr
2121

2222
if sys.version_info[:2] == (2, 6):
@@ -126,7 +126,7 @@ def getfixturemarker(obj):
126126
exceptions."""
127127
try:
128128
return getattr(obj, "_pytestfixturefunction", None)
129-
except Exception:
129+
except TEST_OUTCOME:
130130
# some objects raise errors like request (from flask import request)
131131
# we don't expect them to be fixture functions
132132
return None
@@ -816,7 +816,7 @@ def pytest_fixture_setup(fixturedef, request):
816816
my_cache_key = request.param_index
817817
try:
818818
result = call_fixture_func(fixturefunc, request, kwargs)
819-
except Exception:
819+
except TEST_OUTCOME:
820820
fixturedef.cached_result = (None, my_cache_key, sys.exc_info())
821821
raise
822822
fixturedef.cached_result = (result, my_cache_key, None)

_pytest/main.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
from UserDict import DictMixin as MappingMixin
1515

1616
from _pytest.config import directory_arg, UsageError, hookimpl
17-
from _pytest.runner import collect_one_node, exit
17+
from _pytest.runner import collect_one_node
18+
from _pytest.outcomes import exit
1819

1920
tracebackcutdir = py.path.local(_pytest.__file__).dirpath()
2021

_pytest/outcomes.py

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
"""
2+
exception classes and constants handling test outcomes
3+
as well as functions creating them
4+
"""
5+
from __future__ import absolute_import, division, print_function
6+
import py
7+
import sys
8+
9+
10+
class OutcomeException(BaseException):
11+
""" OutcomeException and its subclass instances indicate and
12+
contain info about test and collection outcomes.
13+
"""
14+
def __init__(self, msg=None, pytrace=True):
15+
BaseException.__init__(self, msg)
16+
self.msg = msg
17+
self.pytrace = pytrace
18+
19+
def __repr__(self):
20+
if self.msg:
21+
val = self.msg
22+
if isinstance(val, bytes):
23+
val = py._builtin._totext(val, errors='replace')
24+
return val
25+
return "<%s instance>" % (self.__class__.__name__,)
26+
__str__ = __repr__
27+
28+
29+
TEST_OUTCOME = (OutcomeException, Exception)
30+
31+
32+
class Skipped(OutcomeException):
33+
# XXX hackish: on 3k we fake to live in the builtins
34+
# in order to have Skipped exception printing shorter/nicer
35+
__module__ = 'builtins'
36+
37+
def __init__(self, msg=None, pytrace=True, allow_module_level=False):
38+
OutcomeException.__init__(self, msg=msg, pytrace=pytrace)
39+
self.allow_module_level = allow_module_level
40+
41+
42+
class Failed(OutcomeException):
43+
""" raised from an explicit call to pytest.fail() """
44+
__module__ = 'builtins'
45+
46+
47+
class Exit(KeyboardInterrupt):
48+
""" raised for immediate program exits (no tracebacks/summaries)"""
49+
def __init__(self, msg="unknown reason"):
50+
self.msg = msg
51+
KeyboardInterrupt.__init__(self, msg)
52+
53+
# exposed helper methods
54+
55+
56+
def exit(msg):
57+
""" exit testing process as if KeyboardInterrupt was triggered. """
58+
__tracebackhide__ = True
59+
raise Exit(msg)
60+
61+
62+
exit.Exception = Exit
63+
64+
65+
def skip(msg=""):
66+
""" skip an executing test with the given message. Note: it's usually
67+
better to use the pytest.mark.skipif marker to declare a test to be
68+
skipped under certain conditions like mismatching platforms or
69+
dependencies. See the pytest_skipping plugin for details.
70+
"""
71+
__tracebackhide__ = True
72+
raise Skipped(msg=msg)
73+
74+
75+
skip.Exception = Skipped
76+
77+
78+
def fail(msg="", pytrace=True):
79+
""" explicitly fail an currently-executing test with the given Message.
80+
81+
:arg pytrace: if false the msg represents the full failure information
82+
and no python traceback will be reported.
83+
"""
84+
__tracebackhide__ = True
85+
raise Failed(msg=msg, pytrace=pytrace)
86+
87+
88+
fail.Exception = Failed
89+
90+
91+
class XFailed(fail.Exception):
92+
""" raised from an explicit call to pytest.xfail() """
93+
94+
95+
def xfail(reason=""):
96+
""" xfail an executing test or setup functions with the given reason."""
97+
__tracebackhide__ = True
98+
raise XFailed(reason)
99+
100+
101+
xfail.Exception = XFailed
102+
103+
104+
def importorskip(modname, minversion=None):
105+
""" return imported module if it has at least "minversion" as its
106+
__version__ attribute. If no minversion is specified the a skip
107+
is only triggered if the module can not be imported.
108+
"""
109+
import warnings
110+
__tracebackhide__ = True
111+
compile(modname, '', 'eval') # to catch syntaxerrors
112+
should_skip = False
113+
114+
with warnings.catch_warnings():
115+
# make sure to ignore ImportWarnings that might happen because
116+
# of existing directories with the same name we're trying to
117+
# import but without a __init__.py file
118+
warnings.simplefilter('ignore')
119+
try:
120+
__import__(modname)
121+
except ImportError:
122+
# Do not raise chained exception here(#1485)
123+
should_skip = True
124+
if should_skip:
125+
raise Skipped("could not import %r" % (modname,), allow_module_level=True)
126+
mod = sys.modules[modname]
127+
if minversion is None:
128+
return mod
129+
verattr = getattr(mod, '__version__', None)
130+
if minversion is not None:
131+
try:
132+
from pkg_resources import parse_version as pv
133+
except ImportError:
134+
raise Skipped("we have a required version for %r but can not import "
135+
"pkg_resources to parse version strings." % (modname,),
136+
allow_module_level=True)
137+
if verattr is None or pv(verattr) < pv(minversion):
138+
raise Skipped("module %r has __version__ %r, required is: %r" % (
139+
modname, verattr, minversion), allow_module_level=True)
140+
return mod

_pytest/python.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
get_real_func, getfslineno, safe_getattr,
2424
safe_str, getlocation, enum,
2525
)
26-
from _pytest.runner import fail
26+
from _pytest.outcomes import fail
2727
from _pytest.mark import transfer_markers
2828

2929
cutdir1 = py.path.local(pluggy.__file__.rstrip("oc"))

_pytest/python_api.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import py
55

66
from _pytest.compat import isclass, izip
7-
from _pytest.runner import fail
7+
from _pytest.outcomes import fail
88
import _pytest._code
99

1010
# builtin pytest.approx helper

_pytest/recwarn.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
import py
88
import sys
99
import warnings
10+
1011
from _pytest.fixtures import yield_fixture
12+
from _pytest.outcomes import fail
1113

1214

1315
@yield_fixture
@@ -197,7 +199,6 @@ def __exit__(self, *exc_info):
197199
if not any(issubclass(r.category, self.expected_warning)
198200
for r in self):
199201
__tracebackhide__ = True
200-
from _pytest.runner import fail
201202
fail("DID NOT WARN. No warnings of type {0} was emitted. "
202203
"The list of emitted warnings is: {1}.".format(
203204
self.expected_warning,

_pytest/runner.py

Lines changed: 4 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,12 @@
88

99
import py
1010
from _pytest._code.code import TerminalRepr, ExceptionInfo
11-
11+
from _pytest.outcomes import skip, Skipped, TEST_OUTCOME
1212

1313
#
1414
# pytest plugin hooks
1515

16+
1617
def pytest_addoption(parser):
1718
group = parser.getgroup("terminal reporting", "reporting", after="general")
1819
group.addoption('--durations',
@@ -445,7 +446,7 @@ def _callfinalizers(self, colitem):
445446
fin = finalizers.pop()
446447
try:
447448
fin()
448-
except Exception:
449+
except TEST_OUTCOME:
449450
# XXX Only first exception will be seen by user,
450451
# ideally all should be reported.
451452
if exc is None:
@@ -492,7 +493,7 @@ def prepare(self, colitem):
492493
self.stack.append(col)
493494
try:
494495
col.setup()
495-
except Exception:
496+
except TEST_OUTCOME:
496497
col._prepare_exc = sys.exc_info()
497498
raise
498499

@@ -505,126 +506,3 @@ def collect_one_node(collector):
505506
if call and check_interactive_exception(call, rep):
506507
ihook.pytest_exception_interact(node=collector, call=call, report=rep)
507508
return rep
508-
509-
510-
# =============================================================
511-
# Test OutcomeExceptions and helpers for creating them.
512-
513-
514-
class OutcomeException(Exception):
515-
""" OutcomeException and its subclass instances indicate and
516-
contain info about test and collection outcomes.
517-
"""
518-
519-
def __init__(self, msg=None, pytrace=True):
520-
Exception.__init__(self, msg)
521-
self.msg = msg
522-
self.pytrace = pytrace
523-
524-
def __repr__(self):
525-
if self.msg:
526-
val = self.msg
527-
if isinstance(val, bytes):
528-
val = py._builtin._totext(val, errors='replace')
529-
return val
530-
return "<%s instance>" % (self.__class__.__name__,)
531-
__str__ = __repr__
532-
533-
534-
class Skipped(OutcomeException):
535-
# XXX hackish: on 3k we fake to live in the builtins
536-
# in order to have Skipped exception printing shorter/nicer
537-
__module__ = 'builtins'
538-
539-
def __init__(self, msg=None, pytrace=True, allow_module_level=False):
540-
OutcomeException.__init__(self, msg=msg, pytrace=pytrace)
541-
self.allow_module_level = allow_module_level
542-
543-
544-
class Failed(OutcomeException):
545-
""" raised from an explicit call to pytest.fail() """
546-
__module__ = 'builtins'
547-
548-
549-
class Exit(KeyboardInterrupt):
550-
""" raised for immediate program exits (no tracebacks/summaries)"""
551-
552-
def __init__(self, msg="unknown reason"):
553-
self.msg = msg
554-
KeyboardInterrupt.__init__(self, msg)
555-
556-
# exposed helper methods
557-
558-
559-
def exit(msg):
560-
""" exit testing process as if KeyboardInterrupt was triggered. """
561-
__tracebackhide__ = True
562-
raise Exit(msg)
563-
564-
565-
exit.Exception = Exit
566-
567-
568-
def skip(msg=""):
569-
""" skip an executing test with the given message. Note: it's usually
570-
better to use the pytest.mark.skipif marker to declare a test to be
571-
skipped under certain conditions like mismatching platforms or
572-
dependencies. See the pytest_skipping plugin for details.
573-
"""
574-
__tracebackhide__ = True
575-
raise Skipped(msg=msg)
576-
577-
578-
skip.Exception = Skipped
579-
580-
581-
def fail(msg="", pytrace=True):
582-
""" explicitly fail an currently-executing test with the given Message.
583-
584-
:arg pytrace: if false the msg represents the full failure information
585-
and no python traceback will be reported.
586-
"""
587-
__tracebackhide__ = True
588-
raise Failed(msg=msg, pytrace=pytrace)
589-
590-
591-
fail.Exception = Failed
592-
593-
594-
def importorskip(modname, minversion=None):
595-
""" return imported module if it has at least "minversion" as its
596-
__version__ attribute. If no minversion is specified the a skip
597-
is only triggered if the module can not be imported.
598-
"""
599-
import warnings
600-
__tracebackhide__ = True
601-
compile(modname, '', 'eval') # to catch syntaxerrors
602-
should_skip = False
603-
604-
with warnings.catch_warnings():
605-
# make sure to ignore ImportWarnings that might happen because
606-
# of existing directories with the same name we're trying to
607-
# import but without a __init__.py file
608-
warnings.simplefilter('ignore')
609-
try:
610-
__import__(modname)
611-
except ImportError:
612-
# Do not raise chained exception here(#1485)
613-
should_skip = True
614-
if should_skip:
615-
raise Skipped("could not import %r" % (modname,), allow_module_level=True)
616-
mod = sys.modules[modname]
617-
if minversion is None:
618-
return mod
619-
verattr = getattr(mod, '__version__', None)
620-
if minversion is not None:
621-
try:
622-
from pkg_resources import parse_version as pv
623-
except ImportError:
624-
raise Skipped("we have a required version for %r but can not import "
625-
"pkg_resources to parse version strings." % (modname,),
626-
allow_module_level=True)
627-
if verattr is None or pv(verattr) < pv(minversion):
628-
raise Skipped("module %r has __version__ %r, required is: %r" % (
629-
modname, verattr, minversion), allow_module_level=True)
630-
return mod

0 commit comments

Comments
 (0)