gh-131178: Add tests for pickletools command-line interface (#131287)

This commit is contained in:
Semyon Moroz 2025-11-22 21:17:06 +04:00 committed by GitHub
parent f21ed37daf
commit 5c25bc5763
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 174 additions and 2 deletions

View file

@ -2839,7 +2839,7 @@ def __init__(self, value):
}
if __name__ == "__main__":
def _main(args=None):
import argparse
parser = argparse.ArgumentParser(
description='disassemble one or more pickle files',
@ -2864,7 +2864,7 @@ def __init__(self, value):
'-p', '--preamble', default="==> {name} <==",
help='if more than one pickle file is specified, print this before'
' each disassembly')
args = parser.parse_args()
args = parser.parse_args(args)
annotate = 30 if args.annotate else 0
memo = {} if args.memo else None
if args.output is None:
@ -2885,3 +2885,7 @@ def __init__(self, value):
finally:
if output is not sys.stdout:
output.close()
if __name__ == "__main__":
_main()

View file

@ -1,7 +1,11 @@
import io
import itertools
import pickle
import pickletools
import tempfile
import textwrap
from test import support
from test.support import os_helper
from test.pickletester import AbstractPickleTests
import doctest
import unittest
@ -514,6 +518,170 @@ def test__all__(self):
support.check__all__(self, pickletools, not_exported=not_exported)
class CommandLineTest(unittest.TestCase):
def setUp(self):
self.filename = tempfile.mktemp()
self.addCleanup(os_helper.unlink, self.filename)
@staticmethod
def text_normalize(string):
return textwrap.dedent(string).strip()
def set_pickle_data(self, data):
with open(self.filename, 'wb') as f:
pickle.dump(data, f)
def invoke_pickletools(self, *flags):
with (
support.captured_stdout() as stdout,
support.captured_stderr() as stderr,
):
pickletools._main(args=[*flags, self.filename])
self.assertEqual(stderr.getvalue(), '')
return self.text_normalize(stdout.getvalue())
def check_output(self, data, expect, *flags):
with self.subTest(data=data, flags=flags):
self.set_pickle_data(data)
res = self.invoke_pickletools(*flags)
expect = self.text_normalize(expect)
self.assertListEqual(res.splitlines(), expect.splitlines())
def test_invocation(self):
# test various combinations of parameters
output_file = tempfile.mktemp()
self.addCleanup(os_helper.unlink, output_file)
base_flags = [
(f'-o={output_file}', f'--output={output_file}'),
('-m', '--memo'),
('-l=2', '--indentlevel=2'),
('-a', '--annotate'),
('-p="Another:"', '--preamble="Another:"'),
]
data = { 'a', 'b', 'c' }
self.set_pickle_data(data)
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_pickletools(*args)
def test_unknown_flag(self):
with self.assertRaises(SystemExit):
with support.captured_stderr() as stderr:
pickletools._main(args=['--unknown'])
self.assertStartsWith(stderr.getvalue(), 'usage: ')
def test_output_flag(self):
# test 'python -m pickletools -o/--output'
output_file = tempfile.mktemp()
self.addCleanup(os_helper.unlink, output_file)
data = ('fake_data',)
expect = r'''
0: \x80 PROTO 5
2: \x95 FRAME 15
11: \x8c SHORT_BINUNICODE 'fake_data'
22: \x94 MEMOIZE (as 0)
23: \x85 TUPLE1
24: \x94 MEMOIZE (as 1)
25: . STOP
highest protocol among opcodes = 4
'''
for flag in [f'-o={output_file}', f'--output={output_file}']:
with self.subTest(data=data, flags=flag):
self.set_pickle_data(data)
res = self.invoke_pickletools(flag)
with open(output_file, 'r') as f:
res_from_file = self.text_normalize(f.read())
expect = self.text_normalize(expect)
self.assertListEqual(res.splitlines(), [])
self.assertListEqual(res_from_file.splitlines(),
expect.splitlines())
def test_memo_flag(self):
# test 'python -m pickletools -m/--memo'
data = ('fake_data',)
expect = r'''
0: \x80 PROTO 5
2: \x95 FRAME 15
11: \x8c SHORT_BINUNICODE 'fake_data'
22: \x94 MEMOIZE (as 0)
23: \x85 TUPLE1
24: \x94 MEMOIZE (as 1)
25: . STOP
highest protocol among opcodes = 4
'''
for flag in ['-m', '--memo']:
self.check_output(data, expect, flag)
def test_indentlevel_flag(self):
# test 'python -m pickletools -l/--indentlevel'
data = ('fake_data',)
expect = r'''
0: \x80 PROTO 5
2: \x95 FRAME 15
11: \x8c SHORT_BINUNICODE 'fake_data'
22: \x94 MEMOIZE (as 0)
23: \x85 TUPLE1
24: \x94 MEMOIZE (as 1)
25: . STOP
highest protocol among opcodes = 4
'''
for flag in ['-l=2', '--indentlevel=2']:
self.check_output(data, expect, flag)
def test_annotate_flag(self):
# test 'python -m pickletools -a/--annotate'
data = ('fake_data',)
expect = r'''
0: \x80 PROTO 5 Protocol version indicator.
2: \x95 FRAME 15 Indicate the beginning of a new frame.
11: \x8c SHORT_BINUNICODE 'fake_data' Push a Python Unicode string object.
22: \x94 MEMOIZE (as 0) Store the stack top into the memo. The stack is not popped.
23: \x85 TUPLE1 Build a one-tuple out of the topmost item on the stack.
24: \x94 MEMOIZE (as 1) Store the stack top into the memo. The stack is not popped.
25: . STOP Stop the unpickling machine.
highest protocol among opcodes = 4
'''
for flag in ['-a', '--annotate']:
self.check_output(data, expect, flag)
def test_preamble_flag(self):
# test 'python -m pickletools -p/--preamble'
data = ('fake_data',)
expect = r'''
Another:
0: \x80 PROTO 5
2: \x95 FRAME 15
11: \x8c SHORT_BINUNICODE 'fake_data'
22: \x94 MEMOIZE (as 0)
23: \x85 TUPLE1
24: \x94 MEMOIZE (as 1)
25: . STOP
highest protocol among opcodes = 4
Another:
0: \x80 PROTO 5
2: \x95 FRAME 15
11: \x8c SHORT_BINUNICODE 'fake_data'
22: \x94 MEMOIZE (as 0)
23: \x85 TUPLE1
24: \x94 MEMOIZE (as 1)
25: . STOP
highest protocol among opcodes = 4
'''
for flag in ['-p=Another:', '--preamble=Another:']:
with self.subTest(data=data, flags=flag):
self.set_pickle_data(data)
with support.captured_stdout() as stdout:
pickletools._main(args=[flag, self.filename, self.filename])
res = self.text_normalize(stdout.getvalue())
expect = self.text_normalize(expect)
self.assertListEqual(res.splitlines(), expect.splitlines())
def load_tests(loader, tests, pattern):
tests.addTest(doctest.DocTestSuite(pickletools))
return tests