test_poll: fix array test
authorZac Medico <zmedico@gentoo.org>
Fri, 16 Dec 2011 21:56:03 +0000 (13:56 -0800)
committerZac Medico <zmedico@gentoo.org>
Fri, 16 Dec 2011 21:56:03 +0000 (13:56 -0800)
Since SpawnProcess no longer uses array, add conditional array support
to PipeReader and use that for tests.

pym/_emerge/AbstractPollTask.py
pym/_emerge/PipeReader.py
pym/portage/tests/process/test_poll.py

index b3c0b2d16e7c74ad7b0269410063ad75091543cc..83e6c7b9c70a8d0374e12e690537eb21646b113c 100644 (file)
@@ -1,6 +1,7 @@
 # Copyright 1999-2011 Gentoo Foundation
 # Distributed under the terms of the GNU General Public License v2
 
+import array
 import errno
 import logging
 import os
@@ -21,6 +22,54 @@ class AbstractPollTask(AsynchronousTask):
        def isAlive(self):
                return bool(self._registered)
 
+       def _read_array(self, f, event):
+               """
+               NOTE: array.fromfile() is used here only for testing purposes,
+               because it has bugs in all known versions of Python (including
+               Python 2.7 and Python 3.2).
+
+               | POLLIN | RETURN
+               | BIT    | VALUE
+               | ---------------------------------------------------
+               | 1      | Read self._bufsize into an instance of
+               |        | array.array('B') and return it, ignoring
+               |        | EOFError and IOError. An empty array
+               |        | indicates EOF.
+               | ---------------------------------------------------
+               | 0      | None
+               """
+               buf = None
+               if event & PollConstants.POLLIN:
+                       buf = array.array('B')
+                       try:
+                               buf.fromfile(f, self._bufsize)
+                       except EOFError:
+                               pass
+                       except TypeError:
+                               # Python 3.2:
+                               # TypeError: read() didn't return bytes
+                               pass
+                       except IOError as e:
+                               # EIO happens with pty on Linux after the
+                               # slave end of the pty has been closed.
+                               if e.errno == errno.EIO:
+                                       # EOF: return empty string of bytes
+                                       pass
+                               elif e.errno == errno.EAGAIN:
+                                       # EAGAIN: return None
+                                       buf = None
+                               else:
+                                       raise
+
+               if buf is not None:
+                       try:
+                               # Python >=3.2
+                               buf = buf.tobytes()
+                       except AttributeError:
+                               buf = buf.tostring()
+
+               return buf
+
        def _read_buf(self, fd, event):
                """
                | POLLIN | RETURN
index 9fedbff6f4beb1df1fc08d7a1b7bcc9158ec3f89..b162fe5ccc2746326566c7424ddb643b88b585e0 100644 (file)
@@ -15,16 +15,22 @@ class PipeReader(AbstractPollTask):
        """
 
        __slots__ = ("input_files",) + \
-               ("_read_data", "_reg_ids")
+               ("_read_data", "_reg_ids", "_use_array")
 
        def _start(self):
                self._reg_ids = set()
                self._read_data = []
+
+               if self._use_array:
+                       output_handler = self._array_output_handler
+               else:
+                       output_handler = self._output_handler
+
                for k, f in self.input_files.items():
                        fcntl.fcntl(f.fileno(), fcntl.F_SETFL,
                                fcntl.fcntl(f.fileno(), fcntl.F_GETFL) | os.O_NONBLOCK)
                        self._reg_ids.add(self.scheduler.register(f.fileno(),
-                               self._registered_events, self._output_handler))
+                               self._registered_events, output_handler))
                self._registered = True
 
        def isAlive(self):
@@ -68,6 +74,25 @@ class PipeReader(AbstractPollTask):
 
                self._unregister_if_appropriate(event)
 
