summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeff Quast <contact@jeffquast.com>2015-09-18 13:02:35 -0700
committerJeff Quast <contact@jeffquast.com>2015-09-18 13:02:35 -0700
commit410c1bac5ad8438179c68024a89057d9966cdfb5 (patch)
tree58e449af3e2ee5b6a1df869d27388ee0463fd6ce
parentda02efe87627c555152d70ed50bf66671d2aabe4 (diff)
parentfaff3e605b2e1d6d30d3f9ded95473ccfbb8daf0 (diff)
downloadpexpect-410c1bac5ad8438179c68024a89057d9966cdfb5.tar.gz
Merge 'origin/master' into 'setwinsize_on_spawn'
especially careful in pexpect/__init__.py, the definition of the 'run' vs. '_run' function has changed; the phrase 'dimension' is removed entirely but functional: it should be allowed through the **kwargs pass-through.
-rw-r--r--ANSI.py7
-rw-r--r--FSM.py7
-rw-r--r--README.rst2
-rw-r--r--doc/api/ANSI.rst14
-rw-r--r--doc/api/index.rst9
-rw-r--r--doc/api/pexpect.rst32
-rw-r--r--doc/api/screen.rst10
-rw-r--r--doc/history.rst10
-rw-r--r--doc/overview.rst8
-rw-r--r--fdpexpect.py7
-rw-r--r--pexpect/__init__.py49
-rw-r--r--pexpect/async.py12
-rw-r--r--pexpect/fdpexpect.py31
-rw-r--r--pexpect/pty_spawn.py72
-rw-r--r--pexpect/pxssh.py11
-rw-r--r--pexpect/replwrap.py3
-rw-r--r--pexpect/screen.py7
-rw-r--r--pexpect/spawnbase.py125
-rw-r--r--pxssh.py7
-rw-r--r--requirements-testing.txt5
-rw-r--r--screen.py7
-rw-r--r--setup.py3
-rwxr-xr-xtests/getch.py5
-rwxr-xr-xtests/interact.py5
-rwxr-xr-xtests/test_interact.py33
-rwxr-xr-xtests/test_isalive.py27
-rw-r--r--tests/test_maxcanon.py32
-rwxr-xr-xtests/test_run.py118
-rwxr-xr-xtools/display-sighandlers.py6
-rwxr-xr-xtools/teamcity-runtests.sh7
30 files changed, 402 insertions, 269 deletions
diff --git a/ANSI.py b/ANSI.py
deleted file mode 100644
index ca1673b..0000000
--- a/ANSI.py
+++ /dev/null
@@ -1,7 +0,0 @@
-import warnings
-
-warnings.warn("This module has been moved to pexpect.ANSI, please update imports.",
- ImportWarning)
-del warnings
-
-from pexpect.ANSI import * # analysis:ignore \ No newline at end of file
diff --git a/FSM.py b/FSM.py
deleted file mode 100644
index 4e1ab49..0000000
--- a/FSM.py
+++ /dev/null
@@ -1,7 +0,0 @@
-import warnings
-
-warnings.warn("This module has been moved to pexpect.FSM, please update imports.",
- ImportWarning)
-del warnings
-
-from pexpect.FSM import * # analysis:ignore \ No newline at end of file
diff --git a/README.rst b/README.rst
index dde7ade..19492c6 100644
--- a/README.rst
+++ b/README.rst
@@ -32,7 +32,7 @@ You can install Pexpect using pip::
`Docs on ReadTheDocs <http://pexpect.readthedocs.org/>`_
-PEXPECT LICENSE
+PEXPECT LICENSE::
http://opensource.org/licenses/isc-license.txt
diff --git a/doc/api/ANSI.rst b/doc/api/ANSI.rst
deleted file mode 100644
index 064563d..0000000
--- a/doc/api/ANSI.rst
+++ /dev/null
@@ -1,14 +0,0 @@
-ANSI - ANSI (VT100) terminal emulator
-=====================================
-
-.. automodule:: pexpect.ANSI
-
-.. autoclass:: term
- :show-inheritance:
-
-.. autoclass:: ANSI
- :show-inheritance:
-
- .. automethod:: write_ch
- .. automethod:: write
- .. automethod:: process \ No newline at end of file
diff --git a/doc/api/index.rst b/doc/api/index.rst
index 1a6a6ae..fd017a5 100644
--- a/doc/api/index.rst
+++ b/doc/api/index.rst
@@ -8,5 +8,10 @@ API documentation
fdpexpect
replwrap
pxssh
- screen
- ANSI
+
+The modules ``pexpect.screen`` and ``pexpect.ANSI`` have been deprecated in
+Pexpect version 4. They were separate from the main use cases for Pexpect, and
+there are better maintained Python terminal emulator packages, such as
+`pyte <https://pypi.python.org/pypi/pyte>`__.
+These modules are still present for now, but we don't advise using them in new
+code.
diff --git a/doc/api/pexpect.rst b/doc/api/pexpect.rst
index 565f0ef..79bbcef 100644
--- a/doc/api/pexpect.rst
+++ b/doc/api/pexpect.rst
@@ -36,9 +36,9 @@ spawn class
.. note::
- With a :class:`spawn` instance, the log files should be open for
- writing binary data. With a :class:`spawnu` instance, they should
- be open for writing unicode text.
+ With :class:`spawn` in bytes mode, the log files should be open for
+ writing binary data. In unicode mode, they should
+ be open for writing unicode text. See :ref:`unicode`.
Controlling the child process
`````````````````````````````
@@ -69,31 +69,35 @@ Controlling the child process
Handling unicode
````````````````
-For backwards compatibility, :class:`spawn` can handle some Unicode: its
-send methods will encode arbitrary unicode as UTF-8 before sending it to the
-child process, and its expect methods can accept ascii-only unicode strings.
-However, for a proper unicode API to a subprocess, use this subclass:
+By default, :class:`spawn` is a bytes interface: its read methods return bytes,
+and its write/send and expect methods expect bytes. If you pass the *encoding*
+parameter to the constructor, it will instead act as a unicode interface:
+strings you send will be encoded using that encoding, and bytes received will
+be decoded before returning them to you. In this mode, patterns for
+:meth:`~spawn.expect` and :meth:`~spawn.expect_exact` should also be unicode.
+
+.. versionchanged:: 4.0
-.. autoclass:: spawnu
- :show-inheritance:
+ :class:`spawn` provides both the bytes and unicode interfaces. In Pexpect
+ 3.x, the unicode interface was provided by a separate ``spawnu`` class.
-There is also a :func:`runu` function, the unicode counterpart to :func:`run`.
+For backwards compatibility, some Unicode is allowed in bytes mode: the
+send methods will encode arbitrary unicode as UTF-8 before sending it to the
+child process, and its expect methods can accept ascii-only unicode strings.
.. note::
Unicode handling with pexpect works the same way on Python 2 and 3, despite
the difference in names. I.e.:
- - :class:`spawn` works with ``str`` on Python 2, and :class:`bytes` on Python 3,
- - :class:`spawnu` works with ``unicode`` on Python 2, and :class:`str` on Python 3.
+ - Bytes mode works with ``str`` on Python 2, and :class:`bytes` on Python 3,
+ - Unicode mode works with ``unicode`` on Python 2, and :class:`str` on Python 3.
run function
------------
.. autofunction:: run
-.. autofunction:: runu
-
Exceptions
----------
diff --git a/doc/api/screen.rst b/doc/api/screen.rst
deleted file mode 100644
index 8268fb9..0000000
--- a/doc/api/screen.rst
+++ /dev/null
@@ -1,10 +0,0 @@
-screen - manage a virtual 'screen'
-==================================
-
-.. automodule:: pexpect.screen
-
-.. autoclass:: screen
- :members:
-
- .. automethod:: __init__
- .. automethod:: __str__ \ No newline at end of file
diff --git a/doc/history.rst b/doc/history.rst
index c9d5640..8186b47 100644
--- a/doc/history.rst
+++ b/doc/history.rst
@@ -12,6 +12,16 @@ Version 4.0
coroutine. You can get the result using ``yield from``, or wrap it in an
:class:`asyncio.Task`. This allows the event loop to do other things while
waiting for output that matches a pattern.
+* Enhancement: allow method as callbacks of argument ``events`` for
+ :func:`pexpect.run` (:ghissue:`176`).
+* 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`).
+* Deprecated ``pexpect.screen`` and ``pexpect.ANSI``. Please use other packages
+ such as `pyte <https://pypi.python.org/pypi/pyte>`__ to emulate a terminal.
+* Removed the independent top-level modules (``pxssh fdpexpect FSM screen ANSI``)
+ which were installed alongside Pexpect. These were moved into the Pexpect
+ package in 3.0, but the old names were left as aliases.
Version 3.4
```````````
diff --git a/doc/overview.rst b/doc/overview.rst
index a04e389..139ba36 100644
--- a/doc/overview.rst
+++ b/doc/overview.rst
@@ -60,7 +60,7 @@ Special EOF and TIMEOUT patterns
--------------------------------
There are two special patterns to match the End Of File (:class:`~pexpect.EOF`)
-or a Timeout condition (:class:`~pexpect.TIMEOUT`). You you can pass these
+or a Timeout condition (:class:`~pexpect.TIMEOUT`). You can pass these
patterns to :meth:`~pexpect.spawn.expect`. These patterns are not regular
expressions. Use them like predefined constants.
@@ -84,13 +84,13 @@ The following code fragment gives an example of this::
# We expect any of these three patterns...
i = child.expect (['Permission denied', 'Terminal type', '[#\$] '])
if i==0:
- print('Permission denied on host. Can't login')
+ print('Permission denied on host. Can\'t login')
child.kill(0)
- elif i==2:
+ elif i==1:
print('Login OK... need to send terminal type.')
child.sendline('vt100')
child.expect('[#\$] ')
- elif i==3:
+ elif i==2:
print('Login OK.')
print('Shell command prompt', child.after)
diff --git a/fdpexpect.py b/fdpexpect.py
deleted file mode 100644
index 26db4c1..0000000
--- a/fdpexpect.py
+++ /dev/null
@@ -1,7 +0,0 @@
-import warnings
-
-warnings.warn("This module has been moved to pexpect.fdpexpect, please update imports.",
- ImportWarning)
-del warnings
-
-from pexpect.fdpexpect import * # analysis:ignore \ No newline at end of file
diff --git a/pexpect/__init__.py b/pexpect/__init__.py
index 2dc7ebd..db5be16 100644
--- a/pexpect/__init__.py
+++ b/pexpect/__init__.py
@@ -71,13 +71,13 @@ from .utils import split_command_line, which, is_executable_file
from .pty_spawn import spawn, spawnu, PY3
from .expect import Expecter, searcher_re, searcher_string
-__version__ = '3.3'
+__version__ = '4.0.dev'
__revision__ = ''
__all__ = ['ExceptionPexpect', 'EOF', 'TIMEOUT', 'spawn', 'spawnu', 'run', 'runu',
'which', 'split_command_line', '__version__', '__revision__']
def run(command, timeout=30, withexitstatus=False, events=None,
- extra_args=None, logfile=None, cwd=None, env=None, dimensions=None):
+ extra_args=None, logfile=None, cwd=None, env=None, **kwargs):
'''
This function runs the given command; waits for it to finish; then
@@ -149,8 +149,8 @@ def run(command, timeout=30, withexitstatus=False, events=None,
Note that you should put newlines in your string if Enter is necessary.
- Like the example above, the responses may also contain callback functions.
- Any callback is a function that takes a dictionary as an argument.
+ Like the example above, the responses may also contain a callback, either
+ a function or method. It should accept a dictionary value as an argument.
The dictionary contains all the locals from the run() function, so you can
access the child spawn object or any other variable defined in run()
(event_count, child, and extra_args are the most useful). A callback may
@@ -159,29 +159,16 @@ def run(command, timeout=30, withexitstatus=False, events=None,
sent to the child. 'extra_args' is not used by directly run(). It provides
a way to pass data to a callback function through run() through the locals
dictionary passed to a callback.
- '''
- return _run(command, timeout=timeout, withexitstatus=withexitstatus,
- events=events, extra_args=extra_args, logfile=logfile, cwd=cwd,
- env=env,dimensions=dimensions, _spawn=spawn)
-
-def runu(command, timeout=30, withexitstatus=False, events=None,
- extra_args=None, logfile=None, cwd=None, env=None, **kwargs):
- """This offers the same interface as :func:`run`, but using unicode.
- Like :class:`spawnu`, you can pass ``encoding`` and ``errors`` parameters,
- which will be used for both input and output.
- """
- return _run(command, timeout=timeout, withexitstatus=withexitstatus,
- events=events, extra_args=extra_args, logfile=logfile, cwd=cwd,
- env=env, _spawn=spawnu, **kwargs)
-
-def _run(command, timeout, withexitstatus, events, extra_args, logfile, cwd,
- env, _spawn, **kwargs):
+ Like :class:`spawn`, passing *encoding* will make it work with unicode
+ instead of bytes. You can pass *codec_errors* to control how errors in
+ encoding and decoding are handled.
+ '''
if timeout == -1:
- child = _spawn(command, maxread=2000, logfile=logfile, cwd=cwd, env=env,
+ child = spawn(command, maxread=2000, logfile=logfile, cwd=cwd, env=env,
**kwargs)
else:
- child = _spawn(command, timeout=timeout, maxread=2000, logfile=logfile,
+ child = spawn(command, timeout=timeout, maxread=2000, logfile=logfile,
cwd=cwd, env=env, **kwargs)
if isinstance(events, list):
patterns= [x for x,y in events]
@@ -206,7 +193,8 @@ def _run(command, timeout, withexitstatus, events, extra_args, logfile, cwd,
child_result_list.append(child.before)
if isinstance(responses[index], child.allowed_string_types):
child.send(responses[index])
- elif isinstance(responses[index], types.FunctionType):
+ elif (isinstance(responses[index], types.FunctionType) or
+ isinstance(responses[index], types.MethodType)):
callback_result = responses[index](locals())
sys.stdout.flush()
if isinstance(callback_result, child.allowed_string_types):
@@ -214,7 +202,9 @@ def _run(command, timeout, withexitstatus, events, extra_args, logfile, cwd,
elif callback_result:
break
else:
- raise TypeError('The callback must be a string or function.')
+ raise TypeError("parameter `event' at index {index} must be "
+ "a string, method, or function: {value!r}"
+ .format(index=index, value=responses[index]))
event_count = event_count + 1
except TIMEOUT:
child_result_list.append(child.before)
@@ -229,4 +219,13 @@ def _run(command, timeout, withexitstatus, events, extra_args, logfile, cwd,
else:
return child_result
+def runu(command, timeout=30, withexitstatus=False, events=None,
+ extra_args=None, logfile=None, cwd=None, env=None, **kwargs):
+ """Deprecated: pass encoding to run() instead.
+ """
+ kwargs.setdefault('encoding', 'utf-8')
+ return run(command, timeout=timeout, withexitstatus=withexitstatus,
+ events=events, extra_args=extra_args, logfile=logfile, cwd=cwd,
+ env=env, **kwargs)
+
# vim: set shiftround expandtab tabstop=4 shiftwidth=4 ft=python autoindent :
diff --git a/pexpect/async.py b/pexpect/async.py
index 50eae3b..ad75994 100644
--- a/pexpect/async.py
+++ b/pexpect/async.py
@@ -6,10 +6,11 @@ from pexpect import EOF
@asyncio.coroutine
def expect_async(expecter, timeout=None):
# First process data that was previously read - if it maches, we don't need
- # async stuff.
- idx = expecter.new_data(expecter.spawn.buffer)
+ # async stuff.
+ previously_read = expecter.spawn.buffer
expecter.spawn.buffer = expecter.spawn.string_type()
- if idx:
+ idx = expecter.new_data(previously_read)
+ if idx is not None:
return idx
transport, pw = yield from asyncio.get_event_loop()\
@@ -36,7 +37,7 @@ class PatternWaiter(asyncio.Protocol):
def data_received(self, data):
spawn = self.expecter.spawn
- s = spawn._coerce_read_string(data)
+ s = spawn._decoder.decode(data)
spawn._log(s, 'read')
if self.fut.done():
@@ -56,6 +57,7 @@ class PatternWaiter(asyncio.Protocol):
# N.B. If this gets called, async will close the pipe (the spawn object)
# for us
try:
+ self.expecter.spawn.flag_eof = True
index = self.expecter.eof()
except EOF as e:
self.error(e)
@@ -67,4 +69,4 @@ class PatternWaiter(asyncio.Protocol):
# We may get here without eof_received being called, e.g on Linux
self.eof_received()
elif exc is not None:
- self.error(exc) \ No newline at end of file
+ self.error(exc)
diff --git a/pexpect/fdpexpect.py b/pexpect/fdpexpect.py
index 96ca2e1..ca8cf07 100644
--- a/pexpect/fdpexpect.py
+++ b/pexpect/fdpexpect.py
@@ -32,7 +32,8 @@ class fdspawn(SpawnBase):
descriptor. For example, you could use it to read through a file looking
for patterns, or to control a modem or serial device. '''
- def __init__ (self, fd, args=None, timeout=30, maxread=2000, searchwindowsize=None, logfile=None):
+ def __init__ (self, fd, args=None, timeout=30, maxread=2000, searchwindowsize=None,
+ logfile=None, encoding=None, codec_errors='strict'):
'''This takes a file descriptor (an int) or an object that support the
fileno() method (returning an int). All Python file-like objects
support fileno(). '''
@@ -50,7 +51,8 @@ class fdspawn(SpawnBase):
self.args = None
self.command = None
- SpawnBase.__init__(self, timeout, maxread, searchwindowsize, logfile)
+ SpawnBase.__init__(self, timeout, maxread, searchwindowsize, logfile,
+ encoding=encoding, codec_errors=codec_errors)
self.child_fd = fd
self.own_fd = False
self.closed = False
@@ -84,3 +86,28 @@ class fdspawn(SpawnBase):
def terminate (self, force=False): # pragma: no cover
raise ExceptionPexpect('This method is not valid for file descriptors.')
+
+ # These four methods are left around for backwards compatibility, but not
+ # documented as part of fdpexpect. You're encouraged to use os.write#
+ # directly.
+ def send(self, s):
+ "Write to fd, return number of bytes written"
+ s = self._coerce_send_string(s)
+ self._log(s, 'send')
+
+ b = self._encoder.encode(s, final=False)
+ return os.write(self.child_fd, b)
+
+ def sendline(self, s):
+ "Write to fd with trailing newline, return number of bytes written"
+ s = self._coerce_send_string(s)
+ return self.send(s + self.linesep)
+
+ def write(self, s):
+ "Write to fd, return None"
+ self.send(s)
+
+ def writelines(self, sequence):
+ "Call self.write() for each item in sequence"
+ for s in sequence:
+ self.write(s)
diff --git a/pexpect/pty_spawn.py b/pexpect/pty_spawn.py
index 23c6470..cba635e 100644
--- a/pexpect/pty_spawn.py
+++ b/pexpect/pty_spawn.py
@@ -14,7 +14,7 @@ import ptyprocess
from ptyprocess.ptyprocess import use_native_pty_fork
from .exceptions import ExceptionPexpect, EOF, TIMEOUT
-from .spawnbase import SpawnBase, SpawnBaseUnicode
+from .spawnbase import SpawnBase
from .utils import which, split_command_line
@contextmanager
@@ -30,7 +30,6 @@ PY3 = (sys.version_info[0] >= 3)
class spawn(SpawnBase):
'''This is the main class interface for Pexpect. Use this class to start
and control child applications. '''
- ptyprocess_class = ptyprocess.PtyProcess
# This is purely informational now - changing it has no effect
use_native_pty_fork = use_native_pty_fork
@@ -38,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=True, echo=True, preexec_fn=None,
- dimensions=None):
+ 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::
@@ -118,7 +117,7 @@ class spawn(SpawnBase):
child = pexpect.spawn('some_command')
child.logfile_read = sys.stdout
- Remember to use spawnu instead of spawn for the above code if you are
+ You will need to pass an encoding to spawn in the above code if you are
using Python 3.
To separately log output sent to the child use logfile_send::
@@ -177,7 +176,7 @@ class spawn(SpawnBase):
columns). If this is unspecified, the defaults in ptyprocess will apply.
'''
super(spawn, self).__init__(timeout=timeout, maxread=maxread, searchwindowsize=searchwindowsize,
- logfile=logfile)
+ logfile=logfile, encoding=encoding, codec_errors=codec_errors)
self.STDIN_FILENO = pty.STDIN_FILENO
self.STDOUT_FILENO = pty.STDOUT_FILENO
self.STDERR_FILENO = pty.STDERR_FILENO
@@ -285,7 +284,7 @@ class spawn(SpawnBase):
if dimensions is not None:
kwargs['dimensions'] = dimensions
- self.ptyproc = self.ptyprocess_class.spawn(self.args, env=self.env,
+ self.ptyproc = ptyprocess.PtyProcess.spawn(self.args, env=self.env,
cwd=self.cwd, **kwargs)
self.pid = self.ptyproc.pid
@@ -483,7 +482,7 @@ class spawn(SpawnBase):
On Linux systems, this is 4096 (defined by N_TTY_BUF_SIZE). All
other systems honor the POSIX.1 definition PC_MAX_CANON -- 1024
- on OSX, 256 on OpenSolaris, 255 on FreeBSD.
+ on OSX, 256 on OpenSolaris, and 1920 on FreeBSD.
This value may be discovered using fpathconf(3)::
@@ -511,10 +510,8 @@ class spawn(SpawnBase):
s = self._coerce_send_string(s)
self._log(s, 'send')
- return self._send(s)
-
- def _send(self, s):
- return os.write(self.child_fd, s)
+ b = self._encoder.encode(s, final=False)
+ return os.write(self.child_fd, b)
def sendline(self, s=''):
'''Wraps send(), sending string ``s`` to child process, with
@@ -527,9 +524,11 @@ class spawn(SpawnBase):
n = n + self.send(self.linesep)
return n
- def _log_control(self, byte):
+ def _log_control(self, s):
"""Write control characters to the appropriate log files"""
- self._log(byte, 'send')
+ if self.encoding is not None:
+ s = s.decode(self.encoding, 'replace')
+ self._log(s, 'send')
def sendcontrol(self, char):
'''Helper method that wraps send() with mnemonic access for sending control
@@ -622,10 +621,17 @@ class spawn(SpawnBase):
not read any data from the child, so this will block forever if the
child has unread output and has terminated. In other words, the child
may have printed output then called exit(), but, the child is
- technically still alive until its output is read by the parent. '''
+ technically still alive until its output is read by the parent.
+
+ This method is non-blocking if :meth:`wait` has already been called
+ previously or :meth:`isalive` method returns False. It simply returns
+ the previously determined exit status.
+ '''
ptyproc = self.ptyproc
with _wrap_ptyprocess_err():
+ # exception may occur if "Is some other process attempting
+ # "job control with our child pid?"
exitstatus = ptyproc.wait()
self.status = ptyproc.status
self.exitstatus = ptyproc.exitstatus
@@ -685,11 +691,10 @@ class spawn(SpawnBase):
the stdout and stderr output of the child process is printed. This
simply echos the child stdout and child stderr to the real stdout and
it echos the real stdin to the child stdin. When the user types the
- escape_character this method will stop. The default for
- escape_character is ^]. This should not be confused with ASCII 27 --
- the ESC character. ASCII 29 was chosen for historical merit because
- this is the character used by 'telnet' as the escape character. The
- escape_character will not be sent to the child process.
+ escape_character this method will return None. The escape_character
+ will not be transmitted. The default for escape_character is
+ entered as ``Ctrl - ]``, the very same as BSD telnet. To prevent
+ escaping, escape_character may be set to None.
You may pass in optional input and output filter functions. These
functions should take a string and return a string. The output_filter
@@ -721,7 +726,7 @@ class spawn(SpawnBase):
self.buffer = self.string_type()
mode = tty.tcgetattr(self.STDIN_FILENO)
tty.setraw(self.STDIN_FILENO)
- if PY3:
+ if escape_character is not None and PY3:
escape_character = escape_character.encode('latin-1')
try:
self.__interact_copy(escape_character, input_filter, output_filter)
@@ -771,7 +776,9 @@ class spawn(SpawnBase):
data = self.__interact_read(self.STDIN_FILENO)
if input_filter:
data = input_filter(data)
- i = data.rfind(escape_character)
+ i = -1
+ if escape_character is not None:
+ i = data.rfind(escape_character)
if i != -1:
data = data[:i]
self.__interact_writen(self.child_fd, data)
@@ -806,22 +813,7 @@ class spawn(SpawnBase):
# this actually is an exception.
raise
-
-class spawnu(SpawnBaseUnicode, spawn):
- """Works like spawn, but accepts and returns unicode strings.
-
- Extra parameters:
-
- :param encoding: The encoding to use for communications (default: 'utf-8')
- :param errors: How to handle encoding/decoding errors; one of 'strict'
- (the default), 'ignore', or 'replace', as described
- for :meth:`~bytes.decode` and :meth:`~str.encode`.
- """
- ptyprocess_class = ptyprocess.PtyProcessUnicode
-
- def _send(self, s):
- return os.write(self.child_fd, s.encode(self.encoding, self.errors))
-
- def _log_control(self, byte):
- s = byte.decode(self.encoding, 'replace')
- self._log(s, 'send')
+def spawnu(*args, **kwargs):
+ """Deprecated: pass encoding to spawn() instead."""
+ kwargs.setdefault('encoding', 'utf-8')
+ return spawn(*args, **kwargs)
diff --git a/pexpect/pxssh.py b/pexpect/pxssh.py
index 71f56a0..4638164 100644
--- a/pexpect/pxssh.py
+++ b/pexpect/pxssh.py
@@ -95,9 +95,12 @@ class pxssh (spawn):
def __init__ (self, timeout=30, maxread=2000, searchwindowsize=None,
logfile=None, cwd=None, env=None, ignore_sighup=True, echo=True,
- options={}):
+ options={}, encoding=None, codec_errors='strict'):
- spawn.__init__(self, None, timeout=timeout, maxread=maxread, searchwindowsize=searchwindowsize, logfile=logfile, cwd=cwd, env=env, ignore_sighup=ignore_sighup, echo=echo)
+ spawn.__init__(self, None, timeout=timeout, maxread=maxread,
+ searchwindowsize=searchwindowsize, logfile=logfile,
+ cwd=cwd, env=env, ignore_sighup=ignore_sighup, echo=echo,
+ encoding=encoding, codec_errors=codec_errors)
self.name = '<pxssh>'
@@ -169,7 +172,7 @@ class pxssh (spawn):
# maximum time for reading the entire prompt
total_timeout = timeout_multiplier * 3.0
- prompt = b''
+ prompt = self.string_type()
begin = time.time()
expired = 0.0
timeout = first_char_timeout
@@ -334,7 +337,7 @@ class pxssh (spawn):
if not self.set_unique_prompt():
self.close()
raise ExceptionPxssh('could not set shell prompt '
- '(recieved: %r, expected: %r).' % (
+ '(received: %r, expected: %r).' % (
self.before, self.PROMPT,))
return True
diff --git a/pexpect/replwrap.py b/pexpect/replwrap.py
index 7b0e823..83a09c2 100644
--- a/pexpect/replwrap.py
+++ b/pexpect/replwrap.py
@@ -11,6 +11,7 @@ PY3 = (sys.version_info[0] >= 3)
if PY3:
def u(s): return s
+ basestring = str
else:
def u(s): return s.decode('utf-8')
@@ -37,7 +38,7 @@ class REPLWrapper(object):
new_prompt=PEXPECT_PROMPT,
continuation_prompt=PEXPECT_CONTINUATION_PROMPT,
extra_init_cmd=None):
- if isinstance(cmd_or_spawn, str):
+ if isinstance(cmd_or_spawn, basestring):
self.child = pexpect.spawnu(cmd_or_spawn, echo=False)
else:
self.child = cmd_or_spawn
diff --git a/pexpect/screen.py b/pexpect/screen.py
index efe9ee5..0bced89 100644
--- a/pexpect/screen.py
+++ b/pexpect/screen.py
@@ -27,6 +27,13 @@ import codecs
import copy
import sys
+import warnings
+
+warnings.warn(("pexpect.screen and pexpect.ANSI are deprecated. "
+ "We recommend using pyte to emulate a terminal screen: "
+ "https://pypi.python.org/pypi/pyte"),
+ stacklevel=2)
+
NUL = 0 # Fill character; ignored on input.
ENQ = 5 # Transmit answerback message.
BEL = 7 # Ring the bell.
diff --git a/pexpect/spawnbase.py b/pexpect/spawnbase.py
index d79c5c0..9fd2e18 100644
--- a/pexpect/spawnbase.py
+++ b/pexpect/spawnbase.py
@@ -7,35 +7,30 @@ from .exceptions import ExceptionPexpect, EOF, TIMEOUT
from .expect import Expecter, searcher_string, searcher_re
PY3 = (sys.version_info[0] >= 3)
+text_type = str if PY3 else unicode
+
+class _NullCoder(object):
+ """Pass bytes through unchanged."""
+ @staticmethod
+ def encode(b, final=False):
+ return b
+
+ @staticmethod
+ def decode(b, final=False):
+ return b
class SpawnBase(object):
"""A base class providing the backwards-compatible spawn API for Pexpect.
- This should not be instantiated directly: use :class:`pexpect.spawn` or :class:`pexpect.fdpexpect.fdspawn`."""
- string_type = bytes
- if PY3:
- allowed_string_types = (bytes, str)
- linesep = os.linesep.encode('ascii')
- crlf = '\r\n'.encode('ascii')
-
- @staticmethod
- def write_to_stdout(b):
- try:
- return sys.stdout.buffer.write(b)
- except AttributeError:
- # If stdout has been replaced, it may not have .buffer
- return sys.stdout.write(b.decode('ascii', 'replace'))
- else:
- allowed_string_types = (basestring,) # analysis:ignore
- linesep = os.linesep
- crlf = '\r\n'
- write_to_stdout = sys.stdout.write
-
+ This should not be instantiated directly: use :class:`pexpect.spawn` or
+ :class:`pexpect.fdpexpect.fdspawn`.
+ """
encoding = None
pid = None
flag_eof = False
- def __init__(self, timeout=30, maxread=2000, searchwindowsize=None, logfile=None):
+ def __init__(self, timeout=30, maxread=2000, searchwindowsize=None,
+ logfile=None, encoding=None, codec_errors='strict'):
self.stdin = sys.stdin
self.stdout = sys.stdout
self.stderr = sys.stderr
@@ -63,7 +58,7 @@ class SpawnBase(object):
# max bytes to read at one time into buffer
self.maxread = maxread
# This is the read buffer. See maxread.
- self.buffer = self.string_type()
+ self.buffer = bytes() if (encoding is None) else text_type()
# Data before searchwindowsize point is preserved, but not searched.
self.searchwindowsize = searchwindowsize
# Delay used before sending data to child. Time in seconds.
@@ -79,6 +74,42 @@ class SpawnBase(object):
self.name = '<' + repr(self) + '>'
self.closed = True
+ # Unicode interface
+ self.encoding = encoding
+ self.codec_errors = codec_errors
+ if encoding is None:
+ # bytes mode (accepts some unicode for backwards compatibility)
+ self._encoder = self._decoder = _NullCoder()
+ self.string_type = bytes
+ self.crlf = b'\r\n'
+ if PY3:
+ self.allowed_string_types = (bytes, str)
+ self.linesep = os.linesep.encode('ascii')
+ def write_to_stdout(b):
+ try:
+ return sys.stdout.buffer.write(b)
+ except AttributeError:
+ # If stdout has been replaced, it may not have .buffer
+ return sys.stdout.write(b.decode('ascii', 'replace'))
+ self.write_to_stdout = write_to_stdout
+ else:
+ self.allowed_string_types = (basestring,) # analysis:ignore
+ self.linesep = os.linesep
+ self.write_to_stdout = sys.stdout.write
+ else:
+ # unicode mode
+ self._encoder = codecs.getincrementalencoder(encoding)(codec_errors)
+ self._decoder = codecs.getincrementaldecoder(encoding)(codec_errors)
+ self.string_type = text_type
+ self.crlf = u'\r\n'
+ self.allowed_string_types = (text_type, )
+ if PY3:
+ self.linesep = os.linesep
+ else:
+ self.linesep = os.linesep.decode('ascii')
+ # This can handle unicode in both Python 2 and 3
+ self.write_to_stdout = sys.stdout.write
+
def _log(self, s, direction):
if self.logfile is not None:
self.logfile.write(s)
@@ -88,22 +119,19 @@ class SpawnBase(object):
second_log.write(s)
second_log.flush()
- @staticmethod
- def _coerce_expect_string(s):
- if not isinstance(s, bytes):
+ # For backwards compatibility, in bytes mode (when encoding is None)
+ # unicode is accepted for send and expect. Unicode mode is strictly unicode
+ # only.
+ def _coerce_expect_string(self, s):
+ if self.encoding is None and not isinstance(s, bytes):
return s.encode('ascii')
return s
- @staticmethod
- def _coerce_send_string(s):
- if not isinstance(s, bytes):
+ def _coerce_send_string(self, s):
+ if self.encoding is None and not isinstance(s, bytes):
return s.encode('utf-8')
return s
- @staticmethod
- def _coerce_read_string(s):
- return s
-
def read_nonblocking(self, size=1, timeout=None):
"""This reads data from the file descriptor.
@@ -125,7 +153,7 @@ class SpawnBase(object):
self.flag_eof = True
raise EOF('End Of File (EOF). Empty string style platform.')
- s = self._coerce_read_string(s)
+ s = self._decoder.decode(s, final=False)
self._log(s, 'read')
return s
@@ -451,34 +479,3 @@ class SpawnBase(object):
# We rely on subclasses to implement close(). If they don't, it's not
# clear what a context manager should do.
self.close()
-
-class SpawnBaseUnicode(SpawnBase):
- if PY3:
- string_type = str
- allowed_string_types = (str, )
- linesep = os.linesep
- crlf = '\r\n'
- else:
- string_type = unicode
- allowed_string_types = (unicode, )
- linesep = os.linesep.decode('ascii')
- crlf = '\r\n'.decode('ascii')
- # This can handle unicode in both Python 2 and 3
- write_to_stdout = sys.stdout.write
-
- def __init__(self, *args, **kwargs):
- self.encoding = kwargs.pop('encoding', 'utf-8')
- self.errors = kwargs.pop('errors', 'strict')
- self._decoder = codecs.getincrementaldecoder(self.encoding)(errors=self.errors)
- super(SpawnBaseUnicode, self).__init__(*args, **kwargs)
-
- @staticmethod
- def _coerce_expect_string(s):
- return s
-
- @staticmethod
- def _coerce_send_string(s):
- return s
-
- def _coerce_read_string(self, s):
- return self._decoder.decode(s, final=False) \ No newline at end of file
diff --git a/pxssh.py b/pxssh.py
deleted file mode 100644
index 1849769..0000000
--- a/pxssh.py
+++ /dev/null
@@ -1,7 +0,0 @@
-import warnings
-
-warnings.warn("This module has been moved to pexpect.pxssh, please update imports.",
- ImportWarning)
-del warnings
-
-from pexpect.pxssh import * # analysis:ignore \ No newline at end of file
diff --git a/requirements-testing.txt b/requirements-testing.txt
new file mode 100644
index 0000000..1894122
--- /dev/null
+++ b/requirements-testing.txt
@@ -0,0 +1,5 @@
+pytest
+pytest-cov
+coverage
+coveralls
+pytest-capturelog
diff --git a/screen.py b/screen.py
deleted file mode 100644
index 0358c41..0000000
--- a/screen.py
+++ /dev/null
@@ -1,7 +0,0 @@
-import warnings
-
-warnings.warn("This module has been moved to pexpect.screen, please update imports.",
- ImportWarning)
-del warnings
-
-from pexpect.screen import * # analysis:ignore \ No newline at end of file
diff --git a/setup.py b/setup.py
index 126749a..ed0740e 100644
--- a/setup.py
+++ b/setup.py
@@ -29,7 +29,6 @@ The Pexpect interface was designed to be easy to use.
setup (name='pexpect',
version=version,
- py_modules=['pxssh', 'fdpexpect', 'FSM', 'screen', 'ANSI'],
packages=['pexpect'],
description='Pexpect allows easy control of interactive console applications.',
long_description=long_description,
@@ -61,5 +60,5 @@ setup (name='pexpect',
'Topic :: System :: Software Distribution',
'Topic :: Terminals',
],
- install_requires=['ptyprocess'],
+ install_requires=['ptyprocess>=0.5'],
)
diff --git a/tests/getch.py b/tests/getch.py
index 41e3224..7175e33 100755
--- a/tests/getch.py
+++ b/tests/getch.py
@@ -18,6 +18,7 @@ PEXPECT LICENSE
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
'''
+from __future__ import print_function
import sys, tty, termios
if hasattr(sys.stdin, 'buffer'):
@@ -27,13 +28,13 @@ else:
stdin = sys.stdin
def main():
- print('READY')
+ print('READY', end='\r\n')
while True:
try:
val = ord(stdin.read(1))
except KeyboardInterrupt:
val = 3
- sys.stdout.write('%d<STOP>\r\n' % (val,))
+ print('%d<STOP>' % (val,), end='\r\n')
if val == 0:
# StopIteration equivalent is ctrl+' ' (\x00, NUL)
break
diff --git a/tests/interact.py b/tests/interact.py
index 9f8e672..2c1c1b7 100755
--- a/tests/interact.py
+++ b/tests/interact.py
@@ -33,7 +33,10 @@ import sys
def main():
p = pexpect.spawn(sys.executable + ' echo_w_prompt.py',
env=no_coverage_env())
- p.interact()
+ escape_character = chr(29) # default matches api
+ if len(sys.argv) > 1 and sys.argv[1] == '--no-escape':
+ escape_character = None
+ p.interact(escape_character=escape_character)
print("Escaped interact")
if __name__ == '__main__':
diff --git a/tests/test_interact.py b/tests/test_interact.py
index 06fc44a..86a5b7c 100755
--- a/tests/test_interact.py
+++ b/tests/test_interact.py
@@ -57,6 +57,21 @@ class InteractTestCase (PexpectTestCase.PexpectTestCase):
assert not p.isalive()
assert p.exitstatus == 0
+ def test_interact_escape_None(self):
+ " Return only after Termination when `escape_character=None'. "
+ p = pexpect.spawn('{self.interact_py} --no-escape'.format(self=self),
+ timeout=5, env=self.env)
+ p.expect('<in >')
+ p.sendcontrol(']')
+ p.sendline('')
+ p.expect('<out>\x1d')
+ p.sendcontrol('d')
+ p.expect('<eof>')
+ p.expect_exact('Escaped interact')
+ p.expect(pexpect.EOF)
+ assert not p.isalive()
+ assert p.exitstatus == 0
+
def test_interact_spawn_eof(self):
" Ensure subprocess receives EOF and exit. "
p = pexpect.spawn(self.interact_py, timeout=5, env=self.env)
@@ -66,9 +81,12 @@ class InteractTestCase (PexpectTestCase.PexpectTestCase):
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)
+ # strangely, on travis-ci, sendeof() terminates the subprocess,
+ # it doesn't receive ^D, just immediately throws EOF.
+ idx = p.expect_exact(['<eof>', pexpect.EOF])
+ if idx == 0:
+ p.expect_exact('Escaped interact')
+ p.expect(pexpect.EOF)
assert not p.isalive()
assert p.exitstatus == 0
@@ -81,9 +99,12 @@ class InteractTestCase (PexpectTestCase.PexpectTestCase):
p.expect('<out>ɑlpha')
p.expect('<out>Βeta')
p.sendeof()
- p.expect_exact('<eof>')
- p.expect_exact('Escaped interact')
- p.expect(pexpect.EOF)
+ # strangely, on travis-ci, sendeof() terminates the subprocess,
+ # it doesn't receive ^D, just immediately throws EOF.
+ idx = p.expect_exact(['<eof>', pexpect.EOF])
+ if idx == 0:
+ p.expect_exact('Escaped interact')
+ p.expect(pexpect.EOF)
assert not p.isalive()
assert p.exitstatus == 0
diff --git a/tests/test_isalive.py b/tests/test_isalive.py
index 5168a52..cd79d09 100755
--- a/tests/test_isalive.py
+++ b/tests/test_isalive.py
@@ -25,22 +25,33 @@ import sys
import time
from . import PexpectTestCase
+
class IsAliveTestCase(PexpectTestCase.PexpectTestCase):
+ """Various tests for the running status of processes."""
- def test_expect_wait (self):
- '''This tests that calling wait on a finished process works as expected.
- '''
- p = pexpect.spawn('sleep 3')
+ def test_expect_wait(self):
+ """Ensure consistency in wait() and isalive()."""
+ p = pexpect.spawn('sleep 1')
assert p.isalive()
- p.wait()
+ assert p.wait() == 0
assert not p.isalive()
+ # In previous versions of ptyprocess/pexpect, calling wait() a second
+ # time would raise an exception, but not since v4.0
+ assert p.wait() == 0
+ def test_expect_wait_after_termination(self):
+ """Ensure wait on a process terminated by kill -9."""
p = pexpect.spawn('sleep 3')
assert p.isalive()
p.kill(9)
time.sleep(1)
- with self.assertRaises(pexpect.ExceptionPexpect):
- p.wait()
+
+ # when terminated, the exitstatus is None, but p.signalstatus
+ # and p.terminated reflects that the kill -9 nature.
+ assert p.wait() is None
+ assert p.signalstatus == 9
+ assert p.terminated == True
+ assert not p.isalive()
def test_signal_wait(self):
'''Test calling wait with a process terminated by a signal.'''
@@ -102,7 +113,7 @@ class IsAliveTestCase(PexpectTestCase.PexpectTestCase):
p = pexpect.spawn('cat')
assert p.isalive()
assert p.isalive()
- p.kill(9)
+ p.sendeof()
p.expect(pexpect.EOF)
assert not p.isalive()
assert not p.isalive()
diff --git a/tests/test_maxcanon.py b/tests/test_maxcanon.py
index bbd08f3..772a3b7 100644
--- a/tests/test_maxcanon.py
+++ b/tests/test_maxcanon.py
@@ -1,18 +1,23 @@
""" Module for canonical-mode tests. """
+# std imports
import sys
import os
-
+# local
import pexpect
from . import PexpectTestCase
+# 3rd-party
+import pytest
+
class TestCaseCanon(PexpectTestCase.PexpectTestCase):
"""
Test expected Canonical mode behavior (limited input line length).
All systems use the value of MAX_CANON which can be found using
- fpathconf(3) value PC_MAX_CANON -- with the exception of Linux.
+ fpathconf(3) value PC_MAX_CANON -- with the exception of Linux
+ and FreeBSD.
Linux, though defining a value of 255, actually honors the value
of 4096 from linux kernel include file tty.h definition
@@ -22,6 +27,10 @@ class TestCaseCanon(PexpectTestCase.PexpectTestCase):
implement this bit, and acts as if it is always set." Although these
tests ensure it is enabled, this is a non-op for Linux.
+ FreeBSD supports neither, and instead uses a fraction (1/5) of the tty
+ speed which is always 9600. Therefor, the maximum limited input line
+ length is 9600 / 5 = 1920.
+
These tests only ensure the correctness of the behavior described by
the sendline() docstring. pexpect is not particularly involved in
these scenarios, though if we wish to expose some kind of interface
@@ -43,10 +52,17 @@ class TestCaseCanon(PexpectTestCase.PexpectTestCase):
# SunOS allows PC_MAX_CANON + 1; see
# https://bitbucket.org/illumos/illumos-gate/src/d07a59219ab7fd2a7f39eb47c46cf083c88e932f/usr/src/uts/common/io/ldterm.c?at=default#cl-1888
self.max_input = os.fpathconf(0, 'PC_MAX_CANON') + 1
+ elif sys.platform.lower().startswith('freebsd'):
+ # http://lists.freebsd.org/pipermail/freebsd-stable/2009-October/052318.html
+ self.max_input = 9600 / 5
else:
# All others (probably) limit exactly at PC_MAX_CANON
self.max_input = os.fpathconf(0, 'PC_MAX_CANON')
+ @pytest.mark.skipif(
+ sys.platform.lower().startswith('freebsd'),
+ reason='os.write to BLOCK indefinitely on FreeBSD in this case'
+ )
def test_under_max_canon(self):
" BEL is not sent by terminal driver at maximum bytes - 1. "
# given,
@@ -71,8 +87,8 @@ class TestCaseCanon(PexpectTestCase.PexpectTestCase):
child.expect_exact('_' * send_bytes)
# BEL is not found,
- with self.assertRaises(pexpect.TIMEOUT, timeout=5):
- child.expect_exact('\a')
+ with self.assertRaises(pexpect.TIMEOUT):
+ child.expect_exact('\a', timeout=1)
# cleanup,
child.sendeof() # exit cat(1)
@@ -81,6 +97,10 @@ class TestCaseCanon(PexpectTestCase.PexpectTestCase):
assert not child.isalive()
assert child.exitstatus == 0
+ @pytest.mark.skipif(
+ sys.platform.lower().startswith('freebsd'),
+ reason='os.write to BLOCK indefinitely on FreeBSD in this case'
+ )
def test_beyond_max_icanon(self):
" a single BEL is sent when maximum bytes is reached. "
# given,
@@ -116,6 +136,10 @@ class TestCaseCanon(PexpectTestCase.PexpectTestCase):
assert not child.isalive()
assert child.exitstatus == 0
+ @pytest.mark.skipif(
+ sys.platform.lower().startswith('freebsd'),
+ reason='os.write to BLOCK indefinitely on FreeBSD in this case'
+ )
def test_max_no_icanon(self):
" may exceed maximum input bytes if canonical mode is disabled. "
# given,
diff --git a/tests/test_run.py b/tests/test_run.py
index c018b4d..1b3c92f 100755
--- a/tests/test_run.py
+++ b/tests/test_run.py
@@ -29,12 +29,29 @@ from . import PexpectTestCase
unicode_type = str if pexpect.PY3 else unicode
-def timeout_callback (d):
-# print d["event_count"],
- if d["event_count"]>3:
+
+def timeout_callback(values):
+ if values["event_count"] > 3:
return 1
return 0
+
+def function_events_callback(values):
+ try:
+ previous_echoed = (values["child_result_list"][-1]
+ .decode().split("\n")[-2].strip())
+ if previous_echoed.endswith("stage-1"):
+ return "echo stage-2\n"
+ elif previous_echoed.endswith("stage-2"):
+ return "echo stage-3\n"
+ elif previous_echoed.endswith("stage-3"):
+ return "exit\n"
+ else:
+ raise Exception("Unexpected output {0}".format(previous_echoed))
+ except IndexError:
+ return "echo stage-1\n"
+
+
class RunFuncTestCase(PexpectTestCase.PexpectTestCase):
runfunc = staticmethod(pexpect.run)
cr = b'\r'
@@ -51,27 +68,34 @@ class RunFuncTestCase(PexpectTestCase.PexpectTestCase):
os.unlink(self.rcfile)
super(RunFuncTestCase, self).tearDown()
- def test_run_exit (self):
+ def test_run_exit(self):
(data, exitstatus) = self.runfunc('python exit1.py', withexitstatus=1)
assert exitstatus == 1, "Exit status of 'python exit1.py' should be 1."
- def test_run (self):
- the_old_way = subprocess.Popen(args=['uname', '-m', '-n'],
- stdout=subprocess.PIPE).communicate()[0].rstrip()
- (the_new_way, exitstatus) = self.runfunc('uname -m -n', withexitstatus=1)
+ def test_run(self):
+ the_old_way = subprocess.Popen(
+ args=['uname', '-m', '-n'],
+ stdout=subprocess.PIPE
+ ).communicate()[0].rstrip()
+
+ (the_new_way, exitstatus) = self.runfunc(
+ 'uname -m -n', withexitstatus=1)
the_new_way = the_new_way.replace(self.cr, self.empty).rstrip()
+
self.assertEqual(self.prep_subprocess_out(the_old_way), the_new_way)
self.assertEqual(exitstatus, 0)
- def test_run_callback (self): # TODO it seems like this test could block forever if run fails...
- self.runfunc("cat", timeout=1, events={pexpect.TIMEOUT:timeout_callback})
+ def test_run_callback(self):
+ # TODO it seems like this test could block forever if run fails...
+ events = {pexpect.TIMEOUT: timeout_callback}
+ self.runfunc("cat", timeout=1, events=events)
- def test_run_bad_exitstatus (self):
- (the_new_way, exitstatus) = self.runfunc('ls -l /najoeufhdnzkxjd',
- withexitstatus=1)
+ def test_run_bad_exitstatus(self):
+ (the_new_way, exitstatus) = self.runfunc(
+ 'ls -l /najoeufhdnzkxjd', withexitstatus=1)
assert exitstatus != 0
- def test_run_tuple_list (self):
+ def test_run_event_as_string(self):
events = [
# second match on 'abc', echo 'def'
('abc\r\n.*GO:', 'echo "def"\n'),
@@ -88,30 +112,80 @@ class RunFuncTestCase(PexpectTestCase.PexpectTestCase):
timeout=10)
assert exitstatus == 0
+ def test_run_event_as_function(self):
+ events = [
+ ('GO:', function_events_callback)
+ ]
+
+ (data, exitstatus) = pexpect.run(
+ 'bash --rcfile {0}'.format(self.rcfile),
+ withexitstatus=True,
+ events=events,
+ timeout=10)
+ assert exitstatus == 0
+
+ def test_run_event_as_method(self):
+ events = [
+ ('GO:', self._method_events_callback)
+ ]
+
+ (data, exitstatus) = pexpect.run(
+ 'bash --rcfile {0}'.format(self.rcfile),
+ withexitstatus=True,
+ events=events,
+ timeout=10)
+ assert exitstatus == 0
+
+ def test_run_event_typeerror(self):
+ events = [('GO:', -1)]
+ with self.assertRaises(TypeError):
+ pexpect.run('bash --rcfile {0}'.format(self.rcfile),
+ withexitstatus=True,
+ events=events,
+ timeout=10)
+
+ def _method_events_callback(self, values):
+ try:
+ previous_echoed = (values["child_result_list"][-1].decode()
+ .split("\n")[-2].strip())
+ if previous_echoed.endswith("foo1"):
+ return "echo foo2\n"
+ elif previous_echoed.endswith("foo2"):
+ return "echo foo3\n"
+ elif previous_echoed.endswith("foo3"):
+ return "exit\n"
+ else:
+ raise Exception("Unexpected output {0!r}"
+ .format(previous_echoed))
+ except IndexError:
+ return "echo foo1\n"
+
+
class RunUnicodeFuncTestCase(RunFuncTestCase):
runfunc = staticmethod(pexpect.runu)
cr = b'\r'.decode('ascii')
empty = b''.decode('ascii')
prep_subprocess_out = staticmethod(lambda x: x.decode('utf-8', 'replace'))
+
def test_run_unicode(self):
if pexpect.PY3:
- c = chr(254) # þ
+ char = chr(254) # þ
pattern = '<in >'
else:
- c = unichr(254) # analysis:ignore
+ char = unichr(254) # analysis:ignore
pattern = '<in >'.decode('ascii')
- def callback(d):
- if d['event_count'] == 0:
- return c + '\n'
+ def callback(values):
+ if values['event_count'] == 0:
+ return char + '\n'
else:
return True # Stop the child process
output = pexpect.runu(sys.executable + ' echo_w_prompt.py',
- env={'PYTHONIOENCODING':'utf-8'},
- events={pattern:callback})
+ env={'PYTHONIOENCODING': 'utf-8'},
+ events={pattern: callback})
assert isinstance(output, unicode_type), type(output)
- assert '<out>'+c in output, output
+ assert ('<out>' + char) in output, output
if __name__ == '__main__':
unittest.main()
diff --git a/tools/display-sighandlers.py b/tools/display-sighandlers.py
index 98445e9..f3559f7 100755
--- a/tools/display-sighandlers.py
+++ b/tools/display-sighandlers.py
@@ -12,7 +12,11 @@ for name, value in [(signal_name, getattr(signal, signal_name))
for signal_name in dir(signal)
if signal_name.startswith('SIG')
and not signal_name.startswith('SIG_')]:
- handler = signal.getsignal(value)
+ try:
+ handler = signal.getsignal(value)
+ except ValueError:
+ # FreeBSD: signal number out of range
+ handler = 'out of range'
description = {
signal.SIG_IGN: "ignored(SIG_IGN)",
signal.SIG_DFL: "default(SIG_DFL)"
diff --git a/tools/teamcity-runtests.sh b/tools/teamcity-runtests.sh
index b74f179..bcb28f7 100755
--- a/tools/teamcity-runtests.sh
+++ b/tools/teamcity-runtests.sh
@@ -14,6 +14,7 @@ export PYTHONIOENCODING=UTF8
export LANG=en_US.UTF-8
pyversion=$1
+shift
here=$(cd `dirname $0`; pwd)
osrel=$(uname -s)
venv=teamcity-pexpect
@@ -25,7 +26,9 @@ if [ -z $venv_wrapper ]; then
fi
. ${venv_wrapper}
-workon ${venv} || mkvirtualenv -p `which python${pyversion}` ${venv} || true
+rmvirtualenv ${venv} || true
+mkvirtualenv -p `which python${pyversion}` ${venv} || true
+workon ${venv}
# install ptyprocess
cd $here/../../ptyprocess
@@ -44,7 +47,7 @@ py.test \
--junit-xml=results.${osrel}.py${pyversion}.xml \
--verbose \
--verbose \
- || ret=$?
+ "$@" || ret=$?
if [ $ret -ne 0 ]; then
# we always exit 0, preferring instead the jUnit XML