|  | 
| 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,93 @@ 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 | +                        LOAD_CONST               0 (None) | 
|  | 2358 | +                        RETURN_VALUE | 
|  | 2359 | +        ''' | 
|  | 2360 | +        for flag in ['-C', '--show-caches']: | 
|  | 2361 | +            self.check_output(source, expect, flag) | 
|  | 2362 | + | 
|  | 2363 | +    def test_show_offsets(self): | 
|  | 2364 | +        # test 'python -m dis -O/--show-offsets' | 
|  | 2365 | +        source = 'pass' | 
|  | 2366 | +        expect = ''' | 
|  | 2367 | +            0          0       RESUME                   0 | 
|  | 2368 | +
 | 
|  | 2369 | +            1          2       LOAD_CONST               0 (None) | 
|  | 2370 | +                       4       RETURN_VALUE | 
|  | 2371 | +        ''' | 
|  | 2372 | +        for flag in ['-O', '--show-offsets']: | 
|  | 2373 | +            self.check_output(source, expect, flag) | 
|  | 2374 | + | 
|  | 2375 | + | 
| 2284 | 2376 | if __name__ == "__main__": | 
| 2285 | 2377 |     unittest.main() | 
0 commit comments