diff options
author | Jeff Quast <contact@jeffquast.com> | 2014-06-06 14:14:45 +0000 |
---|---|---|
committer | Jeff Quast <contact@jeffquast.com> | 2014-06-06 14:14:45 +0000 |
commit | 06f1bf9244ec8806288d1664b0421e19c8bd364a (patch) | |
tree | 778d2313ff4d2c66eea576c25cb9c73bd47a25db | |
parent | 26830e713861a94bd8afe93968de07759fb8aa7a (diff) | |
parent | 20ded785263e64c5beec1d594f666bdae5f2c2aa (diff) | |
download | pexpect-git-06f1bf9244ec8806288d1664b0421e19c8bd364a.tar.gz |
Merge branch 'interact-does-not-detect-eof' into issue-44-solaris-try-3
-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 | 86 |
6 files changed, 83 insertions, 64 deletions
diff --git a/doc/history.rst b/doc/history.rst index 81ad48b..5b26521 100644 --- a/doc/history.rst +++ b/doc/history.rst @@ -14,6 +14,9 @@ Version 3.3 * Removed the ``pexpect.psh`` module. This was never documented, and we found no evidence that people use it. The new :mod:`pexpect.replwrap` module provides a more flexible alternative. +* 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 7220437..fa40669 100644 --- a/pexpect/__init__.py +++ b/pexpect/__init__.py @@ -912,12 +912,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.') @@ -1073,23 +1075,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: @@ -1642,10 +1627,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..70a0e08 100755 --- a/tests/test_interact.py +++ b/tests/test_interact.py @@ -27,49 +27,67 @@ import pexpect import unittest 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.save_pythonpath = os.getenv('PYTHONPATH') + + # Ensure 'import pexpect' works in subprocess interact*.py + if not self.save_pythonpath: + os.putenv('PYTHONPATH', self.project_dir) + else: + os.putenv('PYTHONPATH', os.pathsep.join((self.project_dir, + self.save_pythonpath))) + + self.interact_py = ' '.join((self.PYTHONBIN, + 'interact.py',)) + self.interact_ucs_py = ' '.join((self.PYTHONBIN, + 'interact_unicode.py',)) - def test_interact (self): - p = pexpect.spawn(str('%s interact.py' % (self.PYTHONBIN,)), env=self.env) + def tearDown(self): + os.putenv('PYTHONPATH', self.save_pythonpath or '') + + def test_interact_escape(self): + " Ensure `escape_character' value exits interactive mode. " + p = pexpect.spawn(self.interact_py, timeout=5) 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) + 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) + 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() |