* 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:
holger krekel 2010-04-27 21:13:09 +02:00
parent 0d0a7b7fec
commit d5e463605e
14 changed files with 175 additions and 178 deletions

View File

@ -9,6 +9,8 @@ Changes between 1.2.1 and 1.2.2 (release pending)
- added links to the new capturelog and coverage plugins - added links to the new capturelog and coverage plugins
- (issue87) fix unboundlocal error in assertionold code - (issue87) fix unboundlocal error in assertionold code
- (issue86) improve documentation for looponfailing - (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 - ship distribute_setup.py version 0.6.10

View File

@ -38,11 +38,6 @@ py.apipkg.initpkg(__name__, dict(
# helpers for use from test functions or collectors # helpers for use from test functions or collectors
'__onfirstaccess__' : '._test.config:onpytestaccess', '__onfirstaccess__' : '._test.config:onpytestaccess',
'__doc__' : '._test:__doc__', '__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 # configuration/initialization related test api
'config' : '._test.config:config_per_process', 'config' : '._test.config:config_per_process',
'ensuretemp' : '._test.config:ensuretemp', 'ensuretemp' : '._test.config:ensuretemp',

View File

@ -3,7 +3,6 @@ interactive debugging with the Python Debugger.
""" """
import py import py
import pdb, sys, linecache import pdb, sys, linecache
from py._test.outcome import Skipped
def pytest_addoption(parser): def pytest_addoption(parser):
group = parser.getgroup("general") group = parser.getgroup("general")
@ -17,7 +16,8 @@ def pytest_configure(config):
class PdbInvoke: class PdbInvoke:
def pytest_runtest_makereport(self, item, call): 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 # play well with capturing, slightly hackish
capman = item.config.pluginmanager.getplugin('capturemanager') capman = item.config.pluginmanager.getplugin('capturemanager')
capman.suspendcapture() capman.suspendcapture()

View File

@ -3,7 +3,23 @@ collect and run test items and create reports.
""" """
import py, sys 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 # pytest plugin hooks
@ -141,7 +157,7 @@ class ItemTestReport(BaseReport):
self.failed = True self.failed = True
shortrepr = "?" shortrepr = "?"
longrepr = excinfo longrepr = excinfo
elif excinfo.errisinstance(Skipped): elif excinfo.errisinstance(py.test.exc.Skipped):
self.skipped = True self.skipped = True
shortrepr = "s" shortrepr = "s"
longrepr = self.item._repr_failure_py(excinfo) longrepr = self.item._repr_failure_py(excinfo)
@ -180,7 +196,7 @@ class CollectReport(BaseReport):
self.result = result self.result = result
else: else:
self.longrepr = self.collector._repr_failure_py(excinfo) self.longrepr = self.collector._repr_failure_py(excinfo)
if excinfo.errisinstance(Skipped): if excinfo.errisinstance(py.test.exc.Skipped):
self.skipped = True self.skipped = True
self.reason = str(excinfo.value) self.reason = str(excinfo.value)
else: else:
@ -259,3 +275,125 @@ class SetupState(object):
except Exception: except Exception:
col._prepare_exc = sys.exc_info() col._prepare_exc = sys.exc_info()
raise 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

View File

@ -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

View File

@ -4,7 +4,6 @@ managing loading and interacting with pytest plugins.
import py import py
import inspect import inspect
from py._plugin import hookspec from py._plugin import hookspec
from py._test.outcome import Skipped
default_plugins = ( default_plugins = (
"default runner capture mark terminal skipping tmpdir monkeypatch " "default runner capture mark terminal skipping tmpdir monkeypatch "
@ -139,7 +138,7 @@ class PluginManager(object):
mod = importplugin(modname) mod = importplugin(modname)
except KeyboardInterrupt: except KeyboardInterrupt:
raise raise
except Skipped: except py.test.exc.Skipped:
e = py.std.sys.exc_info()[1] e = py.std.sys.exc_info()[1]
self._hints.append("skipped plugin %r: %s" %((modname, e.msg))) self._hints.append("skipped plugin %r: %s" %((modname, e.msg)))
else: else:

View File

@ -6,7 +6,13 @@
""" """
import py 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() # imports used for genitems()
Item = py.test.collect.Item Item = py.test.collect.Item
@ -96,21 +102,21 @@ class Session(object):
""" main loop for running tests. """ """ main loop for running tests. """
self.shouldstop = False self.shouldstop = False
self.sessionstarts() self.sessionstarts()
exitstatus = outcome.EXIT_OK exitstatus = EXIT_OK
try: try:
self._mainloop(colitems) self._mainloop(colitems)
if self._testsfailed: if self._testsfailed:
exitstatus = outcome.EXIT_TESTSFAILED exitstatus = EXIT_TESTSFAILED
self.sessionfinishes(exitstatus=exitstatus) self.sessionfinishes(exitstatus=exitstatus)
except KeyboardInterrupt: except KeyboardInterrupt:
excinfo = py.code.ExceptionInfo() excinfo = py.code.ExceptionInfo()
self.config.hook.pytest_keyboard_interrupt(excinfo=excinfo) self.config.hook.pytest_keyboard_interrupt(excinfo=excinfo)
exitstatus = outcome.EXIT_INTERRUPTED exitstatus = EXIT_INTERRUPTED
except: except:
excinfo = py.code.ExceptionInfo() excinfo = py.code.ExceptionInfo()
self.config.pluginmanager.notify_exception(excinfo) self.config.pluginmanager.notify_exception(excinfo)
exitstatus = outcome.EXIT_INTERNALERROR exitstatus = EXIT_INTERNALERROR
if exitstatus in (outcome.EXIT_INTERNALERROR, outcome.EXIT_INTERRUPTED): if exitstatus in (EXIT_INTERNALERROR, EXIT_INTERRUPTED):
self.sessionfinishes(exitstatus=exitstatus) self.sessionfinishes(exitstatus=exitstatus)
return exitstatus return exitstatus

View File

@ -45,8 +45,6 @@ class TestDoctests:
reprec.assertoutcome(failed=1) reprec.assertoutcome(failed=1)
def test_doctest_unexpected_exception(self, testdir): def test_doctest_unexpected_exception(self, testdir):
from py._test.outcome import Failed
p = testdir.maketxtfile(""" p = testdir.maketxtfile("""
>>> i = 0 >>> i = 0
>>> i = 1 >>> i = 1

View File

@ -197,14 +197,13 @@ class BaseFunctionalTests:
assert rep.when == "call" assert rep.when == "call"
def test_exit_propagates(self, testdir): def test_exit_propagates(self, testdir):
from py._test.outcome import Exit
try: try:
testdir.runitem(""" testdir.runitem("""
from py._test.outcome import Exit import py
def test_func(): def test_func():
raise Exit() raise py.test.exc.Exit()
""") """)
except Exit: except py.test.exc.Exit:
pass pass
else: else:
py.test.fail("did not raise") py.test.fail("did not raise")
@ -216,7 +215,6 @@ class TestExecutionNonForked(BaseFunctionalTests):
return f return f
def test_keyboardinterrupt_propagates(self, testdir): def test_keyboardinterrupt_propagates(self, testdir):
from py._test.outcome import Exit
try: try:
testdir.runitem(""" testdir.runitem("""
def test_func(): def test_func():

View File

@ -1,7 +1,6 @@
import py import py
import types import types
import sys import sys
from py._test.outcome import Skipped
def checksubpackage(name): def checksubpackage(name):
obj = getattr(py, name) obj = getattr(py, name)
@ -52,7 +51,7 @@ def test_importall():
modpath = 'py.%s' % relpath modpath = 'py.%s' % relpath
try: try:
check_import(modpath) check_import(modpath)
except Skipped: except py.test.exc.Skipped:
pass pass
def check_import(modpath): def check_import(modpath):

View File

@ -75,13 +75,14 @@ class TestConfigAPI:
py.test.raises(KeyError, 'config.getvalue("y", o)') py.test.raises(KeyError, 'config.getvalue("y", o)')
def test_config_getvalueorskip(self, testdir): def test_config_getvalueorskip(self, testdir):
from py._test.outcome import Skipped
config = testdir.parseconfig() config = testdir.parseconfig()
py.test.raises(Skipped, "config.getvalueorskip('hello')") py.test.raises(py.test.exc.Skipped,
"config.getvalueorskip('hello')")
verbose = config.getvalueorskip("verbose") verbose = config.getvalueorskip("verbose")
assert verbose == config.option.verbose assert verbose == config.option.verbose
config.option.hello = None 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): def test_config_overwrite(self, testdir):
o = testdir.tmpdir o = testdir.tmpdir

View File

@ -1,6 +1,5 @@
import py import py
from py._test.outcome import Skipped
class TestCollectDeprecated: class TestCollectDeprecated:
@ -191,7 +190,7 @@ class TestDisabled:
l = modcol.collect() l = modcol.collect()
assert len(l) == 1 assert len(l) == 1
recwarn.clear() recwarn.clear()
py.test.raises(Skipped, "modcol.setup()") py.test.raises(py.test.exc.Skipped, "modcol.setup()")
recwarn.pop(DeprecationWarning) recwarn.pop(DeprecationWarning)
def test_disabled_class(self, recwarn, testdir): def test_disabled_class(self, recwarn, testdir):
@ -208,7 +207,7 @@ class TestDisabled:
l = modcol.collect() l = modcol.collect()
assert len(l) == 1 assert len(l) == 1
recwarn.clear() recwarn.clear()
py.test.raises(Skipped, "modcol.setup()") py.test.raises(py.test.exc.Skipped, "modcol.setup()")
recwarn.pop(DeprecationWarning) recwarn.pop(DeprecationWarning)
def test_disabled_class_functional(self, testdir): def test_disabled_class_functional(self, testdir):

View File

@ -16,13 +16,12 @@ class TestRaises:
py.test.raises(ValueError, int, 'hello') py.test.raises(ValueError, int, 'hello')
def test_raises_callable_no_exception(self): def test_raises_callable_no_exception(self):
from py._test.outcome import ExceptionFailure
class A: class A:
def __call__(self): def __call__(self):
pass pass
try: try:
py.test.raises(ValueError, A()) py.test.raises(ValueError, A())
except ExceptionFailure: except py.test.exc.ExceptionFailure:
pass pass
def test_pytest_exit(): def test_pytest_exit():
@ -41,23 +40,23 @@ def test_exception_printing_skip():
assert s.startswith("Skipped") assert s.startswith("Skipped")
def test_importorskip(): def test_importorskip():
from py._test.outcome import Skipped, importorskip importorskip = py.test.importorskip
assert importorskip == py.test.importorskip
try: try:
sys = importorskip("sys") sys = importorskip("sys")
assert sys == py.std.sys assert sys == py.std.sys
#path = py.test.importorskip("os.path") #path = py.test.importorskip("os.path")
#assert path == py.std.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 z')")
py.test.raises(SyntaxError, "py.test.importorskip('x=y')") py.test.raises(SyntaxError, "py.test.importorskip('x=y')")
path = importorskip("py", minversion=".".join(py.__version__)) path = importorskip("py", minversion=".".join(py.__version__))
mod = py.std.types.ModuleType("hello123") mod = py.std.types.ModuleType("hello123")
mod.__version__ = "1.3" mod.__version__ = "1.3"
py.test.raises(Skipped, """ py.test.raises(py.test.exc.Skipped, """
py.test.importorskip("hello123", minversion="5.0") py.test.importorskip("hello123", minversion="5.0")
""") """)
except Skipped: except py.test.exc.Skipped:
print(py.code.ExceptionInfo()) print(py.code.ExceptionInfo())
py.test.fail("spurious skip") py.test.fail("spurious skip")

View File

@ -444,8 +444,7 @@ def test_modulecol_roundtrip(testdir):
class TestTracebackCutting: class TestTracebackCutting:
def test_skip_simple(self): def test_skip_simple(self):
from py._test.outcome import Skipped excinfo = py.test.raises(py.test.exc.Skipped, 'py.test.skip("xxx")')
excinfo = py.test.raises(Skipped, 'py.test.skip("xxx")')
assert excinfo.traceback[-1].frame.code.name == "skip" assert excinfo.traceback[-1].frame.code.name == "skip"
assert excinfo.traceback[-1].ishidden() assert excinfo.traceback[-1].ishidden()