PageRenderTime 45ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/_pytest/assertion/newinterpret.py

https://bitbucket.org/yrttyr/pypy
Python | 333 lines | 309 code | 17 blank | 7 comment | 45 complexity | a810fc9d35a0253995c2405a7ab99b06 MD5 | raw file
  1. """
  2. Find intermediate evalutation results in assert statements through builtin AST.
  3. This should replace oldinterpret.py eventually.
  4. """
  5. import sys
  6. import ast
  7. import py
  8. from _pytest.assertion import util
  9. from _pytest.assertion.reinterpret import BuiltinAssertionError
  10. if sys.platform.startswith("java") and sys.version_info < (2, 5, 2):
  11. # See http://bugs.jython.org/issue1497
  12. _exprs = ("BoolOp", "BinOp", "UnaryOp", "Lambda", "IfExp", "Dict",
  13. "ListComp", "GeneratorExp", "Yield", "Compare", "Call",
  14. "Repr", "Num", "Str", "Attribute", "Subscript", "Name",
  15. "List", "Tuple")
  16. _stmts = ("FunctionDef", "ClassDef", "Return", "Delete", "Assign",
  17. "AugAssign", "Print", "For", "While", "If", "With", "Raise",
  18. "TryExcept", "TryFinally", "Assert", "Import", "ImportFrom",
  19. "Exec", "Global", "Expr", "Pass", "Break", "Continue")
  20. _expr_nodes = set(getattr(ast, name) for name in _exprs)
  21. _stmt_nodes = set(getattr(ast, name) for name in _stmts)
  22. def _is_ast_expr(node):
  23. return node.__class__ in _expr_nodes
  24. def _is_ast_stmt(node):
  25. return node.__class__ in _stmt_nodes
  26. else:
  27. def _is_ast_expr(node):
  28. return isinstance(node, ast.expr)
  29. def _is_ast_stmt(node):
  30. return isinstance(node, ast.stmt)
  31. class Failure(Exception):
  32. """Error found while interpreting AST."""
  33. def __init__(self, explanation=""):
  34. self.cause = sys.exc_info()
  35. self.explanation = explanation
  36. def interpret(source, frame, should_fail=False):
  37. mod = ast.parse(source)
  38. visitor = DebugInterpreter(frame)
  39. try:
  40. visitor.visit(mod)
  41. except Failure:
  42. failure = sys.exc_info()[1]
  43. return getfailure(failure)
  44. if should_fail:
  45. return ("(assertion failed, but when it was re-run for "
  46. "printing intermediate values, it did not fail. Suggestions: "
  47. "compute assert expression before the assert or use --assert=plain)")
  48. def run(offending_line, frame=None):
  49. if frame is None:
  50. frame = py.code.Frame(sys._getframe(1))
  51. return interpret(offending_line, frame)
  52. def getfailure(e):
  53. explanation = util.format_explanation(e.explanation)
  54. value = e.cause[1]
  55. if str(value):
  56. lines = explanation.split('\n')
  57. lines[0] += " << %s" % (value,)
  58. explanation = '\n'.join(lines)
  59. text = "%s: %s" % (e.cause[0].__name__, explanation)
  60. if text.startswith('AssertionError: assert '):
  61. text = text[16:]
  62. return text
  63. operator_map = {
  64. ast.BitOr : "|",
  65. ast.BitXor : "^",
  66. ast.BitAnd : "&",
  67. ast.LShift : "<<",
  68. ast.RShift : ">>",
  69. ast.Add : "+",
  70. ast.Sub : "-",
  71. ast.Mult : "*",
  72. ast.Div : "/",
  73. ast.FloorDiv : "//",
  74. ast.Mod : "%",
  75. ast.Eq : "==",
  76. ast.NotEq : "!=",
  77. ast.Lt : "<",
  78. ast.LtE : "<=",
  79. ast.Gt : ">",
  80. ast.GtE : ">=",
  81. ast.Pow : "**",
  82. ast.Is : "is",
  83. ast.IsNot : "is not",
  84. ast.In : "in",
  85. ast.NotIn : "not in"
  86. }
  87. unary_map = {
  88. ast.Not : "not %s",
  89. ast.Invert : "~%s",
  90. ast.USub : "-%s",
  91. ast.UAdd : "+%s"
  92. }
  93. class DebugInterpreter(ast.NodeVisitor):
  94. """Interpret AST nodes to gleam useful debugging information. """
  95. def __init__(self, frame):
  96. self.frame = frame
  97. def generic_visit(self, node):
  98. # Fallback when we don't have a special implementation.
  99. if _is_ast_expr(node):
  100. mod = ast.Expression(node)
  101. co = self._compile(mod)
  102. try:
  103. result = self.frame.eval(co)
  104. except Exception:
  105. raise Failure()
  106. explanation = self.frame.repr(result)
  107. return explanation, result
  108. elif _is_ast_stmt(node):
  109. mod = ast.Module([node])
  110. co = self._compile(mod, "exec")
  111. try:
  112. self.frame.exec_(co)
  113. except Exception:
  114. raise Failure()
  115. return None, None
  116. else:
  117. raise AssertionError("can't handle %s" %(node,))
  118. def _compile(self, source, mode="eval"):
  119. return compile(source, "<assertion interpretation>", mode)
  120. def visit_Expr(self, expr):
  121. return self.visit(expr.value)
  122. def visit_Module(self, mod):
  123. for stmt in mod.body:
  124. self.visit(stmt)
  125. def visit_Name(self, name):
  126. explanation, result = self.generic_visit(name)
  127. # See if the name is local.
  128. source = "%r in locals() is not globals()" % (name.id,)
  129. co = self._compile(source)
  130. try:
  131. local = self.frame.eval(co)
  132. except Exception:
  133. # have to assume it isn't
  134. local = None
  135. if local is None or not self.frame.is_true(local):
  136. return name.id, result
  137. return explanation, result
  138. def visit_Compare(self, comp):
  139. left = comp.left
  140. left_explanation, left_result = self.visit(left)
  141. for op, next_op in zip(comp.ops, comp.comparators):
  142. next_explanation, next_result = self.visit(next_op)
  143. op_symbol = operator_map[op.__class__]
  144. explanation = "%s %s %s" % (left_explanation, op_symbol,
  145. next_explanation)
  146. source = "__exprinfo_left %s __exprinfo_right" % (op_symbol,)
  147. co = self._compile(source)
  148. try:
  149. result = self.frame.eval(co, __exprinfo_left=left_result,
  150. __exprinfo_right=next_result)
  151. except Exception:
  152. raise Failure(explanation)
  153. try:
  154. if not self.frame.is_true(result):
  155. break
  156. except KeyboardInterrupt:
  157. raise
  158. except:
  159. break
  160. left_explanation, left_result = next_explanation, next_result
  161. if util._reprcompare is not None:
  162. res = util._reprcompare(op_symbol, left_result, next_result)
  163. if res:
  164. explanation = res
  165. return explanation, result
  166. def visit_BoolOp(self, boolop):
  167. is_or = isinstance(boolop.op, ast.Or)
  168. explanations = []
  169. for operand in boolop.values:
  170. explanation, result = self.visit(operand)
  171. explanations.append(explanation)
  172. if result == is_or:
  173. break
  174. name = is_or and " or " or " and "
  175. explanation = "(" + name.join(explanations) + ")"
  176. return explanation, result
  177. def visit_UnaryOp(self, unary):
  178. pattern = unary_map[unary.op.__class__]
  179. operand_explanation, operand_result = self.visit(unary.operand)
  180. explanation = pattern % (operand_explanation,)
  181. co = self._compile(pattern % ("__exprinfo_expr",))
  182. try:
  183. result = self.frame.eval(co, __exprinfo_expr=operand_result)
  184. except Exception:
  185. raise Failure(explanation)
  186. return explanation, result
  187. def visit_BinOp(self, binop):
  188. left_explanation, left_result = self.visit(binop.left)
  189. right_explanation, right_result = self.visit(binop.right)
  190. symbol = operator_map[binop.op.__class__]
  191. explanation = "(%s %s %s)" % (left_explanation, symbol,
  192. right_explanation)
  193. source = "__exprinfo_left %s __exprinfo_right" % (symbol,)
  194. co = self._compile(source)
  195. try:
  196. result = self.frame.eval(co, __exprinfo_left=left_result,
  197. __exprinfo_right=right_result)
  198. except Exception:
  199. raise Failure(explanation)
  200. return explanation, result
  201. def visit_Call(self, call):
  202. func_explanation, func = self.visit(call.func)
  203. arg_explanations = []
  204. ns = {"__exprinfo_func" : func}
  205. arguments = []
  206. for arg in call.args:
  207. arg_explanation, arg_result = self.visit(arg)
  208. arg_name = "__exprinfo_%s" % (len(ns),)
  209. ns[arg_name] = arg_result
  210. arguments.append(arg_name)
  211. arg_explanations.append(arg_explanation)
  212. for keyword in call.keywords:
  213. arg_explanation, arg_result = self.visit(keyword.value)
  214. arg_name = "__exprinfo_%s" % (len(ns),)
  215. ns[arg_name] = arg_result
  216. keyword_source = "%s=%%s" % (keyword.arg)
  217. arguments.append(keyword_source % (arg_name,))
  218. arg_explanations.append(keyword_source % (arg_explanation,))
  219. if call.starargs:
  220. arg_explanation, arg_result = self.visit(call.starargs)
  221. arg_name = "__exprinfo_star"
  222. ns[arg_name] = arg_result
  223. arguments.append("*%s" % (arg_name,))
  224. arg_explanations.append("*%s" % (arg_explanation,))
  225. if call.kwargs:
  226. arg_explanation, arg_result = self.visit(call.kwargs)
  227. arg_name = "__exprinfo_kwds"
  228. ns[arg_name] = arg_result
  229. arguments.append("**%s" % (arg_name,))
  230. arg_explanations.append("**%s" % (arg_explanation,))
  231. args_explained = ", ".join(arg_explanations)
  232. explanation = "%s(%s)" % (func_explanation, args_explained)
  233. args = ", ".join(arguments)
  234. source = "__exprinfo_func(%s)" % (args,)
  235. co = self._compile(source)
  236. try:
  237. result = self.frame.eval(co, **ns)
  238. except Exception:
  239. raise Failure(explanation)
  240. pattern = "%s\n{%s = %s\n}"
  241. rep = self.frame.repr(result)
  242. explanation = pattern % (rep, rep, explanation)
  243. return explanation, result
  244. def _is_builtin_name(self, name):
  245. pattern = "%r not in globals() and %r not in locals()"
  246. source = pattern % (name.id, name.id)
  247. co = self._compile(source)
  248. try:
  249. return self.frame.eval(co)
  250. except Exception:
  251. return False
  252. def visit_Attribute(self, attr):
  253. if not isinstance(attr.ctx, ast.Load):
  254. return self.generic_visit(attr)
  255. source_explanation, source_result = self.visit(attr.value)
  256. explanation = "%s.%s" % (source_explanation, attr.attr)
  257. source = "__exprinfo_expr.%s" % (attr.attr,)
  258. co = self._compile(source)
  259. try:
  260. result = self.frame.eval(co, __exprinfo_expr=source_result)
  261. except Exception:
  262. raise Failure(explanation)
  263. explanation = "%s\n{%s = %s.%s\n}" % (self.frame.repr(result),
  264. self.frame.repr(result),
  265. source_explanation, attr.attr)
  266. # Check if the attr is from an instance.
  267. source = "%r in getattr(__exprinfo_expr, '__dict__', {})"
  268. source = source % (attr.attr,)
  269. co = self._compile(source)
  270. try:
  271. from_instance = self.frame.eval(co, __exprinfo_expr=source_result)
  272. except Exception:
  273. from_instance = None
  274. if from_instance is None or self.frame.is_true(from_instance):
  275. rep = self.frame.repr(result)
  276. pattern = "%s\n{%s = %s\n}"
  277. explanation = pattern % (rep, rep, explanation)
  278. return explanation, result
  279. def visit_Assert(self, assrt):
  280. test_explanation, test_result = self.visit(assrt.test)
  281. explanation = "assert %s" % (test_explanation,)
  282. if not self.frame.is_true(test_result):
  283. try:
  284. raise BuiltinAssertionError
  285. except Exception:
  286. raise Failure(explanation)
  287. return explanation, test_result
  288. def visit_Assign(self, assign):
  289. value_explanation, value_result = self.visit(assign.value)
  290. explanation = "... = %s" % (value_explanation,)
  291. name = ast.Name("__exprinfo_expr", ast.Load(),
  292. lineno=assign.value.lineno,
  293. col_offset=assign.value.col_offset)
  294. new_assign = ast.Assign(assign.targets, name, lineno=assign.lineno,
  295. col_offset=assign.col_offset)
  296. mod = ast.Module([new_assign])
  297. co = self._compile(mod, "exec")
  298. try:
  299. self.frame.exec_(co, __exprinfo_expr=value_result)
  300. except Exception:
  301. raise Failure(explanation)
  302. return explanation, value_result