Skip to content

Commit 56beb89

Browse files
committed
test dump and load of StdLib modules; fix missing flag test
1 parent c802c5d commit 56beb89

File tree

3 files changed

+138
-5
lines changed

3 files changed

+138
-5
lines changed

dill/session.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -387,7 +387,7 @@ def dump_module(
387387
if logger.isEnabledFor(logging.TRACE):
388388
pickler._id_to_name = {id(v): k for k, v in main.__dict__.items()}
389389
pickler.dump(main)
390-
if pickler._saved_byref and logger.isEnabledFor(logging.INFO):
390+
if refonfail and pickler._saved_byref and logger.isEnabledFor(logging.INFO):
391391
saved_byref = {var: "%s.%s" % (mod, obj) for var, mod, obj in pickler._saved_byref}
392392
message = "[dump_module] Variables saved by reference (refonfail):\n"
393393
logger.info(message + _format_log_dict(saved_byref))

dill/tests/test_session.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from types import ModuleType
1515

1616
import dill
17+
from dill import _dill
1718
from dill.session import ipython_filter, EXCLUDE, INCLUDE
1819

1920
session_file = os.path.join(os.path.dirname(__file__), 'session-refimported-%s.pkl')
@@ -268,7 +269,7 @@ def test_load_module_asdict():
268269
assert entitydefs == entities_vars['entitydefs']
269270

270271
def test_lookup_module():
271-
assert not dill._dill._is_builtin_module(local_mod) and local_mod.__package__ == ''
272+
assert not _dill._is_builtin_module(local_mod) and local_mod.__package__ == ''
272273

273274
def lookup(mod, name, obj, lookup_by_name=True):
274275
from dill._dill import _lookup_module, _module_map
@@ -352,12 +353,12 @@ def test_unpickleable_var():
352353
dill.session.settings['refonfail'] = True
353354
name = '__unpickleable'
354355
obj = memoryview(b'')
355-
assert dill._dill._is_builtin_module(builtin_mod)
356-
assert not dill._dill._is_builtin_module(local_mod)
356+
assert _dill._is_builtin_module(builtin_mod)
357+
assert not _dill._is_builtin_module(local_mod)
357358
# assert not dill.pickles(obj)
358359
try:
359360
dill.dumps(obj)
360-
except dill._dill.UNPICKLEABLE_ERRORS:
361+
except _dill.UNPICKLEABLE_ERRORS:
361362
pass
362363
else:
363364
raise Exception("test object should be unpickleable")

dill/tests/test_stdlib_modules.py

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
#!/usr/bin/env python
2+
3+
# Author: Leonardo Gama (@leogama)
4+
# Copyright (c) 2022 The Uncertainty Quantification Foundation.
5+
# License: 3-clause BSD. The full license text is available at:
6+
# - https://github.com/uqfoundation/dill/blob/master/LICENSE
7+
8+
import io
9+
import itertools
10+
import logging
11+
import multiprocessing
12+
import os
13+
import sys
14+
import warnings
15+
16+
import dill
17+
18+
if not dill._dill.OLD310:
19+
STDLIB_MODULES = list(sys.stdlib_module_names)
20+
STDLIB_MODULES += [
21+
# From https://docs.python.org/3.11/library/
22+
'collections.abc', 'concurrent.futures', 'curses.ascii', 'curses.panel', 'curses.textpad',
23+
'html.entities', 'html.parser', 'http.client', 'http.cookiejar', 'http.cookies', 'http.server',
24+
'importlib.metadata', 'importlib.resources', 'importlib.resources.abc', 'logging.config',
25+
'logging.handlers', 'multiprocessing.shared_memory', 'os.path', 'test.support',
26+
'test.support.bytecode_helper', 'test.support.import_helper', 'test.support.os_helper',
27+
'test.support.script_helper', 'test.support.socket_helper', 'test.support.threading_helper',
28+
'test.support.warnings_helper', 'tkinter.colorchooser', 'tkinter.dnd', 'tkinter.font',
29+
'tkinter.messagebox', 'tkinter.scrolledtext', 'tkinter.tix', 'tkinter.ttk', 'unittest.mock',
30+
'urllib.error', 'urllib.parse', 'urllib.request', 'urllib.response', 'urllib.robotparser',
31+
'xml.dom', 'xml.dom.minidom', 'xml.dom.pulldom', 'xml.etree.ElementTree', 'xml.parsers.expat',
32+
'xml.sax', 'xml.sax.handler', 'xml.sax.saxutils', 'xml.sax.xmlreader', 'xmlrpc.client',
33+
'xmlrpc.server',
34+
]
35+
STDLIB_MODULES.sort()
36+
else:
37+
STDLIB_MODULES = [
38+
# From https://docs.python.org/3.9/library/
39+
'__future__', '_thread', 'abc', 'aifc', 'argparse', 'array', 'ast', 'asynchat', 'asyncio',
40+
'asyncore', 'atexit', 'audioop', 'base64', 'bdb', 'binascii', 'binhex', 'bisect', 'builtins',
41+
'bz2', 'calendar', 'cgi', 'cgitb', 'chunk', 'cmath', 'cmd', 'code', 'codecs', 'codeop',
42+
'collections', 'collections.abc', 'colorsys', 'compileall', 'concurrent', 'concurrent.futures',
43+
'configparser', 'contextlib', 'contextvars', 'copy', 'copyreg', 'crypt', 'csv', 'ctypes',
44+
'curses', 'curses.ascii', 'curses.panel', 'curses.textpad', 'dataclasses', 'datetime', 'dbm',
45+
'decimal', 'difflib', 'dis', 'distutils', 'doctest', 'email', 'ensurepip', 'enum', 'errno',
46+
'faulthandler', 'fcntl', 'filecmp', 'fileinput', 'fnmatch', 'formatter', 'fractions', 'ftplib',
47+
'functools', 'gc', 'getopt', 'getpass', 'gettext', 'glob', 'graphlib', 'grp', 'gzip', 'hashlib',
48+
'heapq', 'hmac', 'html', 'html.entities', 'html.parser', 'http', 'http.client',
49+
'http.cookiejar', 'http.cookies', 'http.server', 'imaplib', 'imghdr', 'imp', 'importlib',
50+
'importlib.metadata', 'inspect', 'io', 'ipaddress', 'itertools', 'json', 'keyword', 'linecache',
51+
'locale', 'logging', 'logging.config', 'logging.handlers', 'lzma', 'mailbox', 'mailcap',
52+
'marshal', 'math', 'mimetypes', 'mmap', 'modulefinder', 'msilib', 'msvcrt', 'multiprocessing',
53+
'multiprocessing.shared_memory', 'netrc', 'nis', 'nntplib', 'numbers', 'operator', 'optparse',
54+
'os', 'os.path', 'ossaudiodev', 'parser', 'pathlib', 'pdb', 'pickle', 'pickletools', 'pipes',
55+
'pkgutil', 'platform', 'plistlib', 'poplib', 'posix', 'pprint', 'pty', 'pwd', 'py_compile',
56+
'pyclbr', 'pydoc', 'queue', 'quopri', 'random', 're', 'readline', 'reprlib', 'resource',
57+
'rlcompleter', 'runpy', 'sched', 'secrets', 'select', 'selectors', 'shelve', 'shlex', 'shutil',
58+
'signal', 'site', 'site', 'smtpd', 'smtplib', 'sndhdr', 'socket', 'socketserver', 'spwd',
59+
'sqlite3', 'ssl', 'stat', 'statistics', 'string', 'stringprep', 'struct', 'subprocess', 'sunau',
60+
'symbol', 'symtable', 'sys', 'sysconfig', 'syslog', 'tabnanny', 'tarfile', 'telnetlib',
61+
'tempfile', 'termios', 'test', 'test.support', 'test.support.bytecode_helper',
62+
'test.support.script_helper', 'test.support.socket_helper', 'textwrap', 'threading', 'time',
63+
'timeit', 'tkinter', 'tkinter.colorchooser', 'tkinter.dnd', 'tkinter.font',
64+
'tkinter.messagebox', 'tkinter.scrolledtext', 'tkinter.tix', 'tkinter.ttk', 'token', 'tokenize',
65+
'trace', 'traceback', 'tracemalloc', 'tty', 'turtle', 'types', 'typing', 'unicodedata',
66+
'unittest', 'unittest.mock', 'urllib', 'urllib.error', 'urllib.parse', 'urllib.request',
67+
'urllib.response', 'urllib.robotparser', 'uu', 'uuid', 'venv', 'warnings', 'wave', 'weakref',
68+
'webbrowser', 'winreg', 'winsound', 'wsgiref', 'xdrlib', 'xml.dom', 'xml.dom.minidom',
69+
'xml.dom.pulldom', 'xml.etree.ElementTree', 'xml.parsers.expat', 'xml.sax', 'xml.sax.handler',
70+
'xml.sax.saxutils', 'xml.sax.xmlreader', 'xmlrpc', 'xmlrpc.client', 'xmlrpc.server', 'zipapp',
71+
'zipfile', 'zipimport', 'zlib', 'zoneinfo',
72+
]
73+
74+
def _dump_load_module(module_name, refonfail):
75+
try:
76+
__import__(module_name)
77+
except ImportError:
78+
return None, None
79+
success_load = None
80+
buf = io.BytesIO()
81+
try:
82+
dill.dump_module(buf, module_name, refonfail=refonfail)
83+
except Exception:
84+
print("F", end="")
85+
success_dump = False
86+
return success_dump, success_load
87+
print(":", end="")
88+
success_dump = True
89+
buf.seek(0)
90+
try:
91+
module = dill.load_module(buf)
92+
except Exception:
93+
success_load = False
94+
return success_dump, success_load
95+
success_load = True
96+
return success_dump, success_load
97+
98+
def test_stdlib_modules():
99+
modules = [x for x in STDLIB_MODULES if
100+
not x.startswith('_')
101+
and not x.startswith('test')
102+
and x not in ('antigravity', 'this')]
103+
104+
105+
print("\nTesting pickling and unpickling of Standard Library modules...")
106+
message = "Success rate (%s_module, refonfail=%s): %.1f%% [%d/%d]"
107+
with multiprocessing.Pool(maxtasksperchild=1) as pool:
108+
for refonfail in (False, True):
109+
args = zip(modules, itertools.repeat(refonfail))
110+
result = pool.starmap(_dump_load_module, args, chunksize=1)
111+
dump_successes = sum(dumped for dumped, loaded in result if dumped is not None)
112+
load_successes = sum(loaded for dumped, loaded in result if loaded is not None)
113+
dump_failures = sum(not dumped for dumped, loaded in result if dumped is not None)
114+
load_failures = sum(not loaded for dumped, loaded in result if loaded is not None)
115+
dump_total = dump_successes + dump_failures
116+
load_total = load_successes + load_failures
117+
dump_percent = 100 * dump_successes / dump_total
118+
load_percent = 100 * load_successes / load_total
119+
print()
120+
print(message % ("dump", refonfail, dump_percent, dump_successes, dump_total))
121+
print(message % ("load", refonfail, load_percent, load_successes, load_total))
122+
if refonfail:
123+
failed_dump = [mod for mod, (dumped, _) in zip(modules, result) if dumped is False]
124+
failed_load = [mod for mod, (_, loaded) in zip(modules, result) if loaded is False]
125+
logging.info("dump_module() fails: %s", failed_dump)
126+
logging.info("load_module() fails: %s", failed_load)
127+
assert dump_percent > 95
128+
129+
if __name__ == '__main__':
130+
logging.basicConfig(level=os.environ.get('PYTHONLOGLEVEL', 'WARNING'))
131+
warnings.simplefilter('ignore')
132+
test_stdlib_modules()

0 commit comments

Comments
 (0)