From 37665f9add48760c1d4ad8c1909da4d8645e3146 Mon Sep 17 00:00:00 2001 From: Samuel Bancal Date: Thu, 12 Feb 2015 15:23:02 +0100 Subject: Allows also method callback for events argument in pexpect.run() Updated test_run.py for this case --- pexpect/__init__.py | 3 ++- tests/test_run.py | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/pexpect/__init__.py b/pexpect/__init__.py index c906e89..2bed4e6 100644 --- a/pexpect/__init__.py +++ b/pexpect/__init__.py @@ -206,7 +206,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): diff --git a/tests/test_run.py b/tests/test_run.py index c018b4d..286bf69 100755 --- a/tests/test_run.py +++ b/tests/test_run.py @@ -35,6 +35,20 @@ def timeout_callback (d): return 1 return 0 +def function_events_callback (d): + try: + previous_echoed = d["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}".format(previous_echoed)) + except IndexError: + return "echo foo1\n" + class RunFuncTestCase(PexpectTestCase.PexpectTestCase): runfunc = staticmethod(pexpect.run) cr = b'\r' @@ -88,6 +102,44 @@ class RunFuncTestCase(PexpectTestCase.PexpectTestCase): timeout=10) assert exitstatus == 0 + def test_run_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_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 method_events_callback (self, d): + try: + previous_echoed = d["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}".format(previous_echoed)) + except IndexError: + return "echo foo1\n" + class RunUnicodeFuncTestCase(RunFuncTestCase): runfunc = staticmethod(pexpect.runu) cr = b'\r'.decode('ascii') -- cgit v1.2.1 From 2fd1648c87c5fbeef5adfec262cb7bad34d0ba5c Mon Sep 17 00:00:00 2001 From: Samuel Bancal Date: Thu, 12 Feb 2015 15:45:37 +0100 Subject: Updated Error message to mention MethodType --- pexpect/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pexpect/__init__.py b/pexpect/__init__.py index 2bed4e6..8346b5f 100644 --- a/pexpect/__init__.py +++ b/pexpect/__init__.py @@ -215,7 +215,7 @@ 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('The callback must be a string, function or method.') event_count = event_count + 1 except TIMEOUT: child_result_list.append(child.before) -- cgit v1.2.1 From 983798b20960aef3cfc25bbe8fd965516bcfac0b Mon Sep 17 00:00:00 2001 From: Jeff Quast Date: Sat, 14 Feb 2015 16:44:58 -0800 Subject: Begin __version__ "4.0.dev" as suggested by @takluyver in https://github.com/pexpect/pexpect/issues/174 --- pexpect/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pexpect/__init__.py b/pexpect/__init__.py index 8346b5f..163bfae 100644 --- a/pexpect/__init__.py +++ b/pexpect/__init__.py @@ -71,7 +71,7 @@ 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__'] -- cgit v1.2.1 From 5c56db7fa390af992e2055cb66d7b06faeba7ea8 Mon Sep 17 00:00:00 2001 From: Jeff Quast Date: Sat, 14 Feb 2015 16:46:10 -0800 Subject: PEP8: do not use backslash continuation char '\' "Continuation lines should align ... using Python's implicit line joining inside parentheses" --- pexpect/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pexpect/__init__.py b/pexpect/__init__.py index 163bfae..3c25e21 100644 --- a/pexpect/__init__.py +++ b/pexpect/__init__.py @@ -206,8 +206,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) or \ - isinstance(responses[index], types.MethodType): + 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): -- cgit v1.2.1 From 38518ff6f3a6895884dc190c12ab57efd3884f67 Mon Sep 17 00:00:00 2001 From: Jeff Quast Date: Sat, 14 Feb 2015 16:47:26 -0800 Subject: Display well-formed TypeError for events callback --- pexpect/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pexpect/__init__.py b/pexpect/__init__.py index 3c25e21..5a77f38 100644 --- a/pexpect/__init__.py +++ b/pexpect/__init__.py @@ -215,7 +215,9 @@ def _run(command, timeout, withexitstatus, events, extra_args, logfile, cwd, elif callback_result: break else: - raise TypeError('The callback must be a string, function or method.') + 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) -- cgit v1.2.1 From 242f7d07ec41ee61fbc60508743866344b106f3b Mon Sep 17 00:00:00 2001 From: Jeff Quast Date: Sat, 14 Feb 2015 16:47:44 -0800 Subject: Document that a method may also be used. --- pexpect/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pexpect/__init__.py b/pexpect/__init__.py index 5a77f38..4b153f4 100644 --- a/pexpect/__init__.py +++ b/pexpect/__init__.py @@ -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 -- cgit v1.2.1 From 35de00ce8da686baf560fc9be58e539b3fad8421 Mon Sep 17 00:00:00 2001 From: Jeff Quast Date: Sat, 14 Feb 2015 17:00:09 -0800 Subject: pep8 and cleanup test_run.py 79-line columns, space before '(' in function definition, once line between methods, two lines between classes and functions, No one-character variables. --- tests/test_run.py | 76 ++++++++++++++++++++++++++++++++----------------------- 1 file changed, 45 insertions(+), 31 deletions(-) diff --git a/tests/test_run.py b/tests/test_run.py index 286bf69..da6531e 100755 --- a/tests/test_run.py +++ b/tests/test_run.py @@ -29,25 +29,28 @@ 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 (d): + +def function_events_callback(values): try: - previous_echoed = d["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"): + 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 foo1\n" + return "echo stage-1\n" + class RunFuncTestCase(PexpectTestCase.PexpectTestCase): runfunc = staticmethod(pexpect.run) @@ -65,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_tuple_list(self): events = [ # second match on 'abc', echo 'def' ('abc\r\n.*GO:', 'echo "def"\n'), @@ -102,33 +112,34 @@ class RunFuncTestCase(PexpectTestCase.PexpectTestCase): timeout=10) assert exitstatus == 0 - def test_run_function (self): + def test_run_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_method (self): + + def test_run_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 method_events_callback (self, d): + + def method_events_callback(self, d): try: - previous_echoed = d["child_result_list"][-1].decode().split("\n")[-2].strip() + previous_echoed = (d["child_result_list"][-1].decode() + .split("\n")[-2].strip()) if previous_echoed.endswith("foo1"): return "echo foo2\n" elif previous_echoed.endswith("foo2"): @@ -136,15 +147,18 @@ class RunFuncTestCase(PexpectTestCase.PexpectTestCase): elif previous_echoed.endswith("foo3"): return "exit\n" else: - raise Exception("Unexpected output {0}".format(previous_echoed)) + 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) # þ -- cgit v1.2.1 From caca6e2d3a4562e365187a5b687bb973025a9895 Mon Sep 17 00:00:00 2001 From: Jeff Quast Date: Sat, 14 Feb 2015 17:45:38 -0800 Subject: rename 'test_run_X' as 'test_run_event_as_X" --- tests/test_run.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_run.py b/tests/test_run.py index da6531e..28c3945 100755 --- a/tests/test_run.py +++ b/tests/test_run.py @@ -95,7 +95,7 @@ class RunFuncTestCase(PexpectTestCase.PexpectTestCase): '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'), @@ -112,7 +112,7 @@ class RunFuncTestCase(PexpectTestCase.PexpectTestCase): timeout=10) assert exitstatus == 0 - def test_run_function(self): + def test_run_event_as_function(self): events = [ ('GO:', function_events_callback) ] @@ -124,9 +124,9 @@ class RunFuncTestCase(PexpectTestCase.PexpectTestCase): timeout=10) assert exitstatus == 0 - def test_run_method(self): + def test_run_event_as_method(self): events = [ - ('GO:', self.method_events_callback) + ('GO:', self._method_events_callback) ] (data, exitstatus) = pexpect.run( -- cgit v1.2.1 From e13d4a786d1c012839c0aaf97303d61c113ce10f Mon Sep 17 00:00:00 2001 From: Jeff Quast Date: Sat, 14 Feb 2015 17:45:52 -0800 Subject: Negative test: ensure TypeError is raised. --- tests/test_run.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/test_run.py b/tests/test_run.py index 28c3945..b736ec4 100755 --- a/tests/test_run.py +++ b/tests/test_run.py @@ -136,7 +136,15 @@ class RunFuncTestCase(PexpectTestCase.PexpectTestCase): timeout=10) assert exitstatus == 0 - def method_events_callback(self, d): + 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, d): try: previous_echoed = (d["child_result_list"][-1].decode() .split("\n")[-2].strip()) -- cgit v1.2.1 From 301e1d8a00819c01ba9ecb70fc533619f4907ca4 Mon Sep 17 00:00:00 2001 From: Jeff Quast Date: Sat, 14 Feb 2015 17:49:17 -0800 Subject: style: remove more 1-character variables --- tests/test_run.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/test_run.py b/tests/test_run.py index b736ec4..1b3c92f 100755 --- a/tests/test_run.py +++ b/tests/test_run.py @@ -144,9 +144,9 @@ class RunFuncTestCase(PexpectTestCase.PexpectTestCase): events=events, timeout=10) - def _method_events_callback(self, d): + def _method_events_callback(self, values): try: - previous_echoed = (d["child_result_list"][-1].decode() + previous_echoed = (values["child_result_list"][-1].decode() .split("\n")[-2].strip()) if previous_echoed.endswith("foo1"): return "echo foo2\n" @@ -169,23 +169,23 @@ class RunUnicodeFuncTestCase(RunFuncTestCase): def test_run_unicode(self): if pexpect.PY3: - c = chr(254) # þ + char = chr(254) # þ pattern = '' else: - c = unichr(254) # analysis:ignore + char = unichr(254) # analysis:ignore pattern = ''.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 ''+c in output, output + assert ('' + char) in output, output if __name__ == '__main__': unittest.main() -- cgit v1.2.1 From df3e476c71c8a10d84788b03c79b4167ce6b778b Mon Sep 17 00:00:00 2001 From: Jeff Quast Date: Sat, 14 Feb 2015 17:51:51 -0800 Subject: Document method-callback enhancement under 4.0 --- doc/history.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/history.rst b/doc/history.rst index c9d5640..95bf371 100644 --- a/doc/history.rst +++ b/doc/history.rst @@ -12,6 +12,8 @@ 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`). Version 3.4 ``````````` -- cgit v1.2.1 From 108c476e150a682667a10f4f0864060a55e07a1a Mon Sep 17 00:00:00 2001 From: Nitin Madhok Date: Fri, 27 Mar 2015 03:58:54 -0400 Subject: Fix syntax highlighting for License in README --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 `_ -PEXPECT LICENSE +PEXPECT LICENSE:: http://opensource.org/licenses/isc-license.txt -- cgit v1.2.1 From 8109c6b5109db53b5c9c55b4e6d803ef97b9278a Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Tue, 31 Mar 2015 16:38:53 -0700 Subject: Fix async expect when data was already read Closes gh-195 --- pexpect/async.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pexpect/async.py b/pexpect/async.py index 50eae3b..99a0b28 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()\ -- cgit v1.2.1 From 73aff2be3495b094d6b25e962f4636f83c873f65 Mon Sep 17 00:00:00 2001 From: Everardo Padilla Saca Date: Mon, 20 Apr 2015 23:47:47 +0300 Subject: Escape quote in print() and fix indexes --- doc/overview.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/overview.rst b/doc/overview.rst index a04e389..76fc846 100644 --- a/doc/overview.rst +++ b/doc/overview.rst @@ -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) -- cgit v1.2.1 From 05248aa57643d618bcd5e074bb159a309b0f1eb3 Mon Sep 17 00:00:00 2001 From: Jeff Quast Date: Fri, 24 Apr 2015 20:55:07 -0700 Subject: Always re-create virtualenv. In the case of osx.pexpect.org, a 'brew upgrade' was run, which updated the system python and screwed up re-using the existing --- tools/teamcity-runtests.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/teamcity-runtests.sh b/tools/teamcity-runtests.sh index b74f179..be0b5b6 100755 --- a/tools/teamcity-runtests.sh +++ b/tools/teamcity-runtests.sh @@ -25,7 +25,8 @@ 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} # install ptyprocess cd $here/../../ptyprocess -- cgit v1.2.1