From b18e6439bdf60ed0b9d41dbb65c5ae5da2efd01e 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/ Conflicts: CHANGELOG --- .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 a6fc5757d..4ad059331 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 8b6feb3a2..03c804514 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,10 @@ 2.7.3 (compared to 2.7.2) ----------------------------- +- fix issue744: fix for ast.Call changes in Python 3.5+. Thanks + Guido van Rossum, Matthias Bussonnier, Stefan Zimmermann and + Thomas Kluyver. + - fix issue842: applying markers in classes no longer propagate this markers to superclasses which also have markers. Thanks xmo-odoo for the report and Bruno Oliveira for the PR. diff --git a/_pytest/assertion/rewrite.py b/_pytest/assertion/rewrite.py index 62046d146..29d18d813 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.""" @@ -587,7 +593,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*.""" @@ -677,7 +683,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: @@ -697,7 +703,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]) @@ -724,7 +730,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 @@ -753,7 +759,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 = [] @@ -781,6 +824,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 8bde0c5957c3c132a90d5908a024ddbd8b503081 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 4ad059331..15393b706 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,6 +27,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 195afa0733cdd3a7b8af9a499c7b8e362321b7a9 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 2e1a15b8b..cf8d577ed 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 ec5286ea8c2bb7b5c63a567a759fd32f3bc18b45 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 15393b706..5fedc3cd0 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 a4dbb27faba91fe128bb720dad547a06c62fc164 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 fad569ae1b469d76022ae627d458f8ec6a18e754 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 29d18d813..5e76acd1e 100644 --- a/_pytest/assertion/rewrite.py +++ b/_pytest/assertion/rewrite.py @@ -768,22 +768,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 728d8fbdc5e9b2a12ea3678792f3707de4ff6357 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 570c4cc55a9733024bdd19284cc61c2a897b7712 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 26e7532756f5e6ad1c0c7dc42bb6801467ce8215 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 f70e06d56..6af4dc1ca 100644 --- a/_pytest/main.py +++ b/_pytest/main.py @@ -502,10 +502,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 d4789f1ac4d92f577c657f3b3ba13865d37c5007 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 5e76acd1e..ff41e1129 100644 --- a/_pytest/assertion/rewrite.py +++ b/_pytest/assertion/rewrite.py @@ -769,10 +769,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) @@ -789,6 +786,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 8a0867c580f2a51794b30ae22d792c9ec6628067 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 cf8d577ed..5988a12f7 100644 --- a/tox.ini +++ b/tox.ini @@ -23,6 +23,7 @@ deps= commands= py.test --genscript=pytest1 [testenv:flakes] +basepython = python3.4 deps = pytest-flakes>=0.2 commands = py.test --flakes -m flakes _pytest testing From 15497dcd77de9eefb63f71680d92d95f2b051e29 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 5988a12f7..0b262e562 100644 --- a/tox.ini +++ b/tox.ini @@ -23,7 +23,7 @@ deps= 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 08432c3e97b8929b99ec292a3ac8cf2fdc2bd626 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 5fedc3cd0..79be84547 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,8 +29,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 6719a818e74d095f8b42d052d4ec569aeb7a0197 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 Conflicts: .travis.yml --- .travis.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 79be84547..d2e8e99e9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,19 +10,18 @@ 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 # inprocess tests by default were introduced in 2.8 only; # this TESTENV should be enabled when merged back to master #- TESTENV=py27-subprocess From e227950b0679d9d45aa6076e886130142eaf5150 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 ff41e1129..6dbbd4f49 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):