Skip to content

Commit eb3ad0f

Browse files
committed
pythongh-127637: add tests for dis command-line interface (python#127759)
1 parent 9d0252f commit eb3ad0f

File tree

3 files changed

+123
-4
lines changed

3 files changed

+123
-4
lines changed

Lib/dis.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1051,7 +1051,7 @@ def dis(self):
10511051
return output.getvalue()
10521052

10531053

1054-
def main():
1054+
def main(args=None):
10551055
import argparse
10561056

10571057
parser = argparse.ArgumentParser()
@@ -1060,7 +1060,7 @@ def main():
10601060
parser.add_argument('-O', '--show-offsets', action='store_true',
10611061
help='show instruction offsets')
10621062
parser.add_argument('infile', nargs='?', default='-')
1063-
args = parser.parse_args()
1063+
args = parser.parse_args(args=args)
10641064
if args.infile == '-':
10651065
name = '<stdin>'
10661066
source = sys.stdin.buffer.read()

Lib/test/test_dis.py

Lines changed: 120 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,19 @@
44
import dis
55
import functools
66
import io
7+
import itertools
8+
import opcode
79
import re
810
import sys
11+
import tempfile
12+
import textwrap
913
import types
1014
import unittest
1115
from test.support import (captured_stdout, requires_debug_ranges,
12-
requires_specialization, cpython_only)
16+
requires_specialization, cpython_only,
17+
os_helper)
1318
from test.support.bytecode_helper import BytecodeTestCase
1419

15-
import opcode
1620

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

@@ -2281,5 +2285,119 @@ def _unroll_caches_as_Instructions(instrs, show_caches=False):
22812285
False, None, None, instr.positions)
22822286

22832287

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+
('-P', '--show-positions'),
2326+
('-S', '--specialized'),
2327+
]
2328+
2329+
self.set_source('''
2330+
def f():
2331+
print(x)
2332+
return None
2333+
''')
2334+
2335+
for r in range(1, len(base_flags) + 1):
2336+
for choices in itertools.combinations(base_flags, r=r):
2337+
for args in itertools.product(*choices):
2338+
with self.subTest(args=args[1:]):
2339+
_ = self.invoke_dis(*args)
2340+
2341+
with self.assertRaises(SystemExit):
2342+
# suppress argparse error message
2343+
with contextlib.redirect_stderr(io.StringIO()):
2344+
_ = self.invoke_dis('--unknown')
2345+
2346+
def test_show_cache(self):
2347+
# test 'python -m dis -C/--show-caches'
2348+
source = 'print()'
2349+
expect = '''
2350+
0 RESUME 0
2351+
2352+
1 LOAD_NAME 0 (print)
2353+
PUSH_NULL
2354+
CALL 0
2355+
CACHE 0 (counter: 0)
2356+
CACHE 0 (func_version: 0)
2357+
CACHE 0
2358+
POP_TOP
2359+
LOAD_CONST 0 (None)
2360+
RETURN_VALUE
2361+
'''
2362+
for flag in ['-C', '--show-caches']:
2363+
self.check_output(source, expect, flag)
2364+
2365+
def test_show_offsets(self):
2366+
# test 'python -m dis -O/--show-offsets'
2367+
source = 'pass'
2368+
expect = '''
2369+
0 0 RESUME 0
2370+
2371+
1 2 LOAD_CONST 0 (None)
2372+
4 RETURN_VALUE
2373+
'''
2374+
for flag in ['-O', '--show-offsets']:
2375+
self.check_output(source, expect, flag)
2376+
2377+
def test_show_positions(self):
2378+
# test 'python -m dis -P/--show-positions'
2379+
source = 'pass'
2380+
expect = '''
2381+
0:0-1:0 RESUME 0
2382+
2383+
1:0-1:4 LOAD_CONST 0 (None)
2384+
1:0-1:4 RETURN_VALUE
2385+
'''
2386+
for flag in ['-P', '--show-positions']:
2387+
self.check_output(source, expect, flag)
2388+
2389+
def test_specialized_code(self):
2390+
# test 'python -m dis -S/--specialized'
2391+
source = 'pass'
2392+
expect = '''
2393+
0 RESUME 0
2394+
2395+
1 LOAD_CONST_IMMORTAL 0 (None)
2396+
RETURN_VALUE
2397+
'''
2398+
for flag in ['-S', '--specialized']:
2399+
self.check_output(source, expect, flag)
2400+
2401+
22842402
if __name__ == "__main__":
22852403
unittest.main()
Lines changed: 1 addition & 0 deletions
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)