From 26e64fc45c5825f0d36fe2e04d045be402642e08 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Tue, 23 Jun 2015 18:52:05 +0200 Subject: [PATCH 01/15] 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/ --- .travis.yml | 1 + CHANGELOG | 4 +++ _pytest/assertion/rewrite.py | 62 +++++++++++++++++++++++++++++++++--- 3 files changed, 62 insertions(+), 5 deletions(-) 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) From e2e29284f0dd66d823acf14c17e65a34ceba9f95 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 24 Jun 2015 13:09:41 +0200 Subject: [PATCH 02/15] allow faillure on 35 --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index f2a572de2..9bfdddb70 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,6 +25,8 @@ env: - TESTENV=doctesting - TESTENV=py27-cxfreeze - TESTENV=coveralls + allow_failures: + - TESTENV=py35 script: tox --recreate -i ALL=https://devpi.net/hpk/dev/ -e $TESTENV notifications: From 71a00c32237af5d0b8e4b1f4ec3abab979976069 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 24 Jun 2015 13:16:54 +0200 Subject: [PATCH 03/15] try isntall 35 on tox --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index d9be7f410..d53d79d5b 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 From 3cf82c6594e5d714a127685f50cbb46ff2305671 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 24 Jun 2015 17:34:09 +0200 Subject: [PATCH 04/15] nigh --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 9bfdddb70..7eaf87850 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 From 3bc6c0f936726dad72abe0fb8a517ddb2b7e8223 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 24 Jun 2015 17:51:59 +0200 Subject: [PATCH 05/15] a test --- _pytest/assertion/newinterpret.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/_pytest/assertion/newinterpret.py b/_pytest/assertion/newinterpret.py index d8b27784d..c8d999d48 100644 --- a/_pytest/assertion/newinterpret.py +++ b/_pytest/assertion/newinterpret.py @@ -243,13 +243,13 @@ class DebugInterpreter(ast.NodeVisitor): keyword_source = "%s=%%s" % (keyword.arg) arguments.append(keyword_source % (arg_name,)) arg_explanations.append(keyword_source % (arg_explanation,)) - if call.starargs: + if sys.version_info <= (3,4) and call.starargs: 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 sys.version_info <= (3,4) and call.kwargs: arg_explanation, arg_result = self.visit(call.kwargs) arg_name = "__exprinfo_kwds" ns[arg_name] = arg_result From 167625d24dbdbbd25600a385e586d7c3297016bc Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 24 Jun 2015 21:53:52 +0200 Subject: [PATCH 06/15] simplify + fix --- _pytest/assertion/newinterpret.py | 20 +++++++++++++------- _pytest/assertion/rewrite.py | 12 ++++-------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/_pytest/assertion/newinterpret.py b/_pytest/assertion/newinterpret.py index c8d999d48..20dd8ed5e 100644 --- a/_pytest/assertion/newinterpret.py +++ b/_pytest/assertion/newinterpret.py @@ -238,18 +238,24 @@ class DebugInterpreter(ast.NodeVisitor): arg_explanations.append(arg_explanation) for keyword in call.keywords: arg_explanation, arg_result = self.visit(keyword.value) - arg_name = "__exprinfo_%s" % (len(ns),) - 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 sys.version_info <= (3,4) and call.starargs: + if keyword.arg: + arg_name = "__exprinfo_%s" % (len(ns),) + ns[arg_name] = arg_result + keyword_source = "%s=%%s" % (keyword.arg) + arguments.append(keyword_source % (arg_name,)) + arg_explanations.append(keyword_source % (arg_explanation,)) + else: # starargs in 3.5+ + arg_name = "__exprinfo_star" + ns[arg_name] = arg_result + arguments.append("*%s" % (arg_name,)) + arg_explanations.append("*%s" % (arg_explanation,)) + if getattr(call, 'starargs', None): # no starargs in 3.5 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 sys.version_info <= (3,4) and call.kwargs: + if call.kwargs: 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 cc881fb1a..fae313e30 100644 --- a/_pytest/assertion/rewrite.py +++ b/_pytest/assertion/rewrite.py @@ -774,22 +774,18 @@ class AssertionRewriter(ast.NodeVisitor): new_args = [] new_kwargs = [] for arg in call.args: + res, expl = self.visit(arg) 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) + 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: - 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)) From d774f3ca8648f7f024e0c6fe0e76410b155c89f5 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 24 Jun 2015 22:06:34 +0200 Subject: [PATCH 07/15] generify --- _pytest/assertion/newinterpret.py | 38 +++++++++++++++++++------------ 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/_pytest/assertion/newinterpret.py b/_pytest/assertion/newinterpret.py index 20dd8ed5e..ff20082dc 100644 --- a/_pytest/assertion/newinterpret.py +++ b/_pytest/assertion/newinterpret.py @@ -232,30 +232,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) - for keyword in call.keywords: - arg_explanation, arg_result = self.visit(keyword.value) - if keyword.arg: - arg_name = "__exprinfo_%s" % (len(ns),) - ns[arg_name] = arg_result - keyword_source = "%s=%%s" % (keyword.arg) - arguments.append(keyword_source % (arg_name,)) - arg_explanations.append(keyword_source % (arg_explanation,)) - else: # starargs in 3.5+ + if type(arg) is ast.Starred: arg_name = "__exprinfo_star" ns[arg_name] = arg_result arguments.append("*%s" % (arg_name,)) arg_explanations.append("*%s" % (arg_explanation,)) - if getattr(call, 'starargs', None): # no starargs in 3.5 + 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) + 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 + + 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 From 35bea86c9fcf897428b4d0b3c1e935b7e1312075 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Wed, 15 Jul 2015 14:07:43 -0700 Subject: [PATCH 08/15] No Starred node type on Python 2 --- _pytest/assertion/newinterpret.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/_pytest/assertion/newinterpret.py b/_pytest/assertion/newinterpret.py index ff20082dc..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,7 +238,7 @@ class DebugInterpreter(ast.NodeVisitor): arguments = [] for arg in call.args: arg_explanation, arg_result = self.visit(arg) - if type(arg) is ast.Starred: + if isinstance(arg, _Starred): arg_name = "__exprinfo_star" ns[arg_name] = arg_result arguments.append("*%s" % (arg_name,)) From 62ca4ae963fa99c20a97c9434d77819ea1c708bc Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Wed, 15 Jul 2015 14:28:43 -0700 Subject: [PATCH 09/15] Move Interrupted exception class out of Session --- _pytest/main.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) 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, From 195422f9c08582c4e9c5cd85ead1e0cec1879c34 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Wed, 15 Jul 2015 15:31:35 -0700 Subject: [PATCH 10/15] Fix AST rewriting with starred expressions in function calls --- _pytest/assertion/rewrite.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/_pytest/assertion/rewrite.py b/_pytest/assertion/rewrite.py index fae313e30..97263cd69 100644 --- a/_pytest/assertion/rewrite.py +++ b/_pytest/assertion/rewrite.py @@ -775,10 +775,7 @@ class AssertionRewriter(ast.NodeVisitor): new_kwargs = [] for arg in call.args: res, expl = self.visit(arg) - if type(arg) is ast.Starred: - arg_expls.append("*" + expl) - else: - arg_expls.append(expl) + arg_expls.append(expl) new_args.append(res) for keyword in call.keywords: res, expl = self.visit(keyword.value) @@ -795,6 +792,11 @@ class AssertionRewriter(ast.NodeVisitor): 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` From d73c8e6a5e7ea3b8a570f2c6ea3e6ad5eb4ca2de Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Wed, 15 Jul 2015 15:49:15 -0700 Subject: [PATCH 11/15] Try running flakes tests with Python 3.4 --- tox.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/tox.ini b/tox.ini index d53d79d5b..30ea86d2d 100644 --- a/tox.ini +++ b/tox.ini @@ -31,6 +31,7 @@ commands= commands= py.test --genscript=pytest1 [testenv:flakes] +basepython = python3.4 deps = pytest-flakes>=0.2 commands = py.test --flakes -m flakes _pytest testing From 4e98d2b7f1f54154b0fe95d6997c27c947aeccd8 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Wed, 15 Jul 2015 15:52:28 -0700 Subject: [PATCH 12/15] OK, try running flakes with 2.7 --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 30ea86d2d..730f4ccd4 100644 --- a/tox.ini +++ b/tox.ini @@ -31,7 +31,7 @@ commands= commands= py.test --genscript=pytest1 [testenv:flakes] -basepython = python3.4 +basepython = python2.7 deps = pytest-flakes>=0.2 commands = py.test --flakes -m flakes _pytest testing From 37a09a6c30035068f5c7c95dbc8ac2c2764f3692 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Wed, 15 Jul 2015 15:58:42 -0700 Subject: [PATCH 13/15] No more failures --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7eaf87850..053af4084 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,8 +27,6 @@ env: - TESTENV=doctesting - TESTENV=py27-cxfreeze - TESTENV=coveralls - allow_failures: - - TESTENV=py35 script: tox --recreate -i ALL=https://devpi.net/hpk/dev/ -e $TESTENV notifications: From 077f0d3d660f4149f334616eda7b3deb5e2aadd0 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Wed, 15 Jul 2015 16:03:14 -0700 Subject: [PATCH 14/15] Match .travis.yml env list to tox envs --- .travis.yml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 053af4084..a20a7a017 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,19 +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 From 4462b8325888750864f44d6c38661be5ca720aee Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Thu, 16 Jul 2015 11:04:36 -0700 Subject: [PATCH 15/15] Style fix --- _pytest/assertion/rewrite.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/_pytest/assertion/rewrite.py b/_pytest/assertion/rewrite.py index 97263cd69..b7a6c1c5e 100644 --- a/_pytest/assertion/rewrite.py +++ b/_pytest/assertion/rewrite.py @@ -37,8 +37,8 @@ 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) +else: + ast_Call = lambda a,b,c: ast.Call(a, b, c, None, None) class AssertionRewritingHook(object):