Skip to content

Commit 8be8128

Browse files
[3.13] gh-156106: Add tests for setting and deleting attributes defined in C (GH-156107) (GH-156191)
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 e178a13 commit 8be8128

13 files changed

Lines changed: 292 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 Structure, c_char, c_int
2+
from ctypes import CDLL, Structure, c_char, c_int
3+
from test.support import import_helper
34

45

56
class X(Structure):
@@ -21,6 +22,25 @@ def test_struct(self):
2122
with self.assertRaises(TypeError):
2223
del struct.foo
2324

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

2545
if __name__ == "__main__":
2646
unittest.main()

Lib/test/test_decimal.py

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

42404240
# Attributes cannot be deleted
42414241
for attr in ['prec', 'Emax', 'Emin', 'rounding', 'capitals', 'clamp',
4242-
'flags', 'traps']:
4242+
'flags', 'traps', '_allcr', '_flags', '_traps']:
42434243
self.assertRaises(AttributeError, c.__delattr__, attr)
42444244

42454245
# 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: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -663,6 +663,43 @@ def test_invalid_setattr(self):
663663
msg = "exception context must be None or derive from BaseException"
664664
self.assertRaisesRegex(TE, msg, setattr, exc, '__context__', 1)
665665

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

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

Lib/test/test_fileio.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ def testBlksize(self):
8181
blksize = getattr(fst, 'st_blksize', blksize)
8282
self.assertEqual(self.f._blksize, blksize)
8383

84+
8485
# verify readinto
8586
def testReadintoByteArray(self):
8687
self.f.write(bytes([1, 2, 0, 255]))
@@ -363,6 +364,21 @@ class CAutoFileTests(AutoFileTests, unittest.TestCase):
363364
FileIO = _io.FileIO
364365
modulename = '_io'
365366

367+
def testFinalizing(self):
368+
# test the private _finalizing attribute
369+
self.assertIs(self.f._finalizing, False)
370+
self.f._finalizing = True
371+
self.assertIs(self.f._finalizing, True)
372+
with self.assertRaisesRegex(TypeError,
373+
'attribute value type must be bool'):
374+
self.f._finalizing = 1
375+
with self.assertRaisesRegex(TypeError,
376+
"can't delete numeric/char attribute"):
377+
del self.f._finalizing
378+
# closing a file which is being finalized emits a ResourceWarning
379+
self.f._finalizing = False
380+
381+
366382
class PyAutoFileTests(AutoFileTests, unittest.TestCase):
367383
FileIO = _pyio.FileIO
368384
modulename = '_pyio'

Lib/test/test_frame.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,31 @@ def test_locals_clear_locals(self):
219219
self.assertEqual(outer.f_locals, {})
220220
self.assertEqual(inner.f_locals, {})
221221

222+
def test_f_trace(self):
223+
f, _, _ = self.make_frames()
224+
def tracer(*args):
225+
pass
226+
for value in tracer, 42, None:
227+
f.f_trace = value
228+
self.assertEqual(f.f_trace, value)
229+
f.f_trace = tracer
230+
del f.f_trace
231+
self.assertIsNone(f.f_trace)
232+
233+
def test_f_trace_lines_and_opcodes(self):
234+
f, _, _ = self.make_frames()
235+
for name in 'f_trace_lines', 'f_trace_opcodes':
236+
with self.subTest(name=name):
237+
for value in False, True:
238+
setattr(f, name, value)
239+
self.assertEqual(getattr(f, name), value)
240+
with self.assertRaisesRegex(TypeError,
241+
'attribute value type must be bool'):
242+
setattr(f, name, 1)
243+
with self.assertRaisesRegex(TypeError,
244+
"can't delete numeric/char attribute"):
245+
del f.f_trace_lines
246+
222247
def test_f_trace_opcodes_del(self):
223248
f, _, _ = self.make_frames()
224249
f.f_trace_opcodes = True

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
@@ -4065,6 +4065,29 @@ class CTextIOWrapperTest(TextIOWrapperTest):
40654065
io = io
40664066
shutdown_error = "LookupError: unknown encoding: ascii"
40674067

4068+
def test_chunk_size(self):
4069+
t = self.TextIOWrapper(self.BytesIO(), encoding="utf-8")
4070+
self.assertGreater(t._CHUNK_SIZE, 0)
4071+
t._CHUNK_SIZE = 1024
4072+
self.assertEqual(t._CHUNK_SIZE, 1024)
4073+
with self.assertRaisesRegex(ValueError,
4074+
'a strictly positive integer is required'):
4075+
t._CHUNK_SIZE = 0
4076+
with self.assertRaises(TypeError):
4077+
t._CHUNK_SIZE = 'x'
4078+
with self.assertRaises(ValueError):
4079+
t._CHUNK_SIZE = sys.maxsize + 1
4080+
with self.assertRaises(ValueError):
4081+
t._CHUNK_SIZE = -sys.maxsize - 2
4082+
with self.assertRaises(ValueError):
4083+
t._CHUNK_SIZE = 2**1000
4084+
with self.assertRaises(ValueError):
4085+
t._CHUNK_SIZE = -2**1000
4086+
with self.assertRaisesRegex(AttributeError, 'cannot be deleted'):
4087+
del t._CHUNK_SIZE
4088+
# a failed assignment does not change the value
4089+
self.assertEqual(t._CHUNK_SIZE, 1024)
4090+
40684091
def test_reentrant_seek_during_tell(self):
40694092
# gh-153539: reading short of _CHUNK_SIZE leaves residual bytes in the
40704093
# snapshot, so tell() re-decodes and calls the decoder's getstate(); a

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)