Merge pull request #3292 from RonnyPfannschmidt/exception-attrs

internal refactor port exc FOrmattedExcinfo to attrs, remove old code
This commit is contained in:
Bruno Oliveira 2018-03-12 20:54:52 -03:00 committed by GitHub
commit f8791c9246
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 23 additions and 66 deletions

View File

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

View File

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

View File

@ -0,0 +1 @@
Internal refactoring of ``FormattedExcinfo`` to use ``attrs`` facilities and remove old support code for legacy Python versions.