OILS / mycpp / pass_state_test.py View on Github | oilshell.org

596 lines, 342 significant
1#!/usr/bin/env python3
2"""
3pass_state_test.py: Tests for pass_state.py
4"""
5from __future__ import print_function
6
7import unittest
8
9import pass_state # module under test
10
11
12class VirtualTest(unittest.TestCase):
13
14 def testVirtual(self):
15 """
16 Example:
17
18 class Base(object):
19 def method(self): # we don't know if this is virtual yet
20 pass
21 def x(self):
22 pass
23
24 class Derived(Base):
25 def method(self): # now it's virtual!
26 pass
27 def y(self):
28 pass
29 """
30 v = pass_state.Virtual()
31 v.OnMethod(('Base', ), 'method')
32 v.OnMethod(('Base', ), 'x')
33 v.OnSubclass(('Base', ), ('Derived', ))
34 v.OnMethod(('Derived', ), 'method')
35 v.OnMethod(('Derived', ), 'y')
36
37 v.Calculate()
38
39 print(v.virtuals)
40 self.assertEqual(
41 {
42 (('Base', ), 'method'): None,
43 (('Derived', ), 'method'): (('Base', ), 'method')
44 }, v.virtuals)
45
46 self.assertEqual(True, v.IsVirtual(('Base', ), 'method'))
47 self.assertEqual(True, v.IsVirtual(('Derived', ), 'method'))
48 self.assertEqual(False, v.IsVirtual(('Derived', ), 'y'))
49
50 self.assertEqual(False, v.IsVirtual(('Klass', ), 'z'))
51
52 self.assertEqual(True, v.HasVTable(('Base', )))
53 self.assertEqual(True, v.HasVTable(('Derived', )))
54
55 self.assertEqual(False, v.HasVTable(('Klass', )))
56
57 def testNoInit(self):
58 v = pass_state.Virtual()
59 v.OnMethod(('Base', ), '__init__')
60 v.OnSubclass(('Base', ), ('Derived', ))
61 v.OnMethod(('Derived', ), '__init__')
62 v.Calculate()
63 self.assertEqual(False, v.HasVTable(('Base', )))
64 self.assertEqual(False, v.HasVTable(('Derived', )))
65
66 def testCanReorderFields(self):
67 """
68 class Base(object):
69 def __init__(self):
70 self.s = '' # pointer
71 self.i = 42
72
73 class Derived(Base):
74 def __init__(self):
75 Base.__init__()
76 self.mylist = [] # type: List[str]
77
78 Note: we can't reorder these, even though there are no virtual methods.
79 """
80 v = pass_state.Virtual()
81 v.OnSubclass(('Base2', ), ('Derived2', ))
82 v.Calculate()
83
84 self.assertEqual(False, v.CanReorderFields(('Base2', )))
85 self.assertEqual(False, v.CanReorderFields(('Derived2', )))
86
87 self.assertEqual(True, v.CanReorderFields(('Klass2', )))
88
89 def testBaseCollision(self):
90 v = pass_state.Virtual()
91 v.OnSubclass((
92 'moduleA',
93 'Base',
94 ), (
95 'foo',
96 'Derived',
97 ))
98 with self.assertRaises(AssertionError):
99 v.OnSubclass((
100 'moduleB',
101 'Base',
102 ), (
103 'bar',
104 'Derived',
105 ))
106
107 def testSubclassMapping(self):
108 v = pass_state.Virtual()
109 v.OnMethod((
110 'moduleA',
111 'Base',
112 ), 'frobnicate')
113 v.OnSubclass((
114 'moduleA',
115 'Base',
116 ), (
117 'foo',
118 'Derived',
119 ))
120 v.OnMethod((
121 'foo',
122 'Derived',
123 ), 'frobnicate')
124 v.OnSubclass((
125 'moduleA',
126 'Base',
127 ), (
128 'bar',
129 'Derived',
130 ))
131 v.OnMethod((
132 'bar',
133 'Derived',
134 ), 'frobnicate')
135 v.Calculate()
136 self.assertEqual((('moduleA', 'Base'), 'frobnicate'),
137 v.virtuals[(('foo', 'Derived'), 'frobnicate')])
138 self.assertEqual((('moduleA', 'Base'), 'frobnicate'),
139 v.virtuals[(('bar', 'Derived'), 'frobnicate')])
140 self.assertEqual(None, v.virtuals[(('moduleA', 'Base'), 'frobnicate')])
141
142
143class DummyFact(pass_state.Fact):
144
145 def __init__(self, n: int) -> None:
146 self.n = n
147
148 def name(self):
149 return 'dummy'
150
151 def Generate(self, func: str, statement: int) -> str:
152 return '{},{},{}'.format(func, statement, self.n)
153
154
155class ControlFlowGraphTest(unittest.TestCase):
156
157 def testLinear(self):
158 cfg = pass_state.ControlFlowGraph()
159
160 a = cfg.AddStatement()
161 b = cfg.AddStatement()
162 c = cfg.AddStatement()
163 d = cfg.AddStatement()
164
165 cfg.AddFact(b, DummyFact(1))
166 cfg.AddFact(d, DummyFact(99))
167 cfg.AddFact(d, DummyFact(7))
168
169 expected_edges = {
170 (0, a),
171 (a, b),
172 (b, c),
173 (c, d),
174 }
175 self.assertEqual(expected_edges, cfg.edges)
176
177 self.assertEqual(1, len(cfg.facts[b]))
178 self.assertEqual('foo,1,1', cfg.facts[b][0].Generate('foo', 1))
179 self.assertEqual('dummy', cfg.facts[b][0].name())
180 self.assertEqual(2, len(cfg.facts[d]))
181 self.assertEqual('bar,1,99', cfg.facts[d][0].Generate('bar', 1))
182 self.assertEqual('bar,2,7', cfg.facts[d][1].Generate('bar', 2))
183
184 def testBranches(self):
185 cfg = pass_state.ControlFlowGraph()
186
187 main0 = cfg.AddStatement()
188
189 # branch condition facts all get attached to this statement
190 branch_point = cfg.AddStatement()
191
192 # first statement in if block
193 with pass_state.CfgBranchContext(cfg, branch_point) as branch_ctx:
194 with branch_ctx.AddBranch() as arm0:
195 arm0_a = cfg.AddStatement() # block statement 2
196 arm0_b = cfg.AddStatement() # block statement 2
197 arm0_c = cfg.AddStatement() # block statement 3
198
199 # frist statement in elif block
200 with branch_ctx.AddBranch() as arm1:
201 arm1_a = cfg.AddStatement()
202 arm1_b = cfg.AddStatement() # block statement 2
203
204 # frist statement in else block
205 with branch_ctx.AddBranch() as arm2:
206 arm2_a = cfg.AddStatement()
207 arm2_b = cfg.AddStatement() # block statement 2
208
209 self.assertEqual(arm0_c, arm0.exit)
210 self.assertEqual(arm1_b, arm1.exit)
211 self.assertEqual(arm2_b, arm2.exit)
212
213 join = cfg.AddStatement()
214 end = cfg.AddStatement()
215 """
216 We expecte a graph like this.
217
218 begin
219 |
220 main0
221 |
222 v
223 branch_point
224 / | \
225 arm0_a arm1_a arm2_a
226 | | |
227 arm0_b arm1_b arm2_b
228 | | |
229 arm0_c | |
230 | | /
231 \ | /
232 \ | /
233 \ | /
234 \ | /
235 join
236 |
237 end
238 """
239 # yapf: disable
240 expected_edges = {
241 (0, main0),
242 (main0, branch_point),
243 (branch_point, arm0_a), (branch_point, arm1_a), (branch_point, arm2_a),
244 (arm0_a, arm0_b), (arm0_b, arm0_c),
245 (arm1_a, arm1_b),
246 (arm2_a, arm2_b),
247 (arm0_c, join), (arm1_b, join), (arm2_b, join),
248 (join, end),
249 }
250 # yapf: enable
251 self.assertEqual(expected_edges, cfg.edges)
252
253 def testDeadends(self):
254 """
255 Make sure we don't create orphans in the presence of continue, return,
256 raise, etc...
257 """
258
259 cfg = pass_state.ControlFlowGraph()
260 with pass_state.CfgBranchContext(cfg,
261 cfg.AddStatement()) as branch_ctx:
262 with branch_ctx.AddBranch() as branchA: # if
263 ret = cfg.AddStatement() # return
264 cfg.AddDeadend(ret)
265 """
266 while ...:
267 if ...:
268 continue
269 else:
270 print(...)
271
272 print(...)
273 """
274 with pass_state.CfgLoopContext(cfg) as loop:
275 branch_point = cfg.AddStatement()
276 with pass_state.CfgBranchContext(cfg, branch_point) as branch_ctx:
277 with branch_ctx.AddBranch() as branchB: # if
278 cont = cfg.AddStatement() # continue
279 loop.AddContinue(cont)
280
281 with branch_ctx.AddBranch() as branchC: # else
282 innerC = cfg.AddStatement()
283
284 end = cfg.AddStatement()
285 # yapf: disable
286 expected_edges = {
287 (0, branchA.entry),
288 (branchA.entry, ret),
289 (branchA.entry, loop.entry),
290 (loop.entry, branchB.entry),
291 (branch_point, cont),
292 (cont, loop.entry),
293 (branch_point, innerC),
294 (innerC, end),
295 (innerC, loop.entry),
296 }
297 # yapf: enable
298 self.assertEqual(expected_edges, cfg.edges)
299
300 def testNedstedIf(self):
301 """
302 The mypy AST represents else-if as nested if-statements inside the else arm.
303 """
304 cfg = pass_state.ControlFlowGraph()
305
306 outer_branch_point = cfg.AddStatement()
307 with pass_state.CfgBranchContext(cfg,
308 outer_branch_point) as branch_ctx:
309 with branch_ctx.AddBranch() as branch0: # if
310 branch0_a = cfg.AddStatement()
311
312 with branch_ctx.AddBranch() as branch1: # else
313 with branch1.AddBranch(cfg.AddStatement()) as branch2: # if
314 branch2_a = cfg.AddStatement()
315
316 branch1_a = cfg.AddStatement()
317
318 end = cfg.AddStatement()
319 """
320 We expect a graph like this.
321
322 begin
323 |
324 outer_branch_point +------
325 | | \
326 branch0_a | branch2.entry
327 | | |
328 | | branch2_a
329 | | |
330 | | /
331 | | /
332 | | /
333 | branch1_a
334 | /
335 | /
336 | /
337 | /
338 end _____/
339 """
340 # yapf: disable
341 expected_edges = {
342 (0, outer_branch_point),
343 (outer_branch_point, branch0_a),
344 (outer_branch_point, branch2.entry),
345 (branch2.entry, branch2_a),
346 (branch2_a, branch1_a),
347 (branch0.exit, end),
348 (branch1.exit, end),
349 (branch2.exit, end),
350 }
351 # yapf: enable
352 self.assertEqual(expected_edges, cfg.edges)
353
354 def testLoops(self):
355 cfg = pass_state.ControlFlowGraph()
356
357 with pass_state.CfgLoopContext(cfg) as loopA:
358 branch_point = cfg.AddStatement()
359 with pass_state.CfgBranchContext(cfg, branch_point) as branch_ctx:
360 with branch_ctx.AddBranch() as arm0:
361 arm0_a = cfg.AddStatement()
362 arm0_b = cfg.AddStatement()
363
364 with branch_ctx.AddBranch() as arm1:
365 arm1_a = cfg.AddStatement()
366 arm1_b = cfg.AddStatement()
367
368 self.assertEqual(arm0_b, arm0.exit)
369 self.assertEqual(arm1_b, arm1.exit)
370
371 with pass_state.CfgLoopContext(cfg) as loopB:
372 innerB = cfg.AddStatement()
373
374 self.assertEqual(innerB, loopB.exit)
375
376 end = cfg.AddStatement()
377 """
378 We expecte a graph like this:.
379
380 begin
381 |
382 loopA <------+
383 | |
384 v |
385 branch_point |
386 / \ |
387 arm0_a arm2_a |
388 | | |
389 arm0_b arm2_b |
390 \ / |
391 \ / |
392 loopB <-+ |
393 | | |
394 innerB -+---+
395 |
396 end
397 """
398 # yapf: disable
399 expected_edges = {
400 (0, loopA.entry),
401 (loopA.entry, branch_point),
402 (branch_point, arm0_a), (branch_point, arm1_a),
403 (arm0_a, arm0_b),
404 (arm1_a, arm1_b),
405 (arm0_b, loopB.entry), (arm1_b, loopB.entry),
406 (loopB.entry, innerB),
407 (innerB, loopA.entry), (innerB, loopB.entry),
408 (innerB, end),
409 }
410 # yapf: enable
411 self.assertEqual(expected_edges, cfg.edges)
412
413 def testLoops2(self):
414 cfg = pass_state.ControlFlowGraph()
415
416 with pass_state.CfgLoopContext(cfg) as loopA:
417 with pass_state.CfgLoopContext(cfg) as loopB:
418 innerB = cfg.AddStatement()
419
420 innerA = cfg.AddStatement()
421
422 end = cfg.AddStatement()
423
424 # yapf: disable
425 expected_edges = {
426 (0, loopA.entry),
427 (loopA.entry, loopB.entry),
428 (loopB.entry, innerB),
429 (innerB, innerA),
430 (innerB, loopB.entry),
431 (innerA, loopA.entry),
432 (innerA, end),
433 }
434 # yapf: enable
435 self.assertEqual(expected_edges, cfg.edges)
436
437 def testDeepTry(self):
438 """
439 A code snippet like the following.
440
441 1 while i < n:
442 2 for prog in cases:
443 3 try:
444 4 result = f(prog)
445 except ParseError as e:
446 5 num_exceptions += 1
447 6 continue
448 7 i += 1
449
450 8 mylib.MaybeCollect() # manual GC point
451
452 9 log('num_exceptions = %d', num_exceptions)
453 """
454 cfg = pass_state.ControlFlowGraph()
455
456 with pass_state.CfgLoopContext(cfg) as loopA:
457 with pass_state.CfgLoopContext(cfg) as loopB:
458 with pass_state.CfgBlockContext(cfg) as try_block:
459 try_s0 = cfg.AddStatement()
460
461 with pass_state.CfgBlockContext(
462 cfg, try_block.exit) as except_block:
463 except_s0 = cfg.AddStatement()
464 cont = cfg.AddStatement()
465 loopB.AddContinue(cont)
466
467 a_s0 = cfg.AddStatement()
468 a_s1 = cfg.AddStatement()
469
470 log_stmt = cfg.AddStatement()
471 end = cfg.AddStatement()
472
473 # yapf: disable
474 expected_edges = {
475 (0, loopA.entry),
476 (loopA.entry, loopB.entry),
477 (loopB.entry, try_block.entry),
478 (try_block.entry, try_s0),
479 (try_s0, except_s0),
480 (try_s0, loopB.entry),
481 (except_s0, cont),
482 (cont, loopB.entry),
483 (try_block.exit, a_s0),
484 (a_s0, a_s1),
485 (a_s1, loopA.entry),
486 (a_s1, log_stmt),
487 (log_stmt, end),
488 }
489 # yapf: enable
490 self.assertEqual(expected_edges, cfg.edges)
491
492 def testLoopWithDanglingBlocks(self):
493 """
494 for i in xrange(1000000):
495 try:
496 with ctx_DirStack(d, 'foo') as _:
497 if i % 10000 == 0:
498 raise MyError()
499 pass
500 except MyError:
501 log('exception')
502 """
503 cfg = pass_state.ControlFlowGraph()
504
505 with pass_state.CfgLoopContext(cfg) as loop:
506 with pass_state.CfgBranchContext(cfg,
507 cfg.AddStatement()) as try_ctx:
508 with try_ctx.AddBranch() as try_block:
509 with pass_state.CfgBlockContext(
510 cfg, cfg.AddStatement()) as with_block:
511 with pass_state.CfgBranchContext(
512 cfg, cfg.AddStatement()) as if_ctx:
513 with if_ctx.AddBranch() as if_block:
514 s_raise = cfg.AddStatement()
515 cfg.AddDeadend(s_raise)
516
517 pass_stmt = cfg.AddStatement()
518
519 with try_ctx.AddBranch(try_block.exit) as except_block:
520 log_stmt = cfg.AddStatement()
521
522 # yapf: disable
523 expected_edges = {
524 (0, loop.entry),
525 (loop.entry, try_block.entry),
526 (try_block.entry, with_block.entry),
527 (with_block.entry, if_block.entry),
528 (if_block.entry, s_raise),
529 (if_block.entry, pass_stmt),
530 (pass_stmt, loop.entry),
531 (pass_stmt, log_stmt),
532 (log_stmt, loop.entry),
533 }
534 # yapf: enable
535 self.assertEqual(expected_edges, cfg.edges)
536
537 def testLoopBreak(self):
538 """
539 while ...:
540 if ...:
541 break
542
543 else:
544 try:
545 pass
546
547 except ...:
548 break
549
550 pass
551
552 pass
553 """
554 cfg = pass_state.ControlFlowGraph()
555
556 with pass_state.CfgLoopContext(cfg) as loop:
557 with pass_state.CfgBranchContext(cfg,
558 cfg.AddStatement()) as if_ctx:
559 with if_ctx.AddBranch() as if_block:
560 break1 = cfg.AddStatement()
561 loop.AddBreak(break1)
562
563 with if_ctx.AddBranch() as else_block:
564 with pass_state.CfgBranchContext(
565 cfg, cfg.AddStatement()) as try_ctx:
566 with try_ctx.AddBranch() as try_block:
567 pass1 = cfg.AddStatement()
568
569 with try_ctx.AddBranch(try_block.exit) as except_block:
570 break2 = cfg.AddStatement()
571 loop.AddBreak(break2)
572
573 pass2 = cfg.AddStatement()
574
575 pass3 = cfg.AddStatement()
576
577 # yapf: disable
578 expected_edges = {
579 (0, loop.entry),
580 (loop.entry, if_block.entry),
581 (if_block.entry, break1),
582 (if_block.entry, try_block.entry),
583 (try_block.entry, pass1),
584 (pass1, break2),
585 (pass1, pass2),
586 (pass2, loop.entry),
587 (pass2, pass3),
588 (break1, pass3),
589 (break2, pass3),
590 }
591 # yapf: enable
592 self.assertEqual(expected_edges, cfg.edges)
593
594
595if __name__ == '__main__':
596 unittest.main()