|
4 | 4 | import dis
|
5 | 5 | import functools
|
6 | 6 | import io
|
| 7 | +import itertools |
| 8 | +import opcode |
7 | 9 | import re
|
8 | 10 | import sys
|
| 11 | +import tempfile |
| 12 | +import textwrap |
9 | 13 | import types
|
10 | 14 | import unittest
|
11 | 15 | from test.support import (captured_stdout, requires_debug_ranges,
|
12 |
| - requires_specialization, cpython_only) |
| 16 | + requires_specialization, cpython_only, |
| 17 | + os_helper) |
13 | 18 | from test.support.bytecode_helper import BytecodeTestCase
|
14 | 19 |
|
15 |
| -import opcode |
16 | 20 |
|
17 | 21 | CACHE = dis.opmap["CACHE"]
|
18 | 22 |
|
@@ -2281,5 +2285,91 @@ def _unroll_caches_as_Instructions(instrs, show_caches=False):
|
2281 | 2285 | False, None, None, instr.positions)
|
2282 | 2286 |
|
2283 | 2287 |
|
| 2288 | +class TestDisCLI(unittest.TestCase): |
| 2289 | + |
| 2290 | + def setUp(self): |
| 2291 | + self.filename = tempfile.mktemp() |
| 2292 | + self.addCleanup(os_helper.unlink, self.filename) |
| 2293 | + |
| 2294 | + @staticmethod |
| 2295 | + def text_normalize(string): |
| 2296 | + """Dedent *string* and strip it from its surrounding whitespaces. |
| 2297 | +
|
| 2298 | + This method is used by the other utility functions so that any |
| 2299 | + string to write or to match against can be freely indented. |
| 2300 | + """ |
| 2301 | + return textwrap.dedent(string).strip() |
| 2302 | + |
| 2303 | + def set_source(self, content): |
| 2304 | + with open(self.filename, 'w') as fp: |
| 2305 | + fp.write(self.text_normalize(content)) |
| 2306 | + |
| 2307 | + def invoke_dis(self, *flags): |
| 2308 | + output = io.StringIO() |
| 2309 | + with contextlib.redirect_stdout(output): |
| 2310 | + dis.main(args=[*flags, self.filename]) |
| 2311 | + return self.text_normalize(output.getvalue()) |
| 2312 | + |
| 2313 | + def check_output(self, source, expect, *flags): |
| 2314 | + with self.subTest(source=source, flags=flags): |
| 2315 | + self.set_source(source) |
| 2316 | + res = self.invoke_dis(*flags) |
| 2317 | + expect = self.text_normalize(expect) |
| 2318 | + self.assertListEqual(res.splitlines(), expect.splitlines()) |
| 2319 | + |
| 2320 | + def test_invocation(self): |
| 2321 | + # test various combinations of parameters |
| 2322 | + base_flags = [ |
| 2323 | + ('-C', '--show-caches'), |
| 2324 | + ('-O', '--show-offsets'), |
| 2325 | + ] |
| 2326 | + |
| 2327 | + self.set_source(''' |
| 2328 | + def f(): |
| 2329 | + print(x) |
| 2330 | + return None |
| 2331 | + ''') |
| 2332 | + |
| 2333 | + for r in range(1, len(base_flags) + 1): |
| 2334 | + for choices in itertools.combinations(base_flags, r=r): |
| 2335 | + for args in itertools.product(*choices): |
| 2336 | + with self.subTest(args=args[1:]): |
| 2337 | + _ = self.invoke_dis(*args) |
| 2338 | + |
| 2339 | + with self.assertRaises(SystemExit): |
| 2340 | + # suppress argparse error message |
| 2341 | + with contextlib.redirect_stderr(io.StringIO()): |
| 2342 | + _ = self.invoke_dis('--unknown') |
| 2343 | + |
| 2344 | + def test_show_cache(self): |
| 2345 | + # test 'python -m dis -C/--show-caches' |
| 2346 | + source = 'print()' |
| 2347 | + expect = ''' |
| 2348 | + 0 RESUME 0 |
| 2349 | +
|
| 2350 | + 1 LOAD_NAME 0 (print) |
| 2351 | + PUSH_NULL |
| 2352 | + CALL 0 |
| 2353 | + CACHE 0 (counter: 0) |
| 2354 | + CACHE 0 (func_version: 0) |
| 2355 | + CACHE 0 |
| 2356 | + POP_TOP |
| 2357 | + RETURN_CONST 0 (None) |
| 2358 | + ''' |
| 2359 | + for flag in ['-C', '--show-caches']: |
| 2360 | + self.check_output(source, expect, flag) |
| 2361 | + |
| 2362 | + def test_show_offsets(self): |
| 2363 | + # test 'python -m dis -O/--show-offsets' |
| 2364 | + source = 'pass' |
| 2365 | + expect = ''' |
| 2366 | + 0 0 RESUME 0 |
| 2367 | +
|
| 2368 | + 1 2 RETURN_CONST 0 (None) |
| 2369 | + ''' |
| 2370 | + for flag in ['-O', '--show-offsets']: |
| 2371 | + self.check_output(source, expect, flag) |
| 2372 | + |
| 2373 | + |
2284 | 2374 | if __name__ == "__main__":
|
2285 | 2375 | unittest.main()
|
0 commit comments