From bd6af2cfc8791f80b06f2089b5332dd0df233917 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Mon, 9 Dec 2024 12:26:45 +0100 Subject: [PATCH 1/5] add tests for `dis` command-line interface --- Lib/dis.py | 4 +- Lib/test/test_dis.py | 104 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 104 insertions(+), 4 deletions(-) diff --git a/Lib/dis.py b/Lib/dis.py index 6b3e9ef8399e1c..aa22404c6687e1 100644 --- a/Lib/dis.py +++ b/Lib/dis.py @@ -1115,7 +1115,7 @@ def dis(self): return output.getvalue() -def main(): +def main(args=None): import argparse parser = argparse.ArgumentParser() @@ -1128,7 +1128,7 @@ def main(): parser.add_argument('-S', '--specialized', action='store_true', help='show specialized bytecode') parser.add_argument('infile', nargs='?', default='-') - args = parser.parse_args() + args = parser.parse_args(args=args) if args.infile == '-': name = '' source = sys.stdin.buffer.read() diff --git a/Lib/test/test_dis.py b/Lib/test/test_dis.py index 55890e58ed4bae..21da55bc0f3f7c 100644 --- a/Lib/test/test_dis.py +++ b/Lib/test/test_dis.py @@ -5,15 +5,19 @@ import dis import functools import io +import itertools +import opcode import re import sys +import tempfile +import textwrap import types import unittest from test.support import (captured_stdout, requires_debug_ranges, - requires_specialization, cpython_only) + requires_specialization, cpython_only, + os_helper) from test.support.bytecode_helper import BytecodeTestCase -import opcode CACHE = dis.opmap["CACHE"] @@ -2426,5 +2430,101 @@ def _unroll_caches_as_Instructions(instrs, show_caches=False): False, None, None, instr.positions) +class TestDisCLI(unittest.TestCase): + + def infile(self, content): + filename = tempfile.mktemp() + self.addCleanup(os_helper.unlink, filename) + with open(filename, 'w') as fp: + fp.write(content) + return filename + + def invoke_dis(self, infile, *flags): + output = io.StringIO() + with contextlib.redirect_stdout(output): + dis.main(args=[*flags, infile]) + return output.getvalue() + + def check_output(self, source, expect, *flags): + with self.subTest(flags): + infile = self.infile(source) + res = self.invoke_dis(infile, *flags) + res = textwrap.dedent(res) + expect = textwrap.dedent(expect) + self.assertListEqual(res.splitlines(), expect.splitlines()) + + def test_invokation(self): + # test various combinations of parameters + base_flags = [ + ('-C', '--show-caches'), + ('-O', '--show-offsets'), + ('-P', '--show-positions'), + ('-S', '--specialized'), + ] + + infile = self.infile('def f():\n\tprint(x)\n\treturn None') + + for r in range(1, len(base_flags) + 1): + for choices in itertools.combinations(base_flags, r=r): + for args in itertools.product(*choices): + with self.subTest(args=args[1:]): + _ = self.invoke_dis(infile, *args) + + def test_show_cache(self): + # test 'python -m dis -C/--show-caches' + source = 'print()' + expect = '''\ +0 RESUME 0 + +1 LOAD_NAME 0 (print) + PUSH_NULL + CALL 0 + CACHE 0 (counter: 0) + CACHE 0 (func_version: 0) + CACHE 0 + POP_TOP + LOAD_CONST 0 (None) + RETURN_VALUE +''' + for flag in ['-C', '--show-caches']: + self.check_output(source, expect, flag) + + def test_show_offsets(self): + # test 'python -m dis -O/--show-offsets' + source = 'pass' + expect = '''\ +0 0 RESUME 0 + +1 2 LOAD_CONST 0 (None) + 4 RETURN_VALUE + ''' + for flag in ['-O', '--show-offsets']: + self.check_output(source, expect, flag) + + def test_show_positions(self): + # test 'python -m dis -P/--show-positions' + source = 'pass' + expect = '''\ +0:0-1:0 RESUME 0 + +1:0-1:4 LOAD_CONST 0 (None) +1:0-1:4 RETURN_VALUE +''' + for flag in ['-P', '--show-positions']: + self.check_output(source, expect, flag) + + def test_specialized_code(self): + # test 'python -m dis -S/--specialized' + source = 'pass' + expect = '''\ +0 RESUME 0 + +1 LOAD_CONST_IMMORTAL 0 (None) + RETURN_VALUE +''' + for flag in ['-S', '--specialized']: + self.check_output(source, expect, flag) + + if __name__ == "__main__": unittest.main() From a240b8134e895ecca7017158509dc4a44be4bebd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Mon, 9 Dec 2024 12:35:52 +0100 Subject: [PATCH 2/5] blurb --- .../next/Tests/2024-12-09-12-35-44.gh-issue-127637.KLx-9I.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Tests/2024-12-09-12-35-44.gh-issue-127637.KLx-9I.rst diff --git a/Misc/NEWS.d/next/Tests/2024-12-09-12-35-44.gh-issue-127637.KLx-9I.rst b/Misc/NEWS.d/next/Tests/2024-12-09-12-35-44.gh-issue-127637.KLx-9I.rst new file mode 100644 index 00000000000000..ac5d9827b07199 --- /dev/null +++ b/Misc/NEWS.d/next/Tests/2024-12-09-12-35-44.gh-issue-127637.KLx-9I.rst @@ -0,0 +1 @@ +Add tests for the :mod:`dis` command-line interface. Patch by Bénédikt Tran. From d27781492ac7ca2f3207a32049457535bf190aa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Mon, 9 Dec 2024 16:01:08 +0100 Subject: [PATCH 3/5] address Irit's review --- Lib/test/test_dis.py | 88 +++++++++++++++++++++++--------------------- 1 file changed, 46 insertions(+), 42 deletions(-) diff --git a/Lib/test/test_dis.py b/Lib/test/test_dis.py index 21da55bc0f3f7c..c68c4255e10ce9 100644 --- a/Lib/test/test_dis.py +++ b/Lib/test/test_dis.py @@ -2432,25 +2432,25 @@ def _unroll_caches_as_Instructions(instrs, show_caches=False): class TestDisCLI(unittest.TestCase): - def infile(self, content): - filename = tempfile.mktemp() - self.addCleanup(os_helper.unlink, filename) - with open(filename, 'w') as fp: - fp.write(content) - return filename - - def invoke_dis(self, infile, *flags): + def setUp(self): + self.filename = tempfile.mktemp() + self.addCleanup(os_helper.unlink, self.filename) + + def set_source(self, content): + with open(self.filename, 'w') as fp: + fp.write(textwrap.dedent(content).strip()) + + def invoke_dis(self, *flags): output = io.StringIO() with contextlib.redirect_stdout(output): - dis.main(args=[*flags, infile]) + dis.main(args=[*flags, self.filename]) return output.getvalue() def check_output(self, source, expect, *flags): with self.subTest(flags): - infile = self.infile(source) - res = self.invoke_dis(infile, *flags) + self.set_source(source) + res = self.invoke_dis(*flags) res = textwrap.dedent(res) - expect = textwrap.dedent(expect) self.assertListEqual(res.splitlines(), expect.splitlines()) def test_invokation(self): @@ -2462,66 +2462,70 @@ def test_invokation(self): ('-S', '--specialized'), ] - infile = self.infile('def f():\n\tprint(x)\n\treturn None') + self.set_source(''' + def f(): + print(x) + return None + ''') for r in range(1, len(base_flags) + 1): for choices in itertools.combinations(base_flags, r=r): for args in itertools.product(*choices): with self.subTest(args=args[1:]): - _ = self.invoke_dis(infile, *args) + _ = self.invoke_dis(*args) def test_show_cache(self): # test 'python -m dis -C/--show-caches' source = 'print()' - expect = '''\ -0 RESUME 0 - -1 LOAD_NAME 0 (print) - PUSH_NULL - CALL 0 - CACHE 0 (counter: 0) - CACHE 0 (func_version: 0) - CACHE 0 - POP_TOP - LOAD_CONST 0 (None) - RETURN_VALUE -''' + expect = textwrap.dedent(''' + 0 RESUME 0 + + 1 LOAD_NAME 0 (print) + PUSH_NULL + CALL 0 + CACHE 0 (counter: 0) + CACHE 0 (func_version: 0) + CACHE 0 + POP_TOP + LOAD_CONST 0 (None) + RETURN_VALUE + ''').strip() for flag in ['-C', '--show-caches']: self.check_output(source, expect, flag) def test_show_offsets(self): # test 'python -m dis -O/--show-offsets' source = 'pass' - expect = '''\ -0 0 RESUME 0 + expect = textwrap.dedent(''' + 0 0 RESUME 0 -1 2 LOAD_CONST 0 (None) - 4 RETURN_VALUE - ''' + 1 2 LOAD_CONST 0 (None) + 4 RETURN_VALUE + ''').strip() for flag in ['-O', '--show-offsets']: self.check_output(source, expect, flag) def test_show_positions(self): # test 'python -m dis -P/--show-positions' source = 'pass' - expect = '''\ -0:0-1:0 RESUME 0 + expect = textwrap.dedent(''' + 0:0-1:0 RESUME 0 -1:0-1:4 LOAD_CONST 0 (None) -1:0-1:4 RETURN_VALUE -''' + 1:0-1:4 LOAD_CONST 0 (None) + 1:0-1:4 RETURN_VALUE + ''').strip() for flag in ['-P', '--show-positions']: self.check_output(source, expect, flag) def test_specialized_code(self): # test 'python -m dis -S/--specialized' source = 'pass' - expect = '''\ -0 RESUME 0 + expect = textwrap.dedent(''' + 0 RESUME 0 -1 LOAD_CONST_IMMORTAL 0 (None) - RETURN_VALUE -''' + 1 LOAD_CONST_IMMORTAL 0 (None) + RETURN_VALUE + ''').strip() for flag in ['-S', '--specialized']: self.check_output(source, expect, flag) From c2a0b43ad30b951810969d8f6dbeb0861563ca2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Mon, 9 Dec 2024 17:06:47 +0100 Subject: [PATCH 4/5] address Irit's review (round 2) --- Lib/test/test_dis.py | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/Lib/test/test_dis.py b/Lib/test/test_dis.py index c68c4255e10ce9..f5a99a173dbe21 100644 --- a/Lib/test/test_dis.py +++ b/Lib/test/test_dis.py @@ -2436,21 +2436,30 @@ def setUp(self): self.filename = tempfile.mktemp() self.addCleanup(os_helper.unlink, self.filename) + @staticmethod + def text_normalize(string): + """Dedent *string* and strip it from its surrounding whitespaces. + + This method is used by the other utility functions so that any + string to write or to match against can be freely indented. + """ + return textwrap.dedent(string).strip() + def set_source(self, content): with open(self.filename, 'w') as fp: - fp.write(textwrap.dedent(content).strip()) + fp.write(self.text_normalize(content)) def invoke_dis(self, *flags): output = io.StringIO() with contextlib.redirect_stdout(output): dis.main(args=[*flags, self.filename]) - return output.getvalue() + return self.text_normalize(output.getvalue()) def check_output(self, source, expect, *flags): - with self.subTest(flags): + with self.subTest(source=source, flags=flags): self.set_source(source) res = self.invoke_dis(*flags) - res = textwrap.dedent(res) + expect = self.text_normalize(expect) self.assertListEqual(res.splitlines(), expect.splitlines()) def test_invokation(self): @@ -2477,7 +2486,7 @@ def f(): def test_show_cache(self): # test 'python -m dis -C/--show-caches' source = 'print()' - expect = textwrap.dedent(''' + expect = ''' 0 RESUME 0 1 LOAD_NAME 0 (print) @@ -2489,43 +2498,43 @@ def test_show_cache(self): POP_TOP LOAD_CONST 0 (None) RETURN_VALUE - ''').strip() + ''' for flag in ['-C', '--show-caches']: self.check_output(source, expect, flag) def test_show_offsets(self): # test 'python -m dis -O/--show-offsets' source = 'pass' - expect = textwrap.dedent(''' + expect = ''' 0 0 RESUME 0 1 2 LOAD_CONST 0 (None) 4 RETURN_VALUE - ''').strip() + ''' for flag in ['-O', '--show-offsets']: self.check_output(source, expect, flag) def test_show_positions(self): # test 'python -m dis -P/--show-positions' source = 'pass' - expect = textwrap.dedent(''' + expect = ''' 0:0-1:0 RESUME 0 1:0-1:4 LOAD_CONST 0 (None) 1:0-1:4 RETURN_VALUE - ''').strip() + ''' for flag in ['-P', '--show-positions']: self.check_output(source, expect, flag) def test_specialized_code(self): # test 'python -m dis -S/--specialized' source = 'pass' - expect = textwrap.dedent(''' + expect = ''' 0 RESUME 0 1 LOAD_CONST_IMMORTAL 0 (None) RETURN_VALUE - ''').strip() + ''' for flag in ['-S', '--specialized']: self.check_output(source, expect, flag) From ca68f3bda423bc08cb54657701a393857b9b3a01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Mon, 9 Dec 2024 17:15:41 +0100 Subject: [PATCH 5/5] add test for unknown option --- Lib/test/test_dis.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_dis.py b/Lib/test/test_dis.py index f5a99a173dbe21..c719f571152d61 100644 --- a/Lib/test/test_dis.py +++ b/Lib/test/test_dis.py @@ -2462,7 +2462,7 @@ def check_output(self, source, expect, *flags): expect = self.text_normalize(expect) self.assertListEqual(res.splitlines(), expect.splitlines()) - def test_invokation(self): + def test_invocation(self): # test various combinations of parameters base_flags = [ ('-C', '--show-caches'), @@ -2483,6 +2483,11 @@ def f(): with self.subTest(args=args[1:]): _ = self.invoke_dis(*args) + with self.assertRaises(SystemExit): + # suppress argparse error message + with contextlib.redirect_stderr(io.StringIO()): + _ = self.invoke_dis('--unknown') + def test_show_cache(self): # test 'python -m dis -C/--show-caches' source = 'print()' pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy