Skip to content

Commit ccaacd8

Browse files
[3.14] gh-156106: Add tests for setting and deleting attributes defined in C (GH-156107) (ПР-156184)
Test setting a value of an accepted type, of a wrong type and an invalid value, and deleting the attribute, for the attributes defined with PyMemberDef and PyGetSetDef which were not covered. (cherry picked from commit cdca502)
1 parent e810e1a commit ccaacd8

13 files changed

Lines changed: 295 additions & 2 deletions

Lib/test/test_asyncio/test_futures.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,14 @@ def test_future_cancel_message_setter(self):
255255
f.cancel('my message')
256256
f._cancel_message = 'my new message'
257257
self.assertEqual(f._cancel_message, 'my new message')
258+
f._cancel_message = None
259+
self.assertIsNone(f._cancel_message)
260+
f._cancel_message = 'my new message'
261+
if not isinstance(f, futures._PyFuture):
262+
# The C implementation does not support deletion.
263+
with self.assertRaises(AttributeError):
264+
del f._cancel_message
265+
self.assertEqual(f._cancel_message, 'my new message')
258266

259267
# Also check that the value is used for cancel().
260268
with self.assertRaises(asyncio.CancelledError):

Lib/test/test_ctypes/test_delattr.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import unittest
2-
from ctypes import POINTER, Structure, c_char, c_int
2+
from ctypes import CDLL, POINTER, Structure, c_char, c_int
3+
from test.support import import_helper
34

45

56
class X(Structure):
@@ -26,6 +27,25 @@ def test_struct(self):
2627
with self.assertRaises(TypeError):
2728
del struct.foo
2829

30+
def test_raw(self):
31+
chararray = (c_char * 5)()
32+
with self.assertRaises(AttributeError):
33+
del chararray.raw
34+
35+
def test_func_pointer(self):
36+
# Deleting these attributes restores the default.
37+
dll = CDLL(import_helper.import_module('_ctypes_test').__file__)
38+
func = dll._testfunc_i_bhilfd
39+
func.argtypes = [c_int]
40+
func.restype = c_int
41+
func.errcheck = lambda *args: None
42+
del func.argtypes
43+
self.assertIsNone(func.argtypes)
44+
del func.errcheck
45+
self.assertIsNone(func.errcheck)
46+
del func.restype
47+
self.assertIs(func.restype, c_int)
48+
2949

3050
if __name__ == "__main__":
3151
unittest.main()

Lib/test/test_decimal.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4283,7 +4283,7 @@ def test_invalid_context(self):
42834283

42844284
# Attributes cannot be deleted
42854285
for attr in ['prec', 'Emax', 'Emin', 'rounding', 'capitals', 'clamp',
4286-
'flags', 'traps']:
4286+
'flags', 'traps', '_allcr', '_flags', '_traps']:
42874287
self.assertRaises(AttributeError, c.__delattr__, attr)
42884288

42894289
# Invalid attributes

Lib/test/test_defaultdict.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ def test_basic(self):
3737
self.assertIn(42, d2.keys())
3838
self.assertNotIn(12, d2)
3939
self.assertNotIn(12, d2.keys())
40+
d2.default_factory = list
41+
del d2.default_factory
42+
self.assertEqual(d2.default_factory, None)
4043
d2.default_factory = None
4144
self.assertEqual(d2.default_factory, None)
4245
try:

Lib/test/test_exceptions.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -667,6 +667,44 @@ def test_invalid_setattr(self):
667667
msg = "exception context must be None or derive from BaseException"
668668
self.assertRaisesRegex(TE, msg, setattr, exc, '__context__', 1)
669669

