[py-svn] r8287 - in py/dist/py: . code code/testing documentation/example/pytest magic test test/report/text
hpk at codespeak.net
hpk at codespeak.net
Sat Jan 15 11:26:48 CET 2005
Author: hpk
Date: Sat Jan 15 11:26:48 2005
New Revision: 8287
Added:
py/dist/py/code/traceback.py
Modified:
py/dist/py/__init__.py
py/dist/py/code/excinfo.py
py/dist/py/code/testing/test_excinfo.py
py/dist/py/code/testing/test_source.py
py/dist/py/documentation/example/pytest/failure_demo.py
py/dist/py/magic/exprinfo.py
py/dist/py/test/raises.py
py/dist/py/test/report/text/reporter.py
py/dist/py/test/report/text/summary.py
Log:
major test "reporter" related cleanup. I hope you'll all like it.
It gives IMO a _lot_ better error reporting. Please report any
glitches.
- The higher level introspection
py.code.* classes now have a "Traceback" object which is
a list of TracebackEntries. This allows to manipulate
(cut) tracebacks more easily in order to show relevant
portions.
- the test summary now is structured more easily
(basically you see what happens to show a test failure
in summary.py-repr_failure which calls various other
repr_* functions in order to display locals, stdout/stderr
and whatnot)
- traceback hiding of internal functions now happens
by just setting
__tracebackhide__ = True
in functions that you don't want to see in your traceback.
--fulltrace disables this behaviour.
The py lib currently uses it to hide e.g. a custom import
hook when it just defers to the original hook (you can
change the value of __tracebackhide__ during the execution
of your functions.
Modified: py/dist/py/__init__.py
==============================================================================
--- py/dist/py/__init__.py (original)
+++ py/dist/py/__init__.py Sat Jan 15 11:26:48 2005
@@ -44,7 +44,7 @@
'code.Code' : ('./code/frame.py', 'Code'),
'code.Frame' : ('./code/frame.py', 'Frame'),
'code.ExceptionInfo' : ('./code/excinfo.py', 'ExceptionInfo'),
- 'code.TracebackEntry' : ('./code/excinfo.py', 'TracebackEntry'),
+ 'code.Traceback' : ('./code/traceback.py', 'Traceback'),
'builtin.enumerate' : ('./builtin/enumerate.py', 'enumerate'),
Modified: py/dist/py/code/excinfo.py
==============================================================================
--- py/dist/py/code/excinfo.py (original)
+++ py/dist/py/code/excinfo.py Sat Jan 15 11:26:48 2005
@@ -6,8 +6,6 @@
""" wraps sys.exc_info() objects and offers
help for navigating the traceback.
"""
- TRACEBACK_HIDE = object()
-
def __init__(self, tup=None, exprinfo=None):
# NB. all attributes are private! Subclasses or other
# ExceptionInfo-like classes may have different attributes.
@@ -19,8 +17,8 @@
if exprinfo.startswith('assert '):
strip = 'AssertionError: '
self._excinfo = tup
- self.exprinfo = exprinfo
self.type, self.value, tb = self._excinfo
+ self.traceback = py.code.Traceback(tb)
# get the text representation of the exception
lines = py.std.traceback.format_exception_only(self.type, self.value)
text = ''.join(lines)
@@ -33,69 +31,3 @@
def __str__(self):
return self.exception_text
- def reinterpret(self):
- """Reinterpret the failing statement and returns a detailed information
- about what operations are performed."""
- if self.exprinfo is None:
- from py.__impl__.magic import exprinfo
- self.exprinfo = exprinfo.getmsg(self)
- return self.exprinfo
-
- def __iter__(self):
- current = self._excinfo[2]
- while current is not None:
- special = current.tb_frame.f_locals.get('__traceback__')
- if special is not self.TRACEBACK_HIDE:
- yield TracebackEntry(current)
- current = current.tb_next
-
- def getentries(self, startcondition=None, endcondition=None):
- if startcondition is not None and not callable(startcondition):
- raise TypeError("%r is not callable or None" % startcondition)
- if endcondition is not None and not callable(endcondition):
- raise TypeError("%r is not callable or None" % endcondition)
- result = []
- gen = iter(self)
- for x in gen:
- if startcondition is None or startcondition(x):
- result.append(x)
- break
- for x in gen:
- result.append(x)
- if endcondition is not None and endcondition(x):
- break
- return result
- #entries = list(self)
- #gen = py.builtin.enumerate(self)
- #if start is not None:
- # for i, entry in gen:
- # p,l = entry.frame.code.path, entry.frame.code.firstlineno
- # if start == i or (p,l) == start:
- # break
- #res = []
- #for i, entry in gen:
- # res.append(entry)
- # if end is not None:
- # p,l = entry.frame.code.path, entry.frame.code.firstlineno
- # if i == end or (p,l) == end:
- # break
- #return res
-
-class TracebackEntry(object):
- def __init__(self, rawentry):
- self._rawentry = rawentry
- self.frame = py.code.Frame(rawentry.tb_frame)
- self.lineno = rawentry.tb_lineno - 1
-
- def statement(self):
- source = self.frame.code.fullsource
- return source.getstatement(self.lineno)
- statement = property(statement, None, None,
- "statement of this traceback entry.")
-
- def path(self):
- return self.frame.path
- path = property(path, None, None, "path to the full source code")
-
- def __repr__(self):
- return "<TracebackEntry %s:%d>" %(self.frame.code.path, self.lineno + 1)
Modified: py/dist/py/code/testing/test_excinfo.py
==============================================================================
--- py/dist/py/code/testing/test_excinfo.py (original)
+++ py/dist/py/code/testing/test_excinfo.py Sat Jan 15 11:26:48 2005
@@ -16,12 +16,12 @@
try:
f()
except ValueError:
- info = py.code.ExceptionInfo()
- l = list(info)
+ excinfo = py.code.ExceptionInfo()
linenumbers = [f.func_code.co_firstlineno-1+3,
f.func_code.co_firstlineno-1+1,
g.func_code.co_firstlineno-1+1,]
- foundlinenumbers = [x.lineno for x in info]
+ l = list(excinfo.traceback)
+ foundlinenumbers = [x.lineno for x in l]
print l[0].frame.statement
assert foundlinenumbers == linenumbers
#for x in info:
@@ -36,44 +36,55 @@
def h():
g()
-def test_excinfo_getentries_nocut():
- try:
- h()
- except ValueError:
- pass
- excinfo = py.code.ExceptionInfo()
- entries = excinfo.getentries()
- assert len(entries) == 4 # fragile
- names = ['f','g','h']
- for entry in entries:
+class TestTraceback_f_g_h:
+ def setup_method(self, method):
try:
- names.remove(entry.frame.code.name)
+ h()
except ValueError:
- pass
- assert not names
+ self.excinfo = py.code.ExceptionInfo()
-def test_excinfo_getentries_cut():
- excinfo = py.test.raises(ValueError, h)
- entries = excinfo.getentries(
- lambda x: x.frame.code.name != 'raises',
- lambda x: x.frame.code.name != 'f')
- names = [x.frame.code.name for x in entries]
- assert names == ['h','g']
-
-def test_excinfo_getentries_type_error():
- excinfo = py.test.raises(ValueError, h)
- entries = excinfo.getentries(
- lambda x: x.frame.code.name != 'raises',
- lambda x: x.frame.code.name != 'f')
- names = [x.frame.code.name for x in entries]
- assert names == ['h','g']
+ def test_traceback_entries(self):
+ tb = self.excinfo.traceback
+ entries = list(tb)
+ assert len(tb) == 4 # maybe fragile test
+ assert len(entries) == 4 # maybe fragile test
+ names = ['f', 'g', 'h']
+ for entry in entries:
+ try:
+ names.remove(entry.frame.code.name)
+ except ValueError:
+ pass
+ assert not names
+
+ #def test_traceback_display_func(self):
+ # tb = self.excinfo.traceback
+ # for x in tb:
+ # x.setdisplay(lambda entry: entry.frame.code.name + '\n')
+ ## l = tb.display().rstrip().split('\n')
+ # assert l == ['setup_method', 'h', 'g', 'f']
+
+
+def hello(x):
+ x + 5
+
+def test_tbentry_reinterpret():
+ try:
+ hello("hello")
+ except TypeError:
+ excinfo = py.code.ExceptionInfo()
+ tbentry = excinfo.traceback[-1]
+ msg = tbentry.reinterpret()
+ assert msg.startswith("TypeError: ('hello' + 5)")
+
+#def test_excinfo_getentries_type_error():
+# excinfo = py.test.raises(ValueError, h)
+# entries = excinfo.getentries(
+# lambda x: x.frame.code.name != 'raises',
+# lambda x: x.frame.code.name != 'f')
+# names = [x.frame.code.name for x in entries]
+# assert names == ['h','g']
def test_excinfo_exception_text():
excinfo = py.test.raises(ValueError, h)
assert excinfo.exception_text.startswith('ValueError')
-def test_excinfo_reinterpret():
- x = 'foobar'
- excinfo = py.test.raises(TypeError, "x+5")
- del x
- assert excinfo.reinterpret().startswith("TypeError: ('foobar' + 5)")
Modified: py/dist/py/code/testing/test_source.py
==============================================================================
--- py/dist/py/code/testing/test_source.py (original)
+++ py/dist/py/code/testing/test_source.py Sat Jan 15 11:26:48 2005
@@ -148,7 +148,7 @@
exec co
except ValueError:
excinfo = py.code.ExceptionInfo(py.std.sys.exc_info())
- l = list(excinfo)
+ l = excinfo.traceback
tb = l[0]
inner = l[1]
print "inner frame-statement:", inner.frame.statement
@@ -166,7 +166,7 @@
exec co
f(7)
excinfo = py.test.raises(AssertionError, "f(6)")
- frame = list(excinfo)[-1].frame
+ frame = excinfo.traceback[-1].frame
stmt = frame.code.fullsource.getstatement(frame.lineno)
#print "block", str(block)
assert str(stmt).strip().startswith('assert')
@@ -207,7 +207,7 @@
if teardown:
teardown()
""")
- source = list(excinfo)[-1].statement
+ source = excinfo.traceback[-1].statement
assert str(source).strip() == 'c(1)'
def test_getfuncsource_dynamic():
Added: py/dist/py/code/traceback.py
==============================================================================
--- (empty file)
+++ py/dist/py/code/traceback.py Sat Jan 15 11:26:48 2005
@@ -0,0 +1,69 @@
+from __future__ import generators
+import py
+
+class TracebackEntry(object):
+ exprinfo = None
+
+ def __init__(self, rawentry):
+ self._rawentry = rawentry
+ self.frame = py.code.Frame(rawentry.tb_frame)
+ self.lineno = rawentry.tb_lineno - 1
+
+ def __repr__(self):
+ return "<TracebackEntry %s:%d>" %(self.frame.code.path, self.lineno+1)
+
+ def statement(self):
+ source = self.frame.code.fullsource
+ return source.getstatement(self.lineno)
+ statement = property(statement, None, None,
+ "statement of this traceback entry.")
+
+ def path(self):
+ return self.frame.code.path
+ path = property(path, None, None, "path to the full source code")
+
+ def reinterpret(self):
+ """Reinterpret the failing statement and returns a detailed information
+ about what operations are performed."""
+ if self.exprinfo is None:
+ from py.__impl__.magic import exprinfo
+ source = str(self.statement).strip()
+ x = exprinfo.interpret(source, self.frame, should_fail=True)
+ if not isinstance(x, str):
+ raise TypeError, "interpret returned non-string %r" % (x,)
+ self.exprinfo = x
+ return self.exprinfo
+
+ def getsource(self):
+ """ return failing source code. """
+ source = self.frame.code.fullsource
+ start, end = self.frame.code.firstlineno, self.lineno
+ _, end = source.getstatementrange(end)
+ return source[start:end]
+
+ def __str__(self):
+ try:
+ fn = str(self.path)
+ except py.error.Error:
+ fn = '???'
+ name = self.frame.code.name
+ line = str(self.statement).lstrip()
+ return " File %r:%d in %s\n %s\n" %(fn, self.lineno+1, name, line)
+
+class Traceback(list):
+ Entry = TracebackEntry
+
+ def __init__(self, tb):
+ def f(cur):
+ while cur is not None:
+ yield self.Entry(cur)
+ cur = cur.tb_next
+ list.__init__(self, f(tb))
+
+# def __str__(self):
+# for x in self
+# l = []
+## for func, entry in self._tblist:
+# l.append(entry.display())
+# return "".join(l)
+
Modified: py/dist/py/documentation/example/pytest/failure_demo.py
==============================================================================
--- py/dist/py/documentation/example/pytest/failure_demo.py (original)
+++ py/dist/py/documentation/example/pytest/failure_demo.py Sat Jan 15 11:26:48 2005
@@ -7,6 +7,10 @@
def somefunc(x,y):
otherfunc(x,y)
+def otherfunc_multi(a,b):
+ assert (a ==
+ b)
+
class TestFailing(object):
def test_simple(self):
def f():
@@ -16,6 +20,11 @@
assert f() == g()
+ def test_simple_multiline(self):
+ otherfunc_multi(
+ 42,
+ 6*9)
+
def test_not(self):
def f():
return 42
@@ -109,6 +118,3 @@
def globf(x):
return x+1
-
-def test_systemexit_doesnt_leave():
- raise SystemExit
Modified: py/dist/py/magic/exprinfo.py
==============================================================================
--- py/dist/py/magic/exprinfo.py (original)
+++ py/dist/py/magic/exprinfo.py Sat Jan 15 11:26:48 2005
@@ -420,7 +420,7 @@
#frame = py.code.Frame(frame)
#return interpret(line, frame)
- tb = list(excinfo)[-1]
+ tb = excinfo.traceback[-1]
source = str(tb.statement).strip()
x = interpret(source, tb.frame, should_fail=True)
if not isinstance(x, str):
Modified: py/dist/py/test/raises.py
==============================================================================
--- py/dist/py/test/raises.py (original)
+++ py/dist/py/test/raises.py Sat Jan 15 11:26:48 2005
@@ -7,6 +7,7 @@
exception.
"""
assert args
+ __tracebackhide__ = True
if isinstance(args[0], str):
expr, = args
assert isinstance(expr, str)
@@ -16,9 +17,8 @@
#print "raises frame scope: %r" % frame.f_locals
source = py.code.Source(expr)
try:
- __traceback__ = py.code.ExceptionInfo.TRACEBACK_HIDE
eval(source.compile(), frame.f_globals, loc)
- del __traceback__
+ #del __traceback__
# XXX didn'T mean f_globals == f_locals something special?
# this is destroyed here ...
except ExpectedException:
@@ -29,14 +29,12 @@
func = args[0]
assert callable
try:
- __traceback__ = py.code.ExceptionInfo.TRACEBACK_HIDE
func(*args[1:], **kwargs)
- del __traceback__
+ #del __traceback__
except ExpectedException:
return py.code.ExceptionInfo()
k = ", ".join(["%s=%r" % x for x in kwargs.items()])
if k:
k = ', ' + k
expr = '%s(%r%s)' %(func.__name__, args, k)
- raise ExceptionFailure(expr=args, expected=ExpectedException,
- tbindex = -2)
+ raise ExceptionFailure(expr=args, expected=ExpectedException)
Modified: py/dist/py/test/report/text/reporter.py
==============================================================================
--- py/dist/py/test/report/text/reporter.py (original)
+++ py/dist/py/test/report/text/reporter.py Sat Jan 15 11:26:48 2005
@@ -185,7 +185,7 @@
if writeinfo is not None:
#exc, frame, filename,lineno = self.summary.getexinfo(error)
- tbentries = list(error.excinfo)
+ tbentries = error.excinfo.traceback
filename = tbentries[-1].frame.code.path
lineno = tbentries[-1].lineno
self.out.line('CollectError: %s:%d' % (filename, lineno) )
Modified: py/dist/py/test/report/text/summary.py
==============================================================================
--- py/dist/py/test/report/text/summary.py (original)
+++ py/dist/py/test/report/text/summary.py Sat Jan 15 11:26:48 2005
@@ -19,18 +19,8 @@
self.out.sep("_")
self.out.sep("_", "Collect Error")
self.out.line()
- self.repr_traceback_raw(error.excinfo.getentries())
- #self.repr_failure_result(res)
- #self.out.line()
- #if isinstance(res.excinfo[1], AssertionError):
- # from std.utest.tool import hackexpr
- # res.msg = "failed: " + hackexpr.getmsg(res.excinfo)
- self.out.line(error.excinfo)
-
- #if self.option.showlocals:
- # self.out.sep('- ', 'locals')
- # for name, value in frame.f_locals.items():
- # self.out.line("%-10s = %r" %(name, value))
+ self.repr_failure(error.excinfo)
+
def summary_collect_errors(self):
for error in self.getlist(py.test.collect.Error):
self.repr_collect_error(error)
@@ -56,7 +46,7 @@
d = {}
for res in self.getlist(Item.Skipped):
tbindex = getattr(res, 'tbindex', -1)
- raisingtb = list(res.excinfo)[tbindex]
+ raisingtb = res.excinfo.traceback[tbindex]
fn = raisingtb.frame.code.path
lineno = raisingtb.lineno
d[(fn,lineno)] = res
@@ -68,17 +58,102 @@
self.out.line()
def failures(self):
- for res in self.getlist(Item.Failed):
- self.repr_failure(res)
+ l = self.getlist(Item.Failed)
+ if l:
+ self.out.sep('_')
+ for res in self.getlist(Item.Failed):
+ self.repr_failure(res.excinfo, res)
+
+ def repr_failure(self, excinfo, res=None):
+ traceback = excinfo.traceback
+ if res:
+ self.cut_traceback(traceback, res.item)
+ last = traceback[-1]
+ for entry in traceback:
+ self.out.line("")
+ if entry == last:
+ indent = self.repr_source(entry, 'E')
+ self.repr_failure_explanation(excinfo, indent)
+ else:
+ self.repr_source(entry, '>')
+ self.out.line("")
+ self.out.line("[%s:%d]" %(entry.frame.code.path, entry.lineno+1))
+ self.repr_locals(entry)
+ if res:
+ self.repr_out_err(res)
+ if entry == last:
+ self.out.sep("_")
+ else:
+ self.out.sep("_ ")
+
+ def repr_source(self, entry, marker=">"):
+ try:
+ source = entry.getsource()
+ except py.error.ENOENT:
+ self.out.line("[failure to get at sourcelines from %r]\n" % entry)
+ else:
+ source = source.deindent()
+ for line in source[:-1]:
+ self.out.line(" " + line)
+ lastline = source[-1]
+ self.out.line(marker + " " + lastline)
+ try:
+ s = str(source.getstatement(len(source)-1))
+ except KeyboardInterrupt:
+ raise
+ except:
+ #self.out.line("[failed to get last statement]\n%s" %(source,))
+ s = str(source[-1])
+ #print "XXX %r" % s
+ return 4 + (len(s) - len(s.lstrip()))
+ return 0
- def repr_failure(self, res):
- self.out.line()
- self.out.sep("_")
- #self.out.line()
- self.repr_traceback(res.item, res.excinfo,
- getattr(res, 'tbindex', -1))
- #self.out.line()
- self.repr_failure_result(res)
+ def cut_traceback(self, traceback, item=None, tbindex=None):
+ if self.option.fulltrace or item is None:
+ return
+ path, lineno = item.extpy.getfilelineno()
+ for i, entry in py.builtin.enumerate(traceback):
+ if entry.frame.code.path == path and \
+ entry.frame.code.firstlineno == lineno:
+ del traceback[:i]
+ break
+ # get rid of all frames marked with __tracebackhide__
+ l = []
+ for entry in traceback:
+ try:
+ x = entry.frame.eval("__tracebackhide__")
+ except:
+ x = None
+ if not x:
+ l.append(entry)
+ traceback[:] = l
+ #if tbindex is not None and tbindex < -1:
+ # del traceback[tbindex:]
+
+ def repr_failure_explanation(self, excinfo, indent):
+ info = None
+ if not self.option.nomagic:
+ try:
+ info = excinfo.traceback[-1].reinterpret() # very detailed info
+ except KeyboardInterrupt:
+ raise
+ except:
+ if self.option.verbose > 1:
+ self.out.line("[reinterpretation traceback]")
+ py.std.traceback.print_exc(file=py.std.sys.stdout)
+ else:
+ self.out.line("[reinterpretation failed, increase "
+ "verbosity to see details]")
+ indent = " " * indent
+ if info is None:
+ info = str(excinfo)
+
+ lines = info.split('\n')
+ self.out.line('~' + indent[:-1] + lines.pop(0))
+ for x in lines:
+ self.out.line(indent + x)
+
+ def repr_out_err(self, res):
for name in 'out', 'err':
if hasattr(res, name):
out = getattr(res, name)
@@ -86,97 +161,24 @@
self.out.sep("- ", "recorded std%s" % name)
self.out.line(out.strip())
- def repr_failure_result(self, res):
- exprinfo = None
- if not self.option.nomagic and \
- not isinstance(res.excinfo.value, SystemExit):
- try:
- exprinfo = res.excinfo.reinterpret() # very detailed info
- except KeyboardInterrupt:
- raise
- except:
- if self.option.verbose > 1:
- self.out.line("reinterpretation traceback")
- py.std.traceback.print_exc()
+ def repr_locals(self, entry):
+ if self.option.showlocals:
+ self.out.sep('- ', 'locals')
+ for name, value in entry.frame.f_locals.items():
+ if len(repr(value)) < 70 or not isinstance(value,
+ (list, tuple, dict)):
+ self.out.line("%-10s = %r" %(name, value))
else:
- self.out.line("(reinterpretation failed, "
- "you may increase "
- "verbosity to see details)")
- self.out.line(exprinfo or res.excinfo)
+ self.out.line("%-10s =\\" % (name,))
+ py.std.pprint.pprint(value, stream=self.out)
- def repr_source(self, tb):
- # represent source code for a given traceback entry
- self.out.line()
- try:
- source = tb.frame.code.fullsource
- except py.error.ENOENT:
- self.out.line("failure to get at sourcelines from %r" % tb)
- #self.out.line("(co_filename = %r)" % (frame.f_code.co_filename))
- #self.out.line("(f_lineno = %r)" % (frame.f_lineno))
- return
- start = tb.frame.code.firstlineno
- end = tb.lineno
-
- for line in source[start:end]:
- self.out.line(line.rstrip())
- line = source[end]
- if line.startswith(" "):
- line = line[1:] # to avoid the indentation caused by ">"
- self.out.line(">" + line)
- return
-
- def cut_traceback(self, excinfo, item=None, tbindex=None):
- if self.option.fulltrace or item is None:
- startcondition = endcondition = None
- else:
- path,lineno = item.extpy.getfilelineno()
- def startcondition(entry):
- return entry.frame.code.path == path and \
- entry.frame.code.firstlineno == lineno
- endcondition = None
- entries = excinfo.getentries(startcondition, endcondition)
- # XXX the cutting logic needs refactoring with respect
- # to generative tests (which will not have the
- # test item in their tb-path)
- #
- if not entries:
- entries = excinfo.getentries()
- if tbindex is not None and tbindex < -1:
- entries = entries[:tbindex+1]
- return entries
-
- def repr_traceback(self, item, excinfo, tbindex=None):
- tbentries = self.cut_traceback(excinfo, item, tbindex)
- self.repr_traceback_raw(tbentries)
- self.out.sep('-')
-
- def repr_traceback_raw(self, tbentries):
+ def Xrepr_traceback_raw(self, traceback):
recursioncache = {}
- first = True
for tb in tbentries:
if first:
first = False
else:
self.out.sep('-')
- path = tb.frame.code.path
- lineno = tb.lineno
- name = tb.frame.code.name
- showfn = path
- #showfn = filename.split(os.sep)
- #if len(showfn) > 5:
- # showfn = os.sep.join(['...'] + showfn[-5:])
- self.repr_source(tb)
- self.out.line("")
- self.out.line("[%s:%d]" %(showfn, lineno+1)) # , name))
- if self.option.showlocals:
- self.out.sep('- ', 'locals')
- for name, value in tb.frame.f_locals.items():
- if len(repr(value)) < 70 or not isinstance(value,
- (list, tuple, dict)):
- self.out.line("%-10s = %r" %(name, value))
- else:
- self.out.line("%-10s =\\" % (name,))
- py.std.pprint.pprint(value, stream=self.out)
#self.out.line("%-10s = %r" %(name, value))
key = (path, lineno)
if key not in recursioncache:
More information about the pytest-commit
mailing list