summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeff Quast <contact@jeffquast.com>2014-06-06 14:14:45 +0000
committerJeff Quast <contact@jeffquast.com>2014-06-06 14:14:45 +0000
commit06f1bf9244ec8806288d1664b0421e19c8bd364a (patch)
tree778d2313ff4d2c66eea576c25cb9c73bd47a25db
parent26830e713861a94bd8afe93968de07759fb8aa7a (diff)
parent20ded785263e64c5beec1d594f666bdae5f2c2aa (diff)
downloadpexpect-git-06f1bf9244ec8806288d1664b0421e19c8bd364a.tar.gz
Merge branch 'interact-does-not-detect-eof' into issue-44-solaris-try-3
-rw-r--r--doc/history.rst3
-rw-r--r--pexpect/__init__.py41
-rw-r--r--tests/echo_w_prompt.py8
-rwxr-xr-xtests/interact.py4
-rw-r--r--tests/interact_unicode.py5
-rwxr-xr-xtests/test_interact.py86
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()