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

/_pytest/assertion/newinterpret.py

https://bitbucket.org/kcr/pypy
Python | 333 lines | 309 code | 17 blank | 7 comment | 45 complexity | a810fc9d35a0253995c2405a7ab99b06 MD5 | raw file
Possible License(s): Apache-2.0
  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