Merge pull request #3292 from RonnyPfannschmidt/exception-attrs
internal refactor port exc FOrmattedExcinfo to attrs, remove old code
This commit is contained in:
commit
f8791c9246
|
@ -3,6 +3,8 @@ import inspect
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
from inspect import CO_VARARGS, CO_VARKEYWORDS
|
from inspect import CO_VARARGS, CO_VARKEYWORDS
|
||||||
|
|
||||||
|
import attr
|
||||||
import re
|
import re
|
||||||
from weakref import ref
|
from weakref import ref
|
||||||
from _pytest.compat import _PY2, _PY3, PY35, safe_str
|
from _pytest.compat import _PY2, _PY3, PY35, safe_str
|
||||||
|
@ -458,19 +460,19 @@ class ExceptionInfo(object):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
@attr.s
|
||||||
class FormattedExcinfo(object):
|
class FormattedExcinfo(object):
|
||||||
""" presenting information about failing Functions and Generators. """
|
""" presenting information about failing Functions and Generators. """
|
||||||
# for traceback entries
|
# for traceback entries
|
||||||
flow_marker = ">"
|
flow_marker = ">"
|
||||||
fail_marker = "E"
|
fail_marker = "E"
|
||||||
|
|
||||||
def __init__(self, showlocals=False, style="long", abspath=True, tbfilter=True, funcargs=False):
|
showlocals = attr.ib(default=False)
|
||||||
self.showlocals = showlocals
|
style = attr.ib(default="long")
|
||||||
self.style = style
|
abspath = attr.ib(default=True)
|
||||||
self.tbfilter = tbfilter
|
tbfilter = attr.ib(default=True)
|
||||||
self.funcargs = funcargs
|
funcargs = attr.ib(default=False)
|
||||||
self.abspath = abspath
|
astcache = attr.ib(default=attr.Factory(dict), init=False, repr=False)
|
||||||
self.astcache = {}
|
|
||||||
|
|
||||||
def _getindent(self, source):
|
def _getindent(self, source):
|
||||||
# figure out indent for given source
|
# figure out indent for given source
|
||||||
|
|
|
@ -26,7 +26,7 @@ class Source(object):
|
||||||
for part in parts:
|
for part in parts:
|
||||||
if not part:
|
if not part:
|
||||||
partlines = []
|
partlines = []
|
||||||
if isinstance(part, Source):
|
elif isinstance(part, Source):
|
||||||
partlines = part.lines
|
partlines = part.lines
|
||||||
elif isinstance(part, (tuple, list)):
|
elif isinstance(part, (tuple, list)):
|
||||||
partlines = [x.rstrip("\n") for x in part]
|
partlines = [x.rstrip("\n") for x in part]
|
||||||
|
@ -98,14 +98,14 @@ class Source(object):
|
||||||
newsource.lines = [(indent + line) for line in self.lines]
|
newsource.lines = [(indent + line) for line in self.lines]
|
||||||
return newsource
|
return newsource
|
||||||
|
|
||||||
def getstatement(self, lineno, assertion=False):
|
def getstatement(self, lineno):
|
||||||
""" return Source statement which contains the
|
""" return Source statement which contains the
|
||||||
given linenumber (counted from 0).
|
given linenumber (counted from 0).
|
||||||
"""
|
"""
|
||||||
start, end = self.getstatementrange(lineno, assertion)
|
start, end = self.getstatementrange(lineno)
|
||||||
return self[start:end]
|
return self[start:end]
|
||||||
|
|
||||||
def getstatementrange(self, lineno, assertion=False):
|
def getstatementrange(self, lineno):
|
||||||
""" return (start, end) tuple which spans the minimal
|
""" return (start, end) tuple which spans the minimal
|
||||||
statement region which containing the given lineno.
|
statement region which containing the given lineno.
|
||||||
"""
|
"""
|
||||||
|
@ -131,13 +131,7 @@ class Source(object):
|
||||||
""" return True if source is parseable, heuristically
|
""" return True if source is parseable, heuristically
|
||||||
deindenting it by default.
|
deindenting it by default.
|
||||||
"""
|
"""
|
||||||
try:
|
from parser import suite as syntax_checker
|
||||||
import parser
|
|
||||||
except ImportError:
|
|
||||||
def syntax_checker(x):
|
|
||||||
return compile(x, 'asd', 'exec')
|
|
||||||
else:
|
|
||||||
syntax_checker = parser.suite
|
|
||||||
|
|
||||||
if deindent:
|
if deindent:
|
||||||
source = str(self.deindent())
|
source = str(self.deindent())
|
||||||
|
@ -219,9 +213,9 @@ def getfslineno(obj):
|
||||||
""" Return source location (path, lineno) for the given object.
|
""" Return source location (path, lineno) for the given object.
|
||||||
If the source cannot be determined return ("", -1)
|
If the source cannot be determined return ("", -1)
|
||||||
"""
|
"""
|
||||||
import _pytest._code
|
from .code import Code
|
||||||
try:
|
try:
|
||||||
code = _pytest._code.Code(obj)
|
code = Code(obj)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
try:
|
try:
|
||||||
fn = inspect.getsourcefile(obj) or inspect.getfile(obj)
|
fn = inspect.getsourcefile(obj) or inspect.getfile(obj)
|
||||||
|
@ -259,8 +253,8 @@ def findsource(obj):
|
||||||
|
|
||||||
|
|
||||||
def getsource(obj, **kwargs):
|
def getsource(obj, **kwargs):
|
||||||
import _pytest._code
|
from .code import getrawcode
|
||||||
obj = _pytest._code.getrawcode(obj)
|
obj = getrawcode(obj)
|
||||||
try:
|
try:
|
||||||
strsrc = inspect.getsource(obj)
|
strsrc = inspect.getsource(obj)
|
||||||
except IndentationError:
|
except IndentationError:
|
||||||
|
@ -286,8 +280,6 @@ def deindent(lines, offset=None):
|
||||||
def readline_generator(lines):
|
def readline_generator(lines):
|
||||||
for line in lines:
|
for line in lines:
|
||||||
yield line + '\n'
|
yield line + '\n'
|
||||||
while True:
|
|
||||||
yield ''
|
|
||||||
|
|
||||||
it = readline_generator(lines)
|
it = readline_generator(lines)
|
||||||
|
|
||||||
|
@ -318,9 +310,9 @@ def get_statement_startend2(lineno, node):
|
||||||
# AST's line numbers start indexing at 1
|
# AST's line numbers start indexing at 1
|
||||||
values = []
|
values = []
|
||||||
for x in ast.walk(node):
|
for x in ast.walk(node):
|
||||||
if isinstance(x, ast.stmt) or isinstance(x, ast.ExceptHandler):
|
if isinstance(x, (ast.stmt, ast.ExceptHandler)):
|
||||||
values.append(x.lineno - 1)
|
values.append(x.lineno - 1)
|
||||||
for name in "finalbody", "orelse":
|
for name in ("finalbody", "orelse"):
|
||||||
val = getattr(x, name, None)
|
val = getattr(x, name, None)
|
||||||
if val:
|
if val:
|
||||||
# treat the finally/orelse part as its own statement
|
# treat the finally/orelse part as its own statement
|
||||||
|
@ -338,11 +330,8 @@ def get_statement_startend2(lineno, node):
|
||||||
def getstatementrange_ast(lineno, source, assertion=False, astnode=None):
|
def getstatementrange_ast(lineno, source, assertion=False, astnode=None):
|
||||||
if astnode is None:
|
if astnode is None:
|
||||||
content = str(source)
|
content = str(source)
|
||||||
try:
|
astnode = compile(content, "source", "exec", 1024) # 1024 for AST
|
||||||
astnode = compile(content, "source", "exec", 1024) # 1024 for AST
|
|
||||||
except ValueError:
|
|
||||||
start, end = getstatementrange_old(lineno, source, assertion)
|
|
||||||
return None, start, end
|
|
||||||
start, end = get_statement_startend2(lineno, astnode)
|
start, end = get_statement_startend2(lineno, astnode)
|
||||||
# we need to correct the end:
|
# we need to correct the end:
|
||||||
# - ast-parsing strips comments
|
# - ast-parsing strips comments
|
||||||
|
@ -374,38 +363,3 @@ def getstatementrange_ast(lineno, source, assertion=False, astnode=None):
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
return astnode, start, end
|
return astnode, start, end
|
||||||
|
|
||||||
|
|
||||||
def getstatementrange_old(lineno, source, assertion=False):
|
|
||||||
""" return (start, end) tuple which spans the minimal
|
|
||||||
statement region which containing the given lineno.
|
|
||||||
raise an IndexError if no such statementrange can be found.
|
|
||||||
"""
|
|
||||||
# XXX this logic is only used on python2.4 and below
|
|
||||||
# 1. find the start of the statement
|
|
||||||
from codeop import compile_command
|
|
||||||
for start in range(lineno, -1, -1):
|
|
||||||
if assertion:
|
|
||||||
line = source.lines[start]
|
|
||||||
# the following lines are not fully tested, change with care
|
|
||||||
if 'super' in line and 'self' in line and '__init__' in line:
|
|
||||||
raise IndexError("likely a subclass")
|
|
||||||
if "assert" not in line and "raise" not in line:
|
|
||||||
continue
|
|
||||||
trylines = source.lines[start:lineno + 1]
|
|
||||||
# quick hack to prepare parsing an indented line with
|
|
||||||
# compile_command() (which errors on "return" outside defs)
|
|
||||||
trylines.insert(0, 'def xxx():')
|
|
||||||
trysource = '\n '.join(trylines)
|
|
||||||
# ^ space here
|
|
||||||
try:
|
|
||||||
compile_command(trysource)
|
|
||||||
except (SyntaxError, OverflowError, ValueError):
|
|
||||||
continue
|
|
||||||
|
|
||||||
# 2. find the end of the statement
|
|
||||||
for end in range(lineno + 1, len(source) + 1):
|
|
||||||
trysource = source[start:end]
|
|
||||||
if trysource.isparseable():
|
|
||||||
return start, end
|
|
||||||
raise SyntaxError("no valid source range around line %d " % (lineno,))
|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Internal refactoring of ``FormattedExcinfo`` to use ``attrs`` facilities and remove old support code for legacy Python versions.
|
Loading…
Reference in New Issue