17db96d56Sopenharmony_ci# Written to test interrupted system calls interfering with our many buffered 27db96d56Sopenharmony_ci# IO implementations. http://bugs.python.org/issue12268 37db96d56Sopenharmony_ci# 47db96d56Sopenharmony_ci# It was suggested that this code could be merged into test_io and the tests 57db96d56Sopenharmony_ci# made to work using the same method as the existing signal tests in test_io. 67db96d56Sopenharmony_ci# I was unable to get single process tests using alarm or setitimer that way 77db96d56Sopenharmony_ci# to reproduce the EINTR problems. This process based test suite reproduces 87db96d56Sopenharmony_ci# the problems prior to the issue12268 patch reliably on Linux and OSX. 97db96d56Sopenharmony_ci# - gregory.p.smith 107db96d56Sopenharmony_ci 117db96d56Sopenharmony_ciimport os 127db96d56Sopenharmony_ciimport select 137db96d56Sopenharmony_ciimport signal 147db96d56Sopenharmony_ciimport subprocess 157db96d56Sopenharmony_ciimport sys 167db96d56Sopenharmony_ciimport time 177db96d56Sopenharmony_ciimport unittest 187db96d56Sopenharmony_cifrom test import support 197db96d56Sopenharmony_ci 207db96d56Sopenharmony_ciif not support.has_subprocess_support: 217db96d56Sopenharmony_ci raise unittest.SkipTest("test module requires subprocess") 227db96d56Sopenharmony_ci 237db96d56Sopenharmony_ci# Test import all of the things we're about to try testing up front. 247db96d56Sopenharmony_ciimport _io 257db96d56Sopenharmony_ciimport _pyio 267db96d56Sopenharmony_ci 277db96d56Sopenharmony_ci@unittest.skipUnless(os.name == 'posix', 'tests requires a posix system.') 287db96d56Sopenharmony_ciclass TestFileIOSignalInterrupt: 297db96d56Sopenharmony_ci def setUp(self): 307db96d56Sopenharmony_ci self._process = None 317db96d56Sopenharmony_ci 327db96d56Sopenharmony_ci def tearDown(self): 337db96d56Sopenharmony_ci if self._process and self._process.poll() is None: 347db96d56Sopenharmony_ci try: 357db96d56Sopenharmony_ci self._process.kill() 367db96d56Sopenharmony_ci except OSError: 377db96d56Sopenharmony_ci pass 387db96d56Sopenharmony_ci 397db96d56Sopenharmony_ci def _generate_infile_setup_code(self): 407db96d56Sopenharmony_ci """Returns the infile = ... line of code for the reader process. 417db96d56Sopenharmony_ci 427db96d56Sopenharmony_ci subclasseses should override this to test different IO objects. 437db96d56Sopenharmony_ci """ 447db96d56Sopenharmony_ci return ('import %s as io ;' 457db96d56Sopenharmony_ci 'infile = io.FileIO(sys.stdin.fileno(), "rb")' % 467db96d56Sopenharmony_ci self.modname) 477db96d56Sopenharmony_ci 487db96d56Sopenharmony_ci def fail_with_process_info(self, why, stdout=b'', stderr=b'', 497db96d56Sopenharmony_ci communicate=True): 507db96d56Sopenharmony_ci """A common way to cleanup and fail with useful debug output. 517db96d56Sopenharmony_ci 527db96d56Sopenharmony_ci Kills the process if it is still running, collects remaining output 537db96d56Sopenharmony_ci and fails the test with an error message including the output. 547db96d56Sopenharmony_ci 557db96d56Sopenharmony_ci Args: 567db96d56Sopenharmony_ci why: Text to go after "Error from IO process" in the message. 577db96d56Sopenharmony_ci stdout, stderr: standard output and error from the process so 587db96d56Sopenharmony_ci far to include in the error message. 597db96d56Sopenharmony_ci communicate: bool, when True we call communicate() on the process 607db96d56Sopenharmony_ci after killing it to gather additional output. 617db96d56Sopenharmony_ci """ 627db96d56Sopenharmony_ci if self._process.poll() is None: 637db96d56Sopenharmony_ci time.sleep(0.1) # give it time to finish printing the error. 647db96d56Sopenharmony_ci try: 657db96d56Sopenharmony_ci self._process.terminate() # Ensure it dies. 667db96d56Sopenharmony_ci except OSError: 677db96d56Sopenharmony_ci pass 687db96d56Sopenharmony_ci if communicate: 697db96d56Sopenharmony_ci stdout_end, stderr_end = self._process.communicate() 707db96d56Sopenharmony_ci stdout += stdout_end 717db96d56Sopenharmony_ci stderr += stderr_end 727db96d56Sopenharmony_ci self.fail('Error from IO process %s:\nSTDOUT:\n%sSTDERR:\n%s\n' % 737db96d56Sopenharmony_ci (why, stdout.decode(), stderr.decode())) 747db96d56Sopenharmony_ci 757db96d56Sopenharmony_ci def _test_reading(self, data_to_write, read_and_verify_code): 767db96d56Sopenharmony_ci """Generic buffered read method test harness to validate EINTR behavior. 777db96d56Sopenharmony_ci 787db96d56Sopenharmony_ci Also validates that Python signal handlers are run during the read. 797db96d56Sopenharmony_ci 807db96d56Sopenharmony_ci Args: 817db96d56Sopenharmony_ci data_to_write: String to write to the child process for reading 827db96d56Sopenharmony_ci before sending it a signal, confirming the signal was handled, 837db96d56Sopenharmony_ci writing a final newline and closing the infile pipe. 847db96d56Sopenharmony_ci read_and_verify_code: Single "line" of code to read from a file 857db96d56Sopenharmony_ci object named 'infile' and validate the result. This will be 867db96d56Sopenharmony_ci executed as part of a python subprocess fed data_to_write. 877db96d56Sopenharmony_ci """ 887db96d56Sopenharmony_ci infile_setup_code = self._generate_infile_setup_code() 897db96d56Sopenharmony_ci # Total pipe IO in this function is smaller than the minimum posix OS 907db96d56Sopenharmony_ci # pipe buffer size of 512 bytes. No writer should block. 917db96d56Sopenharmony_ci assert len(data_to_write) < 512, 'data_to_write must fit in pipe buf.' 927db96d56Sopenharmony_ci 937db96d56Sopenharmony_ci # Start a subprocess to call our read method while handling a signal. 947db96d56Sopenharmony_ci self._process = subprocess.Popen( 957db96d56Sopenharmony_ci [sys.executable, '-u', '-c', 967db96d56Sopenharmony_ci 'import signal, sys ;' 977db96d56Sopenharmony_ci 'signal.signal(signal.SIGINT, ' 987db96d56Sopenharmony_ci 'lambda s, f: sys.stderr.write("$\\n")) ;' 997db96d56Sopenharmony_ci + infile_setup_code + ' ;' + 1007db96d56Sopenharmony_ci 'sys.stderr.write("Worm Sign!\\n") ;' 1017db96d56Sopenharmony_ci + read_and_verify_code + ' ;' + 1027db96d56Sopenharmony_ci 'infile.close()' 1037db96d56Sopenharmony_ci ], 1047db96d56Sopenharmony_ci stdin=subprocess.PIPE, stdout=subprocess.PIPE, 1057db96d56Sopenharmony_ci stderr=subprocess.PIPE) 1067db96d56Sopenharmony_ci 1077db96d56Sopenharmony_ci # Wait for the signal handler to be installed. 1087db96d56Sopenharmony_ci worm_sign = self._process.stderr.read(len(b'Worm Sign!\n')) 1097db96d56Sopenharmony_ci if worm_sign != b'Worm Sign!\n': # See also, Dune by Frank Herbert. 1107db96d56Sopenharmony_ci self.fail_with_process_info('while awaiting a sign', 1117db96d56Sopenharmony_ci stderr=worm_sign) 1127db96d56Sopenharmony_ci self._process.stdin.write(data_to_write) 1137db96d56Sopenharmony_ci 1147db96d56Sopenharmony_ci signals_sent = 0 1157db96d56Sopenharmony_ci rlist = [] 1167db96d56Sopenharmony_ci # We don't know when the read_and_verify_code in our child is actually 1177db96d56Sopenharmony_ci # executing within the read system call we want to interrupt. This 1187db96d56Sopenharmony_ci # loop waits for a bit before sending the first signal to increase 1197db96d56Sopenharmony_ci # the likelihood of that. Implementations without correct EINTR 1207db96d56Sopenharmony_ci # and signal handling usually fail this test. 1217db96d56Sopenharmony_ci while not rlist: 1227db96d56Sopenharmony_ci rlist, _, _ = select.select([self._process.stderr], (), (), 0.05) 1237db96d56Sopenharmony_ci self._process.send_signal(signal.SIGINT) 1247db96d56Sopenharmony_ci signals_sent += 1 1257db96d56Sopenharmony_ci if signals_sent > 200: 1267db96d56Sopenharmony_ci self._process.kill() 1277db96d56Sopenharmony_ci self.fail('reader process failed to handle our signals.') 1287db96d56Sopenharmony_ci # This assumes anything unexpected that writes to stderr will also 1297db96d56Sopenharmony_ci # write a newline. That is true of the traceback printing code. 1307db96d56Sopenharmony_ci signal_line = self._process.stderr.readline() 1317db96d56Sopenharmony_ci if signal_line != b'$\n': 1327db96d56Sopenharmony_ci self.fail_with_process_info('while awaiting signal', 1337db96d56Sopenharmony_ci stderr=signal_line) 1347db96d56Sopenharmony_ci 1357db96d56Sopenharmony_ci # We append a newline to our input so that a readline call can 1367db96d56Sopenharmony_ci # end on its own before the EOF is seen and so that we're testing 1377db96d56Sopenharmony_ci # the read call that was interrupted by a signal before the end of 1387db96d56Sopenharmony_ci # the data stream has been reached. 1397db96d56Sopenharmony_ci stdout, stderr = self._process.communicate(input=b'\n') 1407db96d56Sopenharmony_ci if self._process.returncode: 1417db96d56Sopenharmony_ci self.fail_with_process_info( 1427db96d56Sopenharmony_ci 'exited rc=%d' % self._process.returncode, 1437db96d56Sopenharmony_ci stdout, stderr, communicate=False) 1447db96d56Sopenharmony_ci # PASS! 1457db96d56Sopenharmony_ci 1467db96d56Sopenharmony_ci # String format for the read_and_verify_code used by read methods. 1477db96d56Sopenharmony_ci _READING_CODE_TEMPLATE = ( 1487db96d56Sopenharmony_ci 'got = infile.{read_method_name}() ;' 1497db96d56Sopenharmony_ci 'expected = {expected!r} ;' 1507db96d56Sopenharmony_ci 'assert got == expected, (' 1517db96d56Sopenharmony_ci '"{read_method_name} returned wrong data.\\n"' 1527db96d56Sopenharmony_ci '"got data %r\\nexpected %r" % (got, expected))' 1537db96d56Sopenharmony_ci ) 1547db96d56Sopenharmony_ci 1557db96d56Sopenharmony_ci def test_readline(self): 1567db96d56Sopenharmony_ci """readline() must handle signals and not lose data.""" 1577db96d56Sopenharmony_ci self._test_reading( 1587db96d56Sopenharmony_ci data_to_write=b'hello, world!', 1597db96d56Sopenharmony_ci read_and_verify_code=self._READING_CODE_TEMPLATE.format( 1607db96d56Sopenharmony_ci read_method_name='readline', 1617db96d56Sopenharmony_ci expected=b'hello, world!\n')) 1627db96d56Sopenharmony_ci 1637db96d56Sopenharmony_ci def test_readlines(self): 1647db96d56Sopenharmony_ci """readlines() must handle signals and not lose data.""" 1657db96d56Sopenharmony_ci self._test_reading( 1667db96d56Sopenharmony_ci data_to_write=b'hello\nworld!', 1677db96d56Sopenharmony_ci read_and_verify_code=self._READING_CODE_TEMPLATE.format( 1687db96d56Sopenharmony_ci read_method_name='readlines', 1697db96d56Sopenharmony_ci expected=[b'hello\n', b'world!\n'])) 1707db96d56Sopenharmony_ci 1717db96d56Sopenharmony_ci def test_readall(self): 1727db96d56Sopenharmony_ci """readall() must handle signals and not lose data.""" 1737db96d56Sopenharmony_ci self._test_reading( 1747db96d56Sopenharmony_ci data_to_write=b'hello\nworld!', 1757db96d56Sopenharmony_ci read_and_verify_code=self._READING_CODE_TEMPLATE.format( 1767db96d56Sopenharmony_ci read_method_name='readall', 1777db96d56Sopenharmony_ci expected=b'hello\nworld!\n')) 1787db96d56Sopenharmony_ci # read() is the same thing as readall(). 1797db96d56Sopenharmony_ci self._test_reading( 1807db96d56Sopenharmony_ci data_to_write=b'hello\nworld!', 1817db96d56Sopenharmony_ci read_and_verify_code=self._READING_CODE_TEMPLATE.format( 1827db96d56Sopenharmony_ci read_method_name='read', 1837db96d56Sopenharmony_ci expected=b'hello\nworld!\n')) 1847db96d56Sopenharmony_ci 1857db96d56Sopenharmony_ci 1867db96d56Sopenharmony_ciclass CTestFileIOSignalInterrupt(TestFileIOSignalInterrupt, unittest.TestCase): 1877db96d56Sopenharmony_ci modname = '_io' 1887db96d56Sopenharmony_ci 1897db96d56Sopenharmony_ciclass PyTestFileIOSignalInterrupt(TestFileIOSignalInterrupt, unittest.TestCase): 1907db96d56Sopenharmony_ci modname = '_pyio' 1917db96d56Sopenharmony_ci 1927db96d56Sopenharmony_ci 1937db96d56Sopenharmony_ciclass TestBufferedIOSignalInterrupt(TestFileIOSignalInterrupt): 1947db96d56Sopenharmony_ci def _generate_infile_setup_code(self): 1957db96d56Sopenharmony_ci """Returns the infile = ... line of code to make a BufferedReader.""" 1967db96d56Sopenharmony_ci return ('import %s as io ;infile = io.open(sys.stdin.fileno(), "rb") ;' 1977db96d56Sopenharmony_ci 'assert isinstance(infile, io.BufferedReader)' % 1987db96d56Sopenharmony_ci self.modname) 1997db96d56Sopenharmony_ci 2007db96d56Sopenharmony_ci def test_readall(self): 2017db96d56Sopenharmony_ci """BufferedReader.read() must handle signals and not lose data.""" 2027db96d56Sopenharmony_ci self._test_reading( 2037db96d56Sopenharmony_ci data_to_write=b'hello\nworld!', 2047db96d56Sopenharmony_ci read_and_verify_code=self._READING_CODE_TEMPLATE.format( 2057db96d56Sopenharmony_ci read_method_name='read', 2067db96d56Sopenharmony_ci expected=b'hello\nworld!\n')) 2077db96d56Sopenharmony_ci 2087db96d56Sopenharmony_ciclass CTestBufferedIOSignalInterrupt(TestBufferedIOSignalInterrupt, unittest.TestCase): 2097db96d56Sopenharmony_ci modname = '_io' 2107db96d56Sopenharmony_ci 2117db96d56Sopenharmony_ciclass PyTestBufferedIOSignalInterrupt(TestBufferedIOSignalInterrupt, unittest.TestCase): 2127db96d56Sopenharmony_ci modname = '_pyio' 2137db96d56Sopenharmony_ci 2147db96d56Sopenharmony_ci 2157db96d56Sopenharmony_ciclass TestTextIOSignalInterrupt(TestFileIOSignalInterrupt): 2167db96d56Sopenharmony_ci def _generate_infile_setup_code(self): 2177db96d56Sopenharmony_ci """Returns the infile = ... line of code to make a TextIOWrapper.""" 2187db96d56Sopenharmony_ci return ('import %s as io ;' 2197db96d56Sopenharmony_ci 'infile = io.open(sys.stdin.fileno(), encoding="utf-8", newline=None) ;' 2207db96d56Sopenharmony_ci 'assert isinstance(infile, io.TextIOWrapper)' % 2217db96d56Sopenharmony_ci self.modname) 2227db96d56Sopenharmony_ci 2237db96d56Sopenharmony_ci def test_readline(self): 2247db96d56Sopenharmony_ci """readline() must handle signals and not lose data.""" 2257db96d56Sopenharmony_ci self._test_reading( 2267db96d56Sopenharmony_ci data_to_write=b'hello, world!', 2277db96d56Sopenharmony_ci read_and_verify_code=self._READING_CODE_TEMPLATE.format( 2287db96d56Sopenharmony_ci read_method_name='readline', 2297db96d56Sopenharmony_ci expected='hello, world!\n')) 2307db96d56Sopenharmony_ci 2317db96d56Sopenharmony_ci def test_readlines(self): 2327db96d56Sopenharmony_ci """readlines() must handle signals and not lose data.""" 2337db96d56Sopenharmony_ci self._test_reading( 2347db96d56Sopenharmony_ci data_to_write=b'hello\r\nworld!', 2357db96d56Sopenharmony_ci read_and_verify_code=self._READING_CODE_TEMPLATE.format( 2367db96d56Sopenharmony_ci read_method_name='readlines', 2377db96d56Sopenharmony_ci expected=['hello\n', 'world!\n'])) 2387db96d56Sopenharmony_ci 2397db96d56Sopenharmony_ci def test_readall(self): 2407db96d56Sopenharmony_ci """read() must handle signals and not lose data.""" 2417db96d56Sopenharmony_ci self._test_reading( 2427db96d56Sopenharmony_ci data_to_write=b'hello\nworld!', 2437db96d56Sopenharmony_ci read_and_verify_code=self._READING_CODE_TEMPLATE.format( 2447db96d56Sopenharmony_ci read_method_name='read', 2457db96d56Sopenharmony_ci expected="hello\nworld!\n")) 2467db96d56Sopenharmony_ci 2477db96d56Sopenharmony_ciclass CTestTextIOSignalInterrupt(TestTextIOSignalInterrupt, unittest.TestCase): 2487db96d56Sopenharmony_ci modname = '_io' 2497db96d56Sopenharmony_ci 2507db96d56Sopenharmony_ciclass PyTestTextIOSignalInterrupt(TestTextIOSignalInterrupt, unittest.TestCase): 2517db96d56Sopenharmony_ci modname = '_pyio' 2527db96d56Sopenharmony_ci 2537db96d56Sopenharmony_ci 2547db96d56Sopenharmony_ciif __name__ == '__main__': 2557db96d56Sopenharmony_ci unittest.main() 256