diff --git a/Lib/test/test_asyncio/test_futures.py b/Lib/test/test_asyncio/test_futures.py index 320de2180e7db7c..a217177e1deb06d 100644 --- a/Lib/test/test_asyncio/test_futures.py +++ b/Lib/test/test_asyncio/test_futures.py @@ -255,6 +255,14 @@ def test_future_cancel_message_setter(self): f.cancel('my message') f._cancel_message = 'my new message' self.assertEqual(f._cancel_message, 'my new message') + f._cancel_message = None + self.assertIsNone(f._cancel_message) + f._cancel_message = 'my new message' + if not isinstance(f, futures._PyFuture): + # The C implementation does not support deletion. + with self.assertRaises(AttributeError): + del f._cancel_message + self.assertEqual(f._cancel_message, 'my new message') # Also check that the value is used for cancel(). with self.assertRaises(asyncio.CancelledError): diff --git a/Lib/test/test_ctypes/test_delattr.py b/Lib/test/test_ctypes/test_delattr.py index e80b5fa6efb5455..019760eabb3bd46 100644 --- a/Lib/test/test_ctypes/test_delattr.py +++ b/Lib/test/test_ctypes/test_delattr.py @@ -1,5 +1,6 @@ import unittest -from ctypes import Structure, c_char, c_int +from ctypes import CDLL, Structure, c_char, c_int +from test.support import import_helper class X(Structure): @@ -21,6 +22,25 @@ def test_struct(self): with self.assertRaises(TypeError): del struct.foo + def test_raw(self): + chararray = (c_char * 5)() + with self.assertRaises(AttributeError): + del chararray.raw + + def test_func_pointer(self): + # Deleting these attributes restores the default. + dll = CDLL(import_helper.import_module('_ctypes_test').__file__) + func = dll._testfunc_i_bhilfd + func.argtypes = [c_int] + func.restype = c_int + func.errcheck = lambda *args: None + del func.argtypes + self.assertIsNone(func.argtypes) + del func.errcheck + self.assertIsNone(func.errcheck) + del func.restype + self.assertIs(func.restype, c_int) + if __name__ == "__main__": unittest.main() diff --git a/Lib/test/test_decimal.py b/Lib/test/test_decimal.py index a0ba5a8351aebdf..7524822632bae7a 100644 --- a/Lib/test/test_decimal.py +++ b/Lib/test/test_decimal.py @@ -4331,7 +4331,7 @@ def test_invalid_context(self): # Attributes cannot be deleted for attr in ['prec', 'Emax', 'Emin', 'rounding', 'capitals', 'clamp', - 'flags', 'traps']: + 'flags', 'traps', '_allcr', '_flags', '_traps']: self.assertRaises(AttributeError, c.__delattr__, attr) # Invalid attributes diff --git a/Lib/test/test_defaultdict.py b/Lib/test/test_defaultdict.py index cc78f01e3e2ebdc..84a17eca0aa530d 100644 --- a/Lib/test/test_defaultdict.py +++ b/Lib/test/test_defaultdict.py @@ -37,6 +37,9 @@ def test_basic(self): self.assertIn(42, d2.keys()) self.assertNotIn(12, d2) self.assertNotIn(12, d2.keys()) + d2.default_factory = list + del d2.default_factory + self.assertEqual(d2.default_factory, None) d2.default_factory = None self.assertEqual(d2.default_factory, None) try: diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py index 0cee756958f3ad1..c34cf44d722456c 100644 --- a/Lib/test/test_exceptions.py +++ b/Lib/test/test_exceptions.py @@ -682,6 +682,44 @@ def test_invalid_setattr(self): msg = "exception context must be None or derive from BaseException" self.assertRaisesRegex(TE, msg, setattr, exc, '__context__', 1) + def test_object_attributes(self): + # These attributes are implemented as plain object members: + # they accept any object and are reset to None when deleted. + cases = [ + (SyntaxError('msgStr'), 'msg'), + (SyntaxError('msgStr'), 'filename'), + (SyntaxError('msgStr'), 'lineno'), + (SyntaxError('msgStr'), 'offset'), + (SyntaxError('msgStr'), 'end_lineno'), + (SyntaxError('msgStr'), 'end_offset'), + (SyntaxError('msgStr'), 'text'), + (SyntaxError('msgStr'), 'print_file_and_line'), + (SyntaxError('msgStr'), '_metadata'), + (ImportError('msgStr'), 'msg'), + (ImportError('msgStr'), 'name'), + (ImportError('msgStr'), 'path'), + (ImportError('msgStr'), 'name_from'), + (SystemExit(1), 'code'), + (StopIteration(), 'value'), + (NameError('msgStr'), 'name'), + (AttributeError('msgStr'), 'name'), + (AttributeError('msgStr'), 'obj'), + (OSError(2, 'msgStr'), 'errno'), + (OSError(2, 'msgStr'), 'strerror'), + (OSError(2, 'msgStr'), 'filename'), + (OSError(2, 'msgStr'), 'filename2'), + (UnicodeDecodeError('utf-8', b'\xff', 0, 1, 'reasonStr'), 'reason'), + ] + if sys.platform == 'win32': + cases.append((OSError(2, 'msgStr'), 'winerror')) + for exc, name in cases: + with self.subTest(exc=type(exc).__name__, name=name): + for value in 'strValue', 42, [1, 2], None: + setattr(exc, name, value) + self.assertEqual(getattr(exc, name), value) + delattr(exc, name) + self.assertIsNone(getattr(exc, name)) + def test_invalid_delattr(self): TE = TypeError try: @@ -739,6 +777,13 @@ def testChainingDescriptors(self): self.assertTrue(e.__suppress_context__) e.__suppress_context__ = False self.assertFalse(e.__suppress_context__) + with self.assertRaisesRegex(TypeError, + 'attribute value type must be bool'): + e.__suppress_context__ = 1 + with self.assertRaisesRegex(TypeError, + "can't delete numeric/char attribute"): + del e.__suppress_context__ + self.assertFalse(e.__suppress_context__) def testKeywordArgs(self): # test that builtin exception don't take keyword args, diff --git a/Lib/test/test_frame.py b/Lib/test/test_frame.py index a3a329cb578a8dc..a0a11966ebf3c17 100644 --- a/Lib/test/test_frame.py +++ b/Lib/test/test_frame.py @@ -222,6 +222,33 @@ def test_f_lineno_del_segfault(self): with self.assertRaises(AttributeError): del f.f_lineno + def test_f_trace(self): + f, _, _ = self.make_frames() + def tracer(*args): + pass + for value in tracer, 42, None: + f.f_trace = value + self.assertEqual(f.f_trace, value) + f.f_trace = tracer + del f.f_trace + self.assertIsNone(f.f_trace) + + def test_f_trace_lines_and_opcodes(self): + f, _, _ = self.make_frames() + for name in 'f_trace_lines', 'f_trace_opcodes': + with self.subTest(name=name): + for value in False, True: + setattr(f, name, value) + self.assertEqual(getattr(f, name), value) + with self.assertRaisesRegex(TypeError, + 'attribute value type must be bool'): + setattr(f, name, 1) + with self.assertRaisesRegex(TypeError, + "can't delete numeric/char attribute"): + del f.f_trace_lines + with self.assertRaisesRegex(AttributeError, 'cannot be deleted'): + del f.f_trace_opcodes + def test_f_generator(self): # Test f_generator in different contexts. diff --git a/Lib/test/test_funcattrs.py b/Lib/test/test_funcattrs.py index fe14e7cb342c9e5..b2fa10d74580125 100644 --- a/Lib/test/test_funcattrs.py +++ b/Lib/test/test_funcattrs.py @@ -266,6 +266,41 @@ def e(): return num_one, num_two self.fail("__code__ with different numbers of free vars should " "not be possible") + def test___kwdefaults__(self): + def func(a=1, *, b=2, c=3): + return a, b, c + self.assertEqual(func.__kwdefaults__, {'b': 2, 'c': 3}) + func.__kwdefaults__ = {'b': 4} + self.assertEqual(func.__kwdefaults__, {'b': 4}) + self.assertEqual(func(c=5), (1, 4, 5)) + func.__kwdefaults__ = None + self.assertIsNone(func.__kwdefaults__) + self.assertRaises(TypeError, func) + with self.assertRaisesRegex(TypeError, + '__kwdefaults__ must be set to a dict object'): + func.__kwdefaults__ = [('b', 4)] + del func.__kwdefaults__ + self.assertIsNone(func.__kwdefaults__) + + def test_invalid___code___deletion(self): + def func(): pass + with self.assertRaisesRegex(TypeError, + '__code__ must be set to a code object'): + func.__code__ = None + with self.assertRaisesRegex(TypeError, + '__code__ must be set to a code object'): + del func.__code__ + + def test___doc__(self): + def func(): + "docstring" + self.assertEqual(func.__doc__, 'docstring') + for value in 'other', 42, None: + func.__doc__ = value + self.assertEqual(func.__doc__, value) + del func.__doc__ + self.assertIsNone(func.__doc__) + def test_blank_func_defaults(self): self.assertEqual(self.b.__defaults__, None) del self.b.__defaults__ diff --git a/Lib/test/test_io/test_fileio.py b/Lib/test/test_io/test_fileio.py index e53c4749f58cf22..21a160904a7f1e0 100644 --- a/Lib/test/test_io/test_fileio.py +++ b/Lib/test/test_io/test_fileio.py @@ -83,6 +83,12 @@ def testBlksize(self): fst = os.fstat(self.f.fileno()) blksize = getattr(fst, 'st_blksize', blksize) self.assertEqual(self.f._blksize, blksize) + # it is read-only + with self.assertRaises(AttributeError): + self.f._blksize = blksize + with self.assertRaises(AttributeError): + del self.f._blksize + # verify readinto def testReadintoByteArray(self): @@ -503,6 +509,21 @@ class CAutoFileTests(AutoFileTests, unittest.TestCase): FileIO = _io.FileIO modulename = '_io' + def testFinalizing(self): + # test the private _finalizing attribute + self.assertIs(self.f._finalizing, False) + self.f._finalizing = True + self.assertIs(self.f._finalizing, True) + with self.assertRaisesRegex(TypeError, + 'attribute value type must be bool'): + self.f._finalizing = 1 + with self.assertRaisesRegex(TypeError, + "can't delete numeric/char attribute"): + del self.f._finalizing + # closing a file which is being finalized emits a ResourceWarning + self.f._finalizing = False + + class PyAutoFileTests(AutoFileTests, unittest.TestCase): FileIO = _pyio.FileIO modulename = '_pyio' diff --git a/Lib/test/test_io/test_textio.py b/Lib/test/test_io/test_textio.py index 07f6b1415d0dbda..a210b81d877dc1e 100644 --- a/Lib/test/test_io/test_textio.py +++ b/Lib/test/test_io/test_textio.py @@ -1448,6 +1448,29 @@ def _to_memoryview(buf): class CTextIOWrapperTest(TextIOWrapperTest, CTestCase): shutdown_error = "LookupError: unknown encoding: ascii" + def test_chunk_size(self): + t = self.TextIOWrapper(self.BytesIO(), encoding="utf-8") + self.assertGreater(t._CHUNK_SIZE, 0) + t._CHUNK_SIZE = 1024 + self.assertEqual(t._CHUNK_SIZE, 1024) + with self.assertRaisesRegex(ValueError, + 'a strictly positive integer is required'): + t._CHUNK_SIZE = 0 + with self.assertRaises(TypeError): + t._CHUNK_SIZE = 'x' + with self.assertRaises(ValueError): + t._CHUNK_SIZE = sys.maxsize + 1 + with self.assertRaises(ValueError): + t._CHUNK_SIZE = -sys.maxsize - 2 + with self.assertRaises(ValueError): + t._CHUNK_SIZE = 2**1000 + with self.assertRaises(ValueError): + t._CHUNK_SIZE = -2**1000 + with self.assertRaisesRegex(AttributeError, 'cannot be deleted'): + del t._CHUNK_SIZE + # a failed assignment does not change the value + self.assertEqual(t._CHUNK_SIZE, 1024) + def test_initialization(self): r = self.BytesIO(b"\xc3\xa9\n\n") b = self.BufferedReader(r, 1000) diff --git a/Lib/test/test_kqueue.py b/Lib/test/test_kqueue.py index 2649f3a7aee9b96..e4223262e099339 100644 --- a/Lib/test/test_kqueue.py +++ b/Lib/test/test_kqueue.py @@ -126,6 +126,31 @@ def test_create_event(self): self.assertNotEqual(ev, other) + def test_event_attributes(self): + fd = os.open(os.devnull, os.O_WRONLY) + self.addCleanup(os.close, fd) + + ev = select.kevent(fd) + # All attributes are numeric members: they can be set and cannot be + # deleted. + for name, value in (('ident', 1), ('filter', select.KQ_FILTER_WRITE), + ('flags', select.KQ_EV_DELETE), ('fflags', 2), + ('data', 3), ('udata', 4)): + with self.subTest(name=name): + setattr(ev, name, value) + self.assertEqual(getattr(ev, name), value) + with self.assertRaises(TypeError): + setattr(ev, name, 'not a number') + with self.assertRaises(OverflowError): + setattr(ev, name, 2**1000) + with self.assertRaises(OverflowError): + setattr(ev, name, -2**1000) + with self.assertRaisesRegex( + TypeError, "can't delete numeric/char attribute"): + delattr(ev, name) + # a failed assignment does not change the value + self.assertEqual(getattr(ev, name), value) + def test_queue_event(self): serverSocket = socket.create_server(('127.0.0.1', 0)) client = socket.socket() diff --git a/Lib/test/test_pickle.py b/Lib/test/test_pickle.py index 21f45339e8a3442..5b0fd70105447f9 100644 --- a/Lib/test/test_pickle.py +++ b/Lib/test/test_pickle.py @@ -389,6 +389,57 @@ class CPicklerTests(PyPicklerTests): pickler = _pickle.Pickler unpickler = _pickle.Unpickler + def test_c_pickler_attributes(self): + pickler = _pickle.Pickler(io.BytesIO()) + for name in 'bin', 'fast': + with self.subTest(name=name): + for value in 0, 1, True: + setattr(pickler, name, value) + self.assertEqual(getattr(pickler, name), int(value)) + with self.assertRaises(TypeError): + setattr(pickler, name, 'x') + with self.assertRaises(OverflowError): + setattr(pickler, name, sys.maxsize + 1) + with self.assertRaises(OverflowError): + setattr(pickler, name, -sys.maxsize - 2) + with self.assertRaises(OverflowError): + setattr(pickler, name, 2**1000) + with self.assertRaises(OverflowError): + setattr(pickler, name, -2**1000) + with self.assertRaisesRegex( + TypeError, "can't delete numeric/char attribute"): + delattr(pickler, name) + # a failed assignment does not change the value + self.assertEqual(getattr(pickler, name), 1) + + self.assertRaises(AttributeError, getattr, pickler, + 'dispatch_table') + table = {} + pickler.dispatch_table = table + self.assertIs(pickler.dispatch_table, table) + del pickler.dispatch_table + self.assertRaises(AttributeError, getattr, pickler, + 'dispatch_table') + + pickler.memo = {} + self.assertEqual(pickler.memo.copy(), {}) + with self.assertRaisesRegex(TypeError, 'must be a PicklerMemoProxy'): + pickler.memo = None + with self.assertRaisesRegex(TypeError, + 'attribute deletion is not supported'): + del pickler.memo + + def test_c_unpickler_attributes(self): + unpickler = _pickle.Unpickler(io.BytesIO(b'.')) + unpickler.memo = {} + self.assertEqual(unpickler.memo.copy(), {}) + with self.assertRaisesRegex(TypeError, + 'must be an UnpicklerMemoProxy'): + unpickler.memo = None + with self.assertRaisesRegex(TypeError, + 'attribute deletion is not supported'): + del unpickler.memo + class CPersPicklerTests(PyPersPicklerTests): pickler = _pickle.Pickler unpickler = _pickle.Unpickler diff --git a/Lib/test/test_sqlite3/test_dbapi.py b/Lib/test/test_sqlite3/test_dbapi.py index c21448a92361d7e..48f996a07e24a32 100644 --- a/Lib/test/test_sqlite3/test_dbapi.py +++ b/Lib/test/test_sqlite3/test_dbapi.py @@ -487,6 +487,13 @@ def test_connection_init_good_isolation_levels(self): cx.isolation_level = level self.assertEqual(cx.isolation_level, level) + def test_connection_delete_isolation_level(self): + with memory_database() as cx: + with self.assertRaisesRegex(AttributeError, + "cannot delete attribute"): + del cx.isolation_level + self.assertEqual(cx.isolation_level, "") + def test_connection_reinit(self): with memory_database() as cx: cx.text_factory = bytes @@ -1078,6 +1085,8 @@ def test_invalid_array_size(self): self.assertRaises(TypeError, setter, 1.0) self.assertRaises(ValueError, setter, -3) self.assertRaises(OverflowError, setter, UINT32_MAX + 1) + self.assertRaisesRegex(AttributeError, 'cannot be deleted', + delattr, self.cu, 'arraysize') def test_fetchmany(self): # no active SQL statement diff --git a/Lib/test/test_sqlite3/test_transactions.py b/Lib/test/test_sqlite3/test_transactions.py index a3de7a7a82ec1cb..88266aa24648d70 100644 --- a/Lib/test/test_sqlite3/test_transactions.py +++ b/Lib/test/test_sqlite3/test_transactions.py @@ -393,6 +393,9 @@ def test_autocommit_setget_invalid(self): with self.subTest(mode=mode): with self.assertRaisesRegex(ValueError, msg): sqlite.connect(":memory:", autocommit=mode) + with memory_database() as cx: + with self.assertRaisesRegex(ValueError, msg): + cx.autocommit = mode def test_autocommit_disabled(self): expected = [ diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index 2bba665d19343e6..996e609922f41c2 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -1846,6 +1846,32 @@ def test__create_stdlib_context_check_hostname(self): self.assertEqual(ctx.verify_mode, ssl.CERT_OPTIONAL) self.assertTrue(ctx.check_hostname) + def test_delete_sslobject_attributes(self): + # None of the attributes of _ssl._SSLSocket can be deleted. + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + sslobj = ctx.wrap_bio(ssl.MemoryBIO(), ssl.MemoryBIO())._sslobj + for name in 'context', 'owner', 'session', 'session_reused': + with self.subTest(name=name): + value = getattr(sslobj, name) + with self.assertRaises(AttributeError): + delattr(sslobj, name) + self.assertEqual(getattr(sslobj, name), value) + + def test_delete_attributes(self): + # None of the attributes implemented in C can be deleted. + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + names = ['check_hostname', 'verify_mode', 'verify_flags', 'options', + 'minimum_version', 'maximum_version', 'sni_callback', + '_host_flags', 'security_level', 'post_handshake_auth'] + if hasattr(ctx, 'num_tickets'): + names.append('num_tickets') + for name in names: + with self.subTest(name=name): + value = getattr(ctx, name) + with self.assertRaises(AttributeError): + delattr(ctx, name) + self.assertEqual(getattr(ctx, name), value) + def test_check_hostname(self): with warnings_helper.check_warnings(): ctx = ssl.SSLContext(ssl.PROTOCOL_TLS)