diff --git a/.travis.yml b/.travis.yml index 06e554b05..f2a572de2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,7 @@ env: - TESTENV=py26 - TESTENV=py27 - TESTENV=py34 + - TESTENV=py35 - TESTENV=pypy - TESTENV=py27-pexpect - TESTENV=py33-pexpect diff --git a/CHANGELOG b/CHANGELOG index e5221df04..8714d7ad1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,10 @@ 2.8.0.dev (compared to 2.7.X) ----------------------------- +- fix issue744: fix for ast.Call changes in Python 3.5+. Thanks + Guido van Rossum, Matthias Bussonnier, Stefan Zimmermann and + Thomas Kluyver. + - fix issue768: docstrings found in python modules were not setting up session fixtures. Thanks Jason R. Coombs for reporting and Bruno Oliveira for the PR. diff --git a/_pytest/assertion/rewrite.py b/_pytest/assertion/rewrite.py index eeaee6d44..cc881fb1a 100644 --- a/_pytest/assertion/rewrite.py +++ b/_pytest/assertion/rewrite.py @@ -35,6 +35,12 @@ PYC_TAIL = "." + PYTEST_TAG + PYC_EXT REWRITE_NEWLINES = sys.version_info[:2] != (2, 7) and sys.version_info < (3, 2) ASCII_IS_DEFAULT_ENCODING = sys.version_info[0] < 3 +if sys.version_info >= (3,5): + ast_Call = ast.Call +else : + ast_Call = lambda a,b,c : ast.Call(a, b, c, None, None) + + class AssertionRewritingHook(object): """PEP302 Import hook which rewrites asserts.""" @@ -593,7 +599,7 @@ class AssertionRewriter(ast.NodeVisitor): """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) + return ast_Call(attr, list(args), []) def builtin(self, name): """Return the builtin called *name*.""" @@ -683,7 +689,7 @@ class AssertionRewriter(ast.NodeVisitor): 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) + exc = ast_Call(err_name, [fmt], []) if sys.version_info[0] >= 3: raise_ = ast.Raise(exc, None) else: @@ -703,7 +709,7 @@ class AssertionRewriter(ast.NodeVisitor): def visit_Name(self, name): # Display the repr of the name if it's a local variable or # _should_repr_global_name() thinks it's acceptable. - locs = ast.Call(self.builtin("locals"), [], [], None, None) + locs = ast_Call(self.builtin("locals"), [], []) inlocs = ast.Compare(ast.Str(name.id), [ast.In()], [locs]) dorepr = self.helper("should_repr_global_name", name) test = ast.BoolOp(ast.Or(), [inlocs, dorepr]) @@ -730,7 +736,7 @@ class AssertionRewriter(ast.NodeVisitor): res, expl = self.visit(v) body.append(ast.Assign([ast.Name(res_var, ast.Store())], res)) expl_format = self.pop_format_context(ast.Str(expl)) - call = ast.Call(app, [expl_format], [], None, None) + call = ast_Call(app, [expl_format], []) self.on_failure.append(ast.Expr(call)) if i < levels: cond = res @@ -759,7 +765,44 @@ class AssertionRewriter(ast.NodeVisitor): res = self.assign(ast.BinOp(left_expr, binop.op, right_expr)) return res, explanation - def visit_Call(self, call): + def visit_Call_35(self, call): + """ + visit `ast.Call` nodes on Python3.5 and after + """ + new_func, func_expl = self.visit(call.func) + arg_expls = [] + new_args = [] + new_kwargs = [] + for arg in call.args: + if type(arg) is ast.Starred: + new_star, expl = self.visit(arg) + arg_expls.append("*" + expl) + new_args.append(new_star) + else: + res, expl = self.visit(arg) + new_args.append(res) + arg_expls.append(expl) + for keyword in call.keywords: + if keyword.arg: + res, expl = self.visit(keyword.value) + new_kwargs.append(ast.keyword(keyword.arg, res)) + arg_expls.append(keyword.arg + "=" + expl) + else: ## **args have `arg` keywords with an .arg of None + res, expl = self.visit(keyword.value) + new_kwargs.append(ast.keyword(keyword.arg, res)) + arg_expls.append("**" + expl) + + expl = "%s(%s)" % (func_expl, ', '.join(arg_expls)) + new_call = ast.Call(new_func, new_args, new_kwargs) + 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_Call_legacy(self, call): + """ + visit `ast.Call nodes on 3.4 and below` + """ new_func, func_expl = self.visit(call.func) arg_expls = [] new_args = [] @@ -787,6 +830,15 @@ class AssertionRewriter(ast.NodeVisitor): outer_expl = "%s\n{%s = %s\n}" % (res_expl, res_expl, expl) return res, outer_expl + # ast.Call signature changed on 3.5, + # conditionally change which methods is named + # visit_Call depending on Python version + if sys.version_info >= (3, 5): + visit_Call = visit_Call_35 + else: + visit_Call = visit_Call_legacy + + def visit_Attribute(self, attr): if not isinstance(attr.ctx, ast.Load): return self.generic_visit(attr)