diff options
author | Serhiy Storchaka <storchaka@gmail.com> | 2024-07-29 10:40:16 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-07-29 10:40:16 (GMT) |
commit | 0697188084bf61b28f258fbbe867e1010d679b3e (patch) | |
tree | 381e735aead5909e55163cefe416e473b1b892d7 /Lib | |
parent | 3b034d26eb8480f8d12ae11f42d038d24cf8498a (diff) | |
download | cpython-0697188084bf61b28f258fbbe867e1010d679b3e.zip cpython-0697188084bf61b28f258fbbe867e1010d679b3e.tar.gz cpython-0697188084bf61b28f258fbbe867e1010d679b3e.tar.bz2 |
gh-122311: Add more tests for error messages in pickle (GH-122373)
Diffstat (limited to 'Lib')
-rw-r--r-- | Lib/test/pickletester.py | 235 |
1 files changed, 185 insertions, 50 deletions
diff --git a/Lib/test/pickletester.py b/Lib/test/pickletester.py index a2b49e6..3c936b3 100644 --- a/Lib/test/pickletester.py +++ b/Lib/test/pickletester.py @@ -1229,24 +1229,38 @@ class AbstractUnpickleTests: self.assertIs(unpickler.find_class('os.path', 'join'), os.path.join) self.assertIs(unpickler4.find_class('builtins', 'str.upper'), str.upper) - with self.assertRaises(AttributeError): + with self.assertRaisesRegex(AttributeError, + r"module 'builtins' has no attribute 'str\.upper'|" + r"Can't get attribute 'str\.upper' on <module 'builtins'"): unpickler.find_class('builtins', 'str.upper') - with self.assertRaises(AttributeError): + with self.assertRaisesRegex(AttributeError, + "module 'math' has no attribute 'spam'|" + "Can't get attribute 'spam' on <module 'math'"): unpickler.find_class('math', 'spam') - with self.assertRaises(AttributeError): + with self.assertRaisesRegex(AttributeError, + "Can't get attribute 'spam' on <module 'math'"): unpickler4.find_class('math', 'spam') - with self.assertRaises(AttributeError): + with self.assertRaisesRegex(AttributeError, + r"module 'math' has no attribute 'log\.spam'|" + r"Can't get attribute 'log\.spam' on <module 'math'"): unpickler.find_class('math', 'log.spam') - with self.assertRaises(AttributeError): + with self.assertRaisesRegex(AttributeError, + r"Can't get attribute 'log\.spam' on <module 'math'"): unpickler4.find_class('math', 'log.spam') - with self.assertRaises(AttributeError): + with self.assertRaisesRegex(AttributeError, + r"module 'math' has no attribute 'log\.<locals>\.spam'|" + r"Can't get attribute 'log\.<locals>\.spam' on <module 'math'"): unpickler.find_class('math', 'log.<locals>.spam') - with self.assertRaises(AttributeError): + with self.assertRaisesRegex(AttributeError, + r"Can't get local attribute 'log\.<locals>\.spam' on <module 'math'"): unpickler4.find_class('math', 'log.<locals>.spam') - with self.assertRaises(AttributeError): + with self.assertRaisesRegex(AttributeError, + "module 'math' has no attribute ''|" + "Can't get attribute '' on <module 'math'"): unpickler.find_class('math', '') - with self.assertRaises(AttributeError): + with self.assertRaisesRegex(AttributeError, + "Can't get attribute '' on <module 'math'"): unpickler4.find_class('math', '') self.assertRaises(ModuleNotFoundError, unpickler.find_class, 'spam', 'log') self.assertRaises(ValueError, unpickler.find_class, '', 'log') @@ -1568,27 +1582,39 @@ class AbstractPicklingErrorTests: obj = REX([print, ()]) for proto in protocols: with self.subTest(proto=proto): - with self.assertRaises(pickle.PicklingError): + with self.assertRaises(pickle.PicklingError) as cm: self.dumps(obj, proto) + self.assertIn(str(cm.exception), { + f'{obj.__reduce_ex__!r} must return string or tuple', + '__reduce__ must return a string or tuple'}) obj = REX((print,)) for proto in protocols: with self.subTest(proto=proto): - with self.assertRaises(pickle.PicklingError): + with self.assertRaises(pickle.PicklingError) as cm: self.dumps(obj, proto) + self.assertIn(str(cm.exception), { + f'Tuple returned by {obj.__reduce_ex__!r} must have two to six elements', + 'tuple returned by __reduce__ must contain 2 through 6 elements'}) obj = REX((print, (), None, None, None, None, None)) for proto in protocols: with self.subTest(proto=proto): - with self.assertRaises(pickle.PicklingError): + with self.assertRaises(pickle.PicklingError) as cm: self.dumps(obj, proto) + self.assertIn(str(cm.exception), { + f'Tuple returned by {obj.__reduce_ex__!r} must have two to six elements', + 'tuple returned by __reduce__ must contain 2 through 6 elements'}) def test_bad_reconstructor(self): obj = REX((42, ())) for proto in protocols: with self.subTest(proto=proto): - with self.assertRaises(pickle.PicklingError): + with self.assertRaises(pickle.PicklingError) as cm: self.dumps(obj, proto) + self.assertIn(str(cm.exception), { + 'func from save_reduce() must be callable', + 'first item of the tuple returned by __reduce__ must be callable'}) def test_unpickleable_reconstructor(self): obj = REX((UnpickleableCallable(), ())) @@ -1601,8 +1627,11 @@ class AbstractPicklingErrorTests: obj = REX((print, [])) for proto in protocols: with self.subTest(proto=proto): - with self.assertRaises(pickle.PicklingError): + with self.assertRaises(pickle.PicklingError) as cm: self.dumps(obj, proto) + self.assertIn(str(cm.exception), { + 'args from save_reduce() must be a tuple', + 'second item of the tuple returned by __reduce__ must be a tuple'}) def test_unpickleable_reconstructor_args(self): obj = REX((print, (1, 2, UNPICKLEABLE))) @@ -1617,26 +1646,37 @@ class AbstractPicklingErrorTests: with self.subTest(proto=proto): with self.assertRaises((IndexError, pickle.PicklingError)) as cm: self.dumps(obj, proto) + self.assertIn(str(cm.exception), { + 'tuple index out of range', + '__newobj__ arglist is empty'}) obj = REX((copyreg.__newobj__, [REX])) for proto in protocols[2:]: with self.subTest(proto=proto): - with self.assertRaises((IndexError, pickle.PicklingError)): + with self.assertRaises((IndexError, pickle.PicklingError)) as cm: self.dumps(obj, proto) + self.assertIn(str(cm.exception), { + 'args from save_reduce() must be a tuple', + 'second item of the tuple returned by __reduce__ must be a tuple'}) def test_bad_newobj_class(self): obj = REX((copyreg.__newobj__, (NoNew(),))) for proto in protocols[2:]: with self.subTest(proto=proto): - with self.assertRaises(pickle.PicklingError): + with self.assertRaises(pickle.PicklingError) as cm: self.dumps(obj, proto) + self.assertIn(str(cm.exception), { + 'args[0] from __newobj__ args has no __new__', + 'args[0] from __newobj__ args is not a type'}) def test_wrong_newobj_class(self): obj = REX((copyreg.__newobj__, (str,))) for proto in protocols[2:]: with self.subTest(proto=proto): - with self.assertRaises(pickle.PicklingError): + with self.assertRaises(pickle.PicklingError) as cm: self.dumps(obj, proto) + self.assertEqual(str(cm.exception), + 'args[0] from __newobj__ args has the wrong class') def test_unpickleable_newobj_class(self): class LocalREX(REX): pass @@ -1657,34 +1697,62 @@ class AbstractPicklingErrorTests: obj = REX((copyreg.__newobj_ex__, ())) for proto in protocols[2:]: with self.subTest(proto=proto): - with self.assertRaises((ValueError, pickle.PicklingError)): + with self.assertRaises((ValueError, pickle.PicklingError)) as cm: self.dumps(obj, proto) + self.assertIn(str(cm.exception), { + 'not enough values to unpack (expected 3, got 0)', + 'length of the NEWOBJ_EX argument tuple must be exactly 3, not 0'}) obj = REX((copyreg.__newobj_ex__, 42)) for proto in protocols[2:]: with self.subTest(proto=proto): - with self.assertRaises(pickle.PicklingError): + with self.assertRaises(pickle.PicklingError) as cm: self.dumps(obj, proto) + self.assertIn(str(cm.exception), { + 'args from save_reduce() must be a tuple', + 'second item of the tuple returned by __reduce__ must be a tuple'}) obj = REX((copyreg.__newobj_ex__, (REX, 42, {}))) - is_py = self.pickler is pickle._Pickler - for proto in protocols[2:4] if is_py else protocols[2:]: - with self.subTest(proto=proto): - with self.assertRaises((TypeError, pickle.PicklingError)): - self.dumps(obj, proto) + if self.pickler is pickle._Pickler: + for proto in protocols[2:4]: + with self.subTest(proto=proto): + with self.assertRaises(TypeError) as cm: + self.dumps(obj, proto) + self.assertEqual(str(cm.exception), + 'Value after * must be an iterable, not int') + else: + for proto in protocols[2:]: + with self.subTest(proto=proto): + with self.assertRaises(pickle.PicklingError) as cm: + self.dumps(obj, proto) + self.assertEqual(str(cm.exception), + 'second item from NEWOBJ_EX argument tuple must be a tuple, not int') obj = REX((copyreg.__newobj_ex__, (REX, (), []))) - for proto in protocols[2:4] if is_py else protocols[2:]: - with self.subTest(proto=proto): - with self.assertRaises((TypeError, pickle.PicklingError)): - self.dumps(obj, proto) + if self.pickler is pickle._Pickler: + for proto in protocols[2:4]: + with self.subTest(proto=proto): + with self.assertRaises(TypeError) as cm: + self.dumps(obj, proto) + self.assertEqual(str(cm.exception), + 'functools.partial() argument after ** must be a mapping, not list') + else: + for proto in protocols[2:]: + with self.subTest(proto=proto): + with self.assertRaises(pickle.PicklingError) as cm: + self.dumps(obj, proto) + self.assertEqual(str(cm.exception), + 'third item from NEWOBJ_EX argument tuple must be a dict, not list') def test_bad_newobj_ex__class(self): obj = REX((copyreg.__newobj_ex__, (NoNew(), (), {}))) for proto in protocols[2:]: with self.subTest(proto=proto): - with self.assertRaises(pickle.PicklingError): + with self.assertRaises(pickle.PicklingError) as cm: self.dumps(obj, proto) + self.assertIn(str(cm.exception), { + 'args[0] from __newobj_ex__ args has no __new__', + 'first item from NEWOBJ_EX argument tuple must be a class, not NoNew'}) def test_wrong_newobj_ex_class(self): if self.pickler is not pickle._Pickler: @@ -1692,8 +1760,10 @@ class AbstractPicklingErrorTests: obj = REX((copyreg.__newobj_ex__, (str, (), {}))) for proto in protocols[2:]: with self.subTest(proto=proto): - with self.assertRaises(pickle.PicklingError): + with self.assertRaises(pickle.PicklingError) as cm: self.dumps(obj, proto) + self.assertEqual(str(cm.exception), + 'args[0] from __newobj_ex__ args has the wrong class') def test_unpickleable_newobj_ex_class(self): class LocalREX(REX): pass @@ -1730,8 +1800,10 @@ class AbstractPicklingErrorTests: obj = REX((print, (), 'state', None, None, 42)) for proto in protocols: with self.subTest(proto=proto): - with self.assertRaises(pickle.PicklingError): + with self.assertRaises(pickle.PicklingError) as cm: self.dumps(obj, proto) + self.assertEqual(str(cm.exception), + 'sixth element of the tuple returned by __reduce__ must be a function, not int') def test_unpickleable_state_setter(self): obj = REX((print, (), 'state', None, None, UnpickleableCallable())) @@ -1753,8 +1825,11 @@ class AbstractPicklingErrorTests: obj = REX((list, (), None, 42)) for proto in protocols: with self.subTest(proto=proto): - with self.assertRaises((TypeError, pickle.PicklingError)): + with self.assertRaises((TypeError, pickle.PicklingError)) as cm: self.dumps(obj, proto) + self.assertIn(str(cm.exception), { + "'int' object is not iterable", + 'fourth element of the tuple returned by __reduce__ must be an iterator, not int'}) if self.pickler is not pickle._Pickler: # Python implementation is less strict and also accepts iterables. @@ -1763,6 +1838,9 @@ class AbstractPicklingErrorTests: with self.subTest(proto=proto): with self.assertRaises((TypeError, pickle.PicklingError)): self.dumps(obj, proto) + self.assertIn(str(cm.exception), { + "'int' object is not iterable", + 'fourth element of the tuple returned by __reduce__ must be an iterator, not int'}) def test_unpickleable_object_list_items(self): obj = REX_six([1, 2, UNPICKLEABLE]) @@ -1777,14 +1855,20 @@ class AbstractPicklingErrorTests: obj = REX((dict, (), None, None, 42)) for proto in protocols: with self.subTest(proto=proto): - with self.assertRaises((TypeError, pickle.PicklingError)): + with self.assertRaises((TypeError, pickle.PicklingError)) as cm: self.dumps(obj, proto) + self.assertIn(str(cm.exception), { + "'int' object is not iterable", + 'fifth element of the tuple returned by __reduce__ must be an iterator, not int'}) for proto in protocols: obj = REX((dict, (), None, None, iter([('a',)]))) with self.subTest(proto=proto): - with self.assertRaises((ValueError, TypeError)): + with self.assertRaises((ValueError, TypeError)) as cm: self.dumps(obj, proto) + self.assertIn(str(cm.exception), { + 'not enough values to unpack (expected 2, got 1)', + 'dict items iterator must return 2-tuples'}) if self.pickler is not pickle._Pickler: # Python implementation is less strict and also accepts iterables. @@ -1793,6 +1877,8 @@ class AbstractPicklingErrorTests: with self.subTest(proto=proto): with self.assertRaises((TypeError, pickle.PicklingError)): self.dumps(obj, proto) + self.assertEqual(str(cm.exception), + 'dict items iterator must return 2-tuples') def test_unpickleable_object_dict_items(self): obj = REX_seven({'a': UNPICKLEABLE}) @@ -1860,26 +1946,38 @@ class AbstractPicklingErrorTests: obj.__module__ = __name__ for proto in protocols: with self.subTest(proto=proto): - with self.assertRaises(pickle.PicklingError): + with self.assertRaises(pickle.PicklingError) as cm: self.dumps(obj, proto) + self.assertIn(str(cm.exception), { + f"Can't pickle {obj!r}: it's not found as {__name__}.spam", + f"Can't pickle {obj!r}: attribute lookup spam on {__name__} failed"}) obj.__module__ = 'nonexisting' for proto in protocols: with self.subTest(proto=proto): - with self.assertRaises(pickle.PicklingError): + with self.assertRaises(pickle.PicklingError) as cm: self.dumps(obj, proto) + self.assertIn(str(cm.exception), { + f"Can't pickle {obj!r}: it's not found as nonexisting.spam", + f"Can't pickle {obj!r}: import of module 'nonexisting' failed"}) obj.__module__ = '' for proto in protocols: with self.subTest(proto=proto): - with self.assertRaises((ValueError, pickle.PicklingError)): + with self.assertRaises((ValueError, pickle.PicklingError)) as cm: self.dumps(obj, proto) + self.assertIn(str(cm.exception), { + 'Empty module name', + f"Can't pickle {obj!r}: import of module '' failed"}) obj.__module__ = None for proto in protocols: with self.subTest(proto=proto): - with self.assertRaises(pickle.PicklingError): + with self.assertRaises(pickle.PicklingError) as cm: self.dumps(obj, proto) + self.assertIn(str(cm.exception), { + f"Can't pickle {obj!r}: it's not found as __main__.spam", + f"Can't pickle {obj!r}: attribute lookup spam on __main__ failed"}) def test_nonencodable_global_name_error(self): for proto in protocols[:4]: @@ -1888,8 +1986,15 @@ class AbstractPicklingErrorTests: obj = REX(name) obj.__module__ = __name__ with support.swap_item(globals(), name, obj): - with self.assertRaises((UnicodeEncodeError, pickle.PicklingError)): - self.dumps(obj, proto) + if proto == 3 and self.pickler is pickle._Pickler: + with self.assertRaises(UnicodeEncodeError): + self.dumps(obj, proto) + else: + with self.assertRaises(pickle.PicklingError) as cm: + self.dumps(obj, proto) + self.assertIn(str(cm.exception), { + f"can't pickle global identifier '{__name__}.{name}' using pickle protocol {proto}", + f"can't pickle global identifier '{name}' using pickle protocol {proto}"}) def test_nonencodable_module_name_error(self): for proto in protocols[:4]: @@ -1899,8 +2004,15 @@ class AbstractPicklingErrorTests: obj.__module__ = name mod = types.SimpleNamespace(test=obj) with support.swap_item(sys.modules, name, mod): - with self.assertRaises((UnicodeEncodeError, pickle.PicklingError)): - self.dumps(obj, proto) + if proto == 3 and self.pickler is pickle._Pickler: + with self.assertRaises(UnicodeEncodeError): + self.dumps(obj, proto) + else: + with self.assertRaises(pickle.PicklingError) as cm: + self.dumps(obj, proto) + self.assertIn(str(cm.exception), { + f"can't pickle global identifier '{name}.test' using pickle protocol {proto}", + f"can't pickle module identifier '{name}' using pickle protocol {proto}"}) def test_nested_lookup_error(self): # Nested name does not exist @@ -1908,14 +2020,20 @@ class AbstractPicklingErrorTests: obj.__module__ = __name__ for proto in protocols: with self.subTest(proto=proto): - with self.assertRaises(pickle.PicklingError): + with self.assertRaises(pickle.PicklingError) as cm: self.dumps(obj, proto) + self.assertIn(str(cm.exception), { + f"Can't pickle {obj!r}: it's not found as {__name__}.AbstractPickleTests.spam", + f"Can't pickle {obj!r}: attribute lookup AbstractPickleTests.spam on {__name__} failed"}) obj.__module__ = None for proto in protocols: with self.subTest(proto=proto): - with self.assertRaises(pickle.PicklingError): + with self.assertRaises(pickle.PicklingError) as cm: self.dumps(obj, proto) + self.assertIn(str(cm.exception), { + f"Can't pickle {obj!r}: it's not found as __main__.AbstractPickleTests.spam", + f"Can't pickle {obj!r}: attribute lookup AbstractPickleTests.spam on __main__ failed"}) def test_wrong_object_lookup_error(self): # Name is bound to different object @@ -1924,14 +2042,19 @@ class AbstractPicklingErrorTests: AbstractPickleTests.ham = [] for proto in protocols: with self.subTest(proto=proto): - with self.assertRaises(pickle.PicklingError): + with self.assertRaises(pickle.PicklingError) as cm: self.dumps(obj, proto) + self.assertEqual(str(cm.exception), + f"Can't pickle {obj!r}: it's not the same object as {__name__}.AbstractPickleTests") obj.__module__ = None for proto in protocols: with self.subTest(proto=proto): - with self.assertRaises(pickle.PicklingError): + with self.assertRaises(pickle.PicklingError) as cm: self.dumps(obj, proto) + self.assertIn(str(cm.exception), { + f"Can't pickle {obj!r}: it's not found as __main__.AbstractPickleTests", + f"Can't pickle {obj!r}: attribute lookup AbstractPickleTests on __main__ failed"}) def test_local_lookup_error(self): # Test that whichmodule() errors out cleanly when looking up @@ -1941,21 +2064,30 @@ class AbstractPicklingErrorTests: # Since the function is local, lookup will fail for proto in protocols: with self.subTest(proto=proto): - with self.assertRaises((AttributeError, pickle.PicklingError)): + with self.assertRaises((AttributeError, pickle.PicklingError)) as cm: self.dumps(f, proto) + self.assertIn(str(cm.exception), { + f"Can't pickle {f!r}: it's not found as {__name__}.{f.__qualname__}", + f"Can't get local object {f.__qualname__!r}"}) # Same without a __module__ attribute (exercises a different path # in _pickle.c). del f.__module__ for proto in protocols: with self.subTest(proto=proto): - with self.assertRaises((AttributeError, pickle.PicklingError)): + with self.assertRaises((AttributeError, pickle.PicklingError)) as cm: self.dumps(f, proto) + self.assertIn(str(cm.exception), { + f"Can't pickle {f!r}: it's not found as __main__.{f.__qualname__}", + f"Can't get local object {f.__qualname__!r}"}) # Yet a different path. f.__name__ = f.__qualname__ for proto in protocols: with self.subTest(proto=proto): - with self.assertRaises((AttributeError, pickle.PicklingError)): + with self.assertRaises((AttributeError, pickle.PicklingError)) as cm: self.dumps(f, proto) + self.assertIn(str(cm.exception), { + f"Can't pickle {f!r}: it's not found as __main__.{f.__qualname__}", + f"Can't get local object {f.__qualname__!r}"}) def test_reduce_ex_None(self): c = REX_None() @@ -4543,8 +4675,11 @@ class AbstractHookTests: # NotImplemented self.assertIs(math_log, math.log) - with self.assertRaises(pickle.PicklingError): + with self.assertRaises(pickle.PicklingError) as cm: p.dump(g) + self.assertRegex(str(cm.exception), + r'(__reduce__|<bound method .*reducer_override.*>)' + r' must return (a )?string or tuple') with self.assertRaisesRegex( ValueError, 'The reducer just failed'): |