From: Cody Maloney Date: Wed, 3 Sep 2025 09:49:59 +0000 (-0700) Subject: gh-138013: Remove `test_io` load_tests namespace manipulation (#138366) X-Git-Tag: v3.15.0a1~518 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=6dd21e9f56cfc9eb5a5c21382e3241e700d86aa6;p=thirdparty%2FPython%2Fcpython.git gh-138013: Remove `test_io` load_tests namespace manipulation (#138366) Reduce what happens in `load_tests` so that the next change, moving the `Buffered*` tests to `test_bufferdio` is purely mechanical movement and updating imports. This adds two classes, one per I/O implementation, to act as dispatch to the implementation-specific mocks as well as module members. Previously the mappings CTestCase and PyTestCase provide were injected directly during `load_tests`. CTestCase and PyTestCase inherit from `unittest.TestCase` so when the split happens default test discovery will work for the classes in `test_bufferedio`. `test_general` keeps a manual test list for this refactoring; some of the tests (ex. `ProtocolsTest`) aren't currently run and fixing that + helpers to not be picked up is out of my current scope. CTestCase and PyTestCase have an `io` class member which points to the implementation meaning that can be removed from individual test cases which now inherit from them. This code is picking up `MockRawIO` which is defined globally in the module but these should use the mock specific to the I/O implementation being tested. Co-authored-by: Victor Stinner --- diff --git a/Lib/test/test_io/test_general.py b/Lib/test/test_io/test_general.py index 30fe1e2f8660..604b56cea21f 100644 --- a/Lib/test/test_io/test_general.py +++ b/Lib/test/test_io/test_general.py @@ -317,6 +317,45 @@ class PyMockNonBlockWriterIO(MockNonBlockWriterIO, pyio.RawIOBase): BlockingIOError = pyio.BlockingIOError +# Build classes which point to all the right mocks per io implementation +class CTestCase(unittest.TestCase): + io = io + is_C = True + + MockRawIO = CMockRawIO + MisbehavedRawIO = CMisbehavedRawIO + MockFileIO = CMockFileIO + CloseFailureIO = CCloseFailureIO + MockNonBlockWriterIO = CMockNonBlockWriterIO + MockUnseekableIO = CMockUnseekableIO + MockRawIOWithoutRead = CMockRawIOWithoutRead + SlowFlushRawIO = CSlowFlushRawIO + MockCharPseudoDevFileIO = CMockCharPseudoDevFileIO + + # Use the class as a proxy to the io module members. + def __getattr__(self, name): + return getattr(io, name) + + +class PyTestCase(unittest.TestCase): + io = pyio + is_C = False + + MockRawIO = PyMockRawIO + MisbehavedRawIO = PyMisbehavedRawIO + MockFileIO = PyMockFileIO + CloseFailureIO = PyCloseFailureIO + MockNonBlockWriterIO = PyMockNonBlockWriterIO + MockUnseekableIO = PyMockUnseekableIO + MockRawIOWithoutRead = PyMockRawIOWithoutRead + SlowFlushRawIO = PySlowFlushRawIO + MockCharPseudoDevFileIO = PyMockCharPseudoDevFileIO + + # Use the class as a proxy to the _pyio module members. + def __getattr__(self, name): + return getattr(pyio, name) + + class IOTest(unittest.TestCase): def setUp(self): @@ -1083,7 +1122,7 @@ class IOTest(unittest.TestCase): write_count * thread_count) -class CIOTest(IOTest): +class CIOTest(IOTest, CTestCase): def test_IOBase_finalize(self): # Issue #12149: segmentation fault on _PyIOBase_finalize when both a @@ -1207,7 +1246,7 @@ class TestIOCTypes(unittest.TestCase): obj.__setstate__(('', '', 0, {})) self.assertEqual(obj.getvalue(), '') -class PyIOTest(IOTest): +class PyIOTest(IOTest, PyTestCase): pass @@ -1438,7 +1477,7 @@ class SizeofTest: bufio.close() self.assertEqual(sys.getsizeof(bufio), size) -class BufferedReaderTest(unittest.TestCase, CommonBufferedTests): +class BufferedReaderTest(CommonBufferedTests): read_mode = "rb" def test_constructor(self): @@ -1773,7 +1812,7 @@ class BufferedReaderTest(unittest.TestCase, CommonBufferedTests): self.assertEqual(buf.seek(0, io.SEEK_CUR), 0) -class CBufferedReaderTest(BufferedReaderTest, SizeofTest): +class CBufferedReaderTest(BufferedReaderTest, SizeofTest, CTestCase): tp = io.BufferedReader def test_initialization(self): @@ -1832,11 +1871,11 @@ class CBufferedReaderTest(BufferedReaderTest, SizeofTest): self.assertIsInstance(cm.exception.__cause__, TypeError) -class PyBufferedReaderTest(BufferedReaderTest): +class PyBufferedReaderTest(BufferedReaderTest, PyTestCase): tp = pyio.BufferedReader -class BufferedWriterTest(unittest.TestCase, CommonBufferedTests): +class BufferedWriterTest(CommonBufferedTests): write_mode = "wb" def test_constructor(self): @@ -2131,8 +2170,7 @@ class BufferedWriterTest(unittest.TestCase, CommonBufferedTests): t.join() - -class CBufferedWriterTest(BufferedWriterTest, SizeofTest): +class CBufferedWriterTest(BufferedWriterTest, SizeofTest, CTestCase): tp = io.BufferedWriter def test_initialization(self): @@ -2172,10 +2210,10 @@ class CBufferedWriterTest(BufferedWriterTest, SizeofTest): self.tp(self.BytesIO(), 1024, 1024, 1024) -class PyBufferedWriterTest(BufferedWriterTest): +class PyBufferedWriterTest(BufferedWriterTest, PyTestCase): tp = pyio.BufferedWriter -class BufferedRWPairTest(unittest.TestCase): +class BufferedRWPairTest: def test_constructor(self): pair = self.tp(self.MockRawIO(), self.MockRawIO()) @@ -2204,14 +2242,14 @@ class BufferedRWPairTest(unittest.TestCase): self.tp(self.MockRawIO(), self.MockRawIO(), 8, 12) def test_constructor_with_not_readable(self): - class NotReadable(MockRawIO): + class NotReadable(self.MockRawIO): def readable(self): return False self.assertRaises(OSError, self.tp, NotReadable(), self.MockRawIO()) def test_constructor_with_not_writeable(self): - class NotWriteable(MockRawIO): + class NotWriteable(self.MockRawIO): def writable(self): return False @@ -2357,9 +2395,9 @@ class BufferedRWPairTest(unittest.TestCase): writer.close = lambda: None def test_isatty(self): - class SelectableIsAtty(MockRawIO): + class SelectableIsAtty(self.MockRawIO): def __init__(self, isatty): - MockRawIO.__init__(self) + super().__init__() self._isatty = isatty def isatty(self): @@ -2383,10 +2421,10 @@ class BufferedRWPairTest(unittest.TestCase): brw = None ref = None # Shouldn't segfault. -class CBufferedRWPairTest(BufferedRWPairTest): +class CBufferedRWPairTest(BufferedRWPairTest, CTestCase): tp = io.BufferedRWPair -class PyBufferedRWPairTest(BufferedRWPairTest): +class PyBufferedRWPairTest(BufferedRWPairTest, PyTestCase): tp = pyio.BufferedRWPair @@ -2645,7 +2683,7 @@ class BufferedRandomTest(BufferedReaderTest, BufferedWriterTest): test_truncate_on_read_only = None -class CBufferedRandomTest(BufferedRandomTest, SizeofTest): +class CBufferedRandomTest(BufferedRandomTest, SizeofTest, CTestCase): tp = io.BufferedRandom def test_garbage_collection(self): @@ -2658,7 +2696,7 @@ class CBufferedRandomTest(BufferedRandomTest, SizeofTest): self.tp(self.BytesIO(), 1024, 1024, 1024) -class PyBufferedRandomTest(BufferedRandomTest): +class PyBufferedRandomTest(BufferedRandomTest, PyTestCase): tp = pyio.BufferedRandom @@ -4058,8 +4096,7 @@ def _to_memoryview(buf): return memoryview(arr) -class CTextIOWrapperTest(TextIOWrapperTest): - io = io +class CTextIOWrapperTest(TextIOWrapperTest, CTestCase): shutdown_error = "LookupError: unknown encoding: ascii" def test_initialization(self): @@ -4159,8 +4196,7 @@ class CTextIOWrapperTest(TextIOWrapperTest): buf._write_stack) -class PyTextIOWrapperTest(TextIOWrapperTest): - io = pyio +class PyTextIOWrapperTest(TextIOWrapperTest, PyTestCase): shutdown_error = "LookupError: unknown encoding: ascii" @@ -4282,6 +4318,8 @@ class IncrementalNewlineDecoderTest(unittest.TestCase): self.assertEqual(decoder.decode(b"\r\r\n"), "\r\r\n") class CIncrementalNewlineDecoderTest(IncrementalNewlineDecoderTest): + IncrementalNewlineDecoder = io.IncrementalNewlineDecoder + @support.cpython_only def test_uninitialized(self): uninitialized = self.IncrementalNewlineDecoder.__new__( @@ -4293,7 +4331,7 @@ class CIncrementalNewlineDecoderTest(IncrementalNewlineDecoderTest): class PyIncrementalNewlineDecoderTest(IncrementalNewlineDecoderTest): - pass + IncrementalNewlineDecoder = pyio.IncrementalNewlineDecoder # XXX Tests for open() @@ -4662,8 +4700,7 @@ class MiscIOTest(unittest.TestCase): self.assertEqual(b"utf-8", proc.out.strip()) -class CMiscIOTest(MiscIOTest): - io = io +class CMiscIOTest(MiscIOTest, CTestCase): name_of_module = "io", "_io" extra_exported = "BlockingIOError", @@ -4728,8 +4765,7 @@ class CMiscIOTest(MiscIOTest): self.check_daemon_threads_shutdown_deadlock('stderr') -class PyMiscIOTest(MiscIOTest): - io = pyio +class PyMiscIOTest(MiscIOTest, PyTestCase): name_of_module = "_pyio", "io" extra_exported = "BlockingIOError", "open_code", not_exported = "valid_seek_flags", @@ -4990,11 +5026,11 @@ class SignalsTest(unittest.TestCase): self.check_interrupted_write_retry("x", mode="w", encoding="latin1") -class CSignalsTest(SignalsTest): - io = io +class CSignalsTest(SignalsTest, CTestCase): + pass -class PySignalsTest(SignalsTest): - io = pyio +class PySignalsTest(SignalsTest, PyTestCase): + pass # Handling reentrancy issues would slow down _pyio even more, so the # tests are disabled. @@ -5034,27 +5070,6 @@ def load_tests(loader, tests, pattern): ProtocolsTest, ) - # Put the namespaces of the IO module we are testing and some useful mock - # classes in the __dict__ of each test. - mocks = (MockRawIO, MisbehavedRawIO, MockFileIO, CloseFailureIO, - MockNonBlockWriterIO, MockUnseekableIO, MockRawIOWithoutRead, - SlowFlushRawIO, MockCharPseudoDevFileIO) - all_members = io.__all__ - c_io_ns = {name : getattr(io, name) for name in all_members} - py_io_ns = {name : getattr(pyio, name) for name in all_members} - globs = globals() - c_io_ns.update((x.__name__, globs["C" + x.__name__]) for x in mocks) - py_io_ns.update((x.__name__, globs["Py" + x.__name__]) for x in mocks) - for test in tests: - if test.__name__.startswith("C"): - for name, obj in c_io_ns.items(): - setattr(test, name, obj) - test.is_C = True - elif test.__name__.startswith("Py"): - for name, obj in py_io_ns.items(): - setattr(test, name, obj) - test.is_C = False - suite = loader.suiteClass() for test in tests: suite.addTest(loader.loadTestsFromTestCase(test))