Skip to content

Commit 5c25bc5

Browse files
authored
gh-131178: Add tests for pickletools command-line interface (#131287)
1 parent f21ed37 commit 5c25bc5

File tree

2 files changed

+174
-2
lines changed

2 files changed

+174
-2
lines changed

Lib/pickletools.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2839,7 +2839,7 @@ def __init__(self, value):
28392839
}
28402840

28412841

2842-
if __name__ == "__main__":
2842+
def _main(args=None):
28432843
import argparse
28442844
parser = argparse.ArgumentParser(
28452845
description='disassemble one or more pickle files',
@@ -2864,7 +2864,7 @@ def __init__(self, value):
28642864
'-p', '--preamble', default="==> {name} <==",
28652865
help='if more than one pickle file is specified, print this before'
28662866
' each disassembly')
2867-
args = parser.parse_args()
2867+
args = parser.parse_args(args)
28682868
annotate = 30 if args.annotate else 0
28692869
memo = {} if args.memo else None
28702870
if args.output is None:
@@ -2885,3 +2885,7 @@ def __init__(self, value):
28852885
finally:
28862886
if output is not sys.stdout:
28872887
output.close()
2888+
2889+
2890+
if __name__ == "__main__":
2891+
_main()

Lib/test/test_pickletools.py

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import io
2+
import itertools
23
import pickle
34
import pickletools
5+
import tempfile
6+
import textwrap
47
from test import support
8+
from test.support import os_helper
59
from test.pickletester import AbstractPickleTests
610
import doctest
711
import unittest
@@ -514,6 +518,170 @@ def test__all__(self):
514518
support.check__all__(self, pickletools, not_exported=not_exported)
515519

516520

