Hello community,

here is the log from the commit of package python-pexpect for openSUSE:Factory 
checked in at 2018-04-20 17:30:45
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-pexpect (Old)
 and      /work/SRC/openSUSE:Factory/.python-pexpect.new (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-pexpect"

Fri Apr 20 17:30:45 2018 rev:24 rq:598545 version:4.5.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-pexpect/python-pexpect.changes    
2018-02-14 09:45:25.601362343 +0100
+++ /work/SRC/openSUSE:Factory/.python-pexpect.new/python-pexpect.changes       
2018-04-20 17:33:00.346845842 +0200
@@ -1,0 +2,39 @@
+Thu Apr 19 11:58:18 UTC 2018 - [email protected]
+
+- Fix typo and make sure the tests will pass
+
+-------------------------------------------------------------------
+Sat Apr 14 17:14:36 UTC 2018 - [email protected]
+
+- specfile:
+  * enable tests
+
+- update to version 4.5.0:
+  * spawn and fdspawn now have a use_poll parameter. If this is True,
+    they will use select.poll() instead of select.select(). poll()
+    allows file descriptors above 1024, but it must be explicitly
+    enabled due to compatibility concerns (PR #474).
+  * The pxssh.login() method has several new and changed options:
+    + The option password_regex allows changing the password prompt
+      regex, for servers that include password: in a banner before
+      reaching a prompt (PR #468).
+    + login() now allows for setting up SSH tunnels to be requested
+      once logged in to the remote server. This option is ssh_tunnels
+      (PR #473). The structure should be like this:
+        {
+          'local': ['2424:localhost:22'],   # Local SSH tunnels
+          'remote': ['2525:localhost:22'],  # Remote SSH tunnels
+          'dynamic': [8888],                # Dynamic/SOCKS tunnels
+        }
+
+    + The option spawn_local_ssh=False allows subsequent logins from
+      the remote session and treats the session as if it was local (PR
+      #472).
+    + Setting sync_original_prompt=False will prevent changing the
+      prompt to something unique, in case the remote server is
+      sensitive to new lines at login (PR #468).
+    + If ssh_key=True is passed, the SSH client forces forwarding the
+      authentication agent to the remote server instead of providing a
+      key (PR #473).
+
+-------------------------------------------------------------------

Old:
----
  pexpect-4.4.0.tar.gz

New:
----
  pexpect-4.5.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-pexpect.spec ++++++
--- /var/tmp/diff_new_pack.AWQRq2/_old  2018-04-20 17:33:00.946824084 +0200
+++ /var/tmp/diff_new_pack.AWQRq2/_new  2018-04-20 17:33:00.950823939 +0200
@@ -16,26 +16,25 @@
 #
 
 
-%bcond_with tests
-
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 Name:           python-pexpect
-Version:        4.4.0
+Version:        4.5.0
 Release:        0
 Summary:        Pure Python Expect-like module
 License:        ISC
 Group:          Development/Libraries/Python
-Url:            http://pexpect.readthedocs.org/en/latest/
+URL:            http://pexpect.readthedocs.org/en/latest/
 Source:         
https://files.pythonhosted.org/packages/source/p/pexpect/pexpect-%{version}.tar.gz
 BuildRequires:  %{python_module devel}
 BuildRequires:  %{python_module ptyprocess}
+BuildRequires:  %{python_module pytest}
 BuildRequires:  fdupes
+# For man validation
+BuildRequires:  man
+# For test command calls
+BuildRequires:  openssl
 BuildRequires:  python-rpm-macros
-%if %{with tests}
-BuildRequires:  %{python_module pytest}
-%endif
 Requires:       python-ptyprocess
-BuildRoot:      %{_tmppath}/%{name}-%{version}-build
 BuildArch:      noarch
 %python_subpackages
 
@@ -44,11 +43,11 @@
 controlling them; and responding to expected patterns in their output.
 
 %prep
-%setup -n pexpect-%{version}
+%setup -q -n pexpect-%{version}
 
 # Fix wrong-script-interpreter
-find examples -type f -name "*.py" -exec sed -i "s|#!/usr/bin/env python||" {} 
\;
-find examples -type f -name "*.cgi" -exec sed -i "s|##!/usr/bin/env 
python|##!/usr/bin/python|" {} \;
+find examples -type f -name "*.py" -exec sed -i "s|#!%{_bindir}/env python||" 
{} \;
+find examples -type f -name "*.cgi" -exec sed -i "s|##!%{_bindir}/env 
python|##!%{_bindir}/python|" {} \;
 
 %build
 %python_build
@@ -57,15 +56,12 @@
 %python_install
 %python_expand %fdupes %{buildroot}%{$python_sitelib}
 
-%if %{with tests}
 %check
 export LANG=en_US.UTF-8
-%python_expand py.test-%{$python_bun_suffix}
-%endif
+%python_expand py.test-%{$python_bin_suffix}
 
 %files %{python_files}
-%defattr(-,root,root,-)
-%doc LICENSE
+%license LICENSE
 %doc doc/
 %doc examples/
 %{python_sitelib}/pexpect/

++++++ pexpect-4.4.0.tar.gz -> pexpect-4.5.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pexpect-4.4.0/PKG-INFO new/pexpect-4.5.0/PKG-INFO
--- old/pexpect-4.4.0/PKG-INFO  2018-02-10 13:45:15.000000000 +0100
+++ new/pexpect-4.5.0/PKG-INFO  2018-04-13 20:20:48.000000000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: pexpect
-Version: 4.4.0
+Version: 4.5.0
 Summary: Pexpect allows easy control of interactive console applications.
 Home-page: https://pexpect.readthedocs.io/
 Author: Noah Spurrier; Thomas Kluyver; Jeff Quast
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pexpect-4.4.0/doc/conf.py 
new/pexpect-4.5.0/doc/conf.py
--- old/pexpect-4.4.0/doc/conf.py       2018-02-10 13:34:25.000000000 +0100
+++ new/pexpect-4.5.0/doc/conf.py       2018-04-11 21:03:57.000000000 +0200
@@ -52,7 +52,7 @@
 # built documents.
 #
 # The short X.Y version.
-version = '4.4'
+version = '4.5'
 # The full version, including alpha/beta/rc tags.
 release = version
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pexpect-4.4.0/doc/history.rst 
new/pexpect-4.5.0/doc/history.rst
--- old/pexpect-4.4.0/doc/history.rst   2018-02-10 13:32:20.000000000 +0100
+++ new/pexpect-4.5.0/doc/history.rst   2018-04-13 19:49:35.000000000 +0200
@@ -4,6 +4,36 @@
 Releases
 --------
 
+Version 4.5
+```````````
+
+* :class:`~.spawn` and :class:`~.fdspawn` now have a ``use_poll`` parameter.
+  If this is True, they will use :func:`select.poll` instead of 
:func:`select.select`.
+  ``poll()`` allows file descriptors above 1024, but it must be explicitly
+  enabled due to compatibility concerns (:ghpull:`474`).
+* The :meth:`.pxssh.login` method has several new and changed options:
+
+  * The option ``password_regex`` allows changing
+    the password prompt regex, for servers that include ``password:`` in a 
banner
+    before reaching a prompt (:ghpull:`468`).
+  * :meth:`~.pxssh.login` now allows for setting up SSH tunnels to be 
requested once
+    logged in to the remote server. This option is ``ssh_tunnels`` 
(:ghpull:`473`).
+    The structure should be like this::
+
+          {
+            'local': ['2424:localhost:22'],   # Local SSH tunnels
+            'remote': ['2525:localhost:22'],  # Remote SSH tunnels
+            'dynamic': [8888],                # Dynamic/SOCKS tunnels
+          }
+
+  * The option ``spawn_local_ssh=False`` allows subsequent logins from the
+    remote session and treats the session as if it was local (:ghpull:`472`).
+  * Setting ``sync_original_prompt=False`` will prevent changing the prompt to
+    something unique, in case the remote server is sensitive to new lines at 
login
+    (:ghpull:`468`).
+  * If ``ssh_key=True`` is passed, the SSH client forces forwarding the 
authentication
+    agent to the remote server instead of providing a key (:ghpull:`473`).
+
 Version 4.4
 ```````````
 
@@ -295,4 +325,3 @@
   to call the new fork Pexpected. Noah Spurrier agreed to let them use
   the name Pexpect, so Pexpect versions 3 and above are based on this
   fork, which now lives `here on Github <https://github.com/pexpect/pexpect>`_.
-
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pexpect-4.4.0/doc/overview.rst 
new/pexpect-4.5.0/doc/overview.rst
--- old/pexpect-4.4.0/doc/overview.rst  2018-02-10 13:16:58.000000000 +0100
+++ new/pexpect-4.5.0/doc/overview.rst  2018-04-13 19:51:22.000000000 +0200
@@ -261,7 +261,6 @@
 
 .. seealso::
 
-   `winpexpect <https://pypi.python.org/pypi/winpexpect>`__ and
-   `wexpect <https://gist.github.com/anthonyeden/8488763>`__
+   `winpexpect <https://pypi.python.org/pypi/winpexpect>`__ and `wexpect 
<https://gist.github.com/anthonyeden/8488763>`__
      Two unmaintained pexpect-like modules for Windows, which work with a
      hidden console.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pexpect-4.4.0/pexpect/__init__.py 
new/pexpect-4.5.0/pexpect/__init__.py
--- old/pexpect-4.4.0/pexpect/__init__.py       2018-02-10 13:34:36.000000000 
+0100
+++ new/pexpect-4.5.0/pexpect/__init__.py       2018-04-11 21:03:57.000000000 
+0200
@@ -75,7 +75,7 @@
     from .pty_spawn import spawn, spawnu
     from .run import run, runu
 
-__version__ = '4.4.0'
+__version__ = '4.5.0'
 __revision__ = ''
 __all__ = ['ExceptionPexpect', 'EOF', 'TIMEOUT', 'spawn', 'spawnu', 'run', 
'runu',
            'which', 'split_command_line', '__version__', '__revision__']
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pexpect-4.4.0/pexpect/_async.py 
new/pexpect-4.5.0/pexpect/_async.py
--- old/pexpect-4.4.0/pexpect/_async.py 2018-02-10 13:16:58.000000000 +0100
+++ new/pexpect-4.5.0/pexpect/_async.py 2018-04-11 21:03:57.000000000 +0200
@@ -9,6 +9,7 @@
     # async stuff.
     previously_read = expecter.spawn.buffer
     expecter.spawn._buffer = expecter.spawn.buffer_type()
+    expecter.spawn._before = expecter.spawn.buffer_type()
     idx = expecter.new_data(previously_read)
     if idx is not None:
         return idx
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pexpect-4.4.0/pexpect/expect.py 
new/pexpect-4.5.0/pexpect/expect.py
--- old/pexpect-4.4.0/pexpect/expect.py 2018-02-10 13:16:58.000000000 +0100
+++ new/pexpect-4.5.0/pexpect/expect.py 2018-04-11 21:03:57.000000000 +0200
@@ -16,6 +16,7 @@
 
         pos = spawn._buffer.tell()
         spawn._buffer.write(data)
+        spawn._before.write(data)
 
         # determine which chunk of data to search; if a windowsize is
         # specified, this is the *new* data + the preceding <windowsize> bytes
@@ -27,11 +28,11 @@
             window = spawn.buffer
         index = searcher.search(window, len(data))
         if index >= 0:
-            value = spawn.buffer
             spawn._buffer = spawn.buffer_type()
-            spawn._buffer.write(value[searcher.end:])
-            spawn.before = value[: searcher.start]
-            spawn.after = value[searcher.start: searcher.end]
+            spawn._buffer.write(window[searcher.end:])
+            spawn.before = spawn._before.getvalue()[0:-(len(window) - 
searcher.start)]
+            spawn._before = spawn.buffer_type()
+            spawn.after = window[searcher.start: searcher.end]
             spawn.match = searcher.match
             spawn.match_index = index
             # Found a match
@@ -45,6 +46,7 @@
 
         spawn.before = spawn.buffer
         spawn._buffer = spawn.buffer_type()
+        spawn._before = spawn.buffer_type()
         spawn.after = EOF
         index = self.searcher.eof_index
         if index >= 0:
@@ -96,6 +98,7 @@
         try:
             incoming = spawn.buffer
             spawn._buffer = spawn.buffer_type()
+            spawn._before = spawn.buffer_type()
             while True:
                 idx = self.new_data(incoming)
                 # Keep reading until exception or return.
@@ -158,7 +161,7 @@
         '''This returns a human-readable string that represents the state of
         the object.'''
 
-        ss = [(ns[0], '    %d: "%s"' % ns) for ns in self._strings]
+        ss = [(ns[0], '    %d: %r' % ns) for ns in self._strings]
         ss.append((-1, 'searcher_string:'))
         if self.eof_index >= 0:
             ss.append((self.eof_index, '    %d: EOF' % self.eof_index))
@@ -258,13 +261,7 @@
         #    (n, repr(s.pattern))) for n, s in self._searches]
         ss = list()
         for n, s in self._searches:
-            try:
-                ss.append((n, '    %d: re.compile("%s")' % (n, s.pattern)))
-            except UnicodeEncodeError:
-                # for test cases that display __str__ of searches, dont throw
-                # another exception just because stdout is ascii-only, using
-                # repr()
-                ss.append((n, '    %d: re.compile(%r)' % (n, s.pattern)))
+            ss.append((n, '    %d: re.compile(%r)' % (n, s.pattern)))
         ss.append((-1, 'searcher_re:'))
         if self.eof_index >= 0:
             ss.append((self.eof_index, '    %d: EOF' % self.eof_index))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pexpect-4.4.0/pexpect/fdpexpect.py 
new/pexpect-4.5.0/pexpect/fdpexpect.py
--- old/pexpect-4.4.0/pexpect/fdpexpect.py      2018-02-10 13:16:58.000000000 
+0100
+++ new/pexpect-4.5.0/pexpect/fdpexpect.py      2018-04-13 19:35:52.000000000 
+0200
@@ -23,7 +23,7 @@
 
 from .spawnbase import SpawnBase
 from .exceptions import ExceptionPexpect, TIMEOUT
-from .utils import select_ignore_interrupts
+from .utils import select_ignore_interrupts, poll_ignore_interrupts
 import os
 
 __all__ = ['fdspawn']
@@ -34,7 +34,7 @@
     for patterns, or to control a modem or serial device. '''
 
     def __init__ (self, fd, args=None, timeout=30, maxread=2000, 
searchwindowsize=None,
-                  logfile=None, encoding=None, codec_errors='strict'):
+                  logfile=None, encoding=None, codec_errors='strict', 
use_poll=False):
         '''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(). '''
@@ -58,6 +58,7 @@
         self.own_fd = False
         self.closed = False
         self.name = '<file descriptor %d>' % fd
+        self.use_poll = use_poll
 
     def close (self):
         """Close the file descriptor.
@@ -88,7 +89,7 @@
     def terminate (self, force=False):  # pragma: no cover
         '''Deprecated and invalid. Just raises an exception.'''
         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.
@@ -96,19 +97,19 @@
         "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:
@@ -136,7 +137,12 @@
             rlist = [self.child_fd]
             wlist = []
             xlist = []
-            rlist, wlist, xlist = select_ignore_interrupts(rlist, wlist, 
xlist, timeout)
+            if self.use_poll:
+                rlist = poll_ignore_interrupts(rlist, timeout)
+            else:
+                rlist, wlist, xlist = select_ignore_interrupts(
+                    rlist, wlist, xlist, timeout
+                )
             if self.child_fd not in rlist:
                 raise TIMEOUT('Timeout exceeded.')
         return super(fdspawn, self).read_nonblocking(size)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pexpect-4.4.0/pexpect/pty_spawn.py 
new/pexpect-4.5.0/pexpect/pty_spawn.py
--- old/pexpect-4.4.0/pexpect/pty_spawn.py      2018-02-10 13:16:58.000000000 
+0100
+++ new/pexpect-4.5.0/pexpect/pty_spawn.py      2018-04-13 19:35:52.000000000 
+0200
@@ -12,7 +12,9 @@
 
 from .exceptions import ExceptionPexpect, EOF, TIMEOUT
 from .spawnbase import SpawnBase
-from .utils import which, split_command_line, select_ignore_interrupts
+from .utils import (
+    which, split_command_line, select_ignore_interrupts, poll_ignore_interrupts
+)
 
 @contextmanager
 def _wrap_ptyprocess_err():
@@ -34,7 +36,8 @@
     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', dimensions=None):
+                 encoding=None, codec_errors='strict', dimensions=None,
+                 use_poll=False):
         '''This is the constructor. The command parameter may be a string that
         includes a command and any arguments to the command. For example::
 
@@ -171,7 +174,7 @@
         using setecho(False) followed by waitnoecho().  However, for some
         platforms such as Solaris, this is not possible, and should be
         disabled immediately on spawn.
-        
+
         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.
@@ -179,6 +182,9 @@
         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.
+
+        The use_poll attribute enables using select.poll() over select.select()
+        for socket handling. This is handy if your system could have > 1024 fds
         '''
         super(spawn, self).__init__(timeout=timeout, maxread=maxread, 
searchwindowsize=searchwindowsize,
                                     logfile=logfile, encoding=encoding, 
codec_errors=codec_errors)
@@ -196,6 +202,7 @@
             self.name = '<pexpect factory incomplete>'
         else:
             self._spawn(command, args, preexec_fn, dimensions)
+        self.use_poll = use_poll
 
     def __str__(self):
         '''This returns a human-readable string that represents the state of
@@ -439,7 +446,10 @@
         # If isalive() is false, then I pretend that this is the same as EOF.
         if not self.isalive():
             # timeout of 0 means "poll"
-            r, w, e = select_ignore_interrupts([self.child_fd], [], [], 0)
+            if self.use_poll:
+                r = poll_ignore_interrupts([self.child_fd], timeout)
+            else:
+                r, w, e = select_ignore_interrupts([self.child_fd], [], [], 0)
             if not r:
                 self.flag_eof = True
                 raise EOF('End Of File (EOF). Braindead platform.')
@@ -447,12 +457,19 @@
             # Irix takes a long time before it realizes a child was terminated.
             # FIXME So does this mean Irix systems are forced to always have
             # FIXME a 2 second delay when calling read_nonblocking? That sucks.
-            r, w, e = select_ignore_interrupts([self.child_fd], [], [], 2)
+            if self.use_poll:
+                r = poll_ignore_interrupts([self.child_fd], timeout)
+            else:
+                r, w, e = select_ignore_interrupts([self.child_fd], [], [], 2)
             if not r and not self.isalive():
                 self.flag_eof = True
                 raise EOF('End Of File (EOF). Slow platform.')
-
-        r, w, e = select_ignore_interrupts([self.child_fd], [], [], timeout)
+        if self.use_poll:
+            r = poll_ignore_interrupts([self.child_fd], timeout)
+        else:
+            r, w, e = select_ignore_interrupts(
+                [self.child_fd], [], [], timeout
+            )
 
         if not r:
             if not self.isalive():
@@ -764,14 +781,20 @@
 
         return os.read(fd, 1000)
 
-    def __interact_copy(self, escape_character=None,
-            input_filter=None, output_filter=None):
+    def __interact_copy(
+        self, escape_character=None, input_filter=None, output_filter=None
+    ):
 
         '''This is used by the interact() method.
         '''
 
         while self.isalive():
-            r, w, e = select_ignore_interrupts([self.child_fd, 
self.STDIN_FILENO], [], [])
+            if self.use_poll:
+                r = poll_ignore_interrupts([self.child_fd, self.STDIN_FILENO])
+            else:
+                r, w, e = select_ignore_interrupts(
+                    [self.child_fd, self.STDIN_FILENO], [], []
+                )
             if self.child_fd in r:
                 try:
                     data = self.__interact_read(self.child_fd)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pexpect-4.4.0/pexpect/pxssh.py 
new/pexpect-4.5.0/pexpect/pxssh.py
--- old/pexpect-4.4.0/pexpect/pxssh.py  2018-02-10 13:16:58.000000000 +0100
+++ new/pexpect-4.5.0/pexpect/pxssh.py  2018-04-11 21:03:57.000000000 +0200
@@ -23,6 +23,8 @@
 from pexpect import ExceptionPexpect, TIMEOUT, EOF, spawn
 import time
 import os
+import sys
+import re
 
 __all__ = ['ExceptionPxssh', 'pxssh']
 
@@ -31,6 +33,22 @@
     '''Raised for pxssh exceptions.
     '''
 
+if sys.version_info > (3, 0):
+    from shlex import quote
+else:
+    _find_unsafe = re.compile(r'[^\w@%+=:,./-]').search
+
+    def quote(s):
+        """Return a shell-escaped version of the string *s*."""
+        if not s:
+            return "''"
+        if _find_unsafe(s) is None:
+            return s
+
+        # use single quotes, and put single quotes into double quotes
+        # the string $'b is then quoted as '$'"'"'b'
+        return "'" + s.replace("'", "'\"'\"'") + "'"
+
 class pxssh (spawn):
     '''This class extends pexpect.spawn to specialize setting up SSH
     connections. This adds methods for login, logout, and expecting the shell
@@ -91,11 +109,16 @@
             username = raw_input('username: ')
             password = getpass.getpass('password: ')
             s.login (hostname, username, password)
+    
+    `debug_command_string` is only for the test suite to confirm that the 
string
+    generated for SSH is correct, using this will not allow you to do
+    anything other than get a string back from `pxssh.pxssh.login()`.
     '''
 
     def __init__ (self, timeout=30, maxread=2000, searchwindowsize=None,
                     logfile=None, cwd=None, env=None, ignore_sighup=True, 
echo=True,
-                    options={}, encoding=None, codec_errors='strict'):
+                    options={}, encoding=None, codec_errors='strict',
+                    debug_command_string=False):
 
         spawn.__init__(self, None, timeout=timeout, maxread=maxread,
                        searchwindowsize=searchwindowsize, logfile=logfile,
@@ -131,6 +154,8 @@
         # Unsetting SSH_ASKPASS on the remote side doesn't disable it! 
Annoying!
         #self.SSH_OPTS = "-x -o'RSAAuthentication=no' -o 
'PubkeyAuthentication=no'"
         self.force_password = False
+        
+        self.debug_command_string = debug_command_string
 
         # User defined SSH options, eg,
         # ssh.otions = 
dict(StrictHostKeyChecking="no",UserKnownHostsFile="/dev/null")
@@ -195,7 +220,7 @@
         can take 12 seconds. Low latency connections are more likely to fail
         with a low sync_multiplier. Best case sync time gets worse with a
         high sync multiplier (500 ms with default). '''
-
+        
         # All of these timing pace values are magic.
         # I came up with these based on what seemed reliable for
         # connecting to a heavily loaded machine I have.
@@ -227,10 +252,14 @@
 
     ### TODO: This is getting messy and I'm pretty sure this isn't perfect.
     ### TODO: I need to draw a flow chart for this.
+    ### TODO: Unit tests for SSH tunnels, remote SSH command exec, disabling 
original prompt sync
     def login (self, server, username, password='', terminal_type='ansi',
                 original_prompt=r"[#$]", login_timeout=10, port=None,
                 auto_prompt_reset=True, ssh_key=None, quiet=True,
-                sync_multiplier=1, check_local_ip=True):
+                sync_multiplier=1, check_local_ip=True,
+                password_regex=r'(?i)(?:password:)|(?:passphrase for key)',
+                ssh_tunnels={}, spawn_local_ssh=True,
+                sync_original_prompt=True):
         '''This logs the user into the given server.
 
         It uses
@@ -255,7 +284,24 @@
         uses a unique prompt in the :meth:`prompt` method. If the original 
prompt is
         not reset then this will disable the :meth:`prompt` method unless you
         manually set the :attr:`PROMPT` attribute.
+        
+        Set ``password_regex`` if there is a MOTD message with `password` in 
it.
+        Changing this is like playing in traffic, don't (p)expect it to match 
straight
+        away.
+        
+        If you require to connect to another SSH server from the your original 
SSH
+        connection set ``spawn_local_ssh`` to `False` and this will use your 
current
+        session to do so. Setting this option to `False` and not having an 
active session
+        will trigger an error.
+        
+        Set ``ssh_key`` to `True` to force passing the current SSH 
authentication socket to the
+        to the desired ``hostname``.
         '''
+        
+        session_regex_array = ["(?i)are you sure you want to continue 
connecting", original_prompt, password_regex, "(?i)permission denied", 
"(?i)terminal type", TIMEOUT]
+        session_init_regex_array = []
+        session_init_regex_array.extend(session_regex_array)
+        session_init_regex_array.extend(["(?i)connection closed by remote 
host", EOF])
 
         ssh_options = ''.join([" -o '%s=%s'" % (o, v) for (o, v) in 
self.options.items()])
         if quiet:
@@ -267,17 +313,52 @@
         if port is not None:
             ssh_options = ssh_options + ' -p %s'%(str(port))
         if ssh_key is not None:
-            try:
-                os.path.isfile(ssh_key)
-            except:
-                raise ExceptionPxssh('private ssh key does not exist')
-            ssh_options = ssh_options + ' -i %s' % (ssh_key)
+            # Allow forwarding our SSH key to the current session
+            if ssh_key==True:
+                ssh_options = ssh_options + ' -A'
+            else:
+                try:
+                    if spawn_local_ssh:
+                        os.path.isfile(ssh_key)
+                except:
+                    raise ExceptionPxssh('private ssh key does not exist')
+                ssh_options = ssh_options + ' -i %s' % (ssh_key)
+        
+        # SSH tunnels, make sure you know what you're putting into the lists
+        # under each heading. Do not expect these to open 100% of the time,
+        # The port you're requesting might be bound.
+        #
+        # The structure should be like this:
+        # { 'local': ['2424:localhost:22'],  # Local SSH tunnels
+        # 'remote': ['2525:localhost:22'],   # Remote SSH tunnels
+        # 'dynamic': [8888] } # Dynamic/SOCKS tunnels
+        if ssh_tunnels!={} and isinstance({},type(ssh_tunnels)):
+            tunnel_types = {
+                'local':'L',
+                'remote':'R',
+                'dynamic':'D'
+            }
+            for tunnel_type in tunnel_types:
+                cmd_type = tunnel_types[tunnel_type]
+                if tunnel_type in ssh_tunnels:
+                    tunnels = ssh_tunnels[tunnel_type]
+                    for tunnel in tunnels:
+                        if spawn_local_ssh==False:
+                            tunnel = quote(str(tunnel))
+                        ssh_options = ssh_options + ' -' + cmd_type + ' ' + 
str(tunnel)
         cmd = "ssh %s -l %s %s" % (ssh_options, username, server)
+        if self.debug_command_string:
+            return(cmd)
+
+        # Are we asking for a local ssh command or to spawn one in another 
session?
+        if spawn_local_ssh:
+            spawn._spawn(self, cmd)
+        else:
+            self.sendline(cmd)
 
         # This does not distinguish between a remote server 'password' prompt
         # and a local ssh 'passphrase' prompt (for unlocking a private key).
-        spawn._spawn(self, cmd)
-        i = self.expect(["(?i)are you sure you want to continue connecting", 
original_prompt, "(?i)(?:password:)|(?:passphrase for key)", "(?i)permission 
denied", "(?i)terminal type", TIMEOUT, "(?i)connection closed by remote host", 
EOF], timeout=login_timeout)
+        i = self.expect(session_init_regex_array, timeout=login_timeout)
 
         # First phase
         if i==0:
@@ -285,13 +366,13 @@
             # This is what you get if SSH does not have the remote host's
             # public key stored in the 'known_hosts' cache.
             self.sendline("yes")
-            i = self.expect(["(?i)are you sure you want to continue 
connecting", original_prompt, "(?i)(?:password:)|(?:passphrase for key)", 
"(?i)permission denied", "(?i)terminal type", TIMEOUT])
+            i = self.expect(session_regex_array)
         if i==2: # password or passphrase
             self.sendline(password)
-            i = self.expect(["(?i)are you sure you want to continue 
connecting", original_prompt, "(?i)(?:password:)|(?:passphrase for key)", 
"(?i)permission denied", "(?i)terminal type", TIMEOUT])
+            i = self.expect(session_regex_array)
         if i==4:
             self.sendline(terminal_type)
-            i = self.expect(["(?i)are you sure you want to continue 
connecting", original_prompt, "(?i)(?:password:)|(?:passphrase for key)", 
"(?i)permission denied", "(?i)terminal type", TIMEOUT])
+            i = self.expect(session_regex_array)
         if i==7:
             self.close()
             raise ExceptionPxssh('Could not establish connection to host')
@@ -331,9 +412,10 @@
         else: # Unexpected
             self.close()
             raise ExceptionPxssh('unexpected login response')
-        if not self.sync_original_prompt(sync_multiplier):
-            self.close()
-            raise ExceptionPxssh('could not synchronize with original prompt')
+        if sync_original_prompt:
+            if not self.sync_original_prompt(sync_multiplier):
+                self.close()
+                raise ExceptionPxssh('could not synchronize with original 
prompt')
         # We appear to be in.
         # set shell prompt to something unique.
         if auto_prompt_reset:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pexpect-4.4.0/pexpect/spawnbase.py 
new/pexpect-4.5.0/pexpect/spawnbase.py
--- old/pexpect-4.4.0/pexpect/spawnbase.py      2018-02-10 13:16:58.000000000 
+0100
+++ new/pexpect-4.5.0/pexpect/spawnbase.py      2018-04-11 21:03:57.000000000 
+0200
@@ -262,7 +262,7 @@
             # the input is 'foobar'
             index = p.expect(['foobar', 'foo'])
             # returns 0('foobar') if all input is available at once,
-            # but returs 1('foo') if parts of the final 'bar' arrive late
+            # but returns 1('foo') if parts of the final 'bar' arrive late
 
         When a match is found for the given pattern, the class instance
         attribute *match* becomes an re.MatchObject result.  Should an EOF
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pexpect-4.4.0/pexpect/utils.py 
new/pexpect-4.5.0/pexpect/utils.py
--- old/pexpect-4.4.0/pexpect/utils.py  2018-02-10 13:16:58.000000000 +0100
+++ new/pexpect-4.5.0/pexpect/utils.py  2018-04-13 19:35:52.000000000 +0200
@@ -154,3 +154,34 @@
                 # something else caused the select.error, so
                 # this actually is an exception.
                 raise
+
+
+def poll_ignore_interrupts(fds, timeout=None):
+    '''Simple wrapper around poll to register file descriptors and
+    ignore signals.'''
+
+    if timeout is not None:
+        end_time = time.time() + timeout
+
+    poller = select.poll()
+    for fd in fds:
+        poller.register(fd)
+
+    while True:
+        try:
+            timeout_ms = None if timeout is None else timeout * 1000
+            results = poller.poll(timeout_ms)
+            return [afd for afd, _ in results]
+        except InterruptedError:
+            err = sys.exc_info()[1]
+            if err.args[0] == errno.EINTR:
+                # if we loop back we have to subtract the
+                # amount of time we already waited.
+                if timeout is not None:
+                    timeout = end_time - time.time()
+                    if timeout < 0:
+                        return []
+            else:
+                # something else caused the select.error, so
+                # this actually is an exception.
+                raise
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pexpect-4.4.0/setup.cfg new/pexpect-4.5.0/setup.cfg
--- old/pexpect-4.4.0/setup.cfg 2016-05-21 13:43:33.000000000 +0200
+++ new/pexpect-4.5.0/setup.cfg 2018-04-11 21:03:57.000000000 +0200
@@ -1,4 +1,4 @@
-[pytest]
+[tool:pytest]
 norecursedirs = .git
 
 [bdist_wheel]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pexpect-4.4.0/setup.py new/pexpect-4.5.0/setup.py
--- old/pexpect-4.4.0/setup.py  2018-02-10 13:43:58.000000000 +0100
+++ new/pexpect-4.5.0/setup.py  2018-04-11 21:03:57.000000000 +0200
@@ -34,7 +34,7 @@
 Windows.
 """
 
-setup (name='pexpect',
+setup(name='pexpect',
     version=version,
     packages=['pexpect'],
     package_data={'pexpect': ['bashrc.sh']},
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pexpect-4.4.0/tests/log new/pexpect-4.5.0/tests/log
--- old/pexpect-4.4.0/tests/log 2016-08-21 22:02:45.000000000 +0200
+++ new/pexpect-4.5.0/tests/log 1970-01-01 01:00:00.000000000 +0100
@@ -1,20 +0,0 @@
-\,ESC
-P,ESC
-\,ESC
-P,ESC
-\,ESC
-P,ESC
-\,ESC
-P,ESC
-\,ESC
-r,SEMICOLON
-\,ESC
-P,ESC
-\,ESC
-P,ESC
-\,ESC
-P,ESC
-\,ESC
-P,ESC
-\,ESC
-r,SEMICOLON
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pexpect-4.4.0/tests/test_expect.py 
new/pexpect-4.5.0/tests/test_expect.py
--- old/pexpect-4.4.0/tests/test_expect.py      2018-02-10 13:16:58.000000000 
+0100
+++ new/pexpect-4.5.0/tests/test_expect.py      2018-04-11 21:03:57.000000000 
+0200
@@ -408,6 +408,17 @@
         p.buffer = b'Testing'
         p.sendeof ()
 
+    def test_before_across_chunks(self):
+        # https://github.com/pexpect/pexpect/issues/478
+        child = pexpect.spawn(
+            '''/bin/bash -c "openssl rand -base64 {} | head -500 | nl 
--number-format=rz --number-width=5 2>&1 ; echo 'PATTERN!!!'"'''.format(1024 * 
1024 * 2),
+            searchwindowsize=128
+        )
+        child.expect(['PATTERN'])
+        assert len(child.before.splitlines()) == 500
+        assert child.after == b'PATTERN'
+        assert child.buffer == b'!!!\r\n'
+
     def _before_after(self, p):
         p.timeout = 5
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pexpect-4.4.0/tests/test_misc.py 
new/pexpect-4.5.0/tests/test_misc.py
--- old/pexpect-4.4.0/tests/test_misc.py        2016-07-09 23:34:08.000000000 
+0200
+++ new/pexpect-4.5.0/tests/test_misc.py        2018-04-11 21:03:57.000000000 
+0200
@@ -52,6 +52,15 @@
             return 'skip'
         assert child.isatty()
 
+    def test_isatty_poll(self):
+        " Test isatty() is True after spawning process on most platforms. "
+        child = pexpect.spawn('cat', use_poll=True)
+        if not child.isatty() and sys.platform.lower().startswith('sunos'):
+            if hasattr(unittest, 'SkipTest'):
+                raise unittest.SkipTest("Not supported on this platform.")
+            return 'skip'
+        assert child.isatty()
+
     def test_read(self):
         " Test spawn.read by calls of various size. "
         child = pexpect.spawn('cat')
@@ -65,6 +74,19 @@
         remaining = child.read().replace(_CAT_EOF, b'')
         self.assertEqual(remaining, b'abc\r\n')
 
+    def test_read_poll(self):
+        " Test spawn.read by calls of various size. "
+        child = pexpect.spawn('cat', use_poll=True)
+        child.sendline("abc")
+        child.sendeof()
+        self.assertEqual(child.read(0), b'')
+        self.assertEqual(child.read(1), b'a')
+        self.assertEqual(child.read(1), b'b')
+        self.assertEqual(child.read(1), b'c')
+        self.assertEqual(child.read(2), b'\r\n')
+        remaining = child.read().replace(_CAT_EOF, b'')
+        self.assertEqual(remaining, b'abc\r\n')
+
     def test_readline_bin_echo(self):
         " Test spawn('echo'). "
         # given,
@@ -148,7 +170,7 @@
             p.sendline(b'alpha')
             p.expect(b'<out>alpha')
             assert p.isalive()
-        
+
         assert not p.isalive()
 
     def test_terminate(self):
@@ -271,7 +293,7 @@
         expected_output = '{0}:'.format(searcher.__name__)
         idx = 0
         for word in given_words:
-            expected_output += fmt.format(idx, '"{0}"'.format(word))
+            expected_output += fmt.format(idx, "'{0}'".format(word))
             idx += 1
         if plus is not None:
             if plus == pexpect.EOF:
@@ -343,4 +365,3 @@
     unittest.main()
 
 suite = unittest.makeSuite(TestCaseMisc,'test')
-
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pexpect-4.4.0/tests/test_pxssh.py 
new/pexpect-4.5.0/tests/test_pxssh.py
--- old/pexpect-4.4.0/tests/test_pxssh.py       2016-08-21 21:57:33.000000000 
+0200
+++ new/pexpect-4.5.0/tests/test_pxssh.py       2018-04-11 21:03:57.000000000 
+0200
@@ -57,6 +57,56 @@
         else:
             assert False, 'should have raised exception, pxssh.ExceptionPxssh'
 
+    def test_ssh_tunnel_string(self):
+        ssh = pxssh.pxssh(debug_command_string=True)
+        tunnels = { 'local': ['2424:localhost:22'],'remote': 
['2525:localhost:22'],
+            'dynamic': [8888] }
+        confirmation_strings = 0
+        confirmation_array = ['-R 2525:localhost:22','-L 
2424:localhost:22','-D 8888']
+        string = ssh.login('server', 'me', password='s3cret', 
ssh_tunnels=tunnels)
+        for confirmation in confirmation_array:
+            if confirmation in string:
+                confirmation_strings+=1
+
+        if confirmation_strings!=len(confirmation_array):
+            assert False, 'String generated from tunneling is incorrect.'
+
+    def test_remote_ssh_tunnel_string(self):
+        ssh = pxssh.pxssh(debug_command_string=True)
+        tunnels = { 'local': ['2424:localhost:22'],'remote': 
['2525:localhost:22'],
+            'dynamic': [8888] }
+        confirmation_strings = 0
+        confirmation_array = ['-R 2525:localhost:22','-L 
2424:localhost:22','-D 8888']
+        string = ssh.login('server', 'me', password='s3cret', 
ssh_tunnels=tunnels, spawn_local_ssh=False)
+        for confirmation in confirmation_array:
+            if confirmation in string:
+                confirmation_strings+=1
+
+        if confirmation_strings!=len(confirmation_array):
+            assert False, 'String generated from remote tunneling is 
incorrect.'
+
+    def test_ssh_key_string(self):
+        ssh = pxssh.pxssh(debug_command_string=True)
+        confirmation_strings = 0
+        confirmation_array = [' -A']
+        string = ssh.login('server', 'me', password='s3cret', ssh_key=True)
+        for confirmation in confirmation_array:
+            if confirmation in string:
+                confirmation_strings+=1
+
+        if confirmation_strings!=len(confirmation_array):
+            assert False, 'String generated from forcing the SSH agent sock is 
incorrect.'
+
+        confirmation_strings = 0
+        confirmation_array = [' -i True']
+        string = ssh.login('server', 'me', password='s3cret', ssh_key='True')
+        for confirmation in confirmation_array:
+            if confirmation in string:
+                confirmation_strings+=1
+        
+        if confirmation_strings!=len(confirmation_array):
+            assert False, 'String generated from adding an SSH key is 
incorrect.'
+
 
 if __name__ == '__main__':
     unittest.main()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pexpect-4.4.0/tests/test_socket.py 
new/pexpect-4.5.0/tests/test_socket.py
--- old/pexpect-4.4.0/tests/test_socket.py      2016-07-09 23:34:08.000000000 
+0200
+++ new/pexpect-4.5.0/tests/test_socket.py      2018-04-11 21:03:57.000000000 
+0200
@@ -225,7 +225,7 @@
         session.expect(pexpect.EOF)
         self.assertEqual(session.before, b'')
 
-    def test_fd_isalive (self):
+    def test_fd_isalive(self):
         sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
         sock.connect((self.host, self.port))
         session = fdpexpect.fdspawn(sock.fileno(), timeout=10)
@@ -233,13 +233,28 @@
         sock.close()
         assert not session.isalive(), "Should not be alive after close()"
 
-    def test_fd_isatty (self):
+    def test_fd_isalive_poll(self):
+        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        sock.connect((self.host, self.port))
+        session = fdpexpect.fdspawn(sock.fileno(), timeout=10, use_poll=True)
+        assert session.isalive()
+        sock.close()
+        assert not session.isalive(), "Should not be alive after close()"
+
+    def test_fd_isatty(self):
         sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
         sock.connect((self.host, self.port))
         session = fdpexpect.fdspawn(sock.fileno(), timeout=10)
         assert not session.isatty()
         session.close()
 
+    def test_fd_isatty_poll(self):
+        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        sock.connect((self.host, self.port))
+        session = fdpexpect.fdspawn(sock.fileno(), timeout=10, use_poll=True)
+        assert not session.isatty()
+        session.close()
+
     def test_fileobj(self):
         sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
         sock.connect((self.host, self.port))


Reply via email to