mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 18:59:04 +08:00
* Add canonical cross-interpreter serialization tests and fixture bridges Import the complete CPython test_crossinterp module and provide its capsule bridges using existing serialization and validation logic. Keep fixture modes private instead of expanding SharedValue or interpreter lifecycle APIs. Assisted-by: OpenAI Codex: model version unavailable * Simplify canonical cross-interpreter fixture dispatch Match CPython 3.15.0 mode dispatch with one private constructor instead of six forwarding entry points. Keep generic marshal data distinct from code transport, preserve capsule ownership checks and pure-script validation, and leave the canonical tests unchanged. Assisted-by: OpenAI Codex: model version unavailable --------- Co-authored-by: youknowdot <336763669+youknowdot@users.noreply.github.com>
1551 lines
47 KiB
Python
Vendored
1551 lines
47 KiB
Python
Vendored
import contextlib
|
|
import itertools
|
|
import sys
|
|
import traceback
|
|
import types
|
|
import unittest
|
|
import warnings
|
|
|
|
from test import support
|
|
from test.support import import_helper
|
|
|
|
_testinternalcapi = import_helper.import_module('_testinternalcapi')
|
|
_interpreters = import_helper.import_module('_interpreters')
|
|
from _interpreters import NotShareableError
|
|
|
|
from test import _code_definitions as code_defs
|
|
from test import _crossinterp_definitions as defs
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def ignore_byteswarning():
|
|
with warnings.catch_warnings():
|
|
warnings.filterwarnings('ignore', category=BytesWarning)
|
|
yield
|
|
|
|
|
|
# builtin types
|
|
|
|
BUILTINS_TYPES = [o for _, o in __builtins__.items() if isinstance(o, type)]
|
|
EXCEPTION_TYPES = [cls for cls in BUILTINS_TYPES
|
|
if issubclass(cls, BaseException)]
|
|
OTHER_TYPES = [o for n, o in vars(types).items()
|
|
if (isinstance(o, type) and
|
|
n not in ('DynamicClassAttribute', '_GeneratorWrapper'))]
|
|
BUILTIN_TYPES = [
|
|
*BUILTINS_TYPES,
|
|
*OTHER_TYPES,
|
|
]
|
|
|
|
# builtin exceptions
|
|
|
|
try:
|
|
raise Exception
|
|
except Exception as exc:
|
|
CAUGHT = exc
|
|
EXCEPTIONS_WITH_SPECIAL_SIG = {
|
|
BaseExceptionGroup: (lambda msg: (msg, [CAUGHT])),
|
|
ExceptionGroup: (lambda msg: (msg, [CAUGHT])),
|
|
UnicodeError: (lambda msg: (None, msg, None, None, None)),
|
|
UnicodeEncodeError: (lambda msg: ('utf-8', '', 1, 3, msg)),
|
|
UnicodeDecodeError: (lambda msg: ('utf-8', b'', 1, 3, msg)),
|
|
UnicodeTranslateError: (lambda msg: ('', 1, 3, msg)),
|
|
}
|
|
BUILTIN_EXCEPTIONS = [
|
|
*(cls(*sig('error!')) for cls, sig in EXCEPTIONS_WITH_SPECIAL_SIG.items()),
|
|
*(cls('error!') for cls in EXCEPTION_TYPES
|
|
if cls not in EXCEPTIONS_WITH_SPECIAL_SIG),
|
|
]
|
|
|
|
# other builtin objects
|
|
|
|
METHOD = defs.SpamOkay().okay
|
|
BUILTIN_METHOD = [].append
|
|
METHOD_DESCRIPTOR_WRAPPER = str.join
|
|
METHOD_WRAPPER = object().__str__
|
|
WRAPPER_DESCRIPTOR = object.__init__
|
|
BUILTIN_WRAPPERS = {
|
|
METHOD: types.MethodType,
|
|
BUILTIN_METHOD: types.BuiltinMethodType,
|
|
dict.__dict__['fromkeys']: types.ClassMethodDescriptorType,
|
|
types.FunctionType.__code__: types.GetSetDescriptorType,
|
|
types.FunctionType.__globals__: types.MemberDescriptorType,
|
|
METHOD_DESCRIPTOR_WRAPPER: types.MethodDescriptorType,
|
|
METHOD_WRAPPER: types.MethodWrapperType,
|
|
WRAPPER_DESCRIPTOR: types.WrapperDescriptorType,
|
|
staticmethod(defs.SpamOkay.okay): None,
|
|
classmethod(defs.SpamOkay.okay): None,
|
|
property(defs.SpamOkay.okay): None,
|
|
}
|
|
BUILTIN_FUNCTIONS = [
|
|
# types.BuiltinFunctionType
|
|
len,
|
|
sys.is_finalizing,
|
|
sys.exit,
|
|
_testinternalcapi.get_crossinterp_data,
|
|
]
|
|
assert 'emptymod' not in sys.modules
|
|
with import_helper.ready_to_import('emptymod', ''):
|
|
import emptymod as EMPTYMOD
|
|
MODULES = [
|
|
sys,
|
|
defs,
|
|
unittest,
|
|
EMPTYMOD,
|
|
]
|
|
OBJECT = object()
|
|
EXCEPTION = Exception()
|
|
LAMBDA = (lambda: None)
|
|
BUILTIN_SIMPLE = [
|
|
OBJECT,
|
|
# singletons
|
|
None,
|
|
True,
|
|
False,
|
|
Ellipsis,
|
|
NotImplemented,
|
|
# bytes
|
|
*(i.to_bytes(2, 'little', signed=True)
|
|
for i in range(-1, 258)),
|
|
# str
|
|
'hello world',
|
|
'你好世界',
|
|
'',
|
|
# int
|
|
sys.maxsize + 1,
|
|
sys.maxsize,
|
|
-sys.maxsize - 1,
|
|
-sys.maxsize - 2,
|
|
*range(-1, 258),
|
|
2**1000,
|
|
# float
|
|
0.0,
|
|
1.1,
|
|
-1.0,
|
|
0.12345678,
|
|
-0.12345678,
|
|
]
|
|
TUPLE_EXCEPTION = (0, 1.0, EXCEPTION)
|
|
TUPLE_OBJECT = (0, 1.0, OBJECT)
|
|
TUPLE_NESTED_EXCEPTION = (0, 1.0, (EXCEPTION,))
|
|
TUPLE_NESTED_OBJECT = (0, 1.0, (OBJECT,))
|
|
MEMORYVIEW_EMPTY = memoryview(b'')
|
|
MEMORYVIEW_NOT_EMPTY = memoryview(b'spam'*42)
|
|
MAPPING_PROXY_EMPTY = types.MappingProxyType({})
|
|
BUILTIN_CONTAINERS = [
|
|
# tuple (flat)
|
|
(),
|
|
(1,),
|
|
("hello", "world", ),
|
|
(1, True, "hello"),
|
|
TUPLE_EXCEPTION,
|
|
TUPLE_OBJECT,
|
|
# tuple (nested)
|
|
((1,),),
|
|
((1, 2), (3, 4)),
|
|
((1, 2), (3, 4), (5, 6)),
|
|
TUPLE_NESTED_EXCEPTION,
|
|
TUPLE_NESTED_OBJECT,
|
|
# buffer
|
|
MEMORYVIEW_EMPTY,
|
|
MEMORYVIEW_NOT_EMPTY,
|
|
# list
|
|
[],
|
|
[1, 2, 3],
|
|
[[1], (2,), {3: 4}],
|
|
# dict
|
|
{},
|
|
{1: 7, 2: 8, 3: 9},
|
|
{1: [1], 2: (2,), 3: {3: 4}},
|
|
# frozendict
|
|
frozendict(),
|
|
frozendict({1: 7, 2: 8, 3: 9}),
|
|
frozendict({1: [1], 2: (2,), 3: {3: 4}, 4: frozendict({5: 6})}),
|
|
# set
|
|
set(),
|
|
{1, 2, 3},
|
|
{frozenset({1}), (2,)},
|
|
# frozenset
|
|
frozenset([]),
|
|
frozenset({frozenset({1}), (2,)}),
|
|
# bytearray
|
|
bytearray(b''),
|
|
# other
|
|
MAPPING_PROXY_EMPTY,
|
|
types.SimpleNamespace(),
|
|
]
|
|
ns = {}
|
|
exec("""
|
|
try:
|
|
raise Exception
|
|
except Exception as exc:
|
|
TRACEBACK = exc.__traceback__
|
|
FRAME = TRACEBACK.tb_frame
|
|
""", ns, ns)
|
|
BUILTIN_OTHER = [
|
|
# types.CellType
|
|
types.CellType(),
|
|
# types.FrameType
|
|
ns['FRAME'],
|
|
# types.TracebackType
|
|
ns['TRACEBACK'],
|
|
]
|
|
del ns
|
|
|
|
# user-defined objects
|
|
|
|
USER_TOP_INSTANCES = [c(*a) for c, a in defs.TOP_CLASSES.items()]
|
|
USER_NESTED_INSTANCES = [c(*a) for c, a in defs.NESTED_CLASSES.items()]
|
|
USER_INSTANCES = [
|
|
*USER_TOP_INSTANCES,
|
|
*USER_NESTED_INSTANCES,
|
|
]
|
|
USER_EXCEPTIONS = [
|
|
defs.MimimalError('error!'),
|
|
]
|
|
|
|
# shareable objects
|
|
|
|
TUPLES_WITHOUT_EQUALITY = [
|
|
TUPLE_EXCEPTION,
|
|
TUPLE_OBJECT,
|
|
TUPLE_NESTED_EXCEPTION,
|
|
TUPLE_NESTED_OBJECT,
|
|
]
|
|
_UNSHAREABLE_SIMPLE = [
|
|
Ellipsis,
|
|
NotImplemented,
|
|
OBJECT,
|
|
sys.maxsize + 1,
|
|
-sys.maxsize - 2,
|
|
2**1000,
|
|
]
|
|
with ignore_byteswarning():
|
|
_SHAREABLE_SIMPLE = [o for o in BUILTIN_SIMPLE
|
|
if o not in _UNSHAREABLE_SIMPLE]
|
|
_SHAREABLE_CONTAINERS = [
|
|
*(o for o in BUILTIN_CONTAINERS if type(o) is memoryview),
|
|
*(o for o in BUILTIN_CONTAINERS
|
|
if type(o) is tuple and o not in TUPLES_WITHOUT_EQUALITY),
|
|
]
|
|
_UNSHAREABLE_CONTAINERS = [o for o in BUILTIN_CONTAINERS
|
|
if o not in _SHAREABLE_CONTAINERS]
|
|
SHAREABLE = [
|
|
*_SHAREABLE_SIMPLE,
|
|
*_SHAREABLE_CONTAINERS,
|
|
]
|
|
NOT_SHAREABLE = [
|
|
*_UNSHAREABLE_SIMPLE,
|
|
*_UNSHAREABLE_CONTAINERS,
|
|
*BUILTIN_TYPES,
|
|
*BUILTIN_WRAPPERS,
|
|
*BUILTIN_EXCEPTIONS,
|
|
*BUILTIN_FUNCTIONS,
|
|
*MODULES,
|
|
*BUILTIN_OTHER,
|
|
# types.CodeType
|
|
*(f.__code__ for f in defs.FUNCTIONS),
|
|
*(f.__code__ for f in defs.FUNCTION_LIKE),
|
|
# types.FunctionType
|
|
*defs.FUNCTIONS,
|
|
defs.SpamOkay.okay,
|
|
LAMBDA,
|
|
*defs.FUNCTION_LIKE,
|
|
# coroutines and generators
|
|
*defs.FUNCTION_LIKE_APPLIED,
|
|
# user classes
|
|
*defs.CLASSES,
|
|
*USER_INSTANCES,
|
|
# user exceptions
|
|
*USER_EXCEPTIONS,
|
|
]
|
|
|
|
# pickleable objects
|
|
|
|
PICKLEABLE = [
|
|
*BUILTIN_SIMPLE,
|
|
*(o for o in BUILTIN_CONTAINERS if o not in [
|
|
MEMORYVIEW_EMPTY,
|
|
MEMORYVIEW_NOT_EMPTY,
|
|
MAPPING_PROXY_EMPTY,
|
|
] or type(o) is dict),
|
|
*BUILTINS_TYPES,
|
|
*BUILTIN_EXCEPTIONS,
|
|
*BUILTIN_FUNCTIONS,
|
|
*defs.TOP_FUNCTIONS,
|
|
defs.SpamOkay.okay,
|
|
*defs.FUNCTION_LIKE,
|
|
*defs.TOP_CLASSES,
|
|
*USER_TOP_INSTANCES,
|
|
*USER_EXCEPTIONS,
|
|
# from OTHER_TYPES
|
|
types.NoneType,
|
|
types.EllipsisType,
|
|
types.NotImplementedType,
|
|
types.GenericAlias,
|
|
types.UnionType,
|
|
types.SimpleNamespace,
|
|
# from BUILTIN_WRAPPERS
|
|
METHOD,
|
|
BUILTIN_METHOD,
|
|
METHOD_DESCRIPTOR_WRAPPER,
|
|
METHOD_WRAPPER,
|
|
WRAPPER_DESCRIPTOR,
|
|
]
|
|
assert not any(isinstance(o, types.MappingProxyType) for o in PICKLEABLE)
|
|
|
|
|
|
# helpers
|
|
|
|
DEFS = defs
|
|
with open(code_defs.__file__) as infile:
|
|
_code_defs_text = infile.read()
|
|
with open(DEFS.__file__) as infile:
|
|
_defs_text = infile.read()
|
|
_defs_text = _defs_text.replace('from ', '# from ')
|
|
DEFS_TEXT = f"""
|
|
#######################################
|
|
# from {code_defs.__file__}
|
|
|
|
{_code_defs_text}
|
|
|
|
#######################################
|
|
# from {defs.__file__}
|
|
|
|
{_defs_text}
|
|
"""
|
|
del infile, _code_defs_text, _defs_text
|
|
|
|
|
|
def load_defs(module=None):
|
|
"""Return a new copy of the test._crossinterp_definitions module.
|
|
|
|
The module's __name__ matches the "module" arg, which is either
|
|
a str or a module.
|
|
|
|
If the "module" arg is a module then the just-loaded defs are also
|
|
copied into that module.
|
|
|
|
Note that the new module is not added to sys.modules.
|
|
"""
|
|
if module is None:
|
|
modname = DEFS.__name__
|
|
elif isinstance(module, str):
|
|
modname = module
|
|
module = None
|
|
else:
|
|
modname = module.__name__
|
|
# Create the new module and populate it.
|
|
defs = import_helper.create_module(modname)
|
|
defs.__file__ = DEFS.__file__
|
|
exec(DEFS_TEXT, defs.__dict__)
|
|
# Copy the defs into the module arg, if any.
|
|
if module is not None:
|
|
for name, value in defs.__dict__.items():
|
|
if name.startswith('_'):
|
|
continue
|
|
assert not hasattr(module, name), (name, getattr(module, name))
|
|
setattr(module, name, value)
|
|
return defs
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def using___main__():
|
|
"""Make sure __main__ module exists (and clean up after)."""
|
|
modname = '__main__'
|
|
if modname not in sys.modules:
|
|
with import_helper.isolated_modules():
|
|
yield import_helper.add_module(modname)
|
|
else:
|
|
with import_helper.module_restored(modname) as mod:
|
|
yield mod
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def temp_module(modname):
|
|
"""Create the module and add to sys.modules, then remove it after."""
|
|
assert modname not in sys.modules, (modname,)
|
|
with import_helper.isolated_modules():
|
|
yield import_helper.add_module(modname)
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def missing_defs_module(modname, *, prep=False):
|
|
assert modname not in sys.modules, (modname,)
|
|
if prep:
|
|
with import_helper.ready_to_import(modname, DEFS_TEXT):
|
|
yield modname
|
|
else:
|
|
with import_helper.isolated_modules():
|
|
yield modname
|
|
|
|
|
|
class _GetXIDataTests(unittest.TestCase):
|
|
|
|
MODE = None
|
|
|
|
def assert_functions_equal(self, func1, func2):
|
|
assert type(func1) is types.FunctionType, repr(func1)
|
|
assert type(func2) is types.FunctionType, repr(func2)
|
|
self.assertEqual(func1.__name__, func2.__name__)
|
|
self.assertEqual(func1.__code__, func2.__code__)
|
|
self.assertEqual(func1.__defaults__, func2.__defaults__)
|
|
self.assertEqual(func1.__kwdefaults__, func2.__kwdefaults__)
|
|
# We don't worry about __globals__ for now.
|
|
|
|
def assert_exc_args_equal(self, exc1, exc2):
|
|
args1 = exc1.args
|
|
args2 = exc2.args
|
|
if isinstance(exc1, ExceptionGroup):
|
|
self.assertIs(type(args1), type(args2))
|
|
self.assertEqual(len(args1), 2)
|
|
self.assertEqual(len(args1), len(args2))
|
|
self.assertEqual(args1[0], args2[0])
|
|
group1 = args1[1]
|
|
group2 = args2[1]
|
|
self.assertEqual(len(group1), len(group2))
|
|
for grouped1, grouped2 in zip(group1, group2):
|
|
# Currently the "extra" attrs are not preserved
|
|
# (via __reduce__).
|
|
self.assertIs(type(exc1), type(exc2))
|
|
self.assert_exc_equal(grouped1, grouped2)
|
|
else:
|
|
self.assertEqual(args1, args2)
|
|
|
|
def assert_exc_equal(self, exc1, exc2):
|
|
self.assertIs(type(exc1), type(exc2))
|
|
|
|
if type(exc1).__eq__ is not object.__eq__:
|
|
self.assertEqual(exc1, exc2)
|
|
|
|
self.assert_exc_args_equal(exc1, exc2)
|
|
# XXX For now we do not preserve tracebacks.
|
|
if exc1.__traceback__ is not None:
|
|
self.assertEqual(exc1.__traceback__, exc2.__traceback__)
|
|
self.assertEqual(
|
|
getattr(exc1, '__notes__', None),
|
|
getattr(exc2, '__notes__', None),
|
|
)
|
|
# We assume there are no cycles.
|
|
if exc1.__cause__ is None:
|
|
self.assertIs(exc1.__cause__, exc2.__cause__)
|
|
else:
|
|
self.assert_exc_equal(exc1.__cause__, exc2.__cause__)
|
|
if exc1.__context__ is None:
|
|
self.assertIs(exc1.__context__, exc2.__context__)
|
|
else:
|
|
self.assert_exc_equal(exc1.__context__, exc2.__context__)
|
|
|
|
def assert_equal_or_equalish(self, obj, expected):
|
|
cls = type(expected)
|
|
if cls.__eq__ is not object.__eq__:
|
|
self.assertEqual(obj, expected)
|
|
elif cls is types.FunctionType:
|
|
self.assert_functions_equal(obj, expected)
|
|
elif isinstance(expected, BaseException):
|
|
self.assert_exc_equal(obj, expected)
|
|
elif cls is types.MethodType:
|
|
raise NotImplementedError(cls)
|
|
elif cls is types.BuiltinMethodType:
|
|
raise NotImplementedError(cls)
|
|
elif cls is types.MethodWrapperType:
|
|
raise NotImplementedError(cls)
|
|
elif cls.__bases__ == (object,):
|
|
self.assertEqual(obj.__dict__, expected.__dict__)
|
|
else:
|
|
raise NotImplementedError(cls)
|
|
|
|
def get_xidata(self, obj, *, mode=None):
|
|
mode = self._resolve_mode(mode)
|
|
return _testinternalcapi.get_crossinterp_data(obj, mode)
|
|
|
|
def get_roundtrip(self, obj, *, mode=None):
|
|
mode = self._resolve_mode(mode)
|
|
return self._get_roundtrip(obj, mode)
|
|
|
|
def _get_roundtrip(self, obj, mode):
|
|
xid = _testinternalcapi.get_crossinterp_data(obj, mode)
|
|
return _testinternalcapi.restore_crossinterp_data(xid)
|
|
|
|
def assert_roundtrip_identical(self, values, *, mode=None):
|
|
mode = self._resolve_mode(mode)
|
|
for obj in values:
|
|
with self.subTest(repr(obj)):
|
|
got = self._get_roundtrip(obj, mode)
|
|
self.assertIs(got, obj)
|
|
|
|
def assert_roundtrip_equal(self, values, *, mode=None, expecttype=None):
|
|
mode = self._resolve_mode(mode)
|
|
for obj in values:
|
|
with self.subTest(repr(obj)):
|
|
got = self._get_roundtrip(obj, mode)
|
|
if got is obj:
|
|
continue
|
|
self.assertIs(type(got),
|
|
type(obj) if expecttype is None else expecttype)
|
|
self.assert_equal_or_equalish(got, obj)
|
|
|
|
def assert_roundtrip_equal_not_identical(self, values, *,
|
|
mode=None, expecttype=None):
|
|
mode = self._resolve_mode(mode)
|
|
for obj in values:
|
|
with self.subTest(repr(obj)):
|
|
got = self._get_roundtrip(obj, mode)
|
|
self.assertIsNot(got, obj)
|
|
self.assertIs(type(got),
|
|
type(obj) if expecttype is None else expecttype)
|
|
self.assert_equal_or_equalish(got, obj)
|
|
|
|
def assert_roundtrip_not_equal(self, values, *,
|
|
mode=None, expecttype=None):
|
|
mode = self._resolve_mode(mode)
|
|
for obj in values:
|
|
with self.subTest(repr(obj)):
|
|
got = self._get_roundtrip(obj, mode)
|
|
self.assertIsNot(got, obj)
|
|
self.assertIs(type(got),
|
|
type(obj) if expecttype is None else expecttype)
|
|
self.assertNotEqual(got, obj)
|
|
|
|
def assert_not_shareable(self, values, exctype=None, *, mode=None):
|
|
mode = self._resolve_mode(mode)
|
|
for obj in values:
|
|
with self.subTest(repr(obj)):
|
|
with self.assertRaises(NotShareableError) as cm:
|
|
_testinternalcapi.get_crossinterp_data(obj, mode)
|
|
if exctype is not None:
|
|
self.assertIsInstance(cm.exception.__cause__, exctype)
|
|
|
|
def _resolve_mode(self, mode):
|
|
if mode is None:
|
|
mode = self.MODE
|
|
assert mode
|
|
return mode
|
|
|
|
|
|
class PickleTests(_GetXIDataTests):
|
|
|
|
MODE = 'pickle'
|
|
|
|
def test_shareable(self):
|
|
with ignore_byteswarning():
|
|
for obj in SHAREABLE:
|
|
if obj in PICKLEABLE:
|
|
self.assert_roundtrip_equal([obj])
|
|
else:
|
|
self.assert_not_shareable([obj])
|
|
|
|
def test_not_shareable(self):
|
|
with ignore_byteswarning():
|
|
for obj in NOT_SHAREABLE:
|
|
if type(obj) is types.MappingProxyType:
|
|
self.assert_not_shareable([obj])
|
|
elif obj in PICKLEABLE:
|
|
with self.subTest(repr(obj)):
|
|
# We don't worry about checking the actual value.
|
|
# The other tests should cover that well enough.
|
|
got = self.get_roundtrip(obj)
|
|
self.assertIs(type(got), type(obj))
|
|
else:
|
|
self.assert_not_shareable([obj])
|
|
|
|
def test_list(self):
|
|
self.assert_roundtrip_equal_not_identical([
|
|
[],
|
|
[1, 2, 3],
|
|
[[1], (2,), {3: 4}],
|
|
])
|
|
|
|
def test_dict(self):
|
|
self.assert_roundtrip_equal_not_identical([
|
|
{},
|
|
{1: 7, 2: 8, 3: 9},
|
|
{1: [1], 2: (2,), 3: {3: 4}},
|
|
])
|
|
|
|
def test_set(self):
|
|
self.assert_roundtrip_equal_not_identical([
|
|
set(),
|
|
{1, 2, 3},
|
|
{frozenset({1}), (2,)},
|
|
])
|
|
|
|
# classes
|
|
|
|
def assert_class_defs_same(self, defs):
|
|
# Unpickle relative to the unchanged original module.
|
|
self.assert_roundtrip_identical(defs.TOP_CLASSES)
|
|
|
|
instances = []
|
|
for cls, args in defs.TOP_CLASSES.items():
|
|
if cls in defs.CLASSES_WITHOUT_EQUALITY:
|
|
continue
|
|
instances.append(cls(*args))
|
|
self.assert_roundtrip_equal_not_identical(instances)
|
|
|
|
# these don't compare equal
|
|
instances = []
|
|
for cls, args in defs.TOP_CLASSES.items():
|
|
if cls not in defs.CLASSES_WITHOUT_EQUALITY:
|
|
continue
|
|
instances.append(cls(*args))
|
|
self.assert_roundtrip_equal(instances)
|
|
|
|
def assert_class_defs_other_pickle(self, defs, mod):
|
|
# Pickle relative to a different module than the original.
|
|
for cls in defs.TOP_CLASSES:
|
|
assert not hasattr(mod, cls.__name__), (cls, getattr(mod, cls.__name__))
|
|
self.assert_not_shareable(defs.TOP_CLASSES)
|
|
|
|
instances = []
|
|
for cls, args in defs.TOP_CLASSES.items():
|
|
instances.append(cls(*args))
|
|
self.assert_not_shareable(instances)
|
|
|
|
def assert_class_defs_other_unpickle(self, defs, mod, *, fail=False):
|
|
# Unpickle relative to a different module than the original.
|
|
for cls in defs.TOP_CLASSES:
|
|
assert not hasattr(mod, cls.__name__), (cls, getattr(mod, cls.__name__))
|
|
|
|
instances = []
|
|
for cls, args in defs.TOP_CLASSES.items():
|
|
with self.subTest(repr(cls)):
|
|
setattr(mod, cls.__name__, cls)
|
|
xid = self.get_xidata(cls)
|
|
inst = cls(*args)
|
|
instxid = self.get_xidata(inst)
|
|
instances.append(
|
|
(cls, xid, inst, instxid))
|
|
|
|
for cls, xid, inst, instxid in instances:
|
|
with self.subTest(repr(cls)):
|
|
delattr(mod, cls.__name__)
|
|
if fail:
|
|
with self.assertRaises(NotShareableError):
|
|
_testinternalcapi.restore_crossinterp_data(xid)
|
|
continue
|
|
got = _testinternalcapi.restore_crossinterp_data(xid)
|
|
self.assertIsNot(got, cls)
|
|
self.assertNotEqual(got, cls)
|
|
|
|
gotcls = got
|
|
got = _testinternalcapi.restore_crossinterp_data(instxid)
|
|
self.assertIsNot(got, inst)
|
|
self.assertIs(type(got), gotcls)
|
|
if cls in defs.CLASSES_WITHOUT_EQUALITY:
|
|
self.assertNotEqual(got, inst)
|
|
elif cls in defs.BUILTIN_SUBCLASSES:
|
|
self.assertEqual(got, inst)
|
|
else:
|
|
self.assertNotEqual(got, inst)
|
|
|
|
def assert_class_defs_not_shareable(self, defs):
|
|
self.assert_not_shareable(defs.TOP_CLASSES)
|
|
|
|
instances = []
|
|
for cls, args in defs.TOP_CLASSES.items():
|
|
instances.append(cls(*args))
|
|
self.assert_not_shareable(instances)
|
|
|
|
def test_user_class_normal(self):
|
|
self.assert_class_defs_same(defs)
|
|
|
|
def test_user_class_in___main__(self):
|
|
with using___main__() as mod:
|
|
defs = load_defs(mod)
|
|
self.assert_class_defs_same(defs)
|
|
|
|
def test_user_class_not_in___main___with_filename(self):
|
|
with using___main__() as mod:
|
|
defs = load_defs('__main__')
|
|
assert defs.__file__
|
|
mod.__file__ = defs.__file__
|
|
self.assert_class_defs_not_shareable(defs)
|
|
|
|
def test_user_class_not_in___main___without_filename(self):
|
|
with using___main__() as mod:
|
|
defs = load_defs('__main__')
|
|
defs.__file__ = None
|
|
mod.__file__ = None
|
|
self.assert_class_defs_not_shareable(defs)
|
|
|
|
def test_user_class_not_in___main___unpickle_with_filename(self):
|
|
with using___main__() as mod:
|
|
defs = load_defs('__main__')
|
|
assert defs.__file__
|
|
mod.__file__ = defs.__file__
|
|
self.assert_class_defs_other_unpickle(defs, mod)
|
|
|
|
def test_user_class_not_in___main___unpickle_without_filename(self):
|
|
with using___main__() as mod:
|
|
defs = load_defs('__main__')
|
|
defs.__file__ = None
|
|
mod.__file__ = None
|
|
self.assert_class_defs_other_unpickle(defs, mod, fail=True)
|
|
|
|
def test_user_class_in_module(self):
|
|
with temp_module('__spam__') as mod:
|
|
defs = load_defs(mod)
|
|
self.assert_class_defs_same(defs)
|
|
|
|
def test_user_class_not_in_module_with_filename(self):
|
|
with temp_module('__spam__') as mod:
|
|
defs = load_defs(mod.__name__)
|
|
assert defs.__file__
|
|
# For now, we only address this case for __main__.
|
|
self.assert_class_defs_not_shareable(defs)
|
|
|
|
def test_user_class_not_in_module_without_filename(self):
|
|
with temp_module('__spam__') as mod:
|
|
defs = load_defs(mod.__name__)
|
|
defs.__file__ = None
|
|
self.assert_class_defs_not_shareable(defs)
|
|
|
|
def test_user_class_module_missing_then_imported(self):
|
|
with missing_defs_module('__spam__', prep=True) as modname:
|
|
defs = load_defs(modname)
|
|
# For now, we only address this case for __main__.
|
|
self.assert_class_defs_not_shareable(defs)
|
|
|
|
def test_user_class_module_missing_not_available(self):
|
|
with missing_defs_module('__spam__') as modname:
|
|
defs = load_defs(modname)
|
|
self.assert_class_defs_not_shareable(defs)
|
|
|
|
def test_nested_class(self):
|
|
eggs = defs.EggsNested()
|
|
with self.assertRaises(NotShareableError):
|
|
self.get_roundtrip(eggs)
|
|
|
|
# functions
|
|
|
|
def assert_func_defs_same(self, defs):
|
|
# Unpickle relative to the unchanged original module.
|
|
self.assert_roundtrip_identical(defs.TOP_FUNCTIONS)
|
|
|
|
def assert_func_defs_other_pickle(self, defs, mod):
|
|
# Pickle relative to a different module than the original.
|
|
for func in defs.TOP_FUNCTIONS:
|
|
assert not hasattr(mod, func.__name__), (getattr(mod, func.__name__),)
|
|
self.assert_not_shareable(defs.TOP_FUNCTIONS)
|
|
|
|
def assert_func_defs_other_unpickle(self, defs, mod, *, fail=False):
|
|
# Unpickle relative to a different module than the original.
|
|
for func in defs.TOP_FUNCTIONS:
|
|
assert not hasattr(mod, func.__name__), (getattr(mod, func.__name__),)
|
|
|
|
captured = []
|
|
for func in defs.TOP_FUNCTIONS:
|
|
with self.subTest(func):
|
|
setattr(mod, func.__name__, func)
|
|
xid = self.get_xidata(func)
|
|
captured.append(
|
|
(func, xid))
|
|
|
|
for func, xid in captured:
|
|
with self.subTest(func):
|
|
delattr(mod, func.__name__)
|
|
if fail:
|
|
with self.assertRaises(NotShareableError):
|
|
_testinternalcapi.restore_crossinterp_data(xid)
|
|
continue
|
|
got = _testinternalcapi.restore_crossinterp_data(xid)
|
|
self.assertIsNot(got, func)
|
|
self.assertNotEqual(got, func)
|
|
|
|
def assert_func_defs_not_shareable(self, defs):
|
|
self.assert_not_shareable(defs.TOP_FUNCTIONS)
|
|
|
|
def test_user_function_normal(self):
|
|
self.assert_roundtrip_equal(defs.TOP_FUNCTIONS)
|
|
self.assert_func_defs_same(defs)
|
|
|
|
def test_user_func_in___main__(self):
|
|
with using___main__() as mod:
|
|
defs = load_defs(mod)
|
|
self.assert_func_defs_same(defs)
|
|
|
|
def test_user_func_not_in___main___with_filename(self):
|
|
with using___main__() as mod:
|
|
defs = load_defs('__main__')
|
|
assert defs.__file__
|
|
mod.__file__ = defs.__file__
|
|
self.assert_func_defs_not_shareable(defs)
|
|
|
|
def test_user_func_not_in___main___without_filename(self):
|
|
with using___main__() as mod:
|
|
defs = load_defs('__main__')
|
|
defs.__file__ = None
|
|
mod.__file__ = None
|
|
self.assert_func_defs_not_shareable(defs)
|
|
|
|
def test_user_func_not_in___main___unpickle_with_filename(self):
|
|
with using___main__() as mod:
|
|
defs = load_defs('__main__')
|
|
assert defs.__file__
|
|
mod.__file__ = defs.__file__
|
|
self.assert_func_defs_other_unpickle(defs, mod)
|
|
|
|
def test_user_func_not_in___main___unpickle_without_filename(self):
|
|
with using___main__() as mod:
|
|
defs = load_defs('__main__')
|
|
defs.__file__ = None
|
|
mod.__file__ = None
|
|
self.assert_func_defs_other_unpickle(defs, mod, fail=True)
|
|
|
|
def test_user_func_in_module(self):
|
|
with temp_module('__spam__') as mod:
|
|
defs = load_defs(mod)
|
|
self.assert_func_defs_same(defs)
|
|
|
|
def test_user_func_not_in_module_with_filename(self):
|
|
with temp_module('__spam__') as mod:
|
|
defs = load_defs(mod.__name__)
|
|
assert defs.__file__
|
|
# For now, we only address this case for __main__.
|
|
self.assert_func_defs_not_shareable(defs)
|
|
|
|
def test_user_func_not_in_module_without_filename(self):
|
|
with temp_module('__spam__') as mod:
|
|
defs = load_defs(mod.__name__)
|
|
defs.__file__ = None
|
|
self.assert_func_defs_not_shareable(defs)
|
|
|
|
def test_user_func_module_missing_then_imported(self):
|
|
with missing_defs_module('__spam__', prep=True) as modname:
|
|
defs = load_defs(modname)
|
|
# For now, we only address this case for __main__.
|
|
self.assert_func_defs_not_shareable(defs)
|
|
|
|
def test_user_func_module_missing_not_available(self):
|
|
with missing_defs_module('__spam__') as modname:
|
|
defs = load_defs(modname)
|
|
self.assert_func_defs_not_shareable(defs)
|
|
|
|
def test_nested_function(self):
|
|
self.assert_not_shareable(defs.NESTED_FUNCTIONS)
|
|
|
|
# exceptions
|
|
|
|
def test_user_exception_normal(self):
|
|
self.assert_roundtrip_equal([
|
|
defs.MimimalError('error!'),
|
|
])
|
|
self.assert_roundtrip_equal_not_identical([
|
|
defs.RichError('error!', 42),
|
|
])
|
|
|
|
def test_builtin_exception(self):
|
|
msg = 'error!'
|
|
try:
|
|
raise Exception
|
|
except Exception as exc:
|
|
caught = exc
|
|
special = {
|
|
BaseExceptionGroup: (msg, [caught]),
|
|
ExceptionGroup: (msg, [caught]),
|
|
UnicodeError: (None, msg, None, None, None),
|
|
UnicodeEncodeError: ('utf-8', '', 1, 3, msg),
|
|
UnicodeDecodeError: ('utf-8', b'', 1, 3, msg),
|
|
UnicodeTranslateError: ('', 1, 3, msg),
|
|
}
|
|
exceptions = []
|
|
for cls in EXCEPTION_TYPES:
|
|
args = special.get(cls) or (msg,)
|
|
exceptions.append(cls(*args))
|
|
|
|
self.assert_roundtrip_equal(exceptions)
|
|
|
|
|
|
class MarshalTests(_GetXIDataTests):
|
|
|
|
MODE = 'marshal'
|
|
|
|
def test_simple_builtin_singletons(self):
|
|
self.assert_roundtrip_identical([
|
|
True,
|
|
False,
|
|
None,
|
|
Ellipsis,
|
|
])
|
|
self.assert_not_shareable([
|
|
NotImplemented,
|
|
])
|
|
|
|
def test_simple_builtin_objects(self):
|
|
self.assert_roundtrip_equal([
|
|
# int
|
|
*range(-1, 258),
|
|
sys.maxsize + 1,
|
|
sys.maxsize,
|
|
-sys.maxsize - 1,
|
|
-sys.maxsize - 2,
|
|
2**1000,
|
|
# complex
|
|
1+2j,
|
|
# float
|
|
0.0,
|
|
1.1,
|
|
-1.0,
|
|
0.12345678,
|
|
-0.12345678,
|
|
# bytes
|
|
*(i.to_bytes(2, 'little', signed=True)
|
|
for i in range(-1, 258)),
|
|
b'hello world',
|
|
# str
|
|
'hello world',
|
|
'你好世界',
|
|
'',
|
|
])
|
|
self.assert_not_shareable([
|
|
OBJECT,
|
|
types.SimpleNamespace(),
|
|
])
|
|
|
|
def test_bytearray(self):
|
|
# bytearray is special because it unmarshals to bytes, not bytearray.
|
|
self.assert_roundtrip_equal([
|
|
bytearray(),
|
|
bytearray(b'hello world'),
|
|
], expecttype=bytes)
|
|
|
|
def test_compound_immutable_builtin_objects(self):
|
|
self.assert_roundtrip_equal([
|
|
# tuple
|
|
(),
|
|
(1,),
|
|
("hello", "world"),
|
|
(1, True, "hello"),
|
|
# frozenset
|
|
frozenset([1, 2, 3]),
|
|
])
|
|
# nested
|
|
self.assert_roundtrip_equal([
|
|
# tuple
|
|
((1,),),
|
|
((1, 2), (3, 4)),
|
|
((1, 2), (3, 4), (5, 6)),
|
|
# frozenset
|
|
frozenset([frozenset([1]), frozenset([2]), frozenset([3])]),
|
|
])
|
|
|
|
def test_compound_mutable_builtin_objects(self):
|
|
self.assert_roundtrip_equal([
|
|
# list
|
|
[],
|
|
[1, 2, 3],
|
|
# dict
|
|
{},
|
|
{1: 7, 2: 8, 3: 9},
|
|
# set
|
|
set(),
|
|
{1, 2, 3},
|
|
])
|
|
# nested
|
|
self.assert_roundtrip_equal([
|
|
[[1], [2], [3]],
|
|
{1: {'a': True}, 2: {'b': False}},
|
|
{(1, 2, 3,)},
|
|
])
|
|
|
|
def test_compound_builtin_objects_with_bad_items(self):
|
|
bogus = object()
|
|
self.assert_not_shareable([
|
|
(bogus,),
|
|
frozenset([bogus]),
|
|
[bogus],
|
|
{bogus: True},
|
|
{True: bogus},
|
|
{bogus},
|
|
])
|
|
|
|
def test_builtin_code(self):
|
|
self.assert_roundtrip_equal([
|
|
*(f.__code__ for f in defs.FUNCTIONS),
|
|
*(f.__code__ for f in defs.FUNCTION_LIKE),
|
|
])
|
|
|
|
def test_builtin_type(self):
|
|
shareable = [
|
|
StopIteration,
|
|
]
|
|
types = BUILTIN_TYPES
|
|
self.assert_not_shareable(cls for cls in types
|
|
if cls not in shareable)
|
|
self.assert_roundtrip_identical(cls for cls in types
|
|
if cls in shareable)
|
|
|
|
def test_builtin_function(self):
|
|
functions = [
|
|
len,
|
|
sys.is_finalizing,
|
|
sys.exit,
|
|
_testinternalcapi.get_crossinterp_data,
|
|
]
|
|
for func in functions:
|
|
assert type(func) is types.BuiltinFunctionType, func
|
|
|
|
self.assert_not_shareable(functions)
|
|
|
|
def test_builtin_exception(self):
|
|
msg = 'error!'
|
|
try:
|
|
raise Exception
|
|
except Exception as exc:
|
|
caught = exc
|
|
special = {
|
|
BaseExceptionGroup: (msg, [caught]),
|
|
ExceptionGroup: (msg, [caught]),
|
|
# UnicodeError: (None, msg, None, None, None),
|
|
UnicodeEncodeError: ('utf-8', '', 1, 3, msg),
|
|
UnicodeDecodeError: ('utf-8', b'', 1, 3, msg),
|
|
UnicodeTranslateError: ('', 1, 3, msg),
|
|
}
|
|
exceptions = []
|
|
for cls in EXCEPTION_TYPES:
|
|
args = special.get(cls) or (msg,)
|
|
exceptions.append(cls(*args))
|
|
|
|
self.assert_not_shareable(exceptions)
|
|
# Note that StopIteration (the type) can be marshalled,
|
|
# but its instances cannot.
|
|
|
|
def test_module(self):
|
|
assert type(sys) is types.ModuleType, type(sys)
|
|
assert type(defs) is types.ModuleType, type(defs)
|
|
assert type(unittest) is types.ModuleType, type(defs)
|
|
|
|
assert 'emptymod' not in sys.modules
|
|
with import_helper.ready_to_import('emptymod', ''):
|
|
import emptymod
|
|
|
|
self.assert_not_shareable([
|
|
sys,
|
|
defs,
|
|
unittest,
|
|
emptymod,
|
|
])
|
|
|
|
def test_user_class(self):
|
|
self.assert_not_shareable(defs.TOP_CLASSES)
|
|
|
|
instances = []
|
|
for cls, args in defs.TOP_CLASSES.items():
|
|
instances.append(cls(*args))
|
|
self.assert_not_shareable(instances)
|
|
|
|
def test_user_function(self):
|
|
self.assert_not_shareable(defs.TOP_FUNCTIONS)
|
|
|
|
def test_user_exception(self):
|
|
self.assert_not_shareable([
|
|
defs.MimimalError('error!'),
|
|
defs.RichError('error!', 42),
|
|
])
|
|
|
|
|
|
class CodeTests(_GetXIDataTests):
|
|
|
|
MODE = 'code'
|
|
|
|
def test_function_code(self):
|
|
self.assert_roundtrip_equal_not_identical([
|
|
*(f.__code__ for f in defs.FUNCTIONS),
|
|
*(f.__code__ for f in defs.FUNCTION_LIKE),
|
|
])
|
|
|
|
def test_functions(self):
|
|
self.assert_not_shareable([
|
|
*defs.FUNCTIONS,
|
|
*defs.FUNCTION_LIKE,
|
|
])
|
|
|
|
def test_other_objects(self):
|
|
self.assert_not_shareable([
|
|
None,
|
|
True,
|
|
False,
|
|
Ellipsis,
|
|
NotImplemented,
|
|
9999,
|
|
'spam',
|
|
b'spam',
|
|
(),
|
|
[],
|
|
{},
|
|
object(),
|
|
])
|
|
|
|
|
|
class ShareableFuncTests(_GetXIDataTests):
|
|
|
|
MODE = 'func'
|
|
|
|
def test_stateless(self):
|
|
self.assert_roundtrip_equal([
|
|
*defs.STATELESS_FUNCTIONS,
|
|
# Generators can be stateless too.
|
|
*defs.FUNCTION_LIKE,
|
|
])
|
|
|
|
def test_not_stateless(self):
|
|
self.assert_not_shareable([
|
|
*(f for f in defs.FUNCTIONS
|
|
if f not in defs.STATELESS_FUNCTIONS),
|
|
])
|
|
|
|
def test_other_objects(self):
|
|
self.assert_not_shareable([
|
|
None,
|
|
True,
|
|
False,
|
|
Ellipsis,
|
|
NotImplemented,
|
|
9999,
|
|
'spam',
|
|
b'spam',
|
|
(),
|
|
[],
|
|
{},
|
|
object(),
|
|
])
|
|
|
|
|
|
class PureShareableScriptTests(_GetXIDataTests):
|
|
|
|
MODE = 'script-pure'
|
|
|
|
VALID_SCRIPTS = [
|
|
'',
|
|
'spam',
|
|
'# a comment',
|
|
'print("spam")',
|
|
'raise Exception("spam")',
|
|
"""if True:
|
|
do_something()
|
|
""",
|
|
"""if True:
|
|
def spam(x):
|
|
return x
|
|
class Spam:
|
|
def eggs(self):
|
|
return 42
|
|
x = Spam().eggs()
|
|
raise ValueError(spam(x))
|
|
""",
|
|
]
|
|
INVALID_SCRIPTS = [
|
|
' pass', # IndentationError
|
|
'----', # SyntaxError
|
|
"""if True:
|
|
def spam():
|
|
# no body
|
|
spam()
|
|
""", # IndentationError
|
|
]
|
|
|
|
def test_valid_str(self):
|
|
self.assert_roundtrip_not_equal([
|
|
*self.VALID_SCRIPTS,
|
|
], expecttype=types.CodeType)
|
|
|
|
def test_invalid_str(self):
|
|
self.assert_not_shareable([
|
|
*self.INVALID_SCRIPTS,
|
|
])
|
|
|
|
def test_valid_bytes(self):
|
|
self.assert_roundtrip_not_equal([
|
|
*(s.encode('utf8') for s in self.VALID_SCRIPTS),
|
|
], expecttype=types.CodeType)
|
|
|
|
def test_invalid_bytes(self):
|
|
self.assert_not_shareable([
|
|
*(s.encode('utf8') for s in self.INVALID_SCRIPTS),
|
|
])
|
|
|
|
def test_pure_script_code(self):
|
|
self.assert_roundtrip_equal_not_identical([
|
|
*(f.__code__ for f in defs.PURE_SCRIPT_FUNCTIONS),
|
|
])
|
|
|
|
def test_impure_script_code(self):
|
|
self.assert_not_shareable([
|
|
*(f.__code__ for f in defs.SCRIPT_FUNCTIONS
|
|
if f not in defs.PURE_SCRIPT_FUNCTIONS),
|
|
])
|
|
|
|
def test_other_code(self):
|
|
self.assert_not_shareable([
|
|
*(f.__code__ for f in defs.FUNCTIONS
|
|
if f not in defs.SCRIPT_FUNCTIONS),
|
|
*(f.__code__ for f in defs.FUNCTION_LIKE),
|
|
])
|
|
|
|
def test_pure_script_function(self):
|
|
self.assert_roundtrip_not_equal([
|
|
*defs.PURE_SCRIPT_FUNCTIONS,
|
|
], expecttype=types.CodeType)
|
|
|
|
def test_impure_script_function(self):
|
|
self.assert_not_shareable([
|
|
*(f for f in defs.SCRIPT_FUNCTIONS
|
|
if f not in defs.PURE_SCRIPT_FUNCTIONS),
|
|
])
|
|
|
|
def test_other_function(self):
|
|
self.assert_not_shareable([
|
|
*(f for f in defs.FUNCTIONS
|
|
if f not in defs.SCRIPT_FUNCTIONS),
|
|
*defs.FUNCTION_LIKE,
|
|
])
|
|
|
|
def test_other_objects(self):
|
|
self.assert_not_shareable([
|
|
None,
|
|
True,
|
|
False,
|
|
Ellipsis,
|
|
NotImplemented,
|
|
(),
|
|
[],
|
|
{},
|
|
object(),
|
|
])
|
|
|
|
|
|
class ShareableScriptTests(PureShareableScriptTests):
|
|
|
|
MODE = 'script'
|
|
|
|
def test_impure_script_code(self):
|
|
self.assert_roundtrip_equal_not_identical([
|
|
*(f.__code__ for f in defs.SCRIPT_FUNCTIONS
|
|
if f not in defs.PURE_SCRIPT_FUNCTIONS),
|
|
])
|
|
|
|
def test_impure_script_function(self):
|
|
self.assert_roundtrip_not_equal([
|
|
*(f for f in defs.SCRIPT_FUNCTIONS
|
|
if f not in defs.PURE_SCRIPT_FUNCTIONS),
|
|
], expecttype=types.CodeType)
|
|
|
|
|
|
class ShareableFallbackTests(_GetXIDataTests):
|
|
|
|
MODE = 'fallback'
|
|
|
|
def test_shareable(self):
|
|
self.assert_roundtrip_equal(SHAREABLE)
|
|
|
|
def test_not_shareable(self):
|
|
okay = [
|
|
*PICKLEABLE,
|
|
*defs.STATELESS_FUNCTIONS,
|
|
LAMBDA,
|
|
]
|
|
ignored = [
|
|
*TUPLES_WITHOUT_EQUALITY,
|
|
OBJECT,
|
|
METHOD,
|
|
BUILTIN_METHOD,
|
|
METHOD_WRAPPER,
|
|
]
|
|
with ignore_byteswarning():
|
|
self.assert_roundtrip_equal([
|
|
*(o for o in NOT_SHAREABLE
|
|
if o in okay and o not in ignored
|
|
and o is not MAPPING_PROXY_EMPTY),
|
|
])
|
|
self.assert_roundtrip_not_equal([
|
|
*(o for o in NOT_SHAREABLE
|
|
if o in ignored and o is not MAPPING_PROXY_EMPTY),
|
|
])
|
|
self.assert_not_shareable([
|
|
*(o for o in NOT_SHAREABLE if o not in okay),
|
|
MAPPING_PROXY_EMPTY,
|
|
])
|
|
|
|
|
|
class ShareableTypeTests(_GetXIDataTests):
|
|
|
|
MODE = 'xidata'
|
|
|
|
def test_shareable(self):
|
|
self.assert_roundtrip_equal(SHAREABLE)
|
|
|
|
def test_singletons(self):
|
|
self.assert_roundtrip_identical([
|
|
None,
|
|
True,
|
|
False,
|
|
])
|
|
self.assert_not_shareable([
|
|
Ellipsis,
|
|
NotImplemented,
|
|
])
|
|
|
|
def test_types(self):
|
|
self.assert_roundtrip_equal([
|
|
b'spam',
|
|
9999,
|
|
])
|
|
|
|
def test_bytes(self):
|
|
values = (i.to_bytes(2, 'little', signed=True)
|
|
for i in range(-1, 258))
|
|
self.assert_roundtrip_equal(values)
|
|
|
|
def test_strs(self):
|
|
self.assert_roundtrip_equal([
|
|
'hello world',
|
|
'你好世界',
|
|
'',
|
|
])
|
|
|
|
def test_int(self):
|
|
bounds = [sys.maxsize, -sys.maxsize - 1]
|
|
values = itertools.chain(range(-1, 258), bounds)
|
|
self.assert_roundtrip_equal(values)
|
|
|
|
def test_non_shareable_int(self):
|
|
ints = [
|
|
sys.maxsize + 1,
|
|
-sys.maxsize - 2,
|
|
2**1000,
|
|
]
|
|
self.assert_not_shareable(ints, OverflowError)
|
|
|
|
def test_float(self):
|
|
self.assert_roundtrip_equal([
|
|
0.0,
|
|
1.1,
|
|
-1.0,
|
|
0.12345678,
|
|
-0.12345678,
|
|
])
|
|
|
|
def test_tuple(self):
|
|
self.assert_roundtrip_equal([
|
|
(),
|
|
(1,),
|
|
("hello", "world", ),
|
|
(1, True, "hello"),
|
|
])
|
|
# Test nesting
|
|
self.assert_roundtrip_equal([
|
|
((1,),),
|
|
((1, 2), (3, 4)),
|
|
((1, 2), (3, 4), (5, 6)),
|
|
])
|
|
|
|
def test_tuples_containing_non_shareable_types(self):
|
|
non_shareables = [
|
|
EXCEPTION,
|
|
OBJECT,
|
|
]
|
|
for s in non_shareables:
|
|
value = tuple([0, 1.0, s])
|
|
with self.subTest(repr(value)):
|
|
with self.assertRaises(NotShareableError):
|
|
self.get_xidata(value)
|
|
# Check nested as well
|
|
value = tuple([0, 1., (s,)])
|
|
with self.subTest("nested " + repr(value)):
|
|
with self.assertRaises(NotShareableError):
|
|
self.get_xidata(value)
|
|
|
|
# The rest are not shareable.
|
|
|
|
def test_not_shareable(self):
|
|
self.assert_not_shareable(NOT_SHAREABLE)
|
|
|
|
def test_object(self):
|
|
self.assert_not_shareable([
|
|
object(),
|
|
])
|
|
|
|
def test_code(self):
|
|
# types.CodeType
|
|
self.assert_not_shareable([
|
|
*(f.__code__ for f in defs.FUNCTIONS),
|
|
*(f.__code__ for f in defs.FUNCTION_LIKE),
|
|
])
|
|
|
|
def test_function_object(self):
|
|
for func in defs.FUNCTIONS:
|
|
assert type(func) is types.FunctionType, func
|
|
assert type(defs.SpamOkay.okay) is types.FunctionType, func
|
|
assert type(LAMBDA) is types.LambdaType
|
|
|
|
self.assert_not_shareable([
|
|
*defs.FUNCTIONS,
|
|
defs.SpamOkay.okay,
|
|
LAMBDA,
|
|
])
|
|
|
|
def test_builtin_function(self):
|
|
functions = [
|
|
len,
|
|
sys.is_finalizing,
|
|
sys.exit,
|
|
_testinternalcapi.get_crossinterp_data,
|
|
]
|
|
for func in functions:
|
|
assert type(func) is types.BuiltinFunctionType, func
|
|
|
|
self.assert_not_shareable(functions)
|
|
|
|
def test_function_like(self):
|
|
self.assert_not_shareable(defs.FUNCTION_LIKE)
|
|
self.assert_not_shareable(defs.FUNCTION_LIKE_APPLIED)
|
|
|
|
def test_builtin_wrapper(self):
|
|
_wrappers = {
|
|
defs.SpamOkay().okay: types.MethodType,
|
|
[].append: types.BuiltinMethodType,
|
|
dict.__dict__['fromkeys']: types.ClassMethodDescriptorType,
|
|
types.FunctionType.__code__: types.GetSetDescriptorType,
|
|
types.FunctionType.__globals__: types.MemberDescriptorType,
|
|
str.join: types.MethodDescriptorType,
|
|
object().__str__: types.MethodWrapperType,
|
|
object.__init__: types.WrapperDescriptorType,
|
|
}
|
|
for obj, expected in _wrappers.items():
|
|
assert type(obj) is expected, (obj, expected)
|
|
|
|
self.assert_not_shareable([
|
|
*_wrappers,
|
|
staticmethod(defs.SpamOkay.okay),
|
|
classmethod(defs.SpamOkay.okay),
|
|
property(defs.SpamOkay.okay),
|
|
])
|
|
|
|
def test_module(self):
|
|
assert type(sys) is types.ModuleType, type(sys)
|
|
assert type(defs) is types.ModuleType, type(defs)
|
|
assert type(unittest) is types.ModuleType, type(defs)
|
|
|
|
assert 'emptymod' not in sys.modules
|
|
with import_helper.ready_to_import('emptymod', ''):
|
|
import emptymod
|
|
|
|
self.assert_not_shareable([
|
|
sys,
|
|
defs,
|
|
unittest,
|
|
emptymod,
|
|
])
|
|
|
|
def test_class(self):
|
|
self.assert_not_shareable(defs.CLASSES)
|
|
|
|
instances = []
|
|
for cls, args in defs.CLASSES.items():
|
|
instances.append(cls(*args))
|
|
self.assert_not_shareable(instances)
|
|
|
|
def test_builtin_type(self):
|
|
self.assert_not_shareable(BUILTIN_TYPES)
|
|
|
|
def test_exception(self):
|
|
self.assert_not_shareable([
|
|
defs.MimimalError('error!'),
|
|
])
|
|
|
|
def test_builtin_exception(self):
|
|
msg = 'error!'
|
|
try:
|
|
raise Exception
|
|
except Exception as exc:
|
|
caught = exc
|
|
special = {
|
|
BaseExceptionGroup: (msg, [caught]),
|
|
ExceptionGroup: (msg, [caught]),
|
|
# UnicodeError: (None, msg, None, None, None),
|
|
UnicodeEncodeError: ('utf-8', '', 1, 3, msg),
|
|
UnicodeDecodeError: ('utf-8', b'', 1, 3, msg),
|
|
UnicodeTranslateError: ('', 1, 3, msg),
|
|
}
|
|
exceptions = []
|
|
for cls in EXCEPTION_TYPES:
|
|
args = special.get(cls) or (msg,)
|
|
exceptions.append(cls(*args))
|
|
|
|
self.assert_not_shareable(exceptions)
|
|
|
|
def test_builtin_objects(self):
|
|
ns = {}
|
|
exec("""if True:
|
|
try:
|
|
raise Exception
|
|
except Exception as exc:
|
|
TRACEBACK = exc.__traceback__
|
|
FRAME = TRACEBACK.tb_frame
|
|
""", ns, ns)
|
|
|
|
self.assert_not_shareable([
|
|
MAPPING_PROXY_EMPTY,
|
|
types.SimpleNamespace(),
|
|
# types.CellType
|
|
types.CellType(),
|
|
# types.FrameType
|
|
ns['FRAME'],
|
|
# types.TracebackType
|
|
ns['TRACEBACK'],
|
|
])
|
|
|
|
|
|
class CaptureExceptionTests(unittest.TestCase):
|
|
|
|
# Prevent crashes with incompatible TracebackException.format().
|
|
# Regression test for https://github.com/python/cpython/issues/143377.
|
|
|
|
def capture_with_formatter(self, exc, formatter):
|
|
with support.swap_attr(traceback.TracebackException, "format", formatter):
|
|
return _interpreters.capture_exception(exc)
|
|
|
|
def test_capture_exception(self):
|
|
captured = _interpreters.capture_exception(ValueError("hello"))
|
|
|
|
self.assertEqual(captured.type.__name__, "ValueError")
|
|
self.assertEqual(captured.type.__qualname__, "ValueError")
|
|
self.assertEqual(captured.type.__module__, "builtins")
|
|
|
|
self.assertEqual(captured.msg, "hello")
|
|
self.assertEqual(captured.formatted, "ValueError: hello")
|
|
|
|
def test_capture_exception_custom_format(self):
|
|
exc = ValueError("good bye!")
|
|
formatter = lambda self: ["hello\n", "world\n"]
|
|
captured = self.capture_with_formatter(exc, formatter)
|
|
self.assertEqual(captured.msg, "good bye!")
|
|
self.assertEqual(captured.formatted, "ValueError: good bye!")
|
|
self.assertEqual(captured.errdisplay, "hello\nworld")
|
|
|
|
@support.subTests("exc_lines", ([], ["x-no-nl"], ["x-no-nl", "y-no-nl"]))
|
|
def test_capture_exception_invalid_format(self, exc_lines):
|
|
formatter = lambda self: exc_lines
|
|
captured = self.capture_with_formatter(ValueError(), formatter)
|
|
self.assertEqual(captured.msg, "")
|
|
self.assertEqual(captured.formatted, "ValueError: ")
|
|
self.assertEqual(captured.errdisplay, "".join(exc_lines))
|
|
|
|
@unittest.skipUnless(
|
|
support.Py_DEBUG,
|
|
"printing subinterpreter unraisable exceptions requires DEBUG build",
|
|
)
|
|
def test_capture_exception_unraisable_exception(self):
|
|
formatter = lambda self: 1
|
|
with support.catch_unraisable_exception() as cm:
|
|
captured = self.capture_with_formatter(ValueError(), formatter)
|
|
self.assertNotHasAttr(captured, "errdisplay")
|
|
self.assertEqual(cm.unraisable.exc_type, TypeError)
|
|
self.assertEqual(str(cm.unraisable.exc_value),
|
|
"can only join an iterable")
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|