3
from compiler import parse, ast, pycodegen
4
from py._code.assertion import BuiltinAssertionError, _format_explanation
6
passthroughex = py.builtin._sysex
9
def __init__(self, node):
10
self.exc, self.value, self.tb = sys.exc_info()
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.
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.
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.
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.
44
def __new__(rootclass, obj, *args, **kwds):
45
self = object.__new__(rootclass)
47
self.__rootclass__ = rootclass
48
key = self.__viewkey__()
50
self.__class__ = self._viewcache[key]
52
self.__class__ = self._selectsubclass(key)
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)
60
def __viewkey__(self):
61
return self.__obj__.__class__
63
def __matchkey__(self, key, subclasses):
64
if inspect.isclass(key):
65
keys = inspect.getmro(key)
69
result = [C for C in subclasses if key in C.__view__]
74
def _selectsubclass(self, key):
75
subclasses = list(enumsubclasses(self.__rootclass__))
77
if not isinstance(C.__view__, tuple):
78
C.__view__ = (C.__view__,)
79
choices = self.__matchkey__(key, subclasses)
81
return self.__rootclass__
82
elif len(choices) == 1:
85
# combine the multiple choices
86
return type('?', tuple(choices), {})
89
return '%s(%r)' % (self.__rootclass__.__name__, self.__obj__)
92
def enumsubclasses(cls):
93
for subcls in cls.__subclasses__():
94
for subsubclass in enumsubclasses(subcls):
99
class Interpretable(View):
100
"""A parse tree node with a few extra methods."""
103
def is_builtin(self, frame):
106
def eval(self, frame):
107
# fall-back for unknown expression nodes
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:
119
self.explanation = self.explanation or frame.repr(self.result)
121
def run(self, frame):
122
# fall-back for unknown statement nodes
124
expr = ast.Module(None, ast.Stmt([self.__obj__]))
125
expr.filename = '<run>'
126
co = pycodegen.ModuleCodeGenerator(expr).getCode()
128
except passthroughex:
133
def nice_explanation(self):
134
return _format_explanation(self.explanation)
137
class Name(Interpretable):
140
def is_local(self, frame):
141
source = '%r in locals() is not globals()' % self.name
143
return frame.is_true(frame.eval(source))
144
except passthroughex:
149
def is_global(self, frame):
150
source = '%r in globals()' % self.name
152
return frame.is_true(frame.eval(source))
153
except passthroughex:
158
def is_builtin(self, frame):
159
source = '%r not in locals() and %r not in globals()' % (
160
self.name, self.name)
162
return frame.is_true(frame.eval(source))
163
except passthroughex:
168
def eval(self, frame):
169
super(Name, self).eval(frame)
170
if not self.is_local(frame):
171
self.explanation = self.name
173
class Compare(Interpretable):
174
__view__ = ast.Compare
176
def eval(self, frame):
177
expr = Interpretable(self.expr)
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):
184
expr2 = Interpretable(expr2)
186
self.explanation = "%s %s %s" % (
187
expr.explanation, operation, expr2.explanation)
188
source = "__exprinfo_left %s __exprinfo_right" % operation
190
self.result = frame.eval(source,
191
__exprinfo_left=expr.result,
192
__exprinfo_right=expr2.result)
193
except passthroughex:
199
class And(Interpretable):
202
def eval(self, frame):
204
for expr in self.nodes:
205
expr = Interpretable(expr)
207
explanations.append(expr.explanation)
208
self.result = expr.result
209
if not frame.is_true(expr.result):
211
self.explanation = '(' + ' and '.join(explanations) + ')'
213
class Or(Interpretable):
216
def eval(self, frame):
218
for expr in self.nodes:
219
expr = Interpretable(expr)
221
explanations.append(expr.explanation)
222
self.result = expr.result
223
if frame.is_true(expr.result):
225
self.explanation = '(' + ' or '.join(explanations) + ')'
228
# == Unary operations ==
230
for astclass, astpattern in {
231
ast.Not : 'not __exprinfo_expr',
232
ast.Invert : '(~__exprinfo_expr)',
235
class UnaryArith(Interpretable):
238
def eval(self, frame, astpattern=astpattern):
239
expr = Interpretable(self.expr)
241
self.explanation = astpattern.replace('__exprinfo_expr',
244
self.result = frame.eval(astpattern,
245
__exprinfo_expr=expr.result)
246
except passthroughex:
251
keepalive.append(UnaryArith)
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)',
263
class BinaryArith(Interpretable):
266
def eval(self, frame, astpattern=astpattern):
267
left = Interpretable(self.left)
269
right = Interpretable(self.right)
271
self.explanation = (astpattern
272
.replace('__exprinfo_left', left .explanation)
273
.replace('__exprinfo_right', right.explanation))
275
self.result = frame.eval(astpattern,
276
__exprinfo_left=left.result,
277
__exprinfo_right=right.result)
278
except passthroughex:
283
keepalive.append(BinaryArith)
286
class CallFunc(Interpretable):
287
__view__ = ast.CallFunc
289
def is_bool(self, frame):
290
source = 'isinstance(__exprinfo_value, bool)'
292
return frame.is_true(frame.eval(source,
293
__exprinfo_value=self.result))
294
except passthroughex:
299
def eval(self, frame):
300
node = Interpretable(self.node)
303
vars = {'__exprinfo_fn': node.result}
304
source = '__exprinfo_fn('
306
if isinstance(a, ast.Keyword):
313
argname = '__exprinfo_%d' % len(vars)
314
vars[argname] = a.result
316
source += argname + ','
317
explanations.append(a.explanation)
319
source += '%s=%s,' % (keyword, argname)
320
explanations.append('%s=%s' % (keyword, a.explanation))
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)
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(','):
341
self.result = frame.eval(source, **vars)
342
except passthroughex:
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)
350
class Getattr(Interpretable):
351
__view__ = ast.Getattr
353
def eval(self, frame):
354
expr = Interpretable(self.expr)
356
source = '__exprinfo_expr.%s' % self.attrname
358
self.result = frame.eval(source, __exprinfo_expr=expr.result)
359
except passthroughex:
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)
368
from_instance = frame.is_true(
369
frame.eval(source, __exprinfo_expr=expr.result))
370
except passthroughex:
375
r = frame.repr(self.result)
376
self.explanation = '%s\n{%s = %s\n}' % (r, r, self.explanation)
378
# == Re-interpretation of full statements ==
380
class Assert(Interpretable):
381
__view__ = ast.Assert
383
def run(self, frame):
384
test = Interpretable(self.test)
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):
395
raise BuiltinAssertionError
396
except passthroughex:
401
class Assign(Interpretable):
402
__view__ = ast.Assign
404
def run(self, frame):
405
expr = Interpretable(self.expr)
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()
415
frame.exec_(co, __exprinfo_expr=expr.result)
416
except passthroughex:
421
class Discard(Interpretable):
422
__view__ = ast.Discard
424
def run(self, frame):
425
expr = Interpretable(self.expr)
427
self.result = expr.result
428
self.explanation = expr.explanation
430
class Stmt(Interpretable):
433
def run(self, frame):
434
for stmt in self.nodes:
435
stmt = Interpretable(stmt)
439
def report_failure(e):
440
explanation = e.node.nice_explanation()
442
explanation = ", in: " + explanation
445
sys.stdout.write("%s: %s%s\n" % (e.exc.__name__, e.value, explanation))
447
def check(s, frame=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)
456
except passthroughex:
459
e = sys.exc_info()[1]
462
if not frame.is_true(node.result):
463
sys.stderr.write("assertion failed: %s\n" % node.nice_explanation())
466
###########################################################
468
# #########################################################
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)
478
e = sys.exc_info()[1]
480
except passthroughex:
484
traceback.print_exc()
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)")
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)
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,))
507
explanation = e.node.nice_explanation()
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 '):
517
def run(s, frame=None):
519
frame = sys._getframe(1)
520
frame = py.code.Frame(frame)
521
module = Interpretable(parse(s, 'exec').node)
525
e = sys.exc_info()[1]
529
if __name__ == '__main__':
537
check("f() * g() == 5")
539
check("not (f() and g() or 0)")
543
check("len(f()) == 0")
544
check("isinstance(2+3+4, float)")
549
run("assert not f(), 'oops'")
550
run("a, b, c = 1, 2")
553
check("max([f(),g()]) == 4")
554
check("'hello'[g()] == 'h'")
555
run("'guk%d' % h(f())")