|
5 | 5 | import dis
|
6 | 6 | import functools
|
7 | 7 | import io
|
| 8 | +import itertools |
| 9 | +import opcode |
8 | 10 | import re
|
9 | 11 | import sys
|
| 12 | +import tempfile |
| 13 | +import textwrap |
10 | 14 | import types
|
11 | 15 | import unittest
|
12 | 16 | from test.support import (captured_stdout, requires_debug_ranges,
|
13 |
| - requires_specialization, cpython_only) |
| 17 | + requires_specialization, cpython_only, |
| 18 | + os_helper) |
14 | 19 | from test.support.bytecode_helper import BytecodeTestCase
|
15 | 20 |
|
16 |
| -import opcode |
17 | 21 |
|
18 | 22 | CACHE = dis.opmap["CACHE"]
|
19 | 23 |
|
@@ -2426,5 +2430,119 @@ def _unroll_caches_as_Instructions(instrs, show_caches=False):
|
2426 | 2430 | False, None, None, instr.positions)
|
2427 | 2431 |
|
2428 | 2432 |
|
| 2433 | +class TestDisCLI(unittest.TestCase): |
| 2434 | + |
| 2435 | + def setUp(self): |
| 2436 | + self.filename = tempfile.mktemp() |
| 2437 | + self.addCleanup(os_helper.unlink, self.filename) |
| 2438 | + |
| 2439 | + @staticmethod |
| 2440 | + def text_normalize(string): |
| 2441 | + """Dedent *string* and strip it from its surrounding whitespaces. |
| 2442 | +
|
| 2443 | + This method is used by the other utility functions so that any |
| 2444 | + string to write or to match against can be freely indented. |
| 2445 | + """ |
| 2446 | + return textwrap.dedent(string).strip() |
| 2447 | + |
| 2448 | + def set_source(self, content): |
| 2449 | + with open(self.filename, 'w') as fp: |
| 2450 | + fp.write(self.text_normalize(content)) |
| 2451 | + |
| 2452 | + def invoke_dis(self, *flags): |
| 2453 | + output = io.StringIO() |
| 2454 | + with contextlib.redirect_stdout(output): |
| 2455 | + dis.main(args=[*flags, self.filename]) |
| 2456 | + return self.text_normalize(output.getvalue()) |
| 2457 | + |
| 2458 | + def check_output(self, source, expect, *flags): |
| 2459 | + with self.subTest(source=source, flags=flags): |
| 2460 | + self.set_source(source) |
| 2461 | + res = self.invoke_dis(*flags) |
| 2462 | + expect = self.text_normalize(expect) |
| 2463 | + self.assertListEqual(res.splitlines(), expect.splitlines()) |
| 2464 | + |
| 2465 | + def test_invocation(self): |
| 2466 | + # test various combinations of parameters |
| 2467 | + base_flags = [ |
| 2468 | + ('-C', '--show-caches'), |
| 2469 | + ('-O', '--show-offsets'), |
| 2470 | + ('-P', '--show-positions'), |
| 2471 | + ('-S', '--specialized'), |
| 2472 | + ] |
| 2473 | + |
| 2474 | + self.set_source(''' |
| 2475 | + def f(): |
| 2476 | + print(x) |
| 2477 | + return None |
| 2478 | + ''') |
| 2479 | + |
| 2480 | + for r in range(1, len(base_flags) + 1): |
| 2481 | + for choices in itertools.combinations(base_flags, r=r): |
| 2482 | + for args in itertools.product(*choices): |
| 2483 | + with self.subTest(args=args[1:]): |
| 2484 | + _ = self.invoke_dis(*args) |
| 2485 | + |
| 2486 | + with self.assertRaises(SystemExit): |
| 2487 | + # suppress argparse error message |
| 2488 | + with contextlib.redirect_stderr(io.StringIO()): |
| 2489 | + _ = self.invoke_dis('--unknown') |
| 2490 | + |
| 2491 | + def test_show_cache(self): |
| 2492 | + # test 'python -m dis -C/--show-caches' |
| 2493 | + source = 'print()' |
| 2494 | + expect = ''' |
| 2495 | + 0 RESUME 0 |
| 2496 | +
|
| 2497 | + 1 LOAD_NAME 0 (print) |
| 2498 | + PUSH_NULL |
| 2499 | + CALL 0 |
| 2500 | + CACHE 0 (counter: 0) |
| 2501 | + CACHE 0 (func_version: 0) |
| 2502 | + CACHE 0 |
| 2503 | + POP_TOP |
| 2504 | + LOAD_CONST 0 (None) |
| 2505 | + RETURN_VALUE |
| 2506 | + ''' |
| 2507 | + for flag in ['-C', '--show-caches']: |
| 2508 | + self.check_output(source, expect, flag) |
| 2509 | + |
| 2510 | + def test_show_offsets(self): |
| 2511 | + # test 'python -m dis -O/--show-offsets' |
| 2512 | + source = 'pass' |
| 2513 | + expect = ''' |
| 2514 | + 0 0 RESUME 0 |
| 2515 | +
|
| 2516 | + 1 2 LOAD_CONST 0 (None) |
| 2517 | + 4 RETURN_VALUE |
| 2518 | + ''' |
| 2519 | + for flag in ['-O', '--show-offsets']: |
| 2520 | + self.check_output(source, expect, flag) |
| 2521 | + |
| 2522 | + def test_show_positions(self): |
| 2523 | + # test 'python -m dis -P/--show-positions' |
| 2524 | + source = 'pass' |
| 2525 | + expect = ''' |
| 2526 | + 0:0-1:0 RESUME 0 |
| 2527 | +
|
| 2528 | + 1:0-1:4 LOAD_CONST 0 (None) |
| 2529 | + 1:0-1:4 RETURN_VALUE |
| 2530 | + ''' |
| 2531 | + for flag in ['-P', '--show-positions']: |
| 2532 | + self.check_output(source, expect, flag) |
| 2533 | + |
| 2534 | + def test_specialized_code(self): |
| 2535 | + # test 'python -m dis -S/--specialized' |
| 2536 | + source = 'pass' |
| 2537 | + expect = ''' |
| 2538 | + 0 RESUME 0 |
| 2539 | +
|
| 2540 | + 1 LOAD_CONST_IMMORTAL 0 (None) |
| 2541 | + RETURN_VALUE |
| 2542 | + ''' |
| 2543 | + for flag in ['-S', '--specialized']: |
| 2544 | + self.check_output(source, expect, flag) |
| 2545 | + |
| 2546 | + |
2429 | 2547 | if __name__ == "__main__":
|
2430 | 2548 | unittest.main()
|
0 commit comments