Ast Call signature changed on 3.5

fix issue 744 on bitbucket

port of merge request 296

https://bitbucket.org/pytest-dev/pytest/pull-request/296/astcall-signature-changed-on-35
https://bitbucket.org/pytest-dev/pytest/issue/744/
This commit is contained in:
Matthias Bussonnier 2015-06-23 18:52:05 +02:00 committed by Thomas Kluyver
parent 8f4f2c665d
commit 26e64fc45c
3 changed files with 62 additions and 5 deletions

View File

@ -9,6 +9,7 @@ env:
- TESTENV=py26 - TESTENV=py26
- TESTENV=py27 - TESTENV=py27
- TESTENV=py34 - TESTENV=py34
- TESTENV=py35
- TESTENV=pypy - TESTENV=pypy
- TESTENV=py27-pexpect - TESTENV=py27-pexpect
- TESTENV=py33-pexpect - TESTENV=py33-pexpect

View File

@ -1,6 +1,10 @@
2.8.0.dev (compared to 2.7.X) 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 - 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. fixtures. Thanks Jason R. Coombs for reporting and Bruno Oliveira for the PR.

View File

@ -35,6 +35,12 @@ PYC_TAIL = "." + PYTEST_TAG + PYC_EXT
REWRITE_NEWLINES = sys.version_info[:2] != (2, 7) and sys.version_info < (3, 2) REWRITE_NEWLINES = sys.version_info[:2] != (2, 7) and sys.version_info < (3, 2)
ASCII_IS_DEFAULT_ENCODING = sys.version_info[0] < 3 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): class AssertionRewritingHook(object):
"""PEP302 Import hook which rewrites asserts.""" """PEP302 Import hook which rewrites asserts."""
@ -593,7 +599,7 @@ class AssertionRewriter(ast.NodeVisitor):
"""Call a helper in this module.""" """Call a helper in this module."""
py_name = ast.Name("@pytest_ar", ast.Load()) py_name = ast.Name("@pytest_ar", ast.Load())
attr = ast.Attribute(py_name, "_" + name, 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): def builtin(self, name):
"""Return the builtin called *name*.""" """Return the builtin called *name*."""
@ -683,7 +689,7 @@ class AssertionRewriter(ast.NodeVisitor):
msg = self.pop_format_context(template) msg = self.pop_format_context(template)
fmt = self.helper("format_explanation", msg) fmt = self.helper("format_explanation", msg)
err_name = ast.Name("AssertionError", ast.Load()) 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: if sys.version_info[0] >= 3:
raise_ = ast.Raise(exc, None) raise_ = ast.Raise(exc, None)
else: else:
@ -703,7 +709,7 @@ class AssertionRewriter(ast.NodeVisitor):
def visit_Name(self, name): def visit_Name(self, name):
# Display the repr of the name if it's a local variable or # Display the repr of the name if it's a local variable or
# _should_repr_global_name() thinks it's acceptable. # _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]) inlocs = ast.Compare(ast.Str(name.id), [ast.In()], [locs])
dorepr = self.helper("should_repr_global_name", name) dorepr = self.helper("should_repr_global_name", name)
test = ast.BoolOp(ast.Or(), [inlocs, dorepr]) test = ast.BoolOp(ast.Or(), [inlocs, dorepr])
@ -730,7 +736,7 @@ class AssertionRewriter(ast.NodeVisitor):
res, expl = self.visit(v) res, expl = self.visit(v)
body.append(ast.Assign([ast.Name(res_var, ast.Store())], res)) body.append(ast.Assign([ast.Name(res_var, ast.Store())], res))
expl_format = self.pop_format_context(ast.Str(expl)) 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)) self.on_failure.append(ast.Expr(call))
if i < levels: if i < levels:
cond = res cond = res
@ -759,7 +765,44 @@ class AssertionRewriter(ast.NodeVisitor):
res = self.assign(ast.BinOp(left_expr, binop.op, right_expr)) res = self.assign(ast.BinOp(left_expr, binop.op, right_expr))
return res, explanation 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) new_func, func_expl = self.visit(call.func)
arg_expls = [] arg_expls = []
new_args = [] new_args = []
@ -787,6 +830,15 @@ class AssertionRewriter(ast.NodeVisitor):
outer_expl = "%s\n{%s = %s\n}" % (res_expl, res_expl, expl) outer_expl = "%s\n{%s = %s\n}" % (res_expl, res_expl, expl)
return res, outer_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): def visit_Attribute(self, attr):
if not isinstance(attr.ctx, ast.Load): if not isinstance(attr.ctx, ast.Load):
return self.generic_visit(attr) return self.generic_visit(attr)