Skip to content

Commit 225018d

Browse files
[3.15] gh-156106: Add tests for setting and deleting attributes defined in C (GH-156107) (GH-159086)
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 e2b711c commit 225018d

13 files changed

Lines changed: 296 additions & 2 deletions

File tree

‎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
@@ -4288,7 +4288,7 @@ def test_invalid_context(self):
42884288

42894289
# Attributes cannot be deleted
42904290
for attr in ['prec', 'Emax', 'Emin', 'rounding', 'capitals', 'clamp',
4291-
'flags', 'traps']:
4291+
'flags', 'traps', '_allcr', '_flags', '_traps']:
42924292
self.assertRaises(AttributeError, c.__delattr__, attr)
42934293

42944294
# 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
@@ -702,6 +702,44 @@ def test_invalid_setattr(self):
702702
msg = "exception context must be None or derive from BaseException"
703703
self.assertRaisesRegex(TE, msg, setattr, exc, '__context__', 1)
704704

705+
def test_object_attributes(self):
706+
# These attributes are implemented as plain object members:
707+
# they accept any object and are reset to None when deleted.
708+
cases = [
709+
(SyntaxError('msgStr'), 'msg'),
710+
(SyntaxError('msgStr'), 'filename'),
711+
(SyntaxError('msgStr'), 'lineno'),
712+
(SyntaxError('msgStr'), 'offset'),
713+
(SyntaxError('msgStr'), 'end_lineno'),
714+
(SyntaxError('msgStr'), 'end_offset'),
715+
(SyntaxError('msgStr'), 'text'),
716+
(SyntaxError('msgStr'), 'print_file_and_line'),
717+
(SyntaxError('msgStr'), '_metadata'),
718+
(ImportError('msgStr'), 'msg'),
719+
(ImportError('msgStr'), 'name'),
720+
(ImportError('msgStr'), 'path'),
721+
(ImportError('msgStr'), 'name_from'),
722+
(SystemExit(1), 'code'),
723+
(StopIteration(), 'value'),
724+
(NameError('msgStr'), 'name'),
725+
(AttributeError('msgStr'), 'name'),
726+
(AttributeError('msgStr'), 'obj'),
727+
(OSError(2, 'msgStr'), 'errno'),
728+
(OSError(2, 'msgStr'), 'strerror'),
729+
(OSError(2, 'msgStr'), 'filename'),
730+
(OSError(2, 'msgStr'), 'filename2'),
731+
(UnicodeDecodeError('utf-8', b'\xff', 0, 1, 'reasonStr'), 'reason'),
732+
]
733+
if sys.platform == 'win32':
734+
cases.append((OSError(2, 'msgStr'), 'winerror'))
735+
for exc, name in cases:
736+
with self.subTest(exc=type(exc).__name__, name=name):
737+
for value in 'strValue', 42, [1, 2], None:
738+
setattr(exc, name, value)
739+
self.assertEqual(getattr(exc, name), value)
740+
delattr(exc, name)
741+
self.assertIsNone(getattr(exc, name))
742+
705743
def test_invalid_delattr(self):
706744
TE = TypeError
707745
try:
@@ -759,6 +797,13 @@ def testChainingDescriptors(self):
759797
self.assertTrue(e.__suppress_context__)
760798
e.__suppress_context__ = False
761799
self.assertFalse(e.__suppress_context__)
800+
with self.assertRaisesRegex(TypeError,
801+
'attribute value type must be bool'):
802+
e.__suppress_context__ = 1
803+
with self.assertRaisesRegex(TypeError,
804+
"can't delete numeric/char attribute"):
805+
del e.__suppress_context__
806+
self.assertFalse(e.__suppress_context__)
762807

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

‎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/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_io/test_textio.py‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1448,6 +1448,29 @@ def _to_memoryview(buf):
14481448
class CTextIOWrapperTest(TextIOWrapperTest, CTestCase):
14491449
shutdown_error = "LookupError: unknown encoding: ascii"
14501450

1451+
def test_chunk_size(self):
1452+
t = self.TextIOWrapper(self.BytesIO(), encoding="utf-8")
1453+
self.assertGreater(t._CHUNK_SIZE, 0)
1454+
t._CHUNK_SIZE = 1024
1455+
self.assertEqual(t._CHUNK_SIZE, 1024)
1456+
with self.assertRaisesRegex(ValueError,
1457+
'a strictly positive integer is required'):
1458+
t._CHUNK_SIZE = 0
1459+
with self.assertRaises(TypeError):
1460+
t._CHUNK_SIZE = 'x'
1461+
with self.assertRaises(ValueError):
1462+
t._CHUNK_SIZE = sys.maxsize + 1
1463+
with self.assertRaises(ValueError):
1464+
t._CHUNK_SIZE = -sys.maxsize - 2
1465+
with self.assertRaises(ValueError):
1466+
t._CHUNK_SIZE = 2**1000
1467+
with self.assertRaises(ValueError):
1468+
t._CHUNK_SIZE = -2**1000
1469+
with self.assertRaisesRegex(AttributeError, 'cannot be deleted'):
1470+
del t._CHUNK_SIZE
1471+
# a failed assignment does not change the value
1472+
self.assertEqual(t._CHUNK_SIZE, 1024)
1473+
14511474
def test_reentrant_seek_during_tell(self):
14521475
# gh-153539: reading short of _CHUNK_SIZE leaves residual bytes in the
14531476
# 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
@@ -126,6 +126,31 @@ def test_create_event(self):
126126
self.assertNotEqual(ev, other)
127127

128128

129+
def test_event_attributes(self):
130+
fd = os.open(os.devnull, os.O_WRONLY)
131+
self.addCleanup(os.close, fd)
132+
133+
ev = select.kevent(fd)
134+
# All attributes are numeric members: they can be set and cannot be
135+
# deleted.
136+
for name, value in (('ident', 1), ('filter', select.KQ_FILTER_WRITE),
137+
('flags', select.KQ_EV_DELETE), ('fflags', 2),
138+
('data', 3), ('udata', 4)):
139+
with self.subTest(name=name):
140+
setattr(ev, name, value)
141+
self.assertEqual(getattr(ev, name), value)
142+
with self.assertRaises(TypeError):
143+
setattr(ev, name, 'not a number')
144+
with self.assertRaises(OverflowError):
145+
setattr(ev, name, 2**1000)
146+
with self.assertRaises(OverflowError):
147+
setattr(ev, name, -2**1000)
148+
with self.assertRaisesRegex(
149+
TypeError, "can't delete numeric/char attribute"):
150+
delattr(ev, name)
151+
# a failed assignment does not change the value
152+
self.assertEqual(getattr(ev, name), value)
153+
129154
def test_queue_event(self):
130155
serverSocket = socket.create_server(('127.0.0.1', 0))
131156
client = socket.socket()

0 commit comments

Comments
 (0)