Skip to content

Commit cdca502

Browse files
gh-156106: Add tests for setting and deleting attributes defined in C (GH-156107)
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.
1 parent f8c93d4 commit cdca502

13 files changed

Lines changed: 295 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 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
@@ -4331,7 +4331,7 @@ def test_invalid_context(self):
43314331

43324332
# Attributes cannot be deleted
43334333
for attr in ['prec', 'Emax', 'Emin', 'rounding', 'capitals', 'clamp',
4334-
'flags', 'traps']:
4334+
'flags', 'traps', '_allcr', '_flags', '_traps']:
43354335
self.assertRaises(AttributeError, c.__delattr__, attr)
43364336

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

685+
def test_object_attributes(self):
686+
# These attributes are implemented as plain object members:
687+
# they accept any object and are reset to None when deleted.
688+
cases = [
689+
(SyntaxError('msgStr'), 'msg'),
690+
(SyntaxError('msgStr'), 'filename'),
691+
(SyntaxError('msgStr'), 'lineno'),
692+
(SyntaxError('msgStr'), 'offset'),
693+
(SyntaxError('msgStr'), 'end_lineno'),
694+
(SyntaxError('msgStr'), 'end_offset'),
695+
(SyntaxError('msgStr'), 'text'),
696+
(SyntaxError('msgStr'), 'print_file_and_line'),
697+
(SyntaxError('msgStr'), '_metadata'),
698+
(ImportError('msgStr'), 'msg'),
699+
(ImportError('msgStr'), 'name'),
700+
(ImportError('msgStr'), 'path'),
701+
(ImportError('msgStr'), 'name_from'),
702+
(SystemExit(1), 'code'),
703+
(StopIteration(), 'value'),
704+
(NameError('msgStr'), 'name'),
705+
(AttributeError('msgStr'), 'name'),
706+
(AttributeError('msgStr'), 'obj'),
707+
(OSError(2, 'msgStr'), 'errno'),
708+
(OSError(2, 'msgStr'), 'strerror'),
709+
(OSError(2, 'msgStr'), 'filename'),
710+
(OSError(2, 'msgStr'), 'filename2'),
711+
(UnicodeDecodeError('utf-8', b'\xff', 0, 1, 'reasonStr'), 'reason'),
712+
]
713+
if sys.platform == 'win32':
714+
cases.append((OSError(2, 'msgStr'), 'winerror'))
715+
for exc, name in cases:
716+
with self.subTest(exc=type(exc).__name__, name=name):
717+
for value in 'strValue', 42, [1, 2], None:
718+
setattr(exc, name, value)
719+
self.assertEqual(getattr(exc, name), value)
720+
delattr(exc, name)
721+
self.assertIsNone(getattr(exc, name))
722+
685723
def test_invalid_delattr(self):
686724
TE = TypeError
687725
try:
@@ -739,6 +777,13 @@ def testChainingDescriptors(self):
739777
self.assertTrue(e.__suppress_context__)
740778
e.__suppress_context__ = False
741779
self.assertFalse(e.__suppress_context__)
780+
with self.assertRaisesRegex(TypeError,
781+
'attribute value type must be bool'):
782+
e.__suppress_context__ = 1
783+
with self.assertRaisesRegex(TypeError,
784+
"can't delete numeric/char attribute"):
785+
del e.__suppress_context__
786+
self.assertFalse(e.__suppress_context__)
742787

743788
def testKeywordArgs(self):
744789
# 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_initialization(self):
14521475
r = self.BytesIO(b"\xc3\xa9\n\n")
14531476
b = self.BufferedReader(r, 1000)

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)