Skip to content

Commit

Permalink
make Test Outcomes inherit from BaseException instead of exception
Browse files Browse the repository at this point in the history
fixes #580
  • Loading branch information
RonnyPfannschmidt committed Jul 28, 2017
1 parent 1712196 commit 06a4933
Show file tree
Hide file tree
Showing 13 changed files with 176 additions and 156 deletions.
6 changes: 3 additions & 3 deletions _pytest/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
getlocation, getfuncargnames,
safe_getattr,
)
from _pytest.runner import fail
from _pytest.outcomes import fail, TEST_OUTCOME
from _pytest.compat import FuncargnamesCompatAttr


Expand Down Expand Up @@ -121,7 +121,7 @@ def getfixturemarker(obj):
exceptions."""
try:
return getattr(obj, "_pytestfixturefunction", None)
except Exception:
except TEST_OUTCOME:
# some objects raise errors like request (from flask import request)
# we don't expect them to be fixture functions
return None
Expand Down Expand Up @@ -811,7 +811,7 @@ def pytest_fixture_setup(fixturedef, request):
my_cache_key = request.param_index
try:
result = call_fixture_func(fixturefunc, request, kwargs)
except Exception:
except TEST_OUTCOME:
fixturedef.cached_result = (None, my_cache_key, sys.exc_info())
raise
fixturedef.cached_result = (result, my_cache_key, None)
Expand Down
3 changes: 2 additions & 1 deletion _pytest/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
from UserDict import DictMixin as MappingMixin

from _pytest.config import directory_arg, UsageError, hookimpl
from _pytest.runner import collect_one_node, exit
from _pytest.runner import collect_one_node
from _pytest.outcomes import exit

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

Expand Down
141 changes: 141 additions & 0 deletions _pytest/outcomes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
"""
exception classes and constants handling test outcomes
as well as functions creating them
"""
from __future__ import absolute_import, division, print_function
import py
import sys


class OutcomeException(BaseException):
""" OutcomeException and its subclass instances indicate and
contain info about test and collection outcomes.
"""
def __init__(self, msg=None, pytrace=True):
BaseException.__init__(self, msg)
self.msg = msg
self.pytrace = pytrace

def __repr__(self):
if self.msg:
val = self.msg
if isinstance(val, bytes):
val = py._builtin._totext(val, errors='replace')
return val
return "<%s instance>" %(self.__class__.__name__,)
__str__ = __repr__


TEST_OUTCOME = (OutcomeException, Exception)


class Skipped(OutcomeException):
# XXX hackish: on 3k we fake to live in the builtins
# in order to have Skipped exception printing shorter/nicer
__module__ = 'builtins'

def __init__(self, msg=None, pytrace=True, allow_module_level=False):
OutcomeException.__init__(self, msg=msg, pytrace=pytrace)
self.allow_module_level = allow_module_level


class Failed(OutcomeException):
""" raised from an explicit call to pytest.fail() """
__module__ = 'builtins'


class Exit(KeyboardInterrupt):
""" raised for immediate program exits (no tracebacks/summaries)"""
def __init__(self, msg="unknown reason"):
self.msg = msg
KeyboardInterrupt.__init__(self, msg)

# exposed helper methods

def exit(msg):
""" exit testing process as if KeyboardInterrupt was triggered. """
__tracebackhide__ = True
raise Exit(msg)


exit.Exception = Exit


def skip(msg=""):
""" skip an executing test with the given message. Note: it's usually
better to use the pytest.mark.skipif marker to declare a test to be
skipped under certain conditions like mismatching platforms or
dependencies. See the pytest_skipping plugin for details.
"""
__tracebackhide__ = True
raise Skipped(msg=msg)


skip.Exception = Skipped


def fail(msg="", pytrace=True):
""" explicitly fail an currently-executing test with the given Message.
:arg pytrace: if false the msg represents the full failure information
and no python traceback will be reported.
"""
__tracebackhide__ = True
raise Failed(msg=msg, pytrace=pytrace)


fail.Exception = Failed



class XFailed(fail.Exception):
""" raised from an explicit call to pytest.xfail() """


def xfail(reason=""):
""" xfail an executing test or setup functions with the given reason."""
__tracebackhide__ = True
raise XFailed(reason)


xfail.Exception = XFailed



def importorskip(modname, minversion=None):
""" return imported module if it has at least "minversion" as its
__version__ attribute. If no minversion is specified the a skip
is only triggered if the module can not be imported.
"""
import warnings
__tracebackhide__ = True
compile(modname, '', 'eval') # to catch syntaxerrors
should_skip = False

with warnings.catch_warnings():
# make sure to ignore ImportWarnings that might happen because
# of existing directories with the same name we're trying to
# import but without a __init__.py file
warnings.simplefilter('ignore')
try:
__import__(modname)
except ImportError:
# Do not raise chained exception here(#1485)
should_skip = True
if should_skip:
raise Skipped("could not import %r" %(modname,), allow_module_level=True)
mod = sys.modules[modname]
if minversion is None:
return mod
verattr = getattr(mod, '__version__', None)
if minversion is not None:
try:
from pkg_resources import parse_version as pv
except ImportError:
raise Skipped("we have a required version for %r but can not import "
"pkg_resources to parse version strings." % (modname,),
allow_module_level=True)
if verattr is None or pv(verattr) < pv(minversion):
raise Skipped("module %r has __version__ %r, required is: %r" %(
modname, verattr, minversion), allow_module_level=True)
return mod
2 changes: 1 addition & 1 deletion _pytest/python.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
get_real_func, getfslineno, safe_getattr,
safe_str, getlocation, enum,
)
from _pytest.runner import fail
from _pytest.outcomes import fail
from _pytest.mark import transfer_markers

cutdir1 = py.path.local(pluggy.__file__.rstrip("oc"))
Expand Down
2 changes: 1 addition & 1 deletion _pytest/python_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import py

from _pytest.compat import isclass, izip
from _pytest.runner import fail
from _pytest.outcomes import fail
import _pytest._code

# builtin pytest.approx helper
Expand Down
4 changes: 2 additions & 2 deletions _pytest/recwarn.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@
import py
import sys
import warnings
from _pytest.fixtures import yield_fixture

from _pytest.fixtures import yield_fixture
from _pytest.outcomes import fail

@yield_fixture
def recwarn():
Expand Down Expand Up @@ -197,7 +198,6 @@ def __exit__(self, *exc_info):
if not any(issubclass(r.category, self.expected_warning)
for r in self):
__tracebackhide__ = True
from _pytest.runner import fail
fail("DID NOT WARN. No warnings of type {0} was emitted. "
"The list of emitted warnings is: {1}.".format(
self.expected_warning,
Expand Down
125 changes: 3 additions & 122 deletions _pytest/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import py
from _pytest._code.code import TerminalRepr, ExceptionInfo

from _pytest.outcomes import skip, Skipped, TEST_OUTCOME

#
# pytest plugin hooks
Expand Down Expand Up @@ -445,7 +445,7 @@ def _callfinalizers(self, colitem):
fin = finalizers.pop()
try:
fin()
except Exception:
except TEST_OUTCOME:
# XXX Only first exception will be seen by user,
# ideally all should be reported.
if exc is None:
Expand Down Expand Up @@ -492,7 +492,7 @@ def prepare(self, colitem):
self.stack.append(col)
try:
col.setup()
except Exception:
except TEST_OUTCOME:
col._prepare_exc = sys.exc_info()
raise

Expand All @@ -507,124 +507,5 @@ def collect_one_node(collector):
return rep


# =============================================================
# Test OutcomeExceptions and helpers for creating them.


class OutcomeException(Exception):
""" OutcomeException and its subclass instances indicate and
contain info about test and collection outcomes.
"""

def __init__(self, msg=None, pytrace=True):
Exception.__init__(self, msg)
self.msg = msg
self.pytrace = pytrace

def __repr__(self):
if self.msg:
val = self.msg
if isinstance(val, bytes):
val = py._builtin._totext(val, errors='replace')
return val
return "<%s instance>" % (self.__class__.__name__,)
__str__ = __repr__


class Skipped(OutcomeException):
# XXX hackish: on 3k we fake to live in the builtins
# in order to have Skipped exception printing shorter/nicer
__module__ = 'builtins'

def __init__(self, msg=None, pytrace=True, allow_module_level=False):
OutcomeException.__init__(self, msg=msg, pytrace=pytrace)
self.allow_module_level = allow_module_level


class Failed(OutcomeException):
""" raised from an explicit call to pytest.fail() """
__module__ = 'builtins'


class Exit(KeyboardInterrupt):
""" raised for immediate program exits (no tracebacks/summaries)"""

def __init__(self, msg="unknown reason"):
self.msg = msg
KeyboardInterrupt.__init__(self, msg)

# exposed helper methods


def exit(msg):
""" exit testing process as if KeyboardInterrupt was triggered. """
__tracebackhide__ = True
raise Exit(msg)


exit.Exception = Exit


def skip(msg=""):
""" skip an executing test with the given message. Note: it's usually
better to use the pytest.mark.skipif marker to declare a test to be
skipped under certain conditions like mismatching platforms or
dependencies. See the pytest_skipping plugin for details.
"""
__tracebackhide__ = True
raise Skipped(msg=msg)


skip.Exception = Skipped


def fail(msg="", pytrace=True):
""" explicitly fail an currently-executing test with the given Message.
:arg pytrace: if false the msg represents the full failure information
and no python traceback will be reported.
"""
__tracebackhide__ = True
raise Failed(msg=msg, pytrace=pytrace)


fail.Exception = Failed


def importorskip(modname, minversion=None):
""" return imported module if it has at least "minversion" as its
__version__ attribute. If no minversion is specified the a skip
is only triggered if the module can not be imported.
"""
import warnings
__tracebackhide__ = True
compile(modname, '', 'eval') # to catch syntaxerrors
should_skip = False

with warnings.catch_warnings():
# make sure to ignore ImportWarnings that might happen because
# of existing directories with the same name we're trying to
# import but without a __init__.py file
warnings.simplefilter('ignore')
try:
__import__(modname)
except ImportError:
# Do not raise chained exception here(#1485)
should_skip = True
if should_skip:
raise Skipped("could not import %r" % (modname,), allow_module_level=True)
mod = sys.modules[modname]
if minversion is None:
return mod
verattr = getattr(mod, '__version__', None)
if minversion is not None:
try:
from pkg_resources import parse_version as pv
except ImportError:
raise Skipped("we have a required version for %r but can not import "
"pkg_resources to parse version strings." % (modname,),
allow_module_level=True)
if verattr is None or pv(verattr) < pv(minversion):
raise Skipped("module %r has __version__ %r, required is: %r" % (
modname, verattr, minversion), allow_module_level=True)
return mod
Loading

0 comments on commit 06a4933

Please sign in to comment.