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