From c67f45b7166dd74c3cc8e52bf5fb86b2098456c7 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Fri, 9 Mar 2018 12:06:50 +0100 Subject: [PATCH 1/8] simplify a few imports in _pytest._code.source --- _pytest/_code/source.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/_pytest/_code/source.py b/_pytest/_code/source.py index 409961d9a..d7b86edbf 100644 --- a/_pytest/_code/source.py +++ b/_pytest/_code/source.py @@ -131,13 +131,7 @@ class Source(object): """ return True if source is parseable, heuristically deindenting it by default. """ - try: - import parser - except ImportError: - def syntax_checker(x): - return compile(x, 'asd', 'exec') - else: - syntax_checker = parser.suite + from parser import suite as syntax_checker if deindent: source = str(self.deindent()) @@ -219,9 +213,9 @@ def getfslineno(obj): """ Return source location (path, lineno) for the given object. If the source cannot be determined return ("", -1) """ - import _pytest._code + from .code import Code try: - code = _pytest._code.Code(obj) + code = Code(obj) except TypeError: try: fn = inspect.getsourcefile(obj) or inspect.getfile(obj) @@ -259,8 +253,8 @@ def findsource(obj): def getsource(obj, **kwargs): - import _pytest._code - obj = _pytest._code.getrawcode(obj) + from .code import getrawcode + obj = getrawcode(obj) try: strsrc = inspect.getsource(obj) except IndentationError: From 74884b190133c2fbf374ef206233ad045a622a89 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Fri, 9 Mar 2018 14:21:56 +0100 Subject: [PATCH 2/8] turn FormattedExcinfo into a attrs class --- _pytest/_code/code.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/_pytest/_code/code.py b/_pytest/_code/code.py index 84627a435..76e143774 100644 --- a/_pytest/_code/code.py +++ b/_pytest/_code/code.py @@ -3,6 +3,8 @@ import inspect import sys import traceback from inspect import CO_VARARGS, CO_VARKEYWORDS + +import attr import re from weakref import ref from _pytest.compat import _PY2, _PY3, PY35, safe_str @@ -458,19 +460,19 @@ class ExceptionInfo(object): return True +@attr.s class FormattedExcinfo(object): """ presenting information about failing Functions and Generators. """ # for traceback entries flow_marker = ">" fail_marker = "E" - def __init__(self, showlocals=False, style="long", abspath=True, tbfilter=True, funcargs=False): - self.showlocals = showlocals - self.style = style - self.tbfilter = tbfilter - self.funcargs = funcargs - self.abspath = abspath - self.astcache = {} + showlocals = attr.ib(default=False) + style = attr.ib(default="long") + abspath = attr.ib(default=True) + tbfilter = attr.ib(default=True) + funcargs = attr.ib(default=False) + astcache = attr.ib(default=attr.Factory(dict), init=False, repr=False) def _getindent(self, source): # figure out indent for given source From a406ca14d6aff396eaa545ca1e44e0ad5801a403 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Fri, 9 Mar 2018 14:48:28 +0100 Subject: [PATCH 3/8] remove getstatementrange_old - its documented for python <= 2.4 --- _pytest/_code/source.py | 42 ++--------------------------------------- 1 file changed, 2 insertions(+), 40 deletions(-) diff --git a/_pytest/_code/source.py b/_pytest/_code/source.py index d7b86edbf..7972b2908 100644 --- a/_pytest/_code/source.py +++ b/_pytest/_code/source.py @@ -332,11 +332,8 @@ def get_statement_startend2(lineno, node): def getstatementrange_ast(lineno, source, assertion=False, astnode=None): if astnode is None: content = str(source) - try: - astnode = compile(content, "source", "exec", 1024) # 1024 for AST - except ValueError: - start, end = getstatementrange_old(lineno, source, assertion) - return None, start, end + astnode = compile(content, "source", "exec", 1024) # 1024 for AST + start, end = get_statement_startend2(lineno, astnode) # we need to correct the end: # - ast-parsing strips comments @@ -368,38 +365,3 @@ def getstatementrange_ast(lineno, source, assertion=False, astnode=None): else: break 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,)) From 3284d575e8f5fe0e7e3f1bf95516ca975fa83336 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Fri, 9 Mar 2018 14:58:11 +0100 Subject: [PATCH 4/8] readline generator no longer needs to yield empty strings --- _pytest/_code/source.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/_pytest/_code/source.py b/_pytest/_code/source.py index 7972b2908..ddc41dc89 100644 --- a/_pytest/_code/source.py +++ b/_pytest/_code/source.py @@ -280,8 +280,6 @@ def deindent(lines, offset=None): def readline_generator(lines): for line in lines: yield line + '\n' - while True: - yield '' it = readline_generator(lines) From 2fe56b97c9ab5c547d4a8498f93526854151ed5c Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Fri, 9 Mar 2018 15:03:57 +0100 Subject: [PATCH 5/8] remove unused assertion parameter in source and minor cleanups --- _pytest/_code/source.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/_pytest/_code/source.py b/_pytest/_code/source.py index ddc41dc89..304a75289 100644 --- a/_pytest/_code/source.py +++ b/_pytest/_code/source.py @@ -98,14 +98,14 @@ class Source(object): newsource.lines = [(indent + line) for line in self.lines] return newsource - def getstatement(self, lineno, assertion=False): + def getstatement(self, lineno): """ return Source statement which contains the given linenumber (counted from 0). """ - start, end = self.getstatementrange(lineno, assertion) + start, end = self.getstatementrange(lineno) return self[start:end] - def getstatementrange(self, lineno, assertion=False): + def getstatementrange(self, lineno): """ return (start, end) tuple which spans the minimal statement region which containing the given lineno. """ @@ -310,9 +310,9 @@ def get_statement_startend2(lineno, node): # AST's line numbers start indexing at 1 values = [] 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) - for name in "finalbody", "orelse": + for name in ("finalbody", "orelse"): val = getattr(x, name, None) if val: # treat the finally/orelse part as its own statement From 543bac925ad749680f44a0fe5b29517c5751a7ff Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Fri, 9 Mar 2018 16:50:46 +0100 Subject: [PATCH 6/8] fix if-chain in _code.source --- _pytest/_code/source.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/_pytest/_code/source.py b/_pytest/_code/source.py index 304a75289..ca2a11fb4 100644 --- a/_pytest/_code/source.py +++ b/_pytest/_code/source.py @@ -26,7 +26,7 @@ class Source(object): for part in parts: if not part: partlines = [] - if isinstance(part, Source): + elif isinstance(part, Source): partlines = part.lines elif isinstance(part, (tuple, list)): partlines = [x.rstrip("\n") for x in part] @@ -239,7 +239,6 @@ def getfslineno(obj): # helper functions # - def findsource(obj): try: sourcelines, lineno = inspect.findsource(obj) From 5f9bc557ea7be9e38badf098890efbdc571ff944 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Fri, 9 Mar 2018 17:44:39 -0300 Subject: [PATCH 7/8] Fix linting --- _pytest/_code/source.py | 1 + 1 file changed, 1 insertion(+) diff --git a/_pytest/_code/source.py b/_pytest/_code/source.py index ca2a11fb4..cb5e13f05 100644 --- a/_pytest/_code/source.py +++ b/_pytest/_code/source.py @@ -239,6 +239,7 @@ def getfslineno(obj): # helper functions # + def findsource(obj): try: sourcelines, lineno = inspect.findsource(obj) From d2dbbd4caabe21b2ef088cae4e06222d590ec81e Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Fri, 9 Mar 2018 17:46:44 -0300 Subject: [PATCH 8/8] Add CHANGELOG entry --- changelog/3292.trivial.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog/3292.trivial.rst diff --git a/changelog/3292.trivial.rst b/changelog/3292.trivial.rst new file mode 100644 index 000000000..0e60e3431 --- /dev/null +++ b/changelog/3292.trivial.rst @@ -0,0 +1 @@ +Internal refactoring of ``FormattedExcinfo`` to use ``attrs`` facilities and remove old support code for legacy Python versions.