From 9e99313d2e0429adc164eeaf60059e936434afe7 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Wed, 7 Jun 2023 14:38:28 +0300 Subject: [PATCH 1/3] gh-105433: Add `pickle` tests for PEP695 --- Lib/test/test_type_aliases.py | 69 +++++++++++++++++++++++++++++++++-- Lib/test/test_type_params.py | 48 ++++++++++++++++++++++++ 2 files changed, 113 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_type_aliases.py b/Lib/test/test_type_aliases.py index c43499609aaa56..9333189b97b041 100644 --- a/Lib/test/test_type_aliases.py +++ b/Lib/test/test_type_aliases.py @@ -226,8 +226,69 @@ def test_module(self): self.assertEqual(mod_generics_cache.OldStyle.__module__, mod_generics_cache.__name__) + +# All these type aliases are used for pickling tests: +T = TypeVar('T') +type SimpleAlias = int +type RecursiveAlias = dict[str, RecursiveAlias] +type GenericAlias[X] = list[X] +type GenericAliasMultipleTypes[X, Y] = dict[X, Y] +type RecursiveGenericAlias[X] = dict[str, RecursiveAlias[X]] +type BoundGenericAlias[X: int] = set[X] +type ConstrainedGenericAlias[LongName: (str, bytes)] = list[LongName] +type AllTypesAlias[A, *B, **C] = Callable[C, A] | tuple[*B] + + +class TypeAliasPickleTest(unittest.TestCase): def test_pickling(self): - pickled = pickle.dumps(mod_generics_cache.Alias) - self.assertIs(pickle.loads(pickled), mod_generics_cache.Alias) - pickled = pickle.dumps(mod_generics_cache.OldStyle) - self.assertIs(pickle.loads(pickled), mod_generics_cache.OldStyle) + things_to_test = [ + SimpleAlias, + RecursiveAlias, + + GenericAlias, + GenericAlias[T], + GenericAlias[int], + + GenericAliasMultipleTypes, + GenericAliasMultipleTypes[str, T], + GenericAliasMultipleTypes[T, str], + GenericAliasMultipleTypes[int, str], + + RecursiveGenericAlias, + RecursiveGenericAlias[T], + RecursiveGenericAlias[int], + + BoundGenericAlias, + BoundGenericAlias[int], + BoundGenericAlias[T], + + ConstrainedGenericAlias, + ConstrainedGenericAlias[str], + ConstrainedGenericAlias[T], + + AllTypesAlias, + AllTypesAlias[int, str, T, [T, object]], + + # Other modules: + mod_generics_cache.Alias, + mod_generics_cache.OldStyle, + ] + for thing in things_to_test: + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + with self.subTest(thing=thing, proto=proto): + pickled = pickle.dumps(thing, protocol=proto) + self.assertEqual(pickle.loads(pickled), thing) + + type ClassLevel = str + + def test_pickling_local(self): + type A = int + things_to_test = [ + self.ClassLevel, + A, + ] + for thing in things_to_test: + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + with self.subTest(thing=thing, proto=proto): + with self.assertRaises(pickle.PickleError): + pickle.dumps(thing, protocol=proto) diff --git a/Lib/test/test_type_params.py b/Lib/test/test_type_params.py index 7b7b6122c028e5..46b7b5dd16a12d 100644 --- a/Lib/test/test_type_params.py +++ b/Lib/test/test_type_params.py @@ -2,6 +2,7 @@ import textwrap import types import unittest +import pickle from test.support import requires_working_socket, check_syntax_error, run_code from typing import Generic, Sequence, TypeVar, TypeVarTuple, ParamSpec, get_args @@ -849,3 +850,50 @@ def func[A](): ns = run_code(code) self.assertEqual(ns["func"].__type_params__, ()) + + + +# All these type aliases are used for pickling tests: +T = TypeVar('T') +def func1[X](x: X) -> X: ... +def func2[X, Y](x: X | Y) -> X | Y: ... +def func3[X, *Y, **Z](x: X, y: tuple[*Y], z: Z) -> X: ... +def func4[X: int, Y: (bytes, str)](x: X, y: Y) -> X | Y: ... + +class Class1[X]: ... +class Class2[X, Y]: ... +class Class3[X, *Y, **Z]: ... +class Class4[X: int, Y: (bytes, str)]: ... + + +class TypeParamsPickleTest(unittest.TestCase): + def test_pickling(self): + things_to_test = [ + func1, + func2, + func3, + func4, + + Class1, + Class1[int], + Class1[T], + + Class2, + Class2[int, T], + Class2[T, int], + Class2[int, str], + + Class3, + Class3[int, T, str, bytes, [float, object, T]], + + Class4, + Class4[int, bytes], + Class4[T, bytes], + Class4[int, T], + Class4[T, T], + ] + for thing in things_to_test: + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + with self.subTest(thing=thing, proto=proto): + pickled = pickle.dumps(thing, protocol=proto) + self.assertEqual(pickle.loads(pickled), thing) From 66345c2cdf8f96a928a0f4b7cc2870906fc78185 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Wed, 7 Jun 2023 14:47:06 +0300 Subject: [PATCH 2/3] Fix lint --- Lib/test/test_type_aliases.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_type_aliases.py b/Lib/test/test_type_aliases.py index 9333189b97b041..8b28477aa7c4b0 100644 --- a/Lib/test/test_type_aliases.py +++ b/Lib/test/test_type_aliases.py @@ -264,7 +264,7 @@ def test_pickling(self): ConstrainedGenericAlias, ConstrainedGenericAlias[str], - ConstrainedGenericAlias[T], + ConstrainedGenericAlias[T], AllTypesAlias, AllTypesAlias[int, str, T, [T, object]], From 045339163083002ec59f26150fc47b103bdd4aab Mon Sep 17 00:00:00 2001 From: sobolevn Date: Wed, 7 Jun 2023 17:27:31 +0300 Subject: [PATCH 3/3] Also test instances --- Lib/test/test_type_params.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_type_params.py b/Lib/test/test_type_params.py index 46b7b5dd16a12d..5972cdd49bd44f 100644 --- a/Lib/test/test_type_params.py +++ b/Lib/test/test_type_params.py @@ -867,13 +867,21 @@ class Class4[X: int, Y: (bytes, str)]: ... class TypeParamsPickleTest(unittest.TestCase): - def test_pickling(self): + def test_pickling_functions(self): things_to_test = [ func1, func2, func3, func4, + ] + for thing in things_to_test: + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + with self.subTest(thing=thing, proto=proto): + pickled = pickle.dumps(thing, protocol=proto) + self.assertEqual(pickle.loads(pickled), thing) + def test_pickling_classes(self): + things_to_test = [ Class1, Class1[int], Class1[T], @@ -897,3 +905,13 @@ def test_pickling(self): with self.subTest(thing=thing, proto=proto): pickled = pickle.dumps(thing, protocol=proto) self.assertEqual(pickle.loads(pickled), thing) + + for klass in things_to_test: + real_class = getattr(klass, '__origin__', klass) + thing = klass() + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + with self.subTest(thing=thing, proto=proto): + pickled = pickle.dumps(thing, protocol=proto) + # These instances are not equal, + # but class check is good enough: + self.assertIsInstance(pickle.loads(pickled), real_class)