* properly expose and document runtest-protocol related Exceptions
and move all definitions to the runner plugin for now. * also move EXIT codes to session.py, obsoleting outcome.py alltogether. --HG-- branch : trunk
This commit is contained in:
parent
0d0a7b7fec
commit
d5e463605e
|
@ -9,6 +9,8 @@ Changes between 1.2.1 and 1.2.2 (release pending)
|
|||
- added links to the new capturelog and coverage plugins
|
||||
- (issue87) fix unboundlocal error in assertionold code
|
||||
- (issue86) improve documentation for looponfailing
|
||||
- expose some internal test running exceptions under py.test.exc.*
|
||||
and shift raises/importorskip etc. helper definitions to runner plugin .
|
||||
- ship distribute_setup.py version 0.6.10
|
||||
|
||||
|
||||
|
|
|
@ -38,11 +38,6 @@ py.apipkg.initpkg(__name__, dict(
|
|||
# helpers for use from test functions or collectors
|
||||
'__onfirstaccess__' : '._test.config:onpytestaccess',
|
||||
'__doc__' : '._test:__doc__',
|
||||
'raises' : '._test.outcome:raises',
|
||||
'skip' : '._test.outcome:skip',
|
||||
'importorskip' : '._test.outcome:importorskip',
|
||||
'fail' : '._test.outcome:fail',
|
||||
'exit' : '._test.outcome:exit',
|
||||
# configuration/initialization related test api
|
||||
'config' : '._test.config:config_per_process',
|
||||
'ensuretemp' : '._test.config:ensuretemp',
|
||||
|
|
|
@ -3,7 +3,6 @@ interactive debugging with the Python Debugger.
|
|||
"""
|
||||
import py
|
||||
import pdb, sys, linecache
|
||||
from py._test.outcome import Skipped
|
||||
|
||||
def pytest_addoption(parser):
|
||||
group = parser.getgroup("general")
|
||||
|
@ -17,7 +16,8 @@ def pytest_configure(config):
|
|||
|
||||
class PdbInvoke:
|
||||
def pytest_runtest_makereport(self, item, call):
|
||||
if call.excinfo and not call.excinfo.errisinstance(Skipped):
|
||||
if call.excinfo and not \
|
||||
call.excinfo.errisinstance(py.test.exc.Skipped):
|
||||
# play well with capturing, slightly hackish
|
||||
capman = item.config.pluginmanager.getplugin('capturemanager')
|
||||
capman.suspendcapture()
|
||||
|
|
|
@ -3,7 +3,23 @@ collect and run test items and create reports.
|
|||
"""
|
||||
|
||||
import py, sys
|
||||
from py._test.outcome import Skipped
|
||||
|
||||
def pytest_namespace():
|
||||
class exc:
|
||||
""" namespace holding py.test runner exceptions. """
|
||||
Skipped = Skipped
|
||||
ExceptionFailure = ExceptionFailure
|
||||
Failed = Failed
|
||||
Exit = Exit
|
||||
|
||||
return {
|
||||
'exc' : exc,
|
||||
'raises' : raises,
|
||||
'skip' : skip,
|
||||
'importorskip' : importorskip,
|
||||
'fail' : fail,
|
||||
'exit' : exit,
|
||||
}
|
||||
|
||||
#
|
||||
# pytest plugin hooks
|
||||
|
@ -141,7 +157,7 @@ class ItemTestReport(BaseReport):
|
|||
self.failed = True
|
||||
shortrepr = "?"
|
||||
longrepr = excinfo
|
||||
elif excinfo.errisinstance(Skipped):
|
||||
elif excinfo.errisinstance(py.test.exc.Skipped):
|
||||
self.skipped = True
|
||||
shortrepr = "s"
|
||||
longrepr = self.item._repr_failure_py(excinfo)
|
||||
|
@ -180,7 +196,7 @@ class CollectReport(BaseReport):
|
|||
self.result = result
|
||||
else:
|
||||
self.longrepr = self.collector._repr_failure_py(excinfo)
|
||||
if excinfo.errisinstance(Skipped):
|
||||
if excinfo.errisinstance(py.test.exc.Skipped):
|
||||
self.skipped = True
|
||||
self.reason = str(excinfo.value)
|
||||
else:
|
||||
|
@ -259,3 +275,125 @@ class SetupState(object):
|
|||
except Exception:
|
||||
col._prepare_exc = sys.exc_info()
|
||||
raise
|
||||
|
||||
# =============================================================
|
||||
# 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, excinfo=None):
|
||||
self.msg = msg
|
||||
self.excinfo = excinfo
|
||||
|
||||
def __repr__(self):
|
||||
if self.msg:
|
||||
return repr(self.msg)
|
||||
return "<%s instance>" %(self.__class__.__name__,)
|
||||
__str__ = __repr__
|
||||
|
||||
class Skipped(OutcomeException):
|
||||
# XXX slighly hackish: on 3k we fake to live in the builtins
|
||||
# in order to have Skipped exception printing shorter/nicer
|
||||
__module__ = 'builtins'
|
||||
|
||||
class Failed(OutcomeException):
|
||||
""" raised from an explicit call to py.test.fail() """
|
||||
|
||||
class ExceptionFailure(Failed):
|
||||
""" raised by py.test.raises on an exception-assertion mismatch. """
|
||||
def __init__(self, expr, expected, msg=None, excinfo=None):
|
||||
Failed.__init__(self, msg=msg, excinfo=excinfo)
|
||||
self.expr = expr
|
||||
self.expected = expected
|
||||
|
||||
class Exit(KeyboardInterrupt):
|
||||
""" raised by py.test.exit for immediate program exits without tracebacks and reporter/summary. """
|
||||
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)
|
||||
|
||||
def skip(msg=""):
|
||||
""" skip an executing test with the given message. Note: it's usually
|
||||
better use the py.test.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)
|
||||
|
||||
def fail(msg=""):
|
||||
""" explicitely fail an currently-executing test with the given Message. """
|
||||
__tracebackhide__ = True
|
||||
raise Failed(msg=msg)
|
||||
|
||||
def raises(ExpectedException, *args, **kwargs):
|
||||
""" if args[0] is callable: raise AssertionError if calling it with
|
||||
the remaining arguments does not raise the expected exception.
|
||||
if args[0] is a string: raise AssertionError if executing the
|
||||
the string in the calling scope does not raise expected exception.
|
||||
for examples:
|
||||
x = 5
|
||||
raises(TypeError, lambda x: x + 'hello', x=x)
|
||||
raises(TypeError, "x + 'hello'")
|
||||
"""
|
||||
__tracebackhide__ = True
|
||||
assert args
|
||||
if isinstance(args[0], str):
|
||||
code, = args
|
||||
assert isinstance(code, str)
|
||||
frame = sys._getframe(1)
|
||||
loc = frame.f_locals.copy()
|
||||
loc.update(kwargs)
|
||||
#print "raises frame scope: %r" % frame.f_locals
|
||||
try:
|
||||
code = py.code.Source(code).compile()
|
||||
py.builtin.exec_(code, frame.f_globals, loc)
|
||||
# XXX didn'T mean f_globals == f_locals something special?
|
||||
# this is destroyed here ...
|
||||
except ExpectedException:
|
||||
return py.code.ExceptionInfo()
|
||||
else:
|
||||
func = args[0]
|
||||
try:
|
||||
func(*args[1:], **kwargs)
|
||||
except ExpectedException:
|
||||
return py.code.ExceptionInfo()
|
||||
k = ", ".join(["%s=%r" % x for x in kwargs.items()])
|
||||
if k:
|
||||
k = ', ' + k
|
||||
expr = '%s(%r%s)' %(getattr(func, '__name__', func), args, k)
|
||||
raise ExceptionFailure(msg="DID NOT RAISE",
|
||||
expr=args, expected=ExpectedException)
|
||||
|
||||
def importorskip(modname, minversion=None):
|
||||
""" return imported module if it has a higher __version__ than the
|
||||
optionally specified 'minversion' - otherwise call py.test.skip()
|
||||
with a message detailing the mismatch.
|
||||
"""
|
||||
compile(modname, '', 'eval') # to catch syntaxerrors
|
||||
try:
|
||||
mod = __import__(modname, None, None, ['__doc__'])
|
||||
except ImportError:
|
||||
py.test.skip("could not import %r" %(modname,))
|
||||
if minversion is None:
|
||||
return mod
|
||||
verattr = getattr(mod, '__version__', None)
|
||||
if isinstance(minversion, str):
|
||||
minver = minversion.split(".")
|
||||
else:
|
||||
minver = list(minversion)
|
||||
if verattr is None or verattr.split(".") < minver:
|
||||
py.test.skip("module %r has __version__ %r, required is: %r" %(
|
||||
modname, verattr, minversion))
|
||||
return mod
|
||||
|
||||
|
|
|
@ -1,136 +0,0 @@
|
|||
"""
|
||||
Test OutcomeExceptions and helpers for creating them.
|
||||
py.test.skip|fail|raises helper implementations
|
||||
|
||||
"""
|
||||
|
||||
import py
|
||||
import sys
|
||||
|
||||
class OutcomeException(Exception):
|
||||
""" OutcomeException and its subclass instances indicate and
|
||||
contain info about test and collection outcomes.
|
||||
"""
|
||||
def __init__(self, msg=None, excinfo=None):
|
||||
self.msg = msg
|
||||
self.excinfo = excinfo
|
||||
|
||||
def __repr__(self):
|
||||
if self.msg:
|
||||
return repr(self.msg)
|
||||
return "<%s instance>" %(self.__class__.__name__,)
|
||||
__str__ = __repr__
|
||||
|
||||
class Passed(OutcomeException):
|
||||
pass
|
||||
|
||||
class Skipped(OutcomeException):
|
||||
# XXX slighly hackish: on 3k we fake to live in the builtins
|
||||
# in order to have Skipped exception printing shorter/nicer
|
||||
__module__ = 'builtins'
|
||||
|
||||
class Failed(OutcomeException):
|
||||
pass
|
||||
|
||||
class ExceptionFailure(Failed):
|
||||
def __init__(self, expr, expected, msg=None, excinfo=None):
|
||||
Failed.__init__(self, msg=msg, excinfo=excinfo)
|
||||
self.expr = expr
|
||||
self.expected = expected
|
||||
|
||||
class Exit(KeyboardInterrupt):
|
||||
""" for immediate program exits without tracebacks and reporter/summary. """
|
||||
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)
|
||||
|
||||
def skip(msg=""):
|
||||
""" skip an executing test with the given message. Note: it's usually
|
||||
better use the py.test.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)
|
||||
|
||||
def fail(msg=""):
|
||||
""" explicitely fail this executing test with the given Message. """
|
||||
__tracebackhide__ = True
|
||||
raise Failed(msg=msg)
|
||||
|
||||
def raises(ExpectedException, *args, **kwargs):
|
||||
""" if args[0] is callable: raise AssertionError if calling it with
|
||||
the remaining arguments does not raise the expected exception.
|
||||
if args[0] is a string: raise AssertionError if executing the
|
||||
the string in the calling scope does not raise expected exception.
|
||||
for examples:
|
||||
x = 5
|
||||
raises(TypeError, lambda x: x + 'hello', x=x)
|
||||
raises(TypeError, "x + 'hello'")
|
||||
"""
|
||||
__tracebackhide__ = True
|
||||
assert args
|
||||
if isinstance(args[0], str):
|
||||
code, = args
|
||||
assert isinstance(code, str)
|
||||
frame = sys._getframe(1)
|
||||
loc = frame.f_locals.copy()
|
||||
loc.update(kwargs)
|
||||
#print "raises frame scope: %r" % frame.f_locals
|
||||
try:
|
||||
code = py.code.Source(code).compile()
|
||||
py.builtin.exec_(code, frame.f_globals, loc)
|
||||
# XXX didn'T mean f_globals == f_locals something special?
|
||||
# this is destroyed here ...
|
||||
except ExpectedException:
|
||||
return py.code.ExceptionInfo()
|
||||
else:
|
||||
func = args[0]
|
||||
try:
|
||||
func(*args[1:], **kwargs)
|
||||
except ExpectedException:
|
||||
return py.code.ExceptionInfo()
|
||||
k = ", ".join(["%s=%r" % x for x in kwargs.items()])
|
||||
if k:
|
||||
k = ', ' + k
|
||||
expr = '%s(%r%s)' %(getattr(func, '__name__', func), args, k)
|
||||
raise ExceptionFailure(msg="DID NOT RAISE",
|
||||
expr=args, expected=ExpectedException)
|
||||
|
||||
def importorskip(modname, minversion=None):
|
||||
""" return imported module if it has a higher __version__ than the
|
||||
optionally specified 'minversion' - otherwise call py.test.skip()
|
||||
with a message detailing the mismatch.
|
||||
"""
|
||||
compile(modname, '', 'eval') # to catch syntaxerrors
|
||||
try:
|
||||
mod = __import__(modname, None, None, ['__doc__'])
|
||||
except ImportError:
|
||||
py.test.skip("could not import %r" %(modname,))
|
||||
if minversion is None:
|
||||
return mod
|
||||
verattr = getattr(mod, '__version__', None)
|
||||
if isinstance(minversion, str):
|
||||
minver = minversion.split(".")
|
||||
else:
|
||||
minver = list(minversion)
|
||||
if verattr is None or verattr.split(".") < minver:
|
||||
py.test.skip("module %r has __version__ %r, required is: %r" %(
|
||||
modname, verattr, minversion))
|
||||
return mod
|
||||
|
||||
|
||||
|
||||
# exitcodes for the command line
|
||||
EXIT_OK = 0
|
||||
EXIT_TESTSFAILED = 1
|
||||
EXIT_INTERRUPTED = 2
|
||||
EXIT_INTERNALERROR = 3
|
||||
EXIT_NOHOSTS = 4
|
|
@ -4,7 +4,6 @@ managing loading and interacting with pytest plugins.
|
|||
import py
|
||||
import inspect
|
||||
from py._plugin import hookspec
|
||||
from py._test.outcome import Skipped
|
||||
|
||||
default_plugins = (
|
||||
"default runner capture mark terminal skipping tmpdir monkeypatch "
|
||||
|
@ -139,7 +138,7 @@ class PluginManager(object):
|
|||
mod = importplugin(modname)
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except Skipped:
|
||||
except py.test.exc.Skipped:
|
||||
e = py.std.sys.exc_info()[1]
|
||||
self._hints.append("skipped plugin %r: %s" %((modname, e.msg)))
|
||||
else:
|
||||
|
|
|
@ -6,7 +6,13 @@
|
|||
"""
|
||||
|
||||
import py
|
||||
from py._test import outcome
|
||||
|
||||
# exitcodes for the command line
|
||||
EXIT_OK = 0
|
||||
EXIT_TESTSFAILED = 1
|
||||
EXIT_INTERRUPTED = 2
|
||||
EXIT_INTERNALERROR = 3
|
||||
EXIT_NOHOSTS = 4
|
||||
|
||||
# imports used for genitems()
|
||||
Item = py.test.collect.Item
|
||||
|
@ -96,21 +102,21 @@ class Session(object):
|
|||
""" main loop for running tests. """
|
||||
self.shouldstop = False
|
||||
self.sessionstarts()
|
||||
exitstatus = outcome.EXIT_OK
|
||||
exitstatus = EXIT_OK
|
||||
try:
|
||||
self._mainloop(colitems)
|
||||
if self._testsfailed:
|
||||
exitstatus = outcome.EXIT_TESTSFAILED
|
||||
exitstatus = EXIT_TESTSFAILED
|
||||
self.sessionfinishes(exitstatus=exitstatus)
|
||||
except KeyboardInterrupt:
|
||||
excinfo = py.code.ExceptionInfo()
|
||||
self.config.hook.pytest_keyboard_interrupt(excinfo=excinfo)
|
||||
exitstatus = outcome.EXIT_INTERRUPTED
|
||||
exitstatus = EXIT_INTERRUPTED
|
||||
except:
|
||||
excinfo = py.code.ExceptionInfo()
|
||||
self.config.pluginmanager.notify_exception(excinfo)
|
||||
exitstatus = outcome.EXIT_INTERNALERROR
|
||||
if exitstatus in (outcome.EXIT_INTERNALERROR, outcome.EXIT_INTERRUPTED):
|
||||
exitstatus = EXIT_INTERNALERROR
|
||||
if exitstatus in (EXIT_INTERNALERROR, EXIT_INTERRUPTED):
|
||||
self.sessionfinishes(exitstatus=exitstatus)
|
||||
return exitstatus
|
||||
|
||||
|
|
|
@ -45,8 +45,6 @@ class TestDoctests:
|
|||
reprec.assertoutcome(failed=1)
|
||||
|
||||
def test_doctest_unexpected_exception(self, testdir):
|
||||
from py._test.outcome import Failed
|
||||
|
||||
p = testdir.maketxtfile("""
|
||||
>>> i = 0
|
||||
>>> i = 1
|
||||
|
|
|
@ -197,14 +197,13 @@ class BaseFunctionalTests:
|
|||
assert rep.when == "call"
|
||||
|
||||
def test_exit_propagates(self, testdir):
|
||||
from py._test.outcome import Exit
|
||||
try:
|
||||
testdir.runitem("""
|
||||
from py._test.outcome import Exit
|
||||
import py
|
||||
def test_func():
|
||||
raise Exit()
|
||||
raise py.test.exc.Exit()
|
||||
""")
|
||||
except Exit:
|
||||
except py.test.exc.Exit:
|
||||
pass
|
||||
else:
|
||||
py.test.fail("did not raise")
|
||||
|
@ -216,7 +215,6 @@ class TestExecutionNonForked(BaseFunctionalTests):
|
|||
return f
|
||||
|
||||
def test_keyboardinterrupt_propagates(self, testdir):
|
||||
from py._test.outcome import Exit
|
||||
try:
|
||||
testdir.runitem("""
|
||||
def test_func():
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import py
|
||||
import types
|
||||
import sys
|
||||
from py._test.outcome import Skipped
|
||||
|
||||
def checksubpackage(name):
|
||||
obj = getattr(py, name)
|
||||
|
@ -52,7 +51,7 @@ def test_importall():
|
|||
modpath = 'py.%s' % relpath
|
||||
try:
|
||||
check_import(modpath)
|
||||
except Skipped:
|
||||
except py.test.exc.Skipped:
|
||||
pass
|
||||
|
||||
def check_import(modpath):
|
||||
|
|
|
@ -75,13 +75,14 @@ class TestConfigAPI:
|
|||
py.test.raises(KeyError, 'config.getvalue("y", o)')
|
||||
|
||||
def test_config_getvalueorskip(self, testdir):
|
||||
from py._test.outcome import Skipped
|
||||
config = testdir.parseconfig()
|
||||
py.test.raises(Skipped, "config.getvalueorskip('hello')")
|
||||
py.test.raises(py.test.exc.Skipped,
|
||||
"config.getvalueorskip('hello')")
|
||||
verbose = config.getvalueorskip("verbose")
|
||||
assert verbose == config.option.verbose
|
||||
config.option.hello = None
|
||||
py.test.raises(Skipped, "config.getvalueorskip('hello')")
|
||||
py.test.raises(py.test.exc.Skipped,
|
||||
"config.getvalueorskip('hello')")
|
||||
|
||||
def test_config_overwrite(self, testdir):
|
||||
o = testdir.tmpdir
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
|
||||
import py
|
||||
from py._test.outcome import Skipped
|
||||
|
||||
class TestCollectDeprecated:
|
||||
|
||||
|
@ -191,7 +190,7 @@ class TestDisabled:
|
|||
l = modcol.collect()
|
||||
assert len(l) == 1
|
||||
recwarn.clear()
|
||||
py.test.raises(Skipped, "modcol.setup()")
|
||||
py.test.raises(py.test.exc.Skipped, "modcol.setup()")
|
||||
recwarn.pop(DeprecationWarning)
|
||||
|
||||
def test_disabled_class(self, recwarn, testdir):
|
||||
|
@ -208,7 +207,7 @@ class TestDisabled:
|
|||
l = modcol.collect()
|
||||
assert len(l) == 1
|
||||
recwarn.clear()
|
||||
py.test.raises(Skipped, "modcol.setup()")
|
||||
py.test.raises(py.test.exc.Skipped, "modcol.setup()")
|
||||
recwarn.pop(DeprecationWarning)
|
||||
|
||||
def test_disabled_class_functional(self, testdir):
|
||||
|
|
|
@ -16,13 +16,12 @@ class TestRaises:
|
|||
py.test.raises(ValueError, int, 'hello')
|
||||
|
||||
def test_raises_callable_no_exception(self):
|
||||
from py._test.outcome import ExceptionFailure
|
||||
class A:
|
||||
def __call__(self):
|
||||
pass
|
||||
try:
|
||||
py.test.raises(ValueError, A())
|
||||
except ExceptionFailure:
|
||||
except py.test.exc.ExceptionFailure:
|
||||
pass
|
||||
|
||||
def test_pytest_exit():
|
||||
|
@ -41,23 +40,23 @@ def test_exception_printing_skip():
|
|||
assert s.startswith("Skipped")
|
||||
|
||||
def test_importorskip():
|
||||
from py._test.outcome import Skipped, importorskip
|
||||
assert importorskip == py.test.importorskip
|
||||
importorskip = py.test.importorskip
|
||||
try:
|
||||
sys = importorskip("sys")
|
||||
assert sys == py.std.sys
|
||||
#path = py.test.importorskip("os.path")
|
||||
#assert path == py.std.os.path
|
||||
py.test.raises(Skipped, "py.test.importorskip('alskdj')")
|
||||
py.test.raises(py.test.exc.Skipped,
|
||||
"py.test.importorskip('alskdj')")
|
||||
py.test.raises(SyntaxError, "py.test.importorskip('x y z')")
|
||||
py.test.raises(SyntaxError, "py.test.importorskip('x=y')")
|
||||
path = importorskip("py", minversion=".".join(py.__version__))
|
||||
mod = py.std.types.ModuleType("hello123")
|
||||
mod.__version__ = "1.3"
|
||||
py.test.raises(Skipped, """
|
||||
py.test.raises(py.test.exc.Skipped, """
|
||||
py.test.importorskip("hello123", minversion="5.0")
|
||||
""")
|
||||
except Skipped:
|
||||
except py.test.exc.Skipped:
|
||||
print(py.code.ExceptionInfo())
|
||||
py.test.fail("spurious skip")
|
||||
|
||||
|
|
|
@ -444,8 +444,7 @@ def test_modulecol_roundtrip(testdir):
|
|||
|
||||
class TestTracebackCutting:
|
||||
def test_skip_simple(self):
|
||||
from py._test.outcome import Skipped
|
||||
excinfo = py.test.raises(Skipped, 'py.test.skip("xxx")')
|
||||
excinfo = py.test.raises(py.test.exc.Skipped, 'py.test.skip("xxx")')
|
||||
assert excinfo.traceback[-1].frame.code.name == "skip"
|
||||
assert excinfo.traceback[-1].ishidden()
|
||||
|
||||
|
|
Loading…
Reference in New Issue