Merge pull request #801 from Carreau/test-35

Support Python 3.5
This commit is contained in:
Floris Bruynooghe 2015-07-18 11:30:36 +01:00
commit 5e0235946b
6 changed files with 103 additions and 25 deletions

View File

@ -1,5 +1,7 @@
sudo: false sudo: false
language: python language: python
python:
- 'nightly'
# command to install dependencies # command to install dependencies
install: "pip install -U tox" install: "pip install -U tox"
# # command to run tests # # command to run tests
@ -8,18 +10,17 @@ env:
- TESTENV=flakes - TESTENV=flakes
- TESTENV=py26 - TESTENV=py26
- TESTENV=py27 - TESTENV=py27
- TESTENV=py33
- TESTENV=py34 - TESTENV=py34
- TESTENV=py35
- TESTENV=pypy - TESTENV=pypy
- TESTENV=py27-pexpect - TESTENV=py27-pexpect
- TESTENV=py33-pexpect - TESTENV=py34-pexpect
- TESTENV=py27-nobyte - TESTENV=py27-nobyte
- TESTENV=py33
- TESTENV=py27-xdist - TESTENV=py27-xdist
- TESTENV=py33-xdist - TESTENV=py34-xdist
- TESTENV=py27
- TESTENV=py27-trial - TESTENV=py27-trial
- TESTENV=py33 - TESTENV=py34-trial
- TESTENV=py33-trial
- TESTENV=py27-subprocess - TESTENV=py27-subprocess
- TESTENV=doctesting - TESTENV=doctesting
- TESTENV=py27-cxfreeze - TESTENV=py27-cxfreeze

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

@ -33,6 +33,12 @@ else:
def _is_ast_stmt(node): def _is_ast_stmt(node):
return isinstance(node, ast.stmt) 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): class Failure(Exception):
"""Error found while interpreting AST.""" """Error found while interpreting AST."""
@ -232,24 +238,38 @@ class DebugInterpreter(ast.NodeVisitor):
arguments = [] arguments = []
for arg in call.args: for arg in call.args:
arg_explanation, arg_result = self.visit(arg) arg_explanation, arg_result = self.visit(arg)
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),) arg_name = "__exprinfo_%s" % (len(ns),)
ns[arg_name] = arg_result ns[arg_name] = arg_result
arguments.append(arg_name) arguments.append(arg_name)
arg_explanations.append(arg_explanation) arg_explanations.append(arg_explanation)
for keyword in call.keywords: for keyword in call.keywords:
arg_explanation, arg_result = self.visit(keyword.value) arg_explanation, arg_result = self.visit(keyword.value)
if keyword.arg:
arg_name = "__exprinfo_%s" % (len(ns),) arg_name = "__exprinfo_%s" % (len(ns),)
ns[arg_name] = arg_result
keyword_source = "%s=%%s" % (keyword.arg) keyword_source = "%s=%%s" % (keyword.arg)
arguments.append(keyword_source % (arg_name,)) arguments.append(keyword_source % (arg_name,))
arg_explanations.append(keyword_source % (arg_explanation,)) arg_explanations.append(keyword_source % (arg_explanation,))
if call.starargs: 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_explanation, arg_result = self.visit(call.starargs)
arg_name = "__exprinfo_star" arg_name = "__exprinfo_star"
ns[arg_name] = arg_result ns[arg_name] = arg_result
arguments.append("*%s" % (arg_name,)) arguments.append("*%s" % (arg_name,))
arg_explanations.append("*%s" % (arg_explanation,)) arg_explanations.append("*%s" % (arg_explanation,))
if call.kwargs:
if getattr(call, 'kwargs', None):
arg_explanation, arg_result = self.visit(call.kwargs) arg_explanation, arg_result = self.visit(call.kwargs)
arg_name = "__exprinfo_kwds" arg_name = "__exprinfo_kwds"
ns[arg_name] = arg_result ns[arg_name] = arg_result

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,42 @@ 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:
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) new_func, func_expl = self.visit(call.func)
arg_expls = [] arg_expls = []
new_args = [] new_args = []
@ -787,6 +828,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)

View File

@ -498,11 +498,13 @@ class Item(Node):
class NoMatch(Exception): class NoMatch(Exception):
""" raised if matching cannot locate a matching names. """ """ raised if matching cannot locate a matching names. """
class Session(FSCollector):
class Interrupted(KeyboardInterrupt): class Interrupted(KeyboardInterrupt):
""" signals an interrupted test run. """ """ signals an interrupted test run. """
__module__ = 'builtins' # for py3 __module__ = 'builtins' # for py3
class Session(FSCollector):
Interrupted = Interrupted
def __init__(self, config): def __init__(self, config):
FSCollector.__init__(self, config.rootdir, parent=None, FSCollector.__init__(self, config.rootdir, parent=None,
config=config, session=self) config=config, session=self)

View File

@ -2,7 +2,7 @@
minversion=2.0 minversion=2.0
distshare={homedir}/.tox/distshare distshare={homedir}/.tox/distshare
envlist= envlist=
flakes,py26,py27,py33,py34,pypy, flakes,py26,py27,py33,py34,py35,pypy,
{py27,py34}-{pexpect,xdist,trial}, {py27,py34}-{pexpect,xdist,trial},
py27-nobyte,doctesting,py27-cxfreeze py27-nobyte,doctesting,py27-cxfreeze
@ -31,6 +31,7 @@ commands=
commands= py.test --genscript=pytest1 commands= py.test --genscript=pytest1
[testenv:flakes] [testenv:flakes]
basepython = python2.7
deps = pytest-flakes>=0.2 deps = pytest-flakes>=0.2
commands = py.test --flakes -m flakes _pytest testing commands = py.test --flakes -m flakes _pytest testing