|
1 import py |
|
2 import sys, inspect |
|
3 from compiler import parse, ast, pycodegen |
|
4 from py._code.assertion import BuiltinAssertionError, _format_explanation |
|
5 |
|
6 passthroughex = py.builtin._sysex |
|
7 |
|
8 class Failure: |
|
9 def __init__(self, node): |
|
10 self.exc, self.value, self.tb = sys.exc_info() |
|
11 self.node = node |
|
12 |
|
13 class View(object): |
|
14 """View base class. |
|
15 |
|
16 If C is a subclass of View, then C(x) creates a proxy object around |
|
17 the object x. The actual class of the proxy is not C in general, |
|
18 but a *subclass* of C determined by the rules below. To avoid confusion |
|
19 we call view class the class of the proxy (a subclass of C, so of View) |
|
20 and object class the class of x. |
|
21 |
|
22 Attributes and methods not found in the proxy are automatically read on x. |
|
23 Other operations like setting attributes are performed on the proxy, as |
|
24 determined by its view class. The object x is available from the proxy |
|
25 as its __obj__ attribute. |
|
26 |
|
27 The view class selection is determined by the __view__ tuples and the |
|
28 optional __viewkey__ method. By default, the selected view class is the |
|
29 most specific subclass of C whose __view__ mentions the class of x. |
|
30 If no such subclass is found, the search proceeds with the parent |
|
31 object classes. For example, C(True) will first look for a subclass |
|
32 of C with __view__ = (..., bool, ...) and only if it doesn't find any |
|
33 look for one with __view__ = (..., int, ...), and then ..., object,... |
|
34 If everything fails the class C itself is considered to be the default. |
|
35 |
|
36 Alternatively, the view class selection can be driven by another aspect |
|
37 of the object x, instead of the class of x, by overriding __viewkey__. |
|
38 See last example at the end of this module. |
|
39 """ |
|
40 |
|
41 _viewcache = {} |
|
42 __view__ = () |
|
43 |
|
44 def __new__(rootclass, obj, *args, **kwds): |
|
45 self = object.__new__(rootclass) |
|
46 self.__obj__ = obj |
|
47 self.__rootclass__ = rootclass |
|
48 key = self.__viewkey__() |
|
49 try: |
|
50 self.__class__ = self._viewcache[key] |
|
51 except KeyError: |
|
52 self.__class__ = self._selectsubclass(key) |
|
53 return self |
|
54 |
|
55 def __getattr__(self, attr): |
|
56 # attributes not found in the normal hierarchy rooted on View |
|
57 # are looked up in the object's real class |
|
58 return getattr(self.__obj__, attr) |
|
59 |
|
60 def __viewkey__(self): |
|
61 return self.__obj__.__class__ |
|
62 |
|
63 def __matchkey__(self, key, subclasses): |
|
64 if inspect.isclass(key): |
|
65 keys = inspect.getmro(key) |
|
66 else: |
|
67 keys = [key] |
|
68 for key in keys: |
|
69 result = [C for C in subclasses if key in C.__view__] |
|
70 if result: |
|
71 return result |
|
72 return [] |
|
73 |
|
74 def _selectsubclass(self, key): |
|
75 subclasses = list(enumsubclasses(self.__rootclass__)) |
|
76 for C in subclasses: |
|
77 if not isinstance(C.__view__, tuple): |
|
78 C.__view__ = (C.__view__,) |
|
79 choices = self.__matchkey__(key, subclasses) |
|
80 if not choices: |
|
81 return self.__rootclass__ |
|
82 elif len(choices) == 1: |
|
83 return choices[0] |
|
84 else: |
|
85 # combine the multiple choices |
|
86 return type('?', tuple(choices), {}) |
|
87 |
|
88 def __repr__(self): |
|
89 return '%s(%r)' % (self.__rootclass__.__name__, self.__obj__) |
|
90 |
|
91 |
|
92 def enumsubclasses(cls): |
|
93 for subcls in cls.__subclasses__(): |
|
94 for subsubclass in enumsubclasses(subcls): |
|
95 yield subsubclass |
|
96 yield cls |
|
97 |
|
98 |
|
99 class Interpretable(View): |
|
100 """A parse tree node with a few extra methods.""" |
|
101 explanation = None |
|
102 |
|
103 def is_builtin(self, frame): |
|
104 return False |
|
105 |
|
106 def eval(self, frame): |
|
107 # fall-back for unknown expression nodes |
|
108 try: |
|
109 expr = ast.Expression(self.__obj__) |
|
110 expr.filename = '<eval>' |
|
111 self.__obj__.filename = '<eval>' |
|
112 co = pycodegen.ExpressionCodeGenerator(expr).getCode() |
|
113 result = frame.eval(co) |
|
114 except passthroughex: |
|
115 raise |
|
116 except: |
|
117 raise Failure(self) |
|
118 self.result = result |
|
119 self.explanation = self.explanation or frame.repr(self.result) |
|
120 |
|
121 def run(self, frame): |
|
122 # fall-back for unknown statement nodes |
|
123 try: |
|
124 expr = ast.Module(None, ast.Stmt([self.__obj__])) |
|
125 expr.filename = '<run>' |
|
126 co = pycodegen.ModuleCodeGenerator(expr).getCode() |
|
127 frame.exec_(co) |
|
128 except passthroughex: |
|
129 raise |
|
130 except: |
|
131 raise Failure(self) |
|
132 |
|
133 def nice_explanation(self): |
|
134 return _format_explanation(self.explanation) |
|
135 |
|
136 |
|
137 class Name(Interpretable): |
|
138 __view__ = ast.Name |
|
139 |
|
140 def is_local(self, frame): |
|
141 source = '%r in locals() is not globals()' % self.name |
|
142 try: |
|
143 return frame.is_true(frame.eval(source)) |
|
144 except passthroughex: |
|
145 raise |
|
146 except: |
|
147 return False |
|
148 |
|
149 def is_global(self, frame): |
|
150 source = '%r in globals()' % self.name |
|
151 try: |
|
152 return frame.is_true(frame.eval(source)) |
|
153 except passthroughex: |
|
154 raise |
|
155 except: |
|
156 return False |
|
157 |
|
158 def is_builtin(self, frame): |
|
159 source = '%r not in locals() and %r not in globals()' % ( |
|
160 self.name, self.name) |
|
161 try: |
|
162 return frame.is_true(frame.eval(source)) |
|
163 except passthroughex: |
|
164 raise |
|
165 except: |
|
166 return False |
|
167 |
|
168 def eval(self, frame): |
|
169 super(Name, self).eval(frame) |
|
170 if not self.is_local(frame): |
|
171 self.explanation = self.name |
|
172 |
|
173 class Compare(Interpretable): |
|
174 __view__ = ast.Compare |
|
175 |
|
176 def eval(self, frame): |
|
177 expr = Interpretable(self.expr) |
|
178 expr.eval(frame) |
|
179 for operation, expr2 in self.ops: |
|
180 if hasattr(self, 'result'): |
|
181 # shortcutting in chained expressions |
|
182 if not frame.is_true(self.result): |
|
183 break |
|
184 expr2 = Interpretable(expr2) |
|
185 expr2.eval(frame) |
|
186 self.explanation = "%s %s %s" % ( |
|
187 expr.explanation, operation, expr2.explanation) |
|
188 source = "__exprinfo_left %s __exprinfo_right" % operation |
|
189 try: |
|
190 self.result = frame.eval(source, |
|
191 __exprinfo_left=expr.result, |
|
192 __exprinfo_right=expr2.result) |
|
193 except passthroughex: |
|
194 raise |
|
195 except: |
|
196 raise Failure(self) |
|
197 expr = expr2 |
|
198 |
|
199 class And(Interpretable): |
|
200 __view__ = ast.And |
|
201 |
|
202 def eval(self, frame): |
|
203 explanations = [] |
|
204 for expr in self.nodes: |
|
205 expr = Interpretable(expr) |
|
206 expr.eval(frame) |
|
207 explanations.append(expr.explanation) |
|
208 self.result = expr.result |
|
209 if not frame.is_true(expr.result): |
|
210 break |
|
211 self.explanation = '(' + ' and '.join(explanations) + ')' |
|
212 |
|
213 class Or(Interpretable): |
|
214 __view__ = ast.Or |
|
215 |
|
216 def eval(self, frame): |
|
217 explanations = [] |
|
218 for expr in self.nodes: |
|
219 expr = Interpretable(expr) |
|
220 expr.eval(frame) |
|
221 explanations.append(expr.explanation) |
|
222 self.result = expr.result |
|
223 if frame.is_true(expr.result): |
|
224 break |
|
225 self.explanation = '(' + ' or '.join(explanations) + ')' |
|
226 |
|
227 |
|
228 # == Unary operations == |
|
229 keepalive = [] |
|
230 for astclass, astpattern in { |
|
231 ast.Not : 'not __exprinfo_expr', |
|
232 ast.Invert : '(~__exprinfo_expr)', |
|
233 }.items(): |
|
234 |
|
235 class UnaryArith(Interpretable): |
|
236 __view__ = astclass |
|
237 |
|
238 def eval(self, frame, astpattern=astpattern): |
|
239 expr = Interpretable(self.expr) |
|
240 expr.eval(frame) |
|
241 self.explanation = astpattern.replace('__exprinfo_expr', |
|
242 expr.explanation) |
|
243 try: |
|
244 self.result = frame.eval(astpattern, |
|
245 __exprinfo_expr=expr.result) |
|
246 except passthroughex: |
|
247 raise |
|
248 except: |
|
249 raise Failure(self) |
|
250 |
|
251 keepalive.append(UnaryArith) |
|
252 |
|
253 # == Binary operations == |
|
254 for astclass, astpattern in { |
|
255 ast.Add : '(__exprinfo_left + __exprinfo_right)', |
|
256 ast.Sub : '(__exprinfo_left - __exprinfo_right)', |
|
257 ast.Mul : '(__exprinfo_left * __exprinfo_right)', |
|
258 ast.Div : '(__exprinfo_left / __exprinfo_right)', |
|
259 ast.Mod : '(__exprinfo_left % __exprinfo_right)', |
|
260 ast.Power : '(__exprinfo_left ** __exprinfo_right)', |
|
261 }.items(): |
|
262 |
|
263 class BinaryArith(Interpretable): |
|
264 __view__ = astclass |
|
265 |
|
266 def eval(self, frame, astpattern=astpattern): |
|
267 left = Interpretable(self.left) |
|
268 left.eval(frame) |
|
269 right = Interpretable(self.right) |
|
270 right.eval(frame) |
|
271 self.explanation = (astpattern |
|
272 .replace('__exprinfo_left', left .explanation) |
|
273 .replace('__exprinfo_right', right.explanation)) |
|
274 try: |
|
275 self.result = frame.eval(astpattern, |
|
276 __exprinfo_left=left.result, |
|
277 __exprinfo_right=right.result) |
|
278 except passthroughex: |
|
279 raise |
|
280 except: |
|
281 raise Failure(self) |
|
282 |
|
283 keepalive.append(BinaryArith) |
|
284 |
|
285 |
|
286 class CallFunc(Interpretable): |
|
287 __view__ = ast.CallFunc |
|
288 |
|
289 def is_bool(self, frame): |
|
290 source = 'isinstance(__exprinfo_value, bool)' |
|
291 try: |
|
292 return frame.is_true(frame.eval(source, |
|
293 __exprinfo_value=self.result)) |
|
294 except passthroughex: |
|
295 raise |
|
296 except: |
|
297 return False |
|
298 |
|
299 def eval(self, frame): |
|
300 node = Interpretable(self.node) |
|
301 node.eval(frame) |
|
302 explanations = [] |
|
303 vars = {'__exprinfo_fn': node.result} |
|
304 source = '__exprinfo_fn(' |
|
305 for a in self.args: |
|
306 if isinstance(a, ast.Keyword): |
|
307 keyword = a.name |
|
308 a = a.expr |
|
309 else: |
|
310 keyword = None |
|
311 a = Interpretable(a) |
|
312 a.eval(frame) |
|
313 argname = '__exprinfo_%d' % len(vars) |
|
314 vars[argname] = a.result |
|
315 if keyword is None: |
|
316 source += argname + ',' |
|
317 explanations.append(a.explanation) |
|
318 else: |
|
319 source += '%s=%s,' % (keyword, argname) |
|
320 explanations.append('%s=%s' % (keyword, a.explanation)) |
|
321 if self.star_args: |
|
322 star_args = Interpretable(self.star_args) |
|
323 star_args.eval(frame) |
|
324 argname = '__exprinfo_star' |
|
325 vars[argname] = star_args.result |
|
326 source += '*' + argname + ',' |
|
327 explanations.append('*' + star_args.explanation) |
|
328 if self.dstar_args: |
|
329 dstar_args = Interpretable(self.dstar_args) |
|
330 dstar_args.eval(frame) |
|
331 argname = '__exprinfo_kwds' |
|
332 vars[argname] = dstar_args.result |
|
333 source += '**' + argname + ',' |
|
334 explanations.append('**' + dstar_args.explanation) |
|
335 self.explanation = "%s(%s)" % ( |
|
336 node.explanation, ', '.join(explanations)) |
|
337 if source.endswith(','): |
|
338 source = source[:-1] |
|
339 source += ')' |
|
340 try: |
|
341 self.result = frame.eval(source, **vars) |
|
342 except passthroughex: |
|
343 raise |
|
344 except: |
|
345 raise Failure(self) |
|
346 if not node.is_builtin(frame) or not self.is_bool(frame): |
|
347 r = frame.repr(self.result) |
|
348 self.explanation = '%s\n{%s = %s\n}' % (r, r, self.explanation) |
|
349 |
|
350 class Getattr(Interpretable): |
|
351 __view__ = ast.Getattr |
|
352 |
|
353 def eval(self, frame): |
|
354 expr = Interpretable(self.expr) |
|
355 expr.eval(frame) |
|
356 source = '__exprinfo_expr.%s' % self.attrname |
|
357 try: |
|
358 self.result = frame.eval(source, __exprinfo_expr=expr.result) |
|
359 except passthroughex: |
|
360 raise |
|
361 except: |
|
362 raise Failure(self) |
|
363 self.explanation = '%s.%s' % (expr.explanation, self.attrname) |
|
364 # if the attribute comes from the instance, its value is interesting |
|
365 source = ('hasattr(__exprinfo_expr, "__dict__") and ' |
|
366 '%r in __exprinfo_expr.__dict__' % self.attrname) |
|
367 try: |
|
368 from_instance = frame.is_true( |
|
369 frame.eval(source, __exprinfo_expr=expr.result)) |
|
370 except passthroughex: |
|
371 raise |
|
372 except: |
|
373 from_instance = True |
|
374 if from_instance: |
|
375 r = frame.repr(self.result) |
|
376 self.explanation = '%s\n{%s = %s\n}' % (r, r, self.explanation) |
|
377 |
|
378 # == Re-interpretation of full statements == |
|
379 |
|
380 class Assert(Interpretable): |
|
381 __view__ = ast.Assert |
|
382 |
|
383 def run(self, frame): |
|
384 test = Interpretable(self.test) |
|
385 test.eval(frame) |
|
386 # simplify 'assert False where False = ...' |
|
387 if (test.explanation.startswith('False\n{False = ') and |
|
388 test.explanation.endswith('\n}')): |
|
389 test.explanation = test.explanation[15:-2] |
|
390 # print the result as 'assert <explanation>' |
|
391 self.result = test.result |
|
392 self.explanation = 'assert ' + test.explanation |
|
393 if not frame.is_true(test.result): |
|
394 try: |
|
395 raise BuiltinAssertionError |
|
396 except passthroughex: |
|
397 raise |
|
398 except: |
|
399 raise Failure(self) |
|
400 |
|
401 class Assign(Interpretable): |
|
402 __view__ = ast.Assign |
|
403 |
|
404 def run(self, frame): |
|
405 expr = Interpretable(self.expr) |
|
406 expr.eval(frame) |
|
407 self.result = expr.result |
|
408 self.explanation = '... = ' + expr.explanation |
|
409 # fall-back-run the rest of the assignment |
|
410 ass = ast.Assign(self.nodes, ast.Name('__exprinfo_expr')) |
|
411 mod = ast.Module(None, ast.Stmt([ass])) |
|
412 mod.filename = '<run>' |
|
413 co = pycodegen.ModuleCodeGenerator(mod).getCode() |
|
414 try: |
|
415 frame.exec_(co, __exprinfo_expr=expr.result) |
|
416 except passthroughex: |
|
417 raise |
|
418 except: |
|
419 raise Failure(self) |
|
420 |
|
421 class Discard(Interpretable): |
|
422 __view__ = ast.Discard |
|
423 |
|
424 def run(self, frame): |
|
425 expr = Interpretable(self.expr) |
|
426 expr.eval(frame) |
|
427 self.result = expr.result |
|
428 self.explanation = expr.explanation |
|
429 |
|
430 class Stmt(Interpretable): |
|
431 __view__ = ast.Stmt |
|
432 |
|
433 def run(self, frame): |
|
434 for stmt in self.nodes: |
|
435 stmt = Interpretable(stmt) |
|
436 stmt.run(frame) |
|
437 |
|
438 |
|
439 def report_failure(e): |
|
440 explanation = e.node.nice_explanation() |
|
441 if explanation: |
|
442 explanation = ", in: " + explanation |
|
443 else: |
|
444 explanation = "" |
|
445 sys.stdout.write("%s: %s%s\n" % (e.exc.__name__, e.value, explanation)) |
|
446 |
|
447 def check(s, frame=None): |
|
448 if frame is None: |
|
449 frame = sys._getframe(1) |
|
450 frame = py.code.Frame(frame) |
|
451 expr = parse(s, 'eval') |
|
452 assert isinstance(expr, ast.Expression) |
|
453 node = Interpretable(expr.node) |
|
454 try: |
|
455 node.eval(frame) |
|
456 except passthroughex: |
|
457 raise |
|
458 except Failure: |
|
459 e = sys.exc_info()[1] |
|
460 report_failure(e) |
|
461 else: |
|
462 if not frame.is_true(node.result): |
|
463 sys.stderr.write("assertion failed: %s\n" % node.nice_explanation()) |
|
464 |
|
465 |
|
466 ########################################################### |
|
467 # API / Entry points |
|
468 # ######################################################### |
|
469 |
|
470 def interpret(source, frame, should_fail=False): |
|
471 module = Interpretable(parse(source, 'exec').node) |
|
472 #print "got module", module |
|
473 if isinstance(frame, py.std.types.FrameType): |
|
474 frame = py.code.Frame(frame) |
|
475 try: |
|
476 module.run(frame) |
|
477 except Failure: |
|
478 e = sys.exc_info()[1] |
|
479 return getfailure(e) |
|
480 except passthroughex: |
|
481 raise |
|
482 except: |
|
483 import traceback |
|
484 traceback.print_exc() |
|
485 if should_fail: |
|
486 return ("(assertion failed, but when it was re-run for " |
|
487 "printing intermediate values, it did not fail. Suggestions: " |
|
488 "compute assert expression before the assert or use --nomagic)") |
|
489 else: |
|
490 return None |
|
491 |
|
492 def getmsg(excinfo): |
|
493 if isinstance(excinfo, tuple): |
|
494 excinfo = py.code.ExceptionInfo(excinfo) |
|
495 #frame, line = gettbline(tb) |
|
496 #frame = py.code.Frame(frame) |
|
497 #return interpret(line, frame) |
|
498 |
|
499 tb = excinfo.traceback[-1] |
|
500 source = str(tb.statement).strip() |
|
501 x = interpret(source, tb.frame, should_fail=True) |
|
502 if not isinstance(x, str): |
|
503 raise TypeError("interpret returned non-string %r" % (x,)) |
|
504 return x |
|
505 |
|
506 def getfailure(e): |
|
507 explanation = e.node.nice_explanation() |
|
508 if str(e.value): |
|
509 lines = explanation.split('\n') |
|
510 lines[0] += " << %s" % (e.value,) |
|
511 explanation = '\n'.join(lines) |
|
512 text = "%s: %s" % (e.exc.__name__, explanation) |
|
513 if text.startswith('AssertionError: assert '): |
|
514 text = text[16:] |
|
515 return text |
|
516 |
|
517 def run(s, frame=None): |
|
518 if frame is None: |
|
519 frame = sys._getframe(1) |
|
520 frame = py.code.Frame(frame) |
|
521 module = Interpretable(parse(s, 'exec').node) |
|
522 try: |
|
523 module.run(frame) |
|
524 except Failure: |
|
525 e = sys.exc_info()[1] |
|
526 report_failure(e) |
|
527 |
|
528 |
|
529 if __name__ == '__main__': |
|
530 # example: |
|
531 def f(): |
|
532 return 5 |
|
533 def g(): |
|
534 return 3 |
|
535 def h(x): |
|
536 return 'never' |
|
537 check("f() * g() == 5") |
|
538 check("not f()") |
|
539 check("not (f() and g() or 0)") |
|
540 check("f() == g()") |
|
541 i = 4 |
|
542 check("i == f()") |
|
543 check("len(f()) == 0") |
|
544 check("isinstance(2+3+4, float)") |
|
545 |
|
546 run("x = i") |
|
547 check("x == 5") |
|
548 |
|
549 run("assert not f(), 'oops'") |
|
550 run("a, b, c = 1, 2") |
|
551 run("a, b, c = f()") |
|
552 |
|
553 check("max([f(),g()]) == 4") |
|
554 check("'hello'[g()] == 'h'") |
|
555 run("'guk%d' % h(f())") |