+       def _array_output_handler(self, fd, event):
+
+               for f in self.input_files.values():
+                       if f.fileno() == fd:
+                               break
+
+               while True:
+                       data = self._read_array(f, event)
+                       if data is None:
+                               break
+                       if data:
+                               self._read_data.append(data)
+                       else:
+                               self._unregister()
+                               self.wait()
+                               break
+
+               self._unregister_if_appropriate(event)
+
        def _unregister(self):
                """
                Unregister from the scheduler and close open files.
index 9b1f9cb5553d0ffdf3b528bbc9d9d32f7ac42751..f1ddcb3dd323d7c81c2f014e8f7b3566e2ebc5d7 100644 (file)
@@ -1,8 +1,6 @@
 # Copyright 1998-2011 Gentoo Foundation
 # Distributed under the terms of the GNU General Public License v2
 
-import tempfile
-
 from portage import os
 from portage.tests import TestCase
 from portage.util._pty import _create_pty_or_pipe
@@ -10,15 +8,10 @@ from _emerge.PollScheduler import PollScheduler
 from _emerge.PipeReader import PipeReader
 from _emerge.SpawnProcess import SpawnProcess
 
-class _SpawnProcessPty(SpawnProcess):
-       __slots__ = ("got_pty",)
-       def _pipe(self, fd_pipes):
-               got_pty, master_fd, slave_fd = _create_pty_or_pipe()
-               self.got_pty = got_pty
-               return (master_fd, slave_fd)
-
 class PipeReaderTestCase(TestCase):
 
+       _use_array = False
+
        def _testPipeReader(self, test_string, use_pty):
                """
                Use a poll loop to read data from a pipe and assert that
@@ -43,7 +36,7 @@ class PipeReaderTestCase(TestCase):
 
                consumer = PipeReader(
                        input_files={"producer" : master_file},
-                       scheduler=scheduler)
+                       scheduler=scheduler, _use_array=self._use_array)
 
                consumer.start()
 
@@ -58,57 +51,22 @@ class PipeReaderTestCase(TestCase):
                output = consumer.getvalue().decode('ascii', 'replace')
                return (output, got_pty)
 
-       def _testPipeReaderArray(self, test_string, use_pty):
-               """
-               Use a poll loop to read data from a pipe and assert that
-               the data written to the pipe is identical to the data
-               read from the pipe.
-               """
-
-               scheduler = PollScheduler().sched_iface
-               if use_pty:
-                       spawn_process = _SpawnProcessPty
-               else:
-                       spawn_process = SpawnProcess
-
-               fd, logfile = tempfile.mkstemp()
-               os.close(fd)
-               producer = spawn_process(
-                       background=True,
-                       args=["bash", "-c", "echo -n '%s'" % test_string],
-                       env=os.environ,
-                       scheduler=scheduler, logfile=logfile)
-
-               try:
-                       producer.start()
-                       scheduler.schedule()
-                       self.assertEqual(producer.returncode, os.EX_OK)
-
-                       if use_pty:
-                               got_pty = producer.got_pty
-                       else:
-                               got_pty = False
-
-                       with open(logfile, 'rb') as f:
-                               output = f.read().decode('ascii')
-                       return (output, got_pty)
-               finally:
-                       try:
-                               os.unlink(logfile)
-                       except OSError:
-                               pass
-
        def testPipeReader(self):
                for use_pty in (False, True):
-                       for use_array in (False, True):
-                               for x in (1, 2, 5, 6, 7, 8, 2**5, 2**10, 2**12, 2**13, 2**14):
-                                       test_string = x * "a"
-                                       if use_array:
-                                               method = self._testPipeReaderArray
-                                       else:
-                                               method = self._testPipeReader
-                                       output, got_pty = method(test_string, use_pty)
-                                       self.assertEqual(test_string, output,
-                                               "x = %s, len(output) = %s, use_array = %s, "
-                                               "use_pty = %s, got_pty = %s" %
-                                               (x, len(output), use_array, use_pty, got_pty))
+                       for x in (1, 2, 5, 6, 7, 8, 2**5, 2**10, 2**12, 2**13, 2**14):
+                               test_string = x * "a"
+                               output, got_pty = self._testPipeReader(test_string, use_pty)
+                               self.assertEqual(test_string, output,
+                                       "x = %s, len(output) = %s, "
+                                       "use_pty = %s, got_pty = %s" %
+                                       (x, len(output), use_pty, got_pty))
+
+class PipeReaderArrayTestCase(PipeReaderTestCase):
+
+       _use_array = True
+
+       def __init__(self, *args, **kwargs):
+               super(PipeReaderArrayTestCase, self).__init__(*args, **kwargs)
+               # http://bugs.python.org/issue5380
+               # https://bugs.pypy.org/issue956
+               self.todo = True