| 1 | #!/usr/bin/env python3 | 
| 2 | """ | 
| 3 | pass_state_test.py: Tests for pass_state.py | 
| 4 | """ | 
| 5 | from __future__ import print_function | 
| 6 |  | 
| 7 | import unittest | 
| 8 |  | 
| 9 | import pass_state  # module under test | 
| 10 |  | 
| 11 |  | 
| 12 | class 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 |  | 
| 143 | class 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 |  | 
| 155 | class 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 |  | 
| 595 | if __name__ == '__main__': | 
| 596 | unittest.main() |