diff --git a/.travis.yml b/.travis.yml index 06e554b05..a20a7a017 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,7 @@ sudo: false language: python +python: + - 'nightly' # command to install dependencies install: "pip install -U tox" # # command to run tests @@ -8,18 +10,17 @@ env: - TESTENV=flakes - TESTENV=py26 - TESTENV=py27 + - TESTENV=py33 - TESTENV=py34 + - TESTENV=py35 - TESTENV=pypy - TESTENV=py27-pexpect - - TESTENV=py33-pexpect + - TESTENV=py34-pexpect - TESTENV=py27-nobyte - - TESTENV=py33 - TESTENV=py27-xdist - - TESTENV=py33-xdist - - TESTENV=py27 + - TESTENV=py34-xdist - TESTENV=py27-trial - - TESTENV=py33 - - TESTENV=py33-trial + - TESTENV=py34-trial - TESTENV=py27-subprocess - TESTENV=doctesting - TESTENV=py27-cxfreeze diff --git a/CHANGELOG b/CHANGELOG index b0707a5b0..46571fdfc 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/newinterpret.py b/_pytest/assertion/newinterpret.py index d8b27784d..d8e741162 100644 --- a/_pytest/assertion/newinterpret.py +++ b/_pytest/assertion/newinterpret.py @@ -33,6 +33,12 @@ else: def _is_ast_stmt(node): return isinstance(node, ast.stmt) +try: + _Starred = ast.Starred +except AttributeError: + # Python 2. Define a dummy class so isinstance() will always be False. + class _Starred(object): pass + class Failure(Exception): """Error found while interpreting AST.""" @@ -232,24 +238,38 @@ class DebugInterpreter(ast.NodeVisitor): arguments = [] for arg in call.args: arg_explanation, arg_result = self.visit(arg) - arg_name = "__exprinfo_%s" % (len(ns),) - ns[arg_name] = arg_result - arguments.append(arg_name) - arg_explanations.append(arg_explanation) + if isinstance(arg, _Starred): + arg_name = "__exprinfo_star" + ns[arg_name] = arg_result + arguments.append("*%s" % (arg_name,)) + arg_explanations.append("*%s" % (arg_explanation,)) + else: + arg_name = "__exprinfo_%s" % (len(ns),) + ns[arg_name] = arg_result + arguments.append(arg_name) + arg_explanations.append(arg_explanation) for keyword in call.keywords: arg_explanation, arg_result = self.visit(keyword.value) - arg_name = "__exprinfo_%s" % (len(ns),) + if keyword.arg: + arg_name = "__exprinfo_%s" % (len(ns),) + keyword_source = "%s=%%s" % (keyword.arg) + arguments.append(keyword_source % (arg_name,)) + arg_explanations.append(keyword_source % (arg_explanation,)) + else: + arg_name = "__exprinfo_kwds" + arguments.append("**%s" % (arg_name,)) + arg_explanations.append("**%s" % (arg_explanation,)) + ns[arg_name] = arg_result - keyword_source = "%s=%%s" % (keyword.arg) - arguments.append(keyword_source % (arg_name,)) - arg_explanations.append(keyword_source % (arg_explanation,)) - if call.starargs: + + if getattr(call, 'starargs', None): arg_explanation, arg_result = self.visit(call.starargs) arg_name = "__exprinfo_star" ns[arg_name] = arg_result arguments.append("*%s" % (arg_name,)) arg_explanations.append("*%s" % (arg_explanation,)) - if call.kwargs: + + if getattr(call, 'kwargs', None): arg_explanation, arg_result = self.visit(call.kwargs) arg_name = "__exprinfo_kwds" ns[arg_name] = arg_result diff --git a/_pytest/assertion/rewrite.py b/_pytest/assertion/rewrite.py index eeaee6d44..b7a6c1c5e 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,42 @@ 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: + res, expl = self.visit(arg) + arg_expls.append(expl) + new_args.append(res) + for keyword in call.keywords: + res, expl = self.visit(keyword.value) + new_kwargs.append(ast.keyword(keyword.arg, res)) + if keyword.arg: + arg_expls.append(keyword.arg + "=" + expl) + else: ## **args have `arg` keywords with an .arg of None + 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_Starred(self, starred): + # From Python 3.5, a Starred node can appear in a function call + res, expl = self.visit(starred.value) + return starred, '*' + 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 +828,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) diff --git a/_pytest/main.py b/_pytest/main.py index ead63535a..fc9d64cf6 100644 --- a/_pytest/main.py +++ b/_pytest/main.py @@ -498,10 +498,12 @@ class Item(Node): class NoMatch(Exception): """ raised if matching cannot locate a matching names. """ +class Interrupted(KeyboardInterrupt): + """ signals an interrupted test run. """ + __module__ = 'builtins' # for py3 + class Session(FSCollector): - class Interrupted(KeyboardInterrupt): - """ signals an interrupted test run. """ - __module__ = 'builtins' # for py3 + Interrupted = Interrupted def __init__(self, config): FSCollector.__init__(self, config.rootdir, parent=None, diff --git a/tox.ini b/tox.ini index d9be7f410..730f4ccd4 100644 --- a/tox.ini +++ b/tox.ini @@ -2,7 +2,7 @@ minversion=2.0 distshare={homedir}/.tox/distshare envlist= - flakes,py26,py27,py33,py34,pypy, + flakes,py26,py27,py33,py34,py35,pypy, {py27,py34}-{pexpect,xdist,trial}, py27-nobyte,doctesting,py27-cxfreeze @@ -31,6 +31,7 @@ commands= commands= py.test --genscript=pytest1 [testenv:flakes] +basepython = python2.7 deps = pytest-flakes>=0.2 commands = py.test --flakes -m flakes _pytest testing