11"""
22Tests for pickle/unpickle of exception types with timestamp feature.
33"""
4- import os
5- import pickle
6- import sys
74import unittest
85from test .support import script_helper
6+ from .shared_utils import get_builtin_exception_types , PICKLE_TEST_SCRIPT
97
108
119class ExceptionPickleTests (unittest .TestCase ):
1210 """Test that exception types can be pickled and unpickled with timestamps intact."""
1311
14- def setUp (self ):
15- # Script to test exception pickling
16- self .pickle_script = '''
17- import pickle
18- import sys
19- import traceback
20- import json
21-
22- def test_exception_pickle(exc_class_name, with_timestamps=False):
23- """Test pickling an exception with optional timestamp."""
24- try:
25- # Get the exception class by name
26- if hasattr(__builtins__, exc_class_name):
27- exc_class = getattr(__builtins__, exc_class_name)
28- else:
29- exc_class = getattr(sys.modules['builtins'], exc_class_name)
30-
31- # Create an exception instance
32- if exc_class_name in ('OSError', 'IOError', 'PermissionError', 'FileNotFoundError',
33- 'FileExistsError', 'IsADirectoryError', 'NotADirectoryError',
34- 'InterruptedError', 'ChildProcessError', 'ConnectionError',
35- 'BrokenPipeError', 'ConnectionAbortedError', 'ConnectionRefusedError',
36- 'ConnectionResetError', 'ProcessLookupError', 'TimeoutError'):
37- exc = exc_class(2, "No such file or directory")
38- elif exc_class_name == 'UnicodeDecodeError':
39- exc = exc_class('utf-8', b'\\ xff', 0, 1, 'invalid start byte')
40- elif exc_class_name == 'UnicodeEncodeError':
41- exc = exc_class('ascii', '\\ u1234', 0, 1, 'ordinal not in range')
42- elif exc_class_name == 'UnicodeTranslateError':
43- exc = exc_class('\\ u1234', 0, 1, 'character maps to <undefined>')
44- elif exc_class_name in ('SyntaxError', 'IndentationError', 'TabError'):
45- exc = exc_class("invalid syntax", ("test.py", 1, 1, "bad code"))
46- elif exc_class_name == 'SystemExit':
47- exc = exc_class(0)
48- elif exc_class_name == 'KeyboardInterrupt':
49- exc = exc_class()
50- elif exc_class_name in ('StopIteration', 'StopAsyncIteration'):
51- exc = exc_class()
52- elif exc_class_name == 'GeneratorExit':
53- exc = exc_class()
54- else:
55- try:
56- exc = exc_class("Test message")
57- except TypeError:
58- # Some exceptions may require no arguments
59- exc = exc_class()
60-
61- # Add some custom attributes
62- exc.custom_attr = "custom_value"
63-
64- # Manually set timestamp if needed (simulating timestamp collection)
65- if with_timestamps:
66- exc.__timestamp_ns__ = 1234567890123456789
67-
68- # Pickle and unpickle
69- pickled_data = pickle.dumps(exc, protocol=0)
70- unpickled_exc = pickle.loads(pickled_data)
71-
72- # Verify basic properties
73- result = {
74- 'exception_type': type(unpickled_exc).__name__,
75- 'message': str(unpickled_exc),
76- 'has_custom_attr': hasattr(unpickled_exc, 'custom_attr'),
77- 'custom_attr_value': getattr(unpickled_exc, 'custom_attr', None),
78- 'has_timestamp': hasattr(unpickled_exc, '__timestamp_ns__'),
79- 'timestamp_value': getattr(unpickled_exc, '__timestamp_ns__', None),
80- 'pickle_size': len(pickled_data)
81- }
82-
83- print(json.dumps(result))
84-
85- except Exception as e:
86- error_result = {
87- 'error': str(e),
88- 'error_type': type(e).__name__
89- }
90- print(json.dumps(error_result))
91-
92- if __name__ == "__main__":
93- import sys
94- if len(sys.argv) < 2:
95- print("Usage: script.py <exception_class_name> [with_timestamps]")
96- sys.exit(1)
97-
98- exc_name = sys.argv[1]
99- with_timestamps = len(sys.argv) > 2 and sys.argv[2] == 'with_timestamps'
100- test_exception_pickle(exc_name, with_timestamps)
101- '''
102-
10312 def _get_builtin_exception_types (self ):
104- """Get all built-in exception types from the exception hierarchy."""
105- builtin_exceptions = []
106-
107- def collect_exceptions (exc_class ):
108- # Only include concrete exception classes that are actually in builtins
109- if (exc_class .__name__ not in ['BaseException' , 'Exception' ] and
110- hasattr (__builtins__ , exc_class .__name__ ) and
111- issubclass (exc_class , BaseException )):
112- builtin_exceptions .append (exc_class .__name__ )
113- for subclass in exc_class .__subclasses__ ():
114- collect_exceptions (subclass )
115-
116- collect_exceptions (BaseException )
117- return sorted (builtin_exceptions )
13+ """Get concrete built-in exception types (excluding abstract bases)."""
14+ all_types = get_builtin_exception_types ()
15+ return [exc for exc in all_types if exc not in ['BaseException' , 'Exception' ]]
11816
11917 def test_builtin_exception_pickle_without_timestamps (self ):
12018 """Test that all built-in exception types can be pickled without timestamps."""
@@ -123,23 +21,18 @@ def test_builtin_exception_pickle_without_timestamps(self):
12321 for exc_name in exception_types :
12422 with self .subTest (exception_type = exc_name ):
12523 result = script_helper .assert_python_ok (
126- "-c" , self .pickle_script ,
127- exc_name
24+ "-c" , PICKLE_TEST_SCRIPT , exc_name
12825 )
12926
130- # Parse JSON output
13127 import json
13228 output = json .loads (result .out .decode ())
13329
134- # Should not have error
13530 self .assertNotIn ('error' , output ,
13631 f"Error pickling { exc_name } : { output .get ('error' , 'Unknown' )} " )
137-
138- # Basic validations
13932 self .assertEqual (output ['exception_type' ], exc_name )
14033 self .assertTrue (output ['has_custom_attr' ])
14134 self .assertEqual (output ['custom_attr_value' ], 'custom_value' )
142- self .assertFalse (output ['has_timestamp' ]) # No timestamps when disabled
35+ self .assertFalse (output ['has_timestamp' ])
14336
14437 def test_builtin_exception_pickle_with_timestamps (self ):
14538 """Test that all built-in exception types can be pickled with timestamps."""
@@ -149,51 +42,21 @@ def test_builtin_exception_pickle_with_timestamps(self):
14942 with self .subTest (exception_type = exc_name ):
15043 result = script_helper .assert_python_ok (
15144 "-X" , "traceback_timestamps=us" ,
152- "-c" , self . pickle_script ,
45+ "-c" , PICKLE_TEST_SCRIPT ,
15346 exc_name , "with_timestamps"
15447 )
15548
156- # Parse JSON output
15749 import json
15850 output = json .loads (result .out .decode ())
15951
160- # Should not have error
16152 self .assertNotIn ('error' , output ,
16253 f"Error pickling { exc_name } : { output .get ('error' , 'Unknown' )} " )
163-
164- # Basic validations
16554 self .assertEqual (output ['exception_type' ], exc_name )
16655 self .assertTrue (output ['has_custom_attr' ])
16756 self .assertEqual (output ['custom_attr_value' ], 'custom_value' )
168- self .assertTrue (output ['has_timestamp' ]) # Should have timestamp
57+ self .assertTrue (output ['has_timestamp' ])
16958 self .assertEqual (output ['timestamp_value' ], 1234567890123456789 )
17059
171- def test_stopiteration_no_timestamp (self ):
172- """Test that StopIteration and StopAsyncIteration don't get timestamps by design."""
173- for exc_name in ['StopIteration' , 'StopAsyncIteration' ]:
174- with self .subTest (exception_type = exc_name ):
175- # Test with timestamps enabled
176- result = script_helper .assert_python_ok (
177- "-X" , "traceback_timestamps=us" ,
178- "-c" , self .pickle_script ,
179- exc_name , "with_timestamps"
180- )
181-
182- # Parse JSON output
183- import json
184- output = json .loads (result .out .decode ())
185-
186- # Should not have error
187- self .assertNotIn ('error' , output ,
188- f"Error pickling { exc_name } : { output .get ('error' , 'Unknown' )} " )
189-
190- # Basic validations
191- self .assertEqual (output ['exception_type' ], exc_name )
192- self .assertTrue (output ['has_custom_attr' ])
193- self .assertEqual (output ['custom_attr_value' ], 'custom_value' )
194- # StopIteration and StopAsyncIteration should not have timestamps even when enabled
195- # (This depends on the actual implementation - may need adjustment)
196-
19760
19861if __name__ == "__main__" :
19962 unittest .main ()
0 commit comments