diff options
author | Thomas Kluyver <takowl@gmail.com> | 2014-06-15 18:39:52 -0700 |
---|---|---|
committer | Thomas Kluyver <takowl@gmail.com> | 2014-06-15 18:39:52 -0700 |
commit | 9716ea092af443f706c40ce409219b6f7c714013 (patch) | |
tree | 539a93dd6c7edc386d739869372d89c2f542ef1e | |
parent | 7f14d7cd7390816d96d2e511189bec22f6824d91 (diff) | |
parent | 9dc15264f618f9b23257ed8f447648ebf46dbec6 (diff) | |
download | pexpect-9716ea092af443f706c40ce409219b6f7c714013.tar.gz |
Merge pull request #72 from pexpect/interact-does-not-detect-eof
Interact() does not detect EOF
-rw-r--r-- | doc/history.rst | 3 | ||||
-rw-r--r-- | pexpect/__init__.py | 41 | ||||
-rw-r--r-- | tests/echo_w_prompt.py | 8 | ||||
-rwxr-xr-x | tests/interact.py | 4 | ||||
-rw-r--r-- | tests/interact_unicode.py | 5 | ||||
-rwxr-xr-x | tests/test_interact.py | 84 |
6 files changed, 81 insertions, 64 deletions
diff --git a/doc/history.rst b/doc/history.rst index 245b24c..2b924a9 100644 --- a/doc/history.rst +++ b/doc/history.rst @@ -16,6 +16,9 @@ Version 3.3 provides a more flexible alternative. * Fixed ``TypeError: got <type 'str'> ('\r\n') as pattern`` in ``readline()`` method of ``spawnu`` (:ghissue:`67`). +* Fixed issue where EOF was not correctly detected in ``interact()``, causing + a repeating loop of output on Linux, and blocking before EOF on BSD and + Solaris (:ghissue:`49`). Version 3.2 ``````````` diff --git a/pexpect/__init__.py b/pexpect/__init__.py index 5a2cacc..23eef39 100644 --- a/pexpect/__init__.py +++ b/pexpect/__init__.py @@ -914,12 +914,14 @@ class spawn(object): if self.child_fd in r: try: s = os.read(self.child_fd, size) - except OSError: - # Linux does this - self.flag_eof = True - raise EOF('End Of File (EOF). Exception style platform.') + except OSError as err: + if err.args[0] == errno.EIO: + # Linux-style EOF + self.flag_eof = True + raise EOF('End Of File (EOF). Exception style platform.') + raise if s == b'': - # BSD style + # BSD-style EOF self.flag_eof = True raise EOF('End Of File (EOF). Empty string style platform.') @@ -1075,23 +1077,6 @@ class spawn(object): called at the beginning of a line. This method does not send a newline. It is the responsibility of the caller to ensure the eof is sent at the beginning of a line. ''' - - ### Hmmm... how do I send an EOF? - ###C if ((m = write(pty, *buf, p - *buf)) < 0) - ###C return (errno == EWOULDBLOCK) ? n : -1; - #fd = sys.stdin.fileno() - #old = termios.tcgetattr(fd) # remember current state - #attr = termios.tcgetattr(fd) - #attr[3] = attr[3] | termios.ICANON # ICANON must be set to see EOF - #try: # use try/finally to ensure state gets restored - # termios.tcsetattr(fd, termios.TCSADRAIN, attr) - # if hasattr(termios, 'CEOF'): - # os.write(self.child_fd, '%c' % termios.CEOF) - # else: - # # Silly platform does not define CEOF so assume CTRL-D - # os.write(self.child_fd, '%c' % 4) - #finally: # restore state - # termios.tcsetattr(fd, termios.TCSADRAIN, old) if hasattr(termios, 'VEOF'): char = ord(termios.tcgetattr(self.child_fd)[6][termios.VEOF]) else: @@ -1644,10 +1629,14 @@ class spawn(object): if self.child_fd in r: try: data = self.__interact_read(self.child_fd) - except OSError as e: - # The subprocess may have closed before we get to reading it - if e.errno != errno.EIO: - raise + except OSError as err: + if err.args[0] == errno.EIO: + # Linux-style EOF + break + raise + if data == b'': + # BSD-style EOF + break if output_filter: data = output_filter(data) if self.logfile is not None: diff --git a/tests/echo_w_prompt.py b/tests/echo_w_prompt.py index 0706688..3c80553 100644 --- a/tests/echo_w_prompt.py +++ b/tests/echo_w_prompt.py @@ -7,5 +7,9 @@ except NameError: raw_input = input while True: - a = raw_input('<in >') - print('<out>', a, sep='')
\ No newline at end of file + try: + a = raw_input('<in >') + except EOFError: + print('<eof>') + break + print('<out>', a, sep='') diff --git a/tests/interact.py b/tests/interact.py index 60e48c9..9f8e672 100755 --- a/tests/interact.py +++ b/tests/interact.py @@ -29,8 +29,10 @@ from utils import no_coverage_env import pexpect import sys + def main(): - p = pexpect.spawn(sys.executable + ' echo_w_prompt.py', env=no_coverage_env()) + p = pexpect.spawn(sys.executable + ' echo_w_prompt.py', + env=no_coverage_env()) p.interact() print("Escaped interact") diff --git a/tests/interact_unicode.py b/tests/interact_unicode.py index 93426dc..f4c1f55 100644 --- a/tests/interact_unicode.py +++ b/tests/interact_unicode.py @@ -13,9 +13,12 @@ from utils import no_coverage_env import pexpect import sys + def main(): - p = pexpect.spawnu(sys.executable + ' echo_w_prompt.py', env=no_coverage_env()) + p = pexpect.spawnu(sys.executable + ' echo_w_prompt.py', + env=no_coverage_env()) p.interact() + print("Escaped interact") if __name__ == '__main__': main() diff --git a/tests/test_interact.py b/tests/test_interact.py index 623201b..06fc44a 100755 --- a/tests/test_interact.py +++ b/tests/test_interact.py @@ -25,51 +25,67 @@ from __future__ import unicode_literals import os import pexpect import unittest +import sys from . import PexpectTestCase + class InteractTestCase (PexpectTestCase.PexpectTestCase): def setUp(self): super(InteractTestCase, self).setUp() - self.env = os.environ.copy() - # Ensure that Pexpect is importable by the subprocesses. - self.env['PYTHONPATH'] = self.project_dir + os.pathsep + os.environ.get('PYTHONPATH', '') + self.env = env = os.environ.copy() + + # Ensure 'import pexpect' works in subprocess interact*.py + if 'PYTHONPATH' in env: + env['PYTHONPATH'] = os.pathsep.join((self.project_dir, + env['PYTHONPATH'])) + else: + env['PYTHONPATH'] = self.project_dir + + self.interact_py = ' '.join((sys.executable, + 'interact.py',)) + self.interact_ucs_py = ' '.join((sys.executable, + 'interact_unicode.py',)) - def test_interact (self): - p = pexpect.spawn(str('%s interact.py' % (self.PYTHONBIN,)), env=self.env) + def test_interact_escape(self): + " Ensure `escape_character' value exits interactive mode. " + p = pexpect.spawn(self.interact_py, timeout=5, env=self.env) p.expect('<in >') - p.sendline (b'Hello') - p.sendline (b'there') - p.sendline (b'Mr. Python') - p.expect (b'<out>Hello') - p.expect (b'<out>there') - p.expect (b'<out>Mr. Python') - p.sendcontrol(']') + p.sendcontrol(']') # chr(29), the default `escape_character' + # value of pexpect.interact(). p.expect_exact('Escaped interact') - assert p.isalive() - p.sendeof () - p.expect (pexpect.EOF) + p.expect(pexpect.EOF) assert not p.isalive() - assert p.exitstatus == 0, (p.exitstatus, p.before) + assert p.exitstatus == 0 - def test_interact_unicode (self): - p = pexpect.spawnu(str('%s interact_unicode.py' % (self.PYTHONBIN,)), env=self.env) - try: - p.expect('<in >') - p.sendline ('Hello') - p.sendline ('theré') - p.sendline ('Mr. Pyþon') - p.expect ('<out>Hello') - p.expect ('<out>theré') - p.expect ('<out>Mr. Pyþon') - assert p.isalive() - p.sendeof () - p.expect (pexpect.EOF) - assert not p.isalive() - assert p.exitstatus == 0, (p.exitstatus, p.before) - except: - print(p.before) - raise + def test_interact_spawn_eof(self): + " Ensure subprocess receives EOF and exit. " + p = pexpect.spawn(self.interact_py, timeout=5, env=self.env) + p.expect('<in >') + p.sendline(b'alpha') + p.sendline(b'beta') + p.expect(b'<out>alpha') + p.expect(b'<out>beta') + p.sendeof() + p.expect_exact('<eof>') + p.expect_exact('Escaped interact') + p.expect(pexpect.EOF) + assert not p.isalive() + assert p.exitstatus == 0 + def test_interact_spawnu_eof(self): + " Ensure subprocess receives unicode, EOF, and exit. " + p = pexpect.spawnu(self.interact_ucs_py, timeout=5, env=self.env) + p.expect('<in >') + p.sendline('ɑlpha') + p.sendline('Βeta') + p.expect('<out>ɑlpha') + p.expect('<out>Βeta') + p.sendeof() + p.expect_exact('<eof>') + p.expect_exact('Escaped interact') + p.expect(pexpect.EOF) + assert not p.isalive() + assert p.exitstatus == 0 if __name__ == '__main__': unittest.main() |