You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

323 lines
11KB

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