Skip to content

Commit 83afce1

Browse files
committed
Add tests for pickletools command-line interface
1 parent 55815a6 commit 83afce1

File tree

2 files changed

+177
-2
lines changed

2 files changed

+177
-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')
@@ -2862,7 +2862,7 @@ def __init__(self, value):
28622862
'-p', '--preamble', default="==> {name} <==",
28632863
help='if more than one pickle file is specified, print this before'
28642864
' each disassembly')
2865-
args = parser.parse_args()
2865+
args = parser.parse_args(args)
28662866
if not args.pickle_file:
28672867
parser.print_help()
28682868
else:
@@ -2886,3 +2886,7 @@ def __init__(self, value):
28862886
finally:
28872887
if output is not sys.stdout:
28882888
output.close()
2889+
2890+
2891+
if __name__ == "__main__":
2892+
_main()

Lib/test/test_pickletools.py

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1+
import contextlib
12
import io
3+
import itertools
24
import pickle
35
import pickletools
6+
import tempfile
7+
import textwrap
48
from test import support
9+
from test.support import os_helper
510
from test.pickletester import AbstractPickleTests
611
import doctest
712
import unittest
@@ -514,6 +519,172 @@ def test__all__(self):
514519
support.check__all__(self, pickletools, not_exported=not_exported)
515520

516521

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

0 commit comments

Comments
 (0)