Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into default-handle-sighup
Browse files Browse the repository at this point in the history
  • Loading branch information
jquast committed Sep 22, 2015
2 parents ae48068 + ea586fb commit 3c1bc01
Show file tree
Hide file tree
Showing 9 changed files with 91 additions and 52 deletions.
26 changes: 18 additions & 8 deletions doc/FAQ.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
FAQ
===

**Q: Where can I get help with pexpect? Is there a mailing list?**

A: You can use the `pexpect tag on Stackoverflow <http://stackoverflow.com/questions/tagged/pexpect>`__
to ask questions specifically related to Pexpect. For more general Python
support, there's the python-list_ mailing list, and the `#python`_
IRC channel. Please refrain from using github for general
python or systems scripting support.

.. _python-list: https://mail.python.org/mailman/listinfo/python-list
.. _#python: https://www.python.org/community/irc/

**Q: Why don't shell pipe and redirect (| and >) work when I spawn a command?**

A: Remember that Pexpect does NOT interpret shell meta characters such as
Expand Down Expand Up @@ -117,14 +128,13 @@ another application.
**Q: Can I do screen scraping with this thing?**

A: That depends. If your application just does line-oriented output then
this is easy. If it does screen-oriented output then it may work, but it
could be hard. For example, trying to scrape data from the 'top' command
would be hard. The top command repaints the text window.

I am working on an ANSI / VT100 terminal emulator that will have methods
to get characters from an arbitrary X,Y coordinate of the virtual screen.
It works and you can play with it (see :mod:`pexpect.ANSI`), but I have
no working examples at this time.
this is easy. If a program emits many terminal sequences, from video
attributes to screen addressing, such as programs using curses, then
it may become very difficult to ascertain what text is displayed on a screen.

We suggest using the `pyte <https://github.com/selectel/pyte>`_ library to
screen-scrape. The module :mod:`pexpect.ANSI` released with previous versions
of pexpect is now marked deprecated and may be removed in the future.

**Q: I get strange behavior with pexect and gevent**