670+
def test_object_attributes(self):
671+
# These attributes are implemented as plain object members:
672+
# they accept any object and are reset to None when deleted.
673+
cases = [
674+
(SyntaxError('msgStr'), 'msg'),
675+
(SyntaxError('msgStr'), 'filename'),
676+
(SyntaxError('msgStr'), 'lineno'),
677+
(SyntaxError('msgStr'), 'offset'),
678+
(SyntaxError('msgStr'), 'end_lineno'),
679+
(SyntaxError('msgStr'), 'end_offset'),
680+
(SyntaxError('msgStr'), 'text'),
681+
(SyntaxError('msgStr'), 'print_file_and_line'),
682+
(SyntaxError('msgStr'), '_metadata'),
683+
(ImportError('msgStr'), 'msg'),
684+
(ImportError('msgStr'), 'name'),
685+
(ImportError('msgStr'), 'path'),
686+
(ImportError('msgStr'), 'name_from'),
687+
(SystemExit(1), 'code'),
688+
(StopIteration(), 'value'),
689+
(NameError('msgStr'), 'name'),
690+
(AttributeError('msgStr'), 'name'),
691+
(AttributeError('msgStr'), 'obj'),
692+
(OSError(2, 'msgStr'), 'errno'),
693+
(OSError(2, 'msgStr'), 'strerror'),
694+
(OSError(2, 'msgStr'), 'filename'),
695+
(OSError(2, 'msgStr'), 'filename2'),
696+
(UnicodeDecodeError('utf-8', b'\xff', 0, 1, 'reasonStr'), 'reason'),
697+
]
698+
if sys.platform == 'win32':
699+
cases.append((OSError(2, 'msgStr'), 'winerror'))
700+
for exc, name in cases:
701+
with self.subTest(exc=type(exc).__name__, name=name):
702+
for value in 'strValue', 42, [1, 2], None:
703+
setattr(exc, name, value)
704+
self.assertEqual(getattr(exc, name), value)
705+
delattr(exc, name)
706+
self.assertIsNone(getattr(exc, name))
707+
670708
def test_invalid_delattr(self):
671709
TE = TypeError
672710
try:
@@ -724,6 +762,13 @@ def testChainingDescriptors(self):
724762
self.assertTrue(e.__suppress_context__)
725763
e.__suppress_context__ = False
726764
self.assertFalse(e.__suppress_context__)
765+
with self.assertRaisesRegex(TypeError,
766+
'attribute value type must be bool'):
767+
e.__suppress_context__ = 1
768+
with self.assertRaisesRegex(TypeError,
769+
"can't delete numeric/char attribute"):
770+
del e.__suppress_context__
771+
self.assertFalse(e.__suppress_context__)
727772

728773
def testKeywordArgs(self):
729774
# test that builtin exception don't take keyword args,

Lib/test/test_fileio.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,12 @@ def testBlksize(self):
8383
fst = os.fstat(self.f.fileno())
8484
blksize = getattr(fst, 'st_blksize', blksize)
8585
self.assertEqual(self.f._blksize, blksize)
86+
# it is read-only
87+
with self.assertRaises(AttributeError):
88+
self.f._blksize = blksize
89+
with self.assertRaises(AttributeError):
90+
del self.f._blksize
91+
8692

8793
# verify readinto
8894
def testReadintoByteArray(self):
@@ -503,6 +509,21 @@ class CAutoFileTests(AutoFileTests, unittest.TestCase):
503509
FileIO = _io.FileIO
504510
modulename = '_io'
505511

512+
def testFinalizing(self):
513+
# test the private _finalizing attribute
514+
self.assertIs(self.f._finalizing, False)
515+
self.f._finalizing = True
516+
self.assertIs(self.f._finalizing, True)
517+
with self.assertRaisesRegex(TypeError,
518+
'attribute value type must be bool'):
519+
self.f._finalizing = 1
520+
with self.assertRaisesRegex(TypeError,
521+
"can't delete numeric/char attribute"):
522+
del self.f._finalizing
523+
# closing a file which is being finalized emits a ResourceWarning
524+
self.f._finalizing = False
525+
526+
506527
class PyAutoFileTests(AutoFileTests, unittest.TestCase):
507528
FileIO = _pyio.FileIO
508529
modulename = '_pyio'

Lib/test/test_frame.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,33 @@ def test_f_lineno_del_segfault(self):
222222
with self.assertRaises(AttributeError):
223223
del f.f_lineno
224224

225+
def test_f_trace(self):
226+
f, _, _ = self.make_frames()
227+
def tracer(*args):
228+
pass
229+
for value in tracer, 42, None:
230+
f.f_trace = value
231+
self.assertEqual(f.f_trace, value)
232+
f.f_trace = tracer
233+
del f.f_trace
234+
self.assertIsNone(f.f_trace)
235+
236+
def test_f_trace_lines_and_opcodes(self):
237+
f, _, _ = self.make_frames()
238+
for name in 'f_trace_lines', 'f_trace_opcodes':
239+
with self.subTest(name=name):
240+
for value in False, True:
241+
setattr(f, name, value)
242+
self.assertEqual(getattr(f, name), value)
243+
with self.assertRaisesRegex(TypeError,
244+
'attribute value type must be bool'):
245+
setattr(f, name, 1)
246+
with self.assertRaisesRegex(TypeError,
247+
"can't delete numeric/char attribute"):
248+
del f.f_trace_lines
249+
with self.assertRaisesRegex(AttributeError, 'cannot be deleted'):
250+
del f.f_trace_opcodes
251+
225252
def test_f_generator(self):
226253
# Test f_generator in different contexts.
227254

Lib/test/test_funcattrs.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,41 @@ def e(): return num_one, num_two
266266
self.fail("__code__ with different numbers of free vars should "
267267
"not be possible")
268268