521+
class CommandLineTest(unittest.TestCase):
522+
def setUp(self):
523+
self.filename = tempfile.mktemp()
524+
self.addCleanup(os_helper.unlink, self.filename)
525+
526+
@staticmethod
527+
def text_normalize(string):
528+
return textwrap.dedent(string).strip()
529+
530+
def set_pickle_data(self, data):
531+
with open(self.filename, 'wb') as f:
532+
pickle.dump(data, f)
533+
534+
def invoke_pickletools(self, *flags):
535+
with (
536+
support.captured_stdout() as stdout,
537+
support.captured_stderr() as stderr,
538+
):
539+
pickletools._main(args=[*flags, self.filename])
540+
self.assertEqual(stderr.getvalue(), '')
541+
return self.text_normalize(stdout.getvalue())
542+
543+
def check_output(self, data, expect, *flags):
544+
with self.subTest(data=data, flags=flags):
545+
self.set_pickle_data(data)
546+
res = self.invoke_pickletools(*flags)
547+
expect = self.text_normalize(expect)
548+
self.assertListEqual(res.splitlines(), expect.splitlines())
549+
550+
def test_invocation(self):
551+
# test various combinations of parameters
552+
output_file = tempfile.mktemp()
553+
self.addCleanup(os_helper.unlink, output_file)
554+
base_flags = [
555+
(f'-o={output_file}', f'--output={output_file}'),
556+
('-m', '--memo'),
557+
('-l=2', '--indentlevel=2'),
558+
('-a', '--annotate'),
559+
('-p="Another:"', '--preamble="Another:"'),
560+
]
561+
data = { 'a', 'b', 'c' }
562+
563+
self.set_pickle_data(data)
564+
565+
for r in range(1, len(base_flags) + 1):
566+
for choices in itertools.combinations(base_flags, r=r):
567+
for args in itertools.product(*choices):
568+
with self.subTest(args=args[1:]):
569+
self.invoke_pickletools(*args)
570+
571+
def test_unknown_flag(self):
572+
with self.assertRaises(SystemExit):
573+
with support.captured_stderr() as stderr:
574+
pickletools._main(args=['--unknown'])
575+
self.assertStartsWith(stderr.getvalue(), 'usage: ')
576+
577+
def test_output_flag(self):
578+
# test 'python -m pickletools -o/--output'
579+
output_file = tempfile.mktemp()
580+
self.addCleanup(os_helper.unlink, output_file)
581+
data = ('fake_data',)
582+
expect = r'''
583+
0: \x80 PROTO 5
584+
2: \x95 FRAME 15
585+
11: \x8c SHORT_BINUNICODE 'fake_data'
586+
22: \x94 MEMOIZE (as 0)
587+
23: \x85 TUPLE1
588+
24: \x94 MEMOIZE (as 1)
589+
25: . STOP
590+
highest protocol among opcodes = 4
591+
'''
592+
for flag in [f'-o={output_file}', f'--output={output_file}']:
593+
with self.subTest(data=data, flags=flag):
594+
self.set_pickle_data(data)
595+
res = self.invoke_pickletools(flag)
596+
with open(output_file, 'r') as f:
597+
res_from_file = self.text_normalize(f.read())
598+
expect = self.text_normalize(expect)
599+
600+
self.assertListEqual(res.splitlines(), [])
601+
self.assertListEqual(res_from_file.splitlines(),
602+
expect.splitlines())
603+
604+
def test_memo_flag(self):
605+
# test 'python -m pickletools -m/--memo'
606+
data = ('fake_data',)
607+
expect = r'''
608+
0: \x80 PROTO 5
609+
2: \x95 FRAME 15
610+
11: \x8c SHORT_BINUNICODE 'fake_data'
611+
22: \x94 MEMOIZE (as 0)
612+
23: \x85 TUPLE1
613+
24: \x94 MEMOIZE (as 1)
614+
25: . STOP
615+
highest protocol among opcodes = 4
616+
'''
617+
for flag in ['-m', '--memo']:
618+
self.check_output(data, expect, flag)
619+
620+
def test_indentlevel_flag(self):
621+
# test 'python -m pickletools -l/--indentlevel'
622+
data = ('fake_data',)
623+
expect = r'''
624+
0: \x80 PROTO 5
625+
2: \x95 FRAME 15
626+
11: \x8c SHORT_BINUNICODE 'fake_data'
627+
22: \x94 MEMOIZE (as 0)
628+
23: \x85 TUPLE1
629+
24: \x94 MEMOIZE (as 1)
630+
25: . STOP
631+
highest protocol among opcodes = 4
632+
'''
633+
for flag in ['-l=2', '--indentlevel=2']:
634+
self.check_output(data, expect, flag)
635+
636+
def test_annotate_flag(self):
637+
# test 'python -m pickletools -a/--annotate'
638+
data = ('fake_data',)
639+
expect = r'''
640+
0: \x80 PROTO 5 Protocol version indicator.
641+
2: \x95 FRAME 15 Indicate the beginning of a new frame.
642+
11: \x8c SHORT_BINUNICODE 'fake_data' Push a Python Unicode string object.
643+
22: \x94 MEMOIZE (as 0) Store the stack top into the memo. The stack is not popped.
644+
23: \x85 TUPLE1 Build a one-tuple out of the topmost item on the stack.
645+
24: \x94 MEMOIZE (as 1) Store the stack top into the memo. The stack is not popped.
646+
25: . STOP Stop the unpickling machine.
647+
highest protocol among opcodes = 4
648+
'''
649+
for flag in ['-a', '--annotate']:
650+
self.check_output(data, expect, flag)
651+
652+
def test_preamble_flag(self):
653+
# test 'python -m pickletools -p/--preamble'
654+
data = ('fake_data',)
655+
expect = r'''
656+
Another:
657+
0: \x80 PROTO 5
658+
2: \x95 FRAME 15
659+
11: \x8c SHORT_BINUNICODE 'fake_data'
660+
22: \x94 MEMOIZE (as 0)
661+
23: \x85 TUPLE1
662+
24: \x94 MEMOIZE (as 1)
663+
25: . STOP
664+
highest protocol among opcodes = 4
665+
Another:
666+
0: \x80 PROTO 5
667+
2: \x95 FRAME 15
668+
11: \x8c SHORT_BINUNICODE 'fake_data'
669+
22: \x94 MEMOIZE (as 0)
670+
23: \x85 TUPLE1
671+
24: \x94 MEMOIZE (as 1)
672+
25: . STOP
673+
highest protocol among opcodes = 4
674+
'''
675+
for flag in ['-p=Another:', '--preamble=Another:']:
676+
with self.subTest(data=data, flags=flag):
677+
self.set_pickle_data(data)
678+
with support.captured_stdout() as stdout:
679+
pickletools._main(args=[flag, self.filename, self.filename])
680+
res = self.text_normalize(stdout.getvalue())
681+
expect = self.text_normalize(expect)
682+
self.assertListEqual(res.splitlines(), expect.splitlines())
683+
684+
517685
def load_tests(loader, tests, pattern):
518686
tests.addTest(doctest.DocTestSuite(pickletools))
519687
return tests

0 commit comments

Comments
 (0)