Expand Down
3 changes: 3 additions & 0 deletions doc/history.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ Version 4.0
* It is now possible to call :meth:`~.wait` multiple times, or after a process
is already determined to be terminated without raising an exception
(:ghpull:`211`).
* New :class:`pexpect.spawn` keyword argument, ``dimensions=(rows, columns)``
allows setting terminal screen dimensions before launching a program
(:ghissue:`122`).
* Fix regression that prevented executable, but unreadable files from
being found when not specified by absolute path -- such as
/usr/bin/sudo (:ghissue:`104`).
Expand Down
20 changes: 17 additions & 3 deletions pexpect/pty_spawn.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class spawn(SpawnBase):
def __init__(self, command, args=[], timeout=30, maxread=2000,
searchwindowsize=None, logfile=None, cwd=None, env=None,
ignore_sighup=False, echo=True, preexec_fn=None,
encoding=None, codec_errors='strict'):
encoding=None, codec_errors='strict', dimensions=None):
'''This is the constructor. The command parameter may be a string that
includes a command and any arguments to the command. For example::
Expand Down Expand Up @@ -85,6 +85,13 @@ def __init__(self, command, args=[], timeout=30, maxread=2000,
:meth:`~.expect` returns, the full buffer attribute remains up to
size *maxread* irrespective of *searchwindowsize* value.
When the keyword argument ``timeout`` is specified as a number,
(default: *30*), then :class:`TIMEOUT` will be raised after the value
specified has elapsed, in seconds, for any of the :meth:`~.expect`
family of method calls. When None, TIMEOUT will not be raised, and
:meth:`~.expect` may block indefinitely until match.
The logfile member turns on or off logging. All input and output will
be copied to the given file object. Set logfile to None to stop
logging. This is the default. Set logfile to sys.stdout to echo
Expand Down Expand Up @@ -166,6 +173,10 @@ def __init__(self, command, args=[], timeout=30, maxread=2000,
If preexec_fn is given, it will be called in the child process before
launching the given command. This is useful to e.g. reset inherited
signal handlers.
The dimensions attribute specifies the size of the pseudo-terminal as
seen by the subprocess, and is specified as a two-entry tuple (rows,
columns). If this is unspecified, the defaults in ptyprocess will apply.
'''
super(spawn, self).__init__(timeout=timeout, maxread=maxread, searchwindowsize=searchwindowsize,
logfile=logfile, encoding=encoding, codec_errors=codec_errors)
Expand All @@ -182,7 +193,7 @@ def __init__(self, command, args=[], timeout=30, maxread=2000,
self.args = None
self.name = '<pexpect factory incomplete>'
else:
self._spawn(command, args, preexec_fn)
self._spawn(command, args, preexec_fn, dimensions)

def __str__(self):
'''This returns a human-readable string that represents the state of
Expand Down Expand Up @@ -218,7 +229,7 @@ def __str__(self):
s.append('delayafterterminate: ' + str(self.delayafterterminate))
return '\n'.join(s)

def _spawn(self, command, args=[], preexec_fn=None):
def _spawn(self, command, args=[], preexec_fn=None, dimensions=None):
'''This starts the given command in a child process. This does all the
fork/exec type of stuff for a pty. This is called by __init__. If args
is empty then command will be parsed (split on spaces) and args will be
Expand Down Expand Up @@ -273,6 +284,9 @@ def preexec_wrapper():
preexec_fn()
kwargs['preexec_fn'] = preexec_wrapper

if dimensions is not None:
kwargs['dimensions'] = dimensions

self.ptyproc = ptyprocess.PtyProcess.spawn(self.args, env=self.env,
cwd=self.cwd, **kwargs)

Expand Down
10 changes: 6 additions & 4 deletions pexpect/spawnbase.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,10 @@ def expect(self, pattern, timeout=-1, searchwindowsize=-1, async=False):
*before* is all data received up to the exception, while *match* and
*after* attributes are value None.
If timeout is -1 then timeout will be set to the self.timeout value.
When the keyword argument timeout is -1 (default), then TIMEOUT will
raise after the default value specified by the class timeout
attribute. When None, TIMEOUT will not be raised and may block
indefinitely until match.
When the keyword argument searchwindowsize is -1 (default), then the
value specified by the class maxread attribute is used.
Expand Down Expand Up @@ -319,9 +322,8 @@ def expect_list(self, pattern_list, timeout=-1, searchwindowsize=-1,
expressions). This method is similar to the expect() method except that
expect_list() does not recompile the pattern list on every call. This
may help if you are trying to optimize for speed, otherwise just use
the expect() method. This is called by expect(). If timeout==-1 then
the self.timeout value is used. If searchwindowsize==-1 then the
self.searchwindowsize value is used.
the expect() method. This is called by expect().
Like :meth:`expect`, passing ``async=True`` will make this return an
asyncio coroutine.
Expand Down
1 change: 1 addition & 0 deletions tests/sigwinch_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ def handler(signum, frame):
print('SIGWINCH:', getwinsize ())
sys.stdout.flush()

print("Initial Size:", getwinsize())
print("setting handler for SIGWINCH")
signal.signal(signal.SIGWINCH, handler)
print("READY")
Expand Down
6 changes: 4 additions & 2 deletions tests/test_misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,9 +317,11 @@ def test_nonnative_pty_fork(self):
" test forced self.__fork_pty() and __pty_make_controlling_tty "
# given,
class spawn_ourptyfork(pexpect.spawn):
def _spawn(self, command, args=[], preexec_fn=None):
def _spawn(self, command, args=[], preexec_fn=None,
dimensions=None):
self.use_native_pty_fork = False
pexpect.spawn._spawn(self, command, args, preexec_fn)
pexpect.spawn._spawn(self, command, args, preexec_fn,
dimensions)

# exercise,
p = spawn_ourptyfork('cat', echo=False)
Expand Down
9 changes: 9 additions & 0 deletions tests/test_unicode.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,15 @@ def test_readline_bin_echo(self):
# exercise,
assert child.readline() == 'input' + child.crlf

def test_unicode_argv(self):
""" Ensure a program can be executed with unicode arguments. """
p = pexpect.spawn(u'echo ǝpoɔıun'.format(self=self),
timeout=5, encoding='utf8')
p.expect(u'ǝpoɔıun')
p.expect(pexpect.EOF)
assert not p.isalive()
assert p.exitstatus == 0

if __name__ == '__main__':
unittest.main()

Expand Down
9 changes: 7 additions & 2 deletions tests/test_which.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
import subprocess
import tempfile
import shutil
Expand All @@ -21,9 +22,13 @@ def test_which_finds_ls(self):

def test_os_defpath_which(self):
" which() finds an executable in $PATH and returns its abspath. "
fname = 'cc'

bin_dir = tempfile.mkdtemp()
bin_path = os.path.join(bin_dir, fname)
temp_obj = tempfile.NamedTemporaryFile(
suffix=u'.sh', prefix=u'ǝpoɔıun-',
dir=bin_dir, delete=False)
bin_path = temp_obj.name
fname = os.path.basename(temp_obj.name)
save_path = os.environ['PATH']
save_defpath = os.defpath

Expand Down
59 changes: 26 additions & 33 deletions tests/test_winsize.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,39 +25,32 @@

class TestCaseWinsize(PexpectTestCase.PexpectTestCase):

def test_winsize (self):
'''
This tests that the child process can set and get the windows size.
This makes use of an external script sigwinch_report.py.
'''
p1 = pexpect.spawn('%s sigwinch_report.py' % self.PYTHONBIN)
p1.expect('READY', timeout=10)

p1.setwinsize (11,22)
index = p1.expect ([pexpect.TIMEOUT, b'SIGWINCH: \(([0-9]*), ([0-9]*)\)'],
timeout=30)
if index == 0:
self.fail("TIMEOUT -- this platform may not support sigwinch properly.\n" + str(p1))
self.assertEqual(p1.match.group(1, 2), (b"11" ,b"22"))
self.assertEqual(p1.getwinsize(), (11, 22))

time.sleep(1)
p1.setwinsize (24,80)
index = p1.expect ([pexpect.TIMEOUT, b'SIGWINCH: \(([0-9]*), ([0-9]*)\)'],
timeout=10)
if index == 0:
self.fail ("TIMEOUT -- this platform may not support sigwinch properly.\n" + str(p1))
self.assertEqual(p1.match.group(1, 2), (b"24" ,b"80"))
self.assertEqual(p1.getwinsize(), (24, 80))

p1.close()

# def test_parent_resize (self):
# pid = os.getpid()
# p1 = pexpect.spawn('%s sigwinch_report.py' % self.PYTHONBIN)
# time.sleep(10)
# p1.setwinsize (11,22)
# os.kill (pid, signal.SIGWINCH)
def test_initial_winsize(self):
""" Assert initial window dimension size (24, 80). """
p = pexpect.spawn('{self.PYTHONBIN} sigwinch_report.py'
.format(self=self), timeout=3)
# default size by PtyProcess class is 24 rows by 80 columns.
p.expect_exact('Initial Size: (24, 80)')
p.close()

def test_initial_winsize_by_dimension(self):
""" Assert user-parameter window dimension size is initial. """
p = pexpect.spawn('{self.PYTHONBIN} sigwinch_report.py'
.format(self=self), timeout=3,
dimensions=(40, 100))
p.expect_exact('Initial Size: (40, 100)')
p.close()

def test_setwinsize(self):
""" Ensure method .setwinsize() sends signal caught by child. """
p = pexpect.spawn('{self.PYTHONBIN} sigwinch_report.py'
.format(self=self), timeout=3)
# Note that we must await the installation of the child process'
# signal handler,
p.expect_exact('READY')
p.setwinsize(19, 84)
p.expect_exact('SIGWINCH: (19, 84)')
p.close()

if __name__ == '__main__':
unittest.main()
Expand Down

0 comments on commit 3c1bc01

Please sign in to comment.