269+
def test___kwdefaults__(self):
270+
def func(a=1, *, b=2, c=3):
271+
return a, b, c
272+
self.assertEqual(func.__kwdefaults__, {'b': 2, 'c': 3})
273+
func.__kwdefaults__ = {'b': 4}
274+
self.assertEqual(func.__kwdefaults__, {'b': 4})
275+
self.assertEqual(func(c=5), (1, 4, 5))
276+
func.__kwdefaults__ = None
277+
self.assertIsNone(func.__kwdefaults__)
278+
self.assertRaises(TypeError, func)
279+
with self.assertRaisesRegex(TypeError,
280+
'__kwdefaults__ must be set to a dict object'):
281+
func.__kwdefaults__ = [('b', 4)]
282+
del func.__kwdefaults__
283+
self.assertIsNone(func.__kwdefaults__)
284+
285+
def test_invalid___code___deletion(self):
286+
def func(): pass
287+
with self.assertRaisesRegex(TypeError,
288+
'__code__ must be set to a code object'):
289+
func.__code__ = None
290+
with self.assertRaisesRegex(TypeError,
291+
'__code__ must be set to a code object'):
292+
del func.__code__
293+
294+
def test___doc__(self):
295+
def func():
296+
"docstring"
297+
self.assertEqual(func.__doc__, 'docstring')
298+
for value in 'other', 42, None:
299+
func.__doc__ = value
300+
self.assertEqual(func.__doc__, value)
301+
del func.__doc__
302+
self.assertIsNone(func.__doc__)
303+
269304
def test_blank_func_defaults(self):
270305
self.assertEqual(self.b.__defaults__, None)
271306
del self.b.__defaults__

Lib/test/test_io.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4099,6 +4099,29 @@ class CTextIOWrapperTest(TextIOWrapperTest):
40994099
io = io
41004100
shutdown_error = "LookupError: unknown encoding: ascii"
41014101

4102+
def test_chunk_size(self):
4103+
t = self.TextIOWrapper(self.BytesIO(), encoding="utf-8")
4104+
self.assertGreater(t._CHUNK_SIZE, 0)
4105+
t._CHUNK_SIZE = 1024
4106+
self.assertEqual(t._CHUNK_SIZE, 1024)
4107+
with self.assertRaisesRegex(ValueError,
4108+
'a strictly positive integer is required'):
4109+
t._CHUNK_SIZE = 0
4110+
with self.assertRaises(TypeError):
4111+
t._CHUNK_SIZE = 'x'
4112+
with self.assertRaises(ValueError):
4113+
t._CHUNK_SIZE = sys.maxsize + 1
4114+
with self.assertRaises(ValueError):
4115+
t._CHUNK_SIZE = -sys.maxsize - 2
4116+
with self.assertRaises(ValueError):
4117+
t._CHUNK_SIZE = 2**1000
4118+
with self.assertRaises(ValueError):
4119+
t._CHUNK_SIZE = -2**1000
4120+
with self.assertRaisesRegex(AttributeError, 'cannot be deleted'):
4121+
del t._CHUNK_SIZE
4122+
# a failed assignment does not change the value
4123+
self.assertEqual(t._CHUNK_SIZE, 1024)
4124+
41024125
def test_initialization(self):
41034126
r = self.BytesIO(b"\xc3\xa9\n\n")
41044127
b = self.BufferedReader(r, 1000)

Lib/test/test_kqueue.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,31 @@ def test_create_event(self):
110110
self.assertNotEqual(ev, other)
111111

112112

113+
def test_event_attributes(self):
114+
fd = os.open(os.devnull, os.O_WRONLY)
115+
self.addCleanup(os.close, fd)
116+
117+
ev = select.kevent(fd)
118+
# All attributes are numeric members: they can be set and cannot be
119+
# deleted.
120+
for name, value in (('ident', 1), ('filter', select.KQ_FILTER_WRITE),
121+
('flags', select.KQ_EV_DELETE), ('fflags', 2),
122+
('data', 3), ('udata', 4)):
123+
with self.subTest(name=name):
124+
setattr(ev, name, value)
125+
self.assertEqual(getattr(ev, name), value)
126+
with self.assertRaises(TypeError):
127+
setattr(ev, name, 'not a number')
128+
with self.assertRaises(OverflowError):
129+
setattr(ev, name, 2**1000)
130+
with self.assertRaises(OverflowError):
131+
setattr(ev, name, -2**1000)
132+
with self.assertRaisesRegex(
133+
TypeError, "can't delete numeric/char attribute"):
134+
delattr(ev, name)
135+
# a failed assignment does not change the value
136+
self.assertEqual(getattr(ev, name), value)
137+
113138
def test_queue_event(self):
114139
serverSocket = socket.create_server(('127.0.0.1', 0))
115140
client = socket.socket()

0 commit comments

Comments
 (0)