Skip to content

Commit e85f2f1

Browse files
authored
gh-127637: add tests for dis command-line interface (#127759)
1 parent 5eb7fd4 commit e85f2f1

File tree

3 files changed

+123
-4
lines changed

3 files changed

+123
-4
lines changed

Lib/dis.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1115,7 +1115,7 @@ def dis(self):
11151115
return output.getvalue()
11161116

11171117

1118-
def main():
1118+
def main(args=None):
11191119
import argparse
11201120

11211121
parser = argparse.ArgumentParser()
@@ -1128,7 +1128,7 @@ def main():
11281128
parser.add_argument('-S', '--specialized', action='store_true',
11291129
help='show specialized bytecode')
11301130
parser.add_argument('infile', nargs='?', default='-')
1131-
args = parser.parse_args()
1131+
args = parser.parse_args(args=args)
11321132
if args.infile == '-':
11331133
name = '<stdin>'
11341134
source = sys.stdin.buffer.read()

Lib/test/test_dis.py

+120-2
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,19 @@
55
import dis
66
import functools
77
import io
8+
import itertools
9+
import opcode
810
import re
911
import sys
12+
import tempfile
13+
import textwrap
1014
import types
1115
import unittest
1216
from test.support import (captured_stdout, requires_debug_ranges,
13-
requires_specialization, cpython_only)
17+
requires_specialization, cpython_only,
18+
os_helper)
1419
from test.support.bytecode_helper import BytecodeTestCase
1520

16-
import opcode
1721

1822
CACHE = dis.opmap["CACHE"]
1923

@@ -2426,5 +2430,119 @@ def _unroll_caches_as_Instructions(instrs, show_caches=False):
24262430
False, None, None, instr.positions)
24272431

24282432

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+
24292547
if __name__ == "__main__":
24302548
unittest.main()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add tests for the :mod:`dis` command-line interface. Patch by Bénédikt Tran.

0 commit comments

Comments
 (0)