diff --git a/py/__init__.py b/py/__init__.py index a7c63f58a..ad9d6c2a4 100644 --- a/py/__init__.py +++ b/py/__init__.py @@ -133,7 +133,7 @@ initpkg(__name__, 'code.getrawcode' : ('./code/code.py', 'getrawcode'), 'code.patch_builtins' : ('./code/code.py', 'patch_builtins'), 'code.unpatch_builtins' : ('./code/code.py', 'unpatch_builtins'), - 'code._AssertionError' : ('./code/_assertion.py', 'AssertionError'), + 'code._AssertionError' : ('./code/assertion.py', 'AssertionError'), # backports and additions of builtins 'builtin.__doc__' : ('./builtin/__init__.py', '__doc__'), diff --git a/py/code/_assertionnew.py b/py/code/_assertionnew.py index 93af3a57c..e807c6e9b 100644 --- a/py/code/_assertionnew.py +++ b/py/code/_assertionnew.py @@ -7,7 +7,7 @@ import sys import ast import py -from py.__.code._assertion import _format_explanation, BuiltinAssertionError +from py.__.code.assertion import _format_explanation, BuiltinAssertionError class Failure(Exception): @@ -40,6 +40,8 @@ def getfailure(failure): value = failure.cause[1] if str(value): lines = explanation.splitlines() + if not lines: + lines.append("") lines[0] += " << {0}".format(value) explanation = "\n".join(lines) text = "{0}: {1}".format(failure.cause[0].__name__, explanation) @@ -97,7 +99,7 @@ class DebugInterpreter(ast.NodeVisitor): mod = ast.Module([node]) co = self._compile(mod, "exec") try: - frame.exec_(co) + self.frame.exec_(co) except Exception: raise Failure() return None, None @@ -107,6 +109,9 @@ class DebugInterpreter(ast.NodeVisitor): def _compile(self, source, mode="eval"): return compile(source, "", mode) + def visit_Expr(self, expr): + return self.visit(expr.value) + def visit_Module(self, mod): for stmt in mod.body: self.visit(stmt) @@ -175,8 +180,8 @@ class DebugInterpreter(ast.NodeVisitor): left_explanation, left_result = self.visit(binop.left) right_explanation, right_result = self.visit(binop.right) symbol = operator_map[binop.op.__class__] - explanation = "{0} {1} {2}".format(left_explanation, symbol, - right_explanation) + explanation = "({0} {1} {2})".format(left_explanation, symbol, + right_explanation) source = "__exprinfo_left {0} __exprinfo_right".format(symbol) co = self._compile(source) try: diff --git a/py/code/_assertion.py b/py/code/_assertionold.py similarity index 88% rename from py/code/_assertion.py rename to py/code/_assertionold.py index 2682dc659..947fea357 100644 --- a/py/code/_assertion.py +++ b/py/code/_assertionold.py @@ -1,8 +1,7 @@ import py import sys, inspect from compiler import parse, ast, pycodegen -import __builtin__ as cpy_builtin -BuiltinAssertionError = cpy_builtin.AssertionError +from py.__.code.assertion import BuiltinAssertionError, _format_explanation passthroughex = (KeyboardInterrupt, SystemExit, MemoryError) @@ -97,42 +96,6 @@ def enumsubclasses(cls): yield cls - -def _format_explanation(explanation): - # uck! See CallFunc for where \n{ and \n} escape sequences are used - raw_lines = (explanation or '').split('\n') - # escape newlines not followed by { and } - lines = [raw_lines[0]] - for l in raw_lines[1:]: - if l.startswith('{') or l.startswith('}'): - lines.append(l) - else: - lines[-1] += '\\n' + l - - result = lines[:1] - stack = [0] - stackcnt = [0] - for line in lines[1:]: - if line.startswith('{'): - if stackcnt[-1]: - s = 'and ' - else: - s = 'where ' - stack.append(len(result)) - stackcnt[-1] += 1 - stackcnt.append(0) - result.append(' +' + ' '*(len(stack)-1) + s + line[1:]) - else: - assert line.startswith('}') - stack.pop() - stackcnt.pop() - result[stack[-1]] += line[1:] - assert len(stack) == 1 - return '\n'.join(result) - - - - class Interpretable(View): """A parse tree node with a few extra methods.""" explanation = None @@ -566,39 +529,6 @@ def run(s, frame=None): report_failure(e) -class AssertionError(BuiltinAssertionError): - def __init__(self, *args): - BuiltinAssertionError.__init__(self, *args) - if args: - try: - self.msg = str(args[0]) - except (KeyboardInterrupt, SystemExit): - raise - except: - self.msg = "<[broken __repr__] %s at %0xd>" %( - args[0].__class__, id(args[0])) - - else: - f = py.code.Frame(sys._getframe(1)) - try: - source = f.statement - source = str(source.deindent()).strip() - except py.error.ENOENT: - source = None - # this can also occur during reinterpretation, when the - # co_filename is set to "". - if source: - if sys.version_info >= (2, 6): - from py.__.code._assertionnew import interpret as do_interp - else: - do_interp = interpret - self.msg = do_interp(source, f, should_fail=True) - if not self.args: - self.args = (self.msg,) - else: - self.msg = None - - if __name__ == '__main__': # example: def f(): diff --git a/py/code/assertion.py b/py/code/assertion.py new file mode 100644 index 000000000..994a07608 --- /dev/null +++ b/py/code/assertion.py @@ -0,0 +1,76 @@ +try: + import __builtin__ as builtins +except ImportError: + import builtins +import sys +import py + +BuiltinAssertionError = builtins.AssertionError + + +def _format_explanation(explanation): + # uck! See CallFunc for where \n{ and \n} escape sequences are used + raw_lines = (explanation or '').split('\n') + # escape newlines not followed by { and } + lines = [raw_lines[0]] + for l in raw_lines[1:]: + if l.startswith('{') or l.startswith('}'): + lines.append(l) + else: + lines[-1] += '\\n' + l + + result = lines[:1] + stack = [0] + stackcnt = [0] + for line in lines[1:]: + if line.startswith('{'): + if stackcnt[-1]: + s = 'and ' + else: + s = 'where ' + stack.append(len(result)) + stackcnt[-1] += 1 + stackcnt.append(0) + result.append(' +' + ' '*(len(stack)-1) + s + line[1:]) + else: + assert line.startswith('}') + stack.pop() + stackcnt.pop() + result[stack[-1]] += line[1:] + assert len(stack) == 1 + return '\n'.join(result) + + +if sys.version_info >= (2, 6): + from py.__.code._assertionnew import interpret +else: + from py.__.code._assertionold import interpret + + +class AssertionError(BuiltinAssertionError): + + def __init__(self, *args): + BuiltinAssertionError.__init__(self, *args) + if args: + try: + self.msg = str(args[0]) + except (KeyboardInterrupt, SystemExit): + raise + except: + self.msg = "<[broken __repr__] %s at %0xd>" %( + args[0].__class__, id(args[0])) + else: + f = py.code.Frame(sys._getframe(1)) + try: + source = f.statement + source = str(source.deindent()).strip() + except py.error.ENOENT: + source = None + # this can also occur during reinterpretation, when the + # co_filename is set to "". + if source: + self.msg = interpret(source, f, should_fail=True) + if not self.args: + self.args = (self.msg,) + else: + self.msg = None diff --git a/py/code/code.py b/py/code/code.py index 7083d7ad9..9477a1829 100644 --- a/py/code/code.py +++ b/py/code/code.py @@ -197,9 +197,9 @@ class TracebackEntry(object): """Reinterpret the failing statement and returns a detailed information about what operations are performed.""" if self.exprinfo is None: - from py.__.code import _assertion + from py.__.code import assertion source = str(self.statement).strip() - x = _assertion.interpret(source, self.frame, should_fail=True) + x = assertion.interpret(source, self.frame, should_fail=True) if not isinstance(x, str): raise TypeError("interpret returned non-string %r" % (x,)) self.exprinfo = x @@ -739,10 +739,10 @@ oldbuiltins = {} def patch_builtins(assertion=True, compile=True): """ put compile and AssertionError builtins to Python's builtins. """ if assertion: - from py.__.code import _assertion + from py.__.code import assertion l = oldbuiltins.setdefault('AssertionError', []) l.append(py.builtin.builtins.AssertionError) - py.builtin.builtins.AssertionError = _assertion.AssertionError + py.builtin.builtins.AssertionError = assertion.AssertionError if compile: l = oldbuiltins.setdefault('compile', []) l.append(py.builtin.builtins.compile) diff --git a/py/code/testing/test_assertion.py b/py/code/testing/test_assertion.py index 004444a56..7b81fff51 100644 --- a/py/code/testing/test_assertion.py +++ b/py/code/testing/test_assertion.py @@ -1,5 +1,5 @@ import py -from py.__.code._assertion import View +from py.__.code._assertionold import View def exvalue(): return py.std.sys.exc_info()[1]