diff options
29 files changed, 719 insertions, 322 deletions
diff --git a/_pytest/__init__.py b/_pytest/__init__.py index 9d797be039..596e2bcdf2 100644 --- a/_pytest/__init__.py +++ b/_pytest/__init__.py @@ -1,2 +1,2 @@ # -__version__ = '2.0.3' +__version__ = '2.1.0.dev4' diff --git a/_pytest/assertion/__init__.py b/_pytest/assertion/__init__.py new file mode 100644 index 0000000000..e20e4e4b55 --- /dev/null +++ b/_pytest/assertion/__init__.py @@ -0,0 +1,128 @@ +""" +support for presenting detailed information in failing assertions. +""" +import py +import imp +import marshal +import struct +import sys +import pytest +from _pytest.monkeypatch import monkeypatch +from _pytest.assertion import reinterpret, util + +try: + from _pytest.assertion.rewrite import rewrite_asserts +except ImportError: + rewrite_asserts = None +else: + import ast + +def pytest_addoption(parser): + group = parser.getgroup("debugconfig") + group.addoption('--assertmode', action="store", dest="assertmode", + choices=("on", "old", "off", "default"), default="default", + metavar="on|old|off", + help="""control assertion debugging tools. +'off' performs no assertion debugging. +'old' reinterprets the expressions in asserts to glean information. +'on' (the default) rewrites the assert statements in test modules to provide +sub-expression results.""") + group.addoption('--no-assert', action="store_true", default=False, + dest="noassert", help="DEPRECATED equivalent to --assertmode=off") + group.addoption('--nomagic', action="store_true", default=False, + dest="nomagic", help="DEPRECATED equivalent to --assertmode=off") + +class AssertionState: + """State for the assertion plugin.""" + + def __init__(self, config, mode): + self.mode = mode + self.trace = config.trace.root.get("assertion") + +def pytest_configure(config): + warn_about_missing_assertion() + mode = config.getvalue("assertmode") + if config.getvalue("noassert") or config.getvalue("nomagic"): + if mode not in ("off", "default"): + raise pytest.UsageError("assertion options conflict") + mode = "off" + elif mode == "default": + mode = "on" + if mode != "off": + def callbinrepr(op, left, right): + hook_result = config.hook.pytest_assertrepr_compare( + config=config, op=op, left=left, right=right) + for new_expl in hook_result: + if new_expl: + return '\n~'.join(new_expl) + m = monkeypatch() + config._cleanup.append(m.undo) + m.setattr(py.builtin.builtins, 'AssertionError', + reinterpret.AssertionError) + m.setattr(util, '_reprcompare', callbinrepr) + if mode == "on" and rewrite_asserts is None: + mode = "old" + config._assertstate = AssertionState(config, mode) + config._assertstate.trace("configured with mode set to %r" % (mode,)) + +def _write_pyc(co, source_path): + if hasattr(imp, "cache_from_source"): + # Handle PEP 3147 pycs. + pyc = py.path.local(imp.cache_from_source(str(source_path))) + pyc.ensure() + else: + pyc = source_path + "c" + mtime = int(source_path.mtime()) + fp = pyc.open("wb") + try: + fp.write(imp.get_magic()) + fp.write(struct.pack("<l", mtime)) + marshal.dump(co, fp) + finally: + fp.close() + return pyc + +def before_module_import(mod): + if mod.config._assertstate.mode != "on": + return + # Some deep magic: load the source, rewrite the asserts, and write a + # fake pyc, so that it'll be loaded when the module is imported. + source = mod.fspath.read() + try: + tree = ast.parse(source) + except SyntaxError: + # Let this pop up again in the real import. + mod.config._assertstate.trace("failed to parse: %r" % (mod.fspath,)) + return + rewrite_asserts(tree) + try: + co = compile(tree, str(mod.fspath), "exec") + except SyntaxError: + # It's possible that this error is from some bug in the assertion + # rewriting, but I don't know of a fast way to tell. + mod.config._assertstate.trace("failed to compile: %r" % (mod.fspath,)) + return + mod._pyc = _write_pyc(co, mod.fspath) + mod.config._assertstate.trace("wrote pyc: %r" % (mod._pyc,)) + +def after_module_import(mod): + if not hasattr(mod, "_pyc"): + return + state = mod.config._assertstate + try: + mod._pyc.remove() + except py.error.ENOENT: + state.trace("couldn't find pyc: %r" % (mod._pyc,)) + else: + state.trace("removed pyc: %r" % (mod._pyc,)) + +def warn_about_missing_assertion(): + try: + assert False + except AssertionError: + pass + else: + sys.stderr.write("WARNING: failing tests may report as passing because " + "assertions are turned off! (are you using python -O?)\n") + +pytest_assertrepr_compare = util.assertrepr_compare diff --git a/py/_code/_assertionnew.py b/_pytest/assertion/newinterpret.py index afb1b31ff0..2696abda74 100644 --- a/py/_code/_assertionnew.py +++ b/_pytest/assertion/newinterpret.py @@ -1,13 +1,14 @@ """ Find intermediate evalutation results in assert statements through builtin AST. -This should replace _assertionold.py eventually. +This should replace oldinterpret.py eventually. """ import sys import ast import py -from py._code.assertion import _format_explanation, BuiltinAssertionError +from _pytest.assertion import util +from _pytest.assertion.reinterpret import BuiltinAssertionError if sys.platform.startswith("java") and sys.version_info < (2, 5, 2): @@ -59,21 +60,18 @@ def run(offending_line, frame=None): frame = py.code.Frame(sys._getframe(1)) return interpret(offending_line, frame) -def getfailure(failure): - explanation = _format_explanation(failure.explanation) - value = failure.cause[1] +def getfailure(e): + explanation = util.format_explanation(e.explanation) + value = e.cause[1] if str(value): - lines = explanation.splitlines() - if not lines: - lines.append("") - lines[0] += " << %s" % (value,) - explanation = "\n".join(lines) - text = "%s: %s" % (failure.cause[0].__name__, explanation) - if text.startswith("AssertionError: assert "): + lines = explanation.split('\n') + lines[0] += " << %s" % (value,) + explanation = '\n'.join(lines) + text = "%s: %s" % (e.cause[0].__name__, explanation) + if text.startswith('AssertionError: assert '): text = text[16:] return text - operator_map = { ast.BitOr : "|", ast.BitXor : "^", @@ -154,8 +152,8 @@ class DebugInterpreter(ast.NodeVisitor): local = self.frame.eval(co) except Exception: # have to assume it isn't - local = False - if not local: + local = None + if local is None or not self.frame.is_true(local): return name.id, result return explanation, result @@ -175,7 +173,7 @@ class DebugInterpreter(ast.NodeVisitor): except Exception: raise Failure(explanation) try: - if not result: + if not self.frame.is_true(result): break except KeyboardInterrupt: raise @@ -183,9 +181,8 @@ class DebugInterpreter(ast.NodeVisitor): break left_explanation, left_result = next_explanation, next_result - rcomp = py.code._reprcompare - if rcomp: - res = rcomp(op_symbol, left_result, next_result) + if util._reprcompare is not None: + res = util._reprcompare(op_symbol, left_result, next_result) if res: explanation = res return explanation, result @@ -302,8 +299,8 @@ class DebugInterpreter(ast.NodeVisitor): try: from_instance = self.frame.eval(co, __exprinfo_expr=source_result) except Exception: - from_instance = True - if from_instance: + from_instance = None + if from_instance is None or self.frame.is_true(from_instance): rep = self.frame.repr(result) pattern = "%s\n{%s = %s\n}" explanation = pattern % (rep, rep, explanation) @@ -311,11 +308,8 @@ class DebugInterpreter(ast.NodeVisitor): def visit_Assert(self, assrt): test_explanation, test_result = self.visit(assrt.test) - if test_explanation.startswith("False\n{False =") and \ - test_explanation.endswith("\n"): - test_explanation = test_explanation[15:-2] explanation = "assert %s" % (test_explanation,) - if not test_result: + if not self.frame.is_true(test_result): try: raise BuiltinAssertionError except Exception: diff --git a/py/_code/_assertionold.py b/_pytest/assertion/oldinterpret.py index 4e81fb3ef6..e109871415 100644 --- a/py/_code/_assertionold.py +++ b/_pytest/assertion/oldinterpret.py @@ -1,7 +1,8 @@ import py import sys, inspect from compiler import parse, ast, pycodegen -from py._code.assertion import BuiltinAssertionError, _format_explanation +from _pytest.assertion.util import format_explanation +from _pytest.assertion.reinterpret import BuiltinAssertionError passthroughex = py.builtin._sysex @@ -131,7 +132,7 @@ class Interpretable(View): raise Failure(self) def nice_explanation(self): - return _format_explanation(self.explanation) + return format_explanation(self.explanation) class Name(Interpretable): @@ -383,10 +384,6 @@ class Assert(Interpretable): def run(self, frame): test = Interpretable(self.test) test.eval(frame) - # simplify 'assert False where False = ...' - if (test.explanation.startswith('False\n{False = ') and - test.explanation.endswith('\n}')): - test.explanation = test.explanation[15:-2] # print the result as 'assert <explanation>' self.result = test.result self.explanation = 'assert ' + test.explanation diff --git a/_pytest/assertion/reinterpret.py b/_pytest/assertion/reinterpret.py new file mode 100644 index 0000000000..6e9465d8a7 --- /dev/null +++ b/_pytest/assertion/reinterpret.py @@ -0,0 +1,48 @@ +import sys +import py + +BuiltinAssertionError = py.builtin.builtins.AssertionError + +class AssertionError(BuiltinAssertionError): + def __init__(self, *args): + BuiltinAssertionError.__init__(self, *args) + if args: + try: + self.msg = str(args[0]) + except py.builtin._sysex: + 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.code.fullsource + if source is not None: + try: + source = source.getstatement(f.lineno, assertion=True) + except IndexError: + source = None + else: + source = str(source.deindent()).strip() + except py.error.ENOENT: + source = None + # this can also occur during reinterpretation, when the + # co_filename is set to "<run>". + if source: + self.msg = reinterpret(source, f, should_fail=True) + else: + self.msg = "<could not determine information>" + if not self.args: + self.args = (self.msg,) + +if sys.version_info > (3, 0): + AssertionError.__module__ = "builtins" + reinterpret_old = "old reinterpretation not available for py3" +else: + from _pytest.assertion.oldinterpret import interpret as reinterpret_old +if sys.version_info >= (2, 6) or (sys.platform.startswith("java")): + from _pytest.assertion.newinterpret import interpret as reinterpret +else: + reinterpret = reinterpret_old + diff --git a/_pytest/assertion/rewrite.py b/_pytest/assertion/rewrite.py new file mode 100644 index 0000000000..6c9067525a --- /dev/null +++ b/_pytest/assertion/rewrite.py @@ -0,0 +1,340 @@ +"""Rewrite assertion AST to produce nice error messages""" + +import ast +import collections +import itertools +import sys + +import py +from _pytest.assertion import util + + +def rewrite_asserts(mod): + """Rewrite the assert statements in mod.""" + AssertionRewriter().run(mod) + + +_saferepr = py.io.saferepr +from _pytest.assertion.util import format_explanation as _format_explanation + +def _format_boolop(operands, explanations, is_or): + show_explanations = [] + for operand, expl in zip(operands, explanations): + show_explanations.append(expl) + if operand == is_or: + break + return "(" + (is_or and " or " or " and ").join(show_explanations) + ")" + +def _call_reprcompare(ops, results, expls, each_obj): + for i, res, expl in zip(range(len(ops)), results, expls): + try: + done = not res + except Exception: + done = True + if done: + break + if util._reprcompare is not None: + custom = util._reprcompare(ops[i], each_obj[i], each_obj[i + 1]) + if custom is not None: + return custom + return expl + + +unary_map = { + ast.Not : "not %s", + ast.Invert : "~%s", + ast.USub : "-%s", + ast.UAdd : "+%s" +} + +binop_map = { + ast.BitOr : "|", + ast.BitXor : "^", + ast.BitAnd : "&", + ast.LShift : "<<", + ast.RShift : ">>", + ast.Add : "+", + ast.Sub : "-", + ast.Mult : "*", + ast.Div : "/", + ast.FloorDiv : "//", + ast.Mod : "%", + ast.Eq : "==", + ast.NotEq : "!=", + ast.Lt : "<", + ast.LtE : "<=", + ast.Gt : ">", + ast.GtE : ">=", + ast.Pow : "**", + ast.Is : "is", + ast.IsNot : "is not", + ast.In : "in", + ast.NotIn : "not in" +} + + +def set_location(node, lineno, col_offset): + """Set node location information recursively.""" + def _fix(node, lineno, col_offset): + if "lineno" in node._attributes: + node.lineno = lineno + if "col_offset" in node._attributes: + node.col_offset = col_offset + for child in ast.iter_child_nodes(node): + _fix(child, lineno, col_offset) + _fix(node, lineno, col_offset) + return node + + +class AssertionRewriter(ast.NodeVisitor): + + def run(self, mod): + """Find all assert statements in *mod* and rewrite them.""" + if not mod.body: + # Nothing to do. + return + # Insert some special imports at the top of the module but after any + # docstrings and __future__ imports. + aliases = [ast.alias(py.builtin.builtins.__name__, "@py_builtins"), + ast.alias("_pytest.assertion.rewrite", "@pytest_ar")] + expect_docstring = True + pos = 0 + lineno = 0 + for item in mod.body: + if (expect_docstring and isinstance(item, ast.Expr) and + isinstance(item.value, ast.Str)): + doc = item.value.s + if "PYTEST_DONT_REWRITE" in doc: + # The module has disabled assertion rewriting. + return + lineno += len(doc) - 1 + expect_docstring = False + elif (not isinstance(item, ast.ImportFrom) or item.level > 0 and + item.identifier != "__future__"): + lineno = item.lineno + break + pos += 1 + imports = [ast.Import([alias], lineno=lineno, col_offset=0) + for alias in aliases] + mod.body[pos:pos] = imports + # Collect asserts. + nodes = collections.deque([mod]) + while nodes: + node = nodes.popleft() + for name, field in ast.iter_fields(node): + if isinstance(field, list): + new = [] + for i, child in enumerate(field): + if isinstance(child, ast.Assert): + # Transform assert. + new.extend(self.visit(child)) + else: + new.append(child) + if isinstance(child, ast.AST): + nodes.append(child) + setattr(node, name, new) + elif (isinstance(field, ast.AST) and + # Don't recurse into expressions as they can't contain + # asserts. + not isinstance(field, ast.expr)): + nodes.append(field) + + def variable(self): + """Get a new variable.""" + # Use a character invalid in python identifiers to avoid clashing. + name = "@py_assert" + str(next(self.variable_counter)) + self.variables.add(name) + return name + + def assign(self, expr): + """Give *expr* a name.""" + name = self.variable() + self.statements.append(ast.Assign([ast.Name(name, ast.Store())], expr)) + return ast.Name(name, ast.Load()) + + def display(self, expr): + """Call py.io.saferepr on the expression.""" + return self.helper("saferepr", expr) + + def helper(self, name, *args): + """Call a helper in this module.""" + py_name = ast.Name("@pytest_ar", ast.Load()) + attr = ast.Attribute(py_name, "_" + name, ast.Load()) + return ast.Call(attr, list(args), [], None, None) + + def builtin(self, name): + """Return the builtin called *name*.""" + builtin_name = ast.Name("@py_builtins", ast.Load()) + return ast.Attribute(builtin_name, name, ast.Load()) + + def explanation_param(self, expr): + specifier = "py" + str(next(self.variable_counter)) + self.explanation_specifiers[specifier] = expr + return "%(" + specifier + ")s" + + def push_format_context(self): + self.explanation_specifiers = {} + self.stack.append(self.explanation_specifiers) + + def pop_format_context(self, expl_expr): + current = self.stack.pop() + if self.stack: + self.explanation_specifiers = self.stack[-1] + keys = [ast.Str(key) for key in current.keys()] + format_dict = ast.Dict(keys, list(current.values())) + form = ast.BinOp(expl_expr, ast.Mod(), format_dict) + name = "@py_format" + str(next(self.variable_counter)) + self.on_failure.append(ast.Assign([ast.Name(name, ast.Store())], form)) + return ast.Name(name, ast.Load()) + + def generic_visit(self, node): + """Handle expressions we don't have custom code for.""" + assert isinstance(node, ast.expr) + res = self.assign(node) + return res, self.explanation_param(self.display(res)) + + def visit_Assert(self, assert_): + if assert_.msg: + # There's already a message. Don't mess with it. + return [assert_] + self.statements = [] + self.variables = set() + self.variable_counter = itertools.count() + self.stack = [] + self.on_failure = [] + self.push_format_context() + # Rewrite assert into a bunch of statements. + top_condition, explanation = self.visit(assert_.test) + # Create failure message. + body = self.on_failure + negation = ast.UnaryOp(ast.Not(), top_condition) + self.statements.append(ast.If(negation, body, [])) + explanation = "assert " + explanation + template = ast.Str(explanation) + msg = self.pop_format_context(template) + fmt = self.helper("format_explanation", msg) + err_name = ast.Name("AssertionError", ast.Load()) + exc = ast.Call(err_name, [fmt], [], None, None) + if sys.version_info[0] >= 3: + raise_ = ast.Raise(exc, None) + else: + raise_ = ast.Raise(exc, None, None) + body.append(raise_) + # Delete temporary variables. + names = [ast.Name(name, ast.Del()) for name in self.variables] + if names: + delete = ast.Delete(names) + self.statements.append(delete) + # Fix line numbers. + for stmt in self.statements: + set_location(stmt, assert_.lineno, assert_.col_offset) + return self.statements + + def visit_Name(self, name): + # Check if the name is local or not. + locs = ast.Call(self.builtin("locals"), [], [], None, None) + globs = ast.Call(self.builtin("globals"), [], [], None, None) + ops = [ast.In(), ast.IsNot()] + test = ast.Compare(ast.Str(name.id), ops, [locs, globs]) + expr = ast.IfExp(test, self.display(name), ast.Str(name.id)) + return name, self.explanation_param(expr) + + def visit_BoolOp(self, boolop): + operands = [] + explanations = [] + self.push_format_context() + for operand in boolop.values: + res, explanation = self.visit(operand) + operands.append(res) + explanations.append(explanation) + expls = ast.Tuple([ast.Str(expl) for expl in explanations], ast.Load()) + is_or = ast.Num(isinstance(boolop.op, ast.Or)) + expl_template = self.helper("format_boolop", + ast.Tuple(operands, ast.Load()), expls, + is_or) + expl = self.pop_format_context(expl_template) + res = self.assign(ast.BoolOp(boolop.op, operands)) + return res, self.explanation_param(expl) + + def visit_UnaryOp(self, unary): + pattern = unary_map[unary.op.__class__] + operand_res, operand_expl = self.visit(unary.operand) + res = self.assign(ast.UnaryOp(unary.op, operand_res)) + return res, pattern % (operand_expl,) + + def visit_BinOp(self, binop): + symbol = binop_map[binop.op.__class__] + left_expr, left_expl = self.visit(binop.left) + right_expr, right_expl = self.visit(binop.right) + explanation = "(%s %s %s)" % (left_expl, symbol, right_expl) + res = self.assign(ast.BinOp(left_expr, binop.op, right_expr)) + return res, explanation + + def visit_Call(self, call): + new_func, func_expl = self.visit(call.func) + arg_expls = [] + new_args = [] + new_kwargs = [] + new_star = new_kwarg = None + for arg in call.args: + res, expl = self.visit(arg) + new_args.append(res) + arg_expls.append(expl) + for keyword in call.keywords: + res, expl = self.visit(keyword.value) + new_kwargs.append(ast.keyword(keyword.arg, res)) + arg_expls.append(keyword.arg + "=" + expl) + if call.starargs: + new_star, expl = self.visit(call.starargs) + arg_expls.append("*" + expl) + if call.kwargs: + new_kwarg, expl = self.visit(call.kwarg) + arg_expls.append("**" + expl) + expl = "%s(%s)" % (func_expl, ', '.join(arg_expls)) + new_call = ast.Call(new_func, new_args, new_kwargs, new_star, new_kwarg) + res = self.assign(new_call) + res_expl = self.explanation_param(self.display(res)) + outer_expl = "%s\n{%s = %s\n}" % (res_expl, res_expl, expl) + return res, outer_expl + + def visit_Attribute(self, attr): + if not isinstance(attr.ctx, ast.Load): + return self.generic_visit(attr) + value, value_expl = self.visit(attr.value) + res = self.assign(ast.Attribute(value, attr.attr, ast.Load())) + res_expl = self.explanation_param(self.display(res)) + pat = "%s\n{%s = %s.%s\n}" + expl = pat % (res_expl, res_expl, value_expl, attr.attr) + return res, expl + + def visit_Compare(self, comp): + self.push_format_context() + left_res, left_expl = self.visit(comp.left) + res_variables = [self.variable() for i in range(len(comp.ops))] + load_names = [ast.Name(v, ast.Load()) for v in res_variables] + store_names = [ast.Name(v, ast.Store()) for v in res_variables] + it = zip(range(len(comp.ops)), comp.ops, comp.comparators) + expls = [] + syms = [] + results = [left_res] + for i, op, next_operand in it: + next_res, next_expl = self.visit(next_operand) + results.append(next_res) + sym = binop_map[op.__class__] + syms.append(ast.Str(sym)) + expl = "%s %s %s" % (left_expl, sym, next_expl) + expls.append(ast.Str(expl)) + res_expr = ast.Compare(left_res, [op], [next_res]) + self.statements.append(ast.Assign([store_names[i]], res_expr)) + left_res, left_expl = next_res, next_expl + # Use py.code._reprcompare if that's available. + expl_call = self.helper("call_reprcompare", + ast.Tuple(syms, ast.Load()), + ast.Tuple(load_names, ast.Load()), + ast.Tuple(expls, ast.Load()), + ast.Tuple(results, ast.Load())) + if len(comp.ops) > 1: + res = ast.BoolOp(ast.And(), load_names) + else: + res = load_names[0] + return res, self.explanation_param(self.pop_format_context(expl_call)) diff --git a/_pytest/assertion.py b/_pytest/assertion/util.py index d40981c32b..a85ed99f71 100644 --- a/_pytest/assertion.py +++ b/_pytest/assertion/util.py @@ -1,43 +1,79 @@ -""" -support for presented detailed information in failing assertions. -""" +"""Utilities for assertion debugging""" + import py -import sys -from _pytest.monkeypatch import monkeypatch - -def pytest_addoption(parser): - group = parser.getgroup("debugconfig") - group._addoption('--no-assert', action="store_true", default=False, - dest="noassert", - help="disable python assert expression reinterpretation."), - -def pytest_configure(config): - # The _reprcompare attribute on the py.code module is used by - # py._code._assertionnew to detect this plugin was loaded and in - # turn call the hooks defined here as part of the - # DebugInterpreter. - m = monkeypatch() - config._cleanup.append(m.undo) - warn_about_missing_assertion() - if not config.getvalue("noassert") and not config.getvalue("nomagic"): - def callbinrepr(op, left, right): - hook_result = config.hook.pytest_assertrepr_compare( - config=config, op=op, left=left, right=right) - for new_expl in hook_result: - if new_expl: - return '\n~'.join(new_expl) - m.setattr(py.builtin.builtins, - 'AssertionError', py.code._AssertionError) - m.setattr(py.code, '_reprcompare', callbinrepr) - -def warn_about_missing_assertion(): - try: - assert False - except AssertionError: - pass - else: - sys.stderr.write("WARNING: failing tests may report as passing because " - "assertions are turned off! (are you using python -O?)\n") + + +# The _reprcompare attribute on the util module is used by the new assertion +# interpretation code and assertion rewriter to detect this plugin was +# loaded and in turn call the hooks defined here as part of the +# DebugInterpreter. +_reprcompare = None + +def format_explanation(explanation): + """This formats an explanation + + Normally all embedded newlines are escaped, however there are + three exceptions: \n{, \n} and \n~. The first two are intended + cover nested explanations, see function and attribute explanations + for examples (.visit_Call(), visit_Attribute()). The last one is + for when one explanation needs to span multiple lines, e.g. when + displaying diffs. + """ + # simplify 'assert False where False = ...' + where = 0 + while True: + start = where = explanation.find("False\n{False = ", where) + if where == -1: + break + level = 0 + for i, c in enumerate(explanation[start:]): + if c == "{": + level += 1 + elif c == "}": + level -= 1 + if not level: + break + else: + raise AssertionError("unbalanced braces: %r" % (explanation,)) + end = start + i + where = end + if explanation[end - 1] == '\n': + explanation = (explanation[:start] + explanation[start+15:end-1] + + explanation[end+1:]) + where -= 17 + 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('}') 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:]) + elif line.startswith('}'): + assert line.startswith('}') + stack.pop() + stackcnt.pop() + result[stack[-1]] += line[1:] + else: + assert line.startswith('~') + result.append(' '*len(stack) + line[1:]) + assert len(stack) == 1 + return '\n'.join(result) + # Provide basestring in python3 try: @@ -46,7 +82,7 @@ except NameError: basestring = str -def pytest_assertrepr_compare(op, left, right): +def assertrepr_compare(op, left, right): """return specialised explanations for some operators/operands""" width = 80 - 15 - len(op) - 2 # 15 chars indentation, 1 space around op left_repr = py.io.saferepr(left, maxsize=int(width/2)) diff --git a/_pytest/doctest.py b/_pytest/doctest.py index 1378544ba5..aaebab78e5 100644 --- a/_pytest/doctest.py +++ b/_pytest/doctest.py @@ -59,7 +59,7 @@ class DoctestItem(pytest.Item): inner_excinfo = py.code.ExceptionInfo(excinfo.value.exc_info) lines += ["UNEXPECTED EXCEPTION: %s" % repr(inner_excinfo.value)] - + lines += py.std.traceback.format_exception(*excinfo.value.exc_info) return ReprFailDoctest(reprlocation, lines) else: return super(DoctestItem, self).repr_failure(excinfo) diff --git a/_pytest/helpconfig.py b/_pytest/helpconfig.py index b89b33b563..fa81f87e9f 100644 --- a/_pytest/helpconfig.py +++ b/_pytest/helpconfig.py @@ -16,9 +16,6 @@ def pytest_addoption(parser): group.addoption('--traceconfig', action="store_true", dest="traceconfig", default=False, help="trace considerations of conftest.py files."), - group._addoption('--nomagic', - action="store_true", dest="nomagic", default=False, - help="don't reinterpret asserts, no traceback cutting. ") group.addoption('--debug', action="store_true", dest="debug", default=False, help="generate and show internal debugging information.") diff --git a/_pytest/junitxml.py b/_pytest/junitxml.py index d92842db0e..c7a089cce5 100644 --- a/_pytest/junitxml.py +++ b/_pytest/junitxml.py @@ -65,7 +65,8 @@ def pytest_unconfigure(config): class LogXML(object): def __init__(self, logfile, prefix): - self.logfile = logfile + logfile = os.path.expanduser(os.path.expandvars(logfile)) + self.logfile = os.path.normpath(logfile) self.prefix = prefix self.test_logs = [] self.passed = self.skipped = 0 @@ -76,7 +77,7 @@ class LogXML(object): names = report.nodeid.split("::") names[0] = names[0].replace("/", '.') names = tuple(names) - d = {'time': self._durations.pop(names, "0")} + d = {'time': self._durations.pop(report.nodeid, "0")} names = [x.replace(".py", "") for x in names if x != "()"] classnames = names[:-1] if self.prefix: @@ -170,12 +171,11 @@ class LogXML(object): self.append_skipped(report) def pytest_runtest_call(self, item, __multicall__): - names = tuple(item.listnames()) start = time.time() try: return __multicall__.execute() finally: - self._durations[names] = time.time() - start + self._durations[item.nodeid] = time.time() - start def pytest_collectreport(self, report): if not report.passed: diff --git a/_pytest/main.py b/_pytest/main.py index f73ff597a0..ac306a867a 100644 --- a/_pytest/main.py +++ b/_pytest/main.py @@ -46,23 +46,25 @@ def pytest_addoption(parser): def pytest_namespace(): - return dict(collect=dict(Item=Item, Collector=Collector, File=File)) + collect = dict(Item=Item, Collector=Collector, File=File, Session=Session) + return dict(collect=collect) def pytest_configure(config): py.test.config = config # compatibiltiy if config.option.exitfirst: config.option.maxfail = 1 -def pytest_cmdline_main(config): - """ default command line protocol for initialization, session, - running tests and reporting. """ +def wrap_session(config, doit): + """Skeleton command line program""" session = Session(config) session.exitstatus = EXIT_OK + initstate = 0 try: config.pluginmanager.do_configure(config) + initstate = 1 config.hook.pytest_sessionstart(session=session) - config.hook.pytest_collection(session=session) - config.hook.pytest_runtestloop(session=session) + initstate = 2 + doit(config, session) except pytest.UsageError: raise except KeyboardInterrupt: @@ -77,18 +79,24 @@ def pytest_cmdline_main(config): sys.stderr.write("mainloop: caught Spurious SystemExit!\n") if not session.exitstatus and session._testsfailed: session.exitstatus = EXIT_TESTSFAILED - config.hook.pytest_sessionfinish(session=session, - exitstatus=session.exitstatus) - config.pluginmanager.do_unconfigure(config) + if initstate >= 2: + config.hook.pytest_sessionfinish(session=session, + exitstatus=session.exitstatus) + if initstate >= 1: + config.pluginmanager.do_unconfigure(config) return session.exitstatus +def pytest_cmdline_main(config): + return wrap_session(config, _main) + +def _main(config, session): + """ default command line protocol for initialization, session, + running tests and reporting. """ + config.hook.pytest_collection(session=session) + config.hook.pytest_runtestloop(session=session) + def pytest_collection(session): - session.perform_collect() - hook = session.config.hook - hook.pytest_collection_modifyitems(session=session, - config=session.config, items=session.items) - hook.pytest_collection_finish(session=session) - return True + return session.perform_collect() def pytest_runtestloop(session): if session.config.option.collectonly: @@ -374,6 +382,16 @@ class Session(FSCollector): return HookProxy(fspath, self.config) def perform_collect(self, args=None, genitems=True): + hook = self.config.hook + try: + items = self._perform_collect(args, genitems) + hook.pytest_collection_modifyitems(session=self, + config=self.config, items=items) + finally: + hook.pytest_collection_finish(session=self) + return items + + def _perform_collect(self, args, genitems): if args is None: args = self.config.args self.trace("perform_collect", self, args) diff --git a/_pytest/mark.py b/_pytest/mark.py index d9a45930c9..6cc8edcdba 100644 --- a/_pytest/mark.py +++ b/_pytest/mark.py @@ -153,7 +153,7 @@ class MarkInfo: def __repr__(self): return "<MarkInfo %r args=%r kwargs=%r>" % ( - self._name, self.args, self.kwargs) + self.name, self.args, self.kwargs) def pytest_itemcollected(item): if not isinstance(item, pytest.Function): diff --git a/_pytest/pytester.py b/_pytest/pytester.py index 619bba9886..8bfa3d37bd 100644 --- a/_pytest/pytester.py +++ b/_pytest/pytester.py @@ -6,7 +6,7 @@ import re import inspect import time from fnmatch import fnmatch -from _pytest.main import Session +from _pytest.main import Session, EXIT_OK from py.builtin import print_ from _pytest.core import HookRelay @@ -292,13 +292,19 @@ class TmpTestdir: assert '::' not in str(arg) p = py.path.local(arg) x = session.fspath.bestrelpath(p) - return session.perform_collect([x], genitems=False)[0] + config.hook.pytest_sessionstart(session=session) + res = session.perform_collect([x], genitems=False)[0] + config.hook.pytest_sessionfinish(session=session, exitstatus=EXIT_OK) + return res def getpathnode(self, path): - config = self.parseconfig(path) + config = self.parseconfigure(path) session = Session(config) x = session.fspath.bestrelpath(path) - return session.perform_collect([x], genitems=False)[0] + config.hook.pytest_sessionstart(session=session) + res = session.perform_collect([x], genitems=False)[0] + config.hook.pytest_sessionfinish(session=session, exitstatus=EXIT_OK) + return res def genitems(self, colitems): session = colitems[0].session @@ -312,7 +318,9 @@ class TmpTestdir: config = self.parseconfigure(*args) rec = self.getreportrecorder(config) session = Session(config) + config.hook.pytest_sessionstart(session=session) session.perform_collect() + config.hook.pytest_sessionfinish(session=session, exitstatus=EXIT_OK) return session.items, rec def runitem(self, source): @@ -382,6 +390,8 @@ class TmpTestdir: c.basetemp = py.path.local.make_numbered_dir(prefix="reparse", keep=0, rootdir=self.tmpdir, lock_timeout=None) c.parse(args) + c.pluginmanager.do_configure(c) + self.request.addfinalizer(lambda: c.pluginmanager.do_unconfigure(c)) return c finally: py.test.config = oldconfig diff --git a/_pytest/python.py b/_pytest/python.py index 5378dc095e..35b85485c0 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -226,8 +226,13 @@ class Module(pytest.File, PyCollectorMixin): def _importtestmodule(self): # we assume we are only called once per module + from _pytest import assertion + assertion.before_module_import(self) try: - mod = self.fspath.pyimport(ensuresyspath=True) + try: + mod = self.fspath.pyimport(ensuresyspath=True) + finally: + assertion.after_module_import(self) except SyntaxError: excinfo = py.code.ExceptionInfo() raise self.CollectError(excinfo.getrepr(style="short")) @@ -374,7 +379,7 @@ class Generator(FunctionMixin, PyCollectorMixin, pytest.Collector): # test generators are seen as collectors but they also # invoke setup/teardown on popular request # (induced by the common "test_*" naming shared with normal tests) - self.config._setupstate.prepare(self) + self.session._setupstate.prepare(self) # see FunctionMixin.setup and test_setupstate_is_preserved_134 self._preservedparent = self.parent.obj l = [] @@ -721,7 +726,7 @@ class FuncargRequest: def _addfinalizer(self, finalizer, scope): colitem = self._getscopeitem(scope) - self.config._setupstate.addfinalizer( + self._pyfuncitem.session._setupstate.addfinalizer( finalizer=finalizer, colitem=colitem) def __repr__(self): @@ -742,8 +747,10 @@ class FuncargRequest: raise self.LookupError(msg) def showfuncargs(config): - from _pytest.main import Session - session = Session(config) + from _pytest.main import wrap_session + return wrap_session(config, _showfuncargs_main) + +def _showfuncargs_main(config, session): session.perform_collect() if session.items: plugins = session.items[0].getplugins() diff --git a/_pytest/runner.py b/_pytest/runner.py index 4deb8685b9..c1b73e94c7 100644 --- a/_pytest/runner.py +++ b/_pytest/runner.py @@ -14,17 +14,15 @@ def pytest_namespace(): # # pytest plugin hooks -# XXX move to pytest_sessionstart and fix py.test owns tests -def pytest_configure(config): - config._setupstate = SetupState() +def pytest_sessionstart(session): + session._setupstate = SetupState() def pytest_sessionfinish(session, exitstatus): - if hasattr(session.config, '_setupstate'): - hook = session.config.hook - rep = hook.pytest__teardown_final(session=session) - if rep: - hook.pytest__teardown_final_logerror(session=session, report=rep) - session.exitstatus = 1 + hook = session.config.hook + rep = hook.pytest__teardown_final(session=session) + if rep: + hook.pytest__teardown_final_logerror(session=session, report=rep) + session.exitstatus = 1 class NodeInfo: def __init__(self, location): @@ -46,16 +44,16 @@ def runtestprotocol(item, log=True): return reports def pytest_runtest_setup(item): - item.config._setupstate.prepare(item) + item.session._setupstate.prepare(item) def pytest_runtest_call(item): item.runtest() def pytest_runtest_teardown(item): - item.config._setupstate.teardown_exact(item) + item.session._setupstate.teardown_exact(item) def pytest__teardown_final(session): - call = CallInfo(session.config._setupstate.teardown_all, when="teardown") + call = CallInfo(session._setupstate.teardown_all, when="teardown") if call.excinfo: ntraceback = call.excinfo.traceback .cut(excludepath=py._pydir) call.excinfo.traceback = ntraceback.filter() diff --git a/py/__init__.py b/py/__init__.py index 1631ce3d30..65439fe1a2 100644 --- a/py/__init__.py +++ b/py/__init__.py @@ -8,7 +8,7 @@ dictionary or an import path. (c) Holger Krekel and others, 2004-2010 """ -__version__ = '1.4.3' +__version__ = '1.4.4.dev1' from py import _apipkg @@ -70,10 +70,6 @@ _apipkg.initpkg(__name__, attr={'_apipkg': _apipkg}, exportdefs={ 'getrawcode' : '._code.code:getrawcode', 'patch_builtins' : '._code.code:patch_builtins', 'unpatch_builtins' : '._code.code:unpatch_builtins', - '_AssertionError' : '._code.assertion:AssertionError', - '_reinterpret_old' : '._code.assertion:reinterpret_old', - '_reinterpret' : '._code.assertion:reinterpret', - '_reprcompare' : '._code.assertion:_reprcompare', }, # backports and additions of builtins diff --git a/py/_code/assertion.py b/py/_code/assertion.py deleted file mode 100644 index 4ce80c75b1..0000000000 --- a/py/_code/assertion.py +++ /dev/null @@ -1,94 +0,0 @@ -import sys -import py - -BuiltinAssertionError = py.builtin.builtins.AssertionError - -_reprcompare = None # if set, will be called by assert reinterp for comparison ops - -def _format_explanation(explanation): - """This formats an explanation - - Normally all embedded newlines are escaped, however there are - three exceptions: \n{, \n} and \n~. The first two are intended - cover nested explanations, see function and attribute explanations - for examples (.visit_Call(), visit_Attribute()). The last one is - for when one explanation needs to span multiple lines, e.g. when - displaying diffs. - """ - 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('}') 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:]) - elif line.startswith('}'): - assert line.startswith('}') - stack.pop() - stackcnt.pop() - result[stack[-1]] += line[1:] - else: - assert line.startswith('~') - result.append(' '*len(stack) + line[1:]) - assert len(stack) == 1 - return '\n'.join(result) - - -class AssertionError(BuiltinAssertionError): - def __init__(self, *args): - BuiltinAssertionError.__init__(self, *args) - if args: - try: - self.msg = str(args[0]) - except py.builtin._sysex: - 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.code.fullsource - if source is not None: - try: - source = source.getstatement(f.lineno, assertion=True) - except IndexError: - source = None - else: - source = str(source.deindent()).strip() - except py.error.ENOENT: - source = None - # this can also occur during reinterpretation, when the - # co_filename is set to "<run>". - if source: - self.msg = reinterpret(source, f, should_fail=True) - else: - self.msg = "<could not determine information>" - if not self.args: - self.args = (self.msg,) - -if sys.version_info > (3, 0): - AssertionError.__module__ = "builtins" - reinterpret_old = "old reinterpretation not available for py3" -else: - from py._code._assertionold import interpret as reinterpret_old -if sys.version_info >= (2, 6) or (sys.platform.startswith("java")): - from py._code._assertionnew import interpret as reinterpret -else: - reinterpret = reinterpret_old - diff --git a/py/_code/code.py b/py/_code/code.py index 9fbf207969..4612289229 100644 --- a/py/_code/code.py +++ b/py/_code/code.py @@ -145,17 +145,6 @@ class TracebackEntry(object): return self.frame.f_locals locals = property(getlocals, None, None, "locals of underlaying frame") - def reinterpret(self): - """Reinterpret the failing statement and returns a detailed information - about what operations are performed.""" - if self.exprinfo is None: - source = str(self.statement).strip() - x = py.code._reinterpret(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 getfirstlinesource(self): # on Jython this firstlineno can be -1 apparently return max(self.frame.code.firstlineno, 0) @@ -310,7 +299,7 @@ class ExceptionInfo(object): # ExceptionInfo-like classes may have different attributes. if tup is None: tup = sys.exc_info() - if exprinfo is None and isinstance(tup[1], py.code._AssertionError): + if exprinfo is None and isinstance(tup[1], AssertionError): exprinfo = getattr(tup[1], 'msg', None) if exprinfo is None: exprinfo = str(tup[1]) @@ -690,22 +679,15 @@ class ReprFuncArgs(TerminalRepr): oldbuiltins = {} -def patch_builtins(assertion=True, compile=True): - """ put compile and AssertionError builtins to Python's builtins. """ - if assertion: - from py._code import assertion - l = oldbuiltins.setdefault('AssertionError', []) - l.append(py.builtin.builtins.AssertionError) - py.builtin.builtins.AssertionError = assertion.AssertionError +def patch_builtins(compile=True): + """ put compile builtins to Python's builtins. """ if compile: l = oldbuiltins.setdefault('compile', []) l.append(py.builtin.builtins.compile) py.builtin.builtins.compile = py.code.compile -def unpatch_builtins(assertion=True, compile=True): +def unpatch_builtins(compile=True): """ remove compile and AssertionError builtins from Python builtins. """ - if assertion: - py.builtin.builtins.AssertionError = oldbuiltins['AssertionError'].pop() if compile: py.builtin.builtins.compile = oldbuiltins['compile'].pop() diff --git a/py/bin/_findpy.py b/py/bin/_findpy.py deleted file mode 100644 index 92b14b6c5a..0000000000 --- a/py/bin/_findpy.py +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env python - -# -# find and import a version of 'py' -# -import sys -import os -from os.path import dirname as opd, exists, join, basename, abspath - -def searchpy(current): - while 1: - last = current - initpy = join(current, '__init__.py') - if not exists(initpy): - pydir = join(current, 'py') - # recognize py-package and ensure it is importable - if exists(pydir) and exists(join(pydir, '__init__.py')): - #for p in sys.path: - # if p == current: - # return True - if current != sys.path[0]: # if we are already first, then ok - sys.stderr.write("inserting into sys.path: %s\n" % current) - sys.path.insert(0, current) - return True - current = opd(current) - if last == current: - return False - -if not searchpy(abspath(os.curdir)): - if not searchpy(opd(abspath(sys.argv[0]))): - if not searchpy(opd(__file__)): - pass # let's hope it is just on sys.path - -import py -import pytest - -if __name__ == '__main__': - print ("py lib is at %s" % py.__file__) diff --git a/py/bin/py.test b/py/bin/py.test deleted file mode 100755 index 8ecba0e406..0000000000 --- a/py/bin/py.test +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env python -from _findpy import pytest -raise SystemExit(pytest.main()) diff --git a/pypy/interpreter/astcompiler/test/test_compiler.py b/pypy/interpreter/astcompiler/test/test_compiler.py index 24957f7d75..a306a83ef7 100644 --- a/pypy/interpreter/astcompiler/test/test_compiler.py +++ b/pypy/interpreter/astcompiler/test/test_compiler.py @@ -55,7 +55,7 @@ class TestCompiler: co_expr = compile(evalexpr, '<evalexpr>', 'eval') space = self.space pyco_expr = PyCode._from_code(space, co_expr) - w_res = pyco_expr.exec_host_bytecode(space, w_dict, w_dict) + w_res = pyco_expr.exec_host_bytecode(w_dict, w_dict) res = space.str_w(space.repr(w_res)) if not isinstance(expected, float): assert res == repr(expected) diff --git a/pypy/interpreter/baseobjspace.py b/pypy/interpreter/baseobjspace.py index c68819fef3..61428f3191 100644 --- a/pypy/interpreter/baseobjspace.py +++ b/pypy/interpreter/baseobjspace.py @@ -989,10 +989,7 @@ class ObjSpace(object): compiler = self.createcompiler() expression = compiler.compile(expression, '?', 'eval', 0, hidden_applevel=hidden_applevel) - if isinstance(expression, types.CodeType): - # XXX only used by appsupport - expression = PyCode._from_code(self, expression) - if not isinstance(expression, PyCode): + else: raise TypeError, 'space.eval(): expected a string, code or PyCode object' return expression.exec_code(self, w_globals, w_locals) @@ -1007,9 +1004,6 @@ class ObjSpace(object): compiler = self.createcompiler() statement = compiler.compile(statement, filename, 'exec', 0, hidden_applevel=hidden_applevel) - if isinstance(statement, types.CodeType): - # XXX only used by appsupport - statement = PyCode._from_code(self, statement) if not isinstance(statement, PyCode): raise TypeError, 'space.exec_(): expected a string, code or PyCode object' w_key = self.wrap('__builtins__') diff --git a/pypy/interpreter/pycode.py b/pypy/interpreter/pycode.py index 4b33199f6e..09fda72997 100644 --- a/pypy/interpreter/pycode.py +++ b/pypy/interpreter/pycode.py @@ -256,7 +256,7 @@ class PyCode(eval.Code): tuple(self.co_freevars), tuple(self.co_cellvars) ) - def exec_host_bytecode(self, w_dict, w_globals, w_locals): + def exec_host_bytecode(self, w_globals, w_locals): from pypy.interpreter.pyframe import CPythonFrame frame = CPythonFrame(self.space, self, w_globals, None) frame.setdictscope(w_locals) diff --git a/pypy/module/pypyjit/test_pypy_c/model.py b/pypy/module/pypyjit/test_pypy_c/model.py index 73f5e67f02..2646abb746 100644 --- a/pypy/module/pypyjit/test_pypy_c/model.py +++ b/pypy/module/pypyjit/test_pypy_c/model.py @@ -2,6 +2,7 @@ import py import sys import re import os.path +from _pytest.assertion import newinterpret from pypy.tool.jitlogparser.parser import SimpleParser, Function, TraceForOpcode from pypy.tool.jitlogparser.storage import LoopStorage @@ -194,7 +195,7 @@ class InvalidMatch(Exception): # transform self._assert(x, 'foo') into assert x, 'foo' source = source.replace('self._assert(', 'assert ') source = source[:-1] # remove the trailing ')' - self.msg = py.code._reinterpret(source, f, should_fail=True) + self.msg = newinterpret.interpret(source, f, should_fail=True) else: self.msg = "<could not determine information>" diff --git a/pypy/pytest.ini b/pypy/pytest.ini new file mode 100644 index 0000000000..96f068621e --- /dev/null +++ b/pypy/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +addopts = --assertmode=old
\ No newline at end of file diff --git a/pypy/tool/pytest/appsupport.py b/pypy/tool/pytest/appsupport.py index 6513954c88..bb6413dd3b 100644 --- a/pypy/tool/pytest/appsupport.py +++ b/pypy/tool/pytest/appsupport.py @@ -1,8 +1,13 @@ import autopath import py -from pypy.interpreter import gateway +from pypy.interpreter import gateway, pycode from pypy.interpreter.error import OperationError +try: + from _pytest.assertion.newinterpret import interpret +except ImportError: + from _pytest.assertion.oldinterpret import interpret + # ____________________________________________________________ class AppCode(object): @@ -51,13 +56,11 @@ class AppFrame(py.code.Frame): space = self.space for key, w_value in vars.items(): space.setitem(self.w_locals, space.wrap(key), w_value) - return space.eval(code, self.w_globals, self.w_locals) - - def exec_(self, code, **vars): - space = self.space - for key, w_value in vars.items(): - space.setitem(self.w_locals, space.wrap(key), w_value) - space.exec_(code, self.w_globals, self.w_locals) + if isinstance(code, str): + return space.eval(code, self.w_globals, self.w_locals) + pyc = pycode.PyCode._from_code(space, code) + return pyc.exec_host_bytecode(self.w_globals, self.w_locals) + exec_ = eval def repr(self, w_value): return self.space.unwrap(self.space.repr(w_value)) @@ -163,8 +166,8 @@ def build_pytest_assertion(space): except py.error.ENOENT: source = None from pypy import conftest - if source and not py.test.config.option.nomagic: - msg = py.code._reinterpret_old(source, runner, should_fail=True) + if source and py.test.config._assertstate.mode != "off": + msg = interpret(source, runner, should_fail=True) space.setattr(w_self, space.wrap('args'), space.newtuple([space.wrap(msg)])) w_msg = space.wrap(msg) diff --git a/pypy/tool/pytest/test/test_pytestsupport.py b/pypy/tool/pytest/test/test_pytestsupport.py index a175bcd608..015ece30da 100644 --- a/pypy/tool/pytest/test/test_pytestsupport.py +++ b/pypy/tool/pytest/test/test_pytestsupport.py @@ -4,7 +4,7 @@ from pypy.interpreter.argument import Arguments from pypy.interpreter.pycode import PyCode from pypy.interpreter.pyframe import PyFrame from pypy.tool.pytest.appsupport import (AppFrame, build_pytest_assertion, - AppExceptionInfo) + AppExceptionInfo, interpret) import py from pypy.tool.udir import udir import os @@ -22,8 +22,8 @@ def test_AppFrame(space): co = PyCode._from_code(space, somefunc.func_code) pyframe = PyFrame(space, co, space.newdict(), None) runner = AppFrame(space, pyframe) - py.code._reinterpret_old("f = lambda x: x+1", runner, should_fail=False) - msg = py.code._reinterpret_old("assert isinstance(f(2), float)", runner) + interpret("f = lambda x: x+1", runner, should_fail=False) + msg = interpret("assert isinstance(f(2), float)", runner) assert msg.startswith("assert isinstance(3, float)\n" " + where 3 = ") diff --git a/pypy/translator/c/node.py b/pypy/translator/c/node.py index b18528c697..0684ca7426 100644 --- a/pypy/translator/c/node.py +++ b/pypy/translator/c/node.py @@ -1031,7 +1031,7 @@ class PyObjectNode(ContainerNode): if (issubclass(value, BaseException) and value.__module__ == 'exceptions'): return 'PyExc_' + value.__name__ - if value is py.code._AssertionError: + if issubclass(value, AssertionError): return 'PyExc_AssertionError' if value is _StackOverflow: return 'PyExc_RuntimeError' @@ -1,7 +1,5 @@ """ unit and functional testing with Python. -(pypy version of startup script) -see http://pytest.org for details. """ __all__ = ['main'] @@ -9,23 +7,6 @@ from _pytest.core import main, UsageError, _preloadplugins from _pytest import core as cmdline from _pytest import __version__ -# This pytest.py script is located in the pypy source tree -# which has a copy of pytest and py within its source tree. -# If the environment also has an installed version of pytest/py -# we are bound to get warnings so we disable them. -# XXX eventually pytest and py should not be inlined shipped -# with the pypy source code but become a requirement for installation. - -import warnings -warnings.filterwarnings("ignore", - "Module py was already imported", category=UserWarning) -warnings.filterwarnings("ignore", - "Module _pytest was already imported", - category=UserWarning) -warnings.filterwarnings("ignore", - "Module pytest was already imported", - category=UserWarning) - if __name__ == '__main__': # if run as a script or by 'python -m pytest' raise SystemExit(main()) else: |