##// END OF EJS Templates
fix documentation and test of getoutput...
Julian Taylor -
Show More
@@ -1,195 +1,196 b''
1 """Common utilities for the various process_* implementations.
1 """Common utilities for the various process_* implementations.
2
2
3 This file is only meant to be imported by the platform-specific implementations
3 This file is only meant to be imported by the platform-specific implementations
4 of subprocess utilities, and it contains tools that are common to all of them.
4 of subprocess utilities, and it contains tools that are common to all of them.
5 """
5 """
6
6
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8 # Copyright (C) 2010-2011 The IPython Development Team
8 # Copyright (C) 2010-2011 The IPython Development Team
9 #
9 #
10 # Distributed under the terms of the BSD License. The full license is in
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
11 # the file COPYING, distributed as part of this software.
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 import subprocess
17 import subprocess
18 import shlex
18 import shlex
19 import sys
19 import sys
20
20
21 from IPython.utils import py3compat
21 from IPython.utils import py3compat
22
22
23 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
24 # Function definitions
24 # Function definitions
25 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
26
26
27 def read_no_interrupt(p):
27 def read_no_interrupt(p):
28 """Read from a pipe ignoring EINTR errors.
28 """Read from a pipe ignoring EINTR errors.
29
29
30 This is necessary because when reading from pipes with GUI event loops
30 This is necessary because when reading from pipes with GUI event loops
31 running in the background, often interrupts are raised that stop the
31 running in the background, often interrupts are raised that stop the
32 command from completing."""
32 command from completing."""
33 import errno
33 import errno
34
34
35 try:
35 try:
36 return p.read()
36 return p.read()
37 except IOError as err:
37 except IOError as err:
38 if err.errno != errno.EINTR:
38 if err.errno != errno.EINTR:
39 raise
39 raise
40
40
41
41
42 def process_handler(cmd, callback, stderr=subprocess.PIPE):
42 def process_handler(cmd, callback, stderr=subprocess.PIPE):
43 """Open a command in a shell subprocess and execute a callback.
43 """Open a command in a shell subprocess and execute a callback.
44
44
45 This function provides common scaffolding for creating subprocess.Popen()
45 This function provides common scaffolding for creating subprocess.Popen()
46 calls. It creates a Popen object and then calls the callback with it.
46 calls. It creates a Popen object and then calls the callback with it.
47
47
48 Parameters
48 Parameters
49 ----------
49 ----------
50 cmd : str
50 cmd : str
51 A string to be executed with the underlying system shell (by calling
51 A string to be executed with the underlying system shell (by calling
52 :func:`Popen` with ``shell=True``.
52 :func:`Popen` with ``shell=True``.
53
53
54 callback : callable
54 callback : callable
55 A one-argument function that will be called with the Popen object.
55 A one-argument function that will be called with the Popen object.
56
56
57 stderr : file descriptor number, optional
57 stderr : file descriptor number, optional
58 By default this is set to ``subprocess.PIPE``, but you can also pass the
58 By default this is set to ``subprocess.PIPE``, but you can also pass the
59 value ``subprocess.STDOUT`` to force the subprocess' stderr to go into
59 value ``subprocess.STDOUT`` to force the subprocess' stderr to go into
60 the same file descriptor as its stdout. This is useful to read stdout
60 the same file descriptor as its stdout. This is useful to read stdout
61 and stderr combined in the order they are generated.
61 and stderr combined in the order they are generated.
62
62
63 Returns
63 Returns
64 -------
64 -------
65 The return value of the provided callback is returned.
65 The return value of the provided callback is returned.
66 """
66 """
67 sys.stdout.flush()
67 sys.stdout.flush()
68 sys.stderr.flush()
68 sys.stderr.flush()
69 # On win32, close_fds can't be true when using pipes for stdin/out/err
69 # On win32, close_fds can't be true when using pipes for stdin/out/err
70 close_fds = sys.platform != 'win32'
70 close_fds = sys.platform != 'win32'
71 p = subprocess.Popen(cmd, shell=True,
71 p = subprocess.Popen(cmd, shell=True,
72 stdin=subprocess.PIPE,
72 stdin=subprocess.PIPE,
73 stdout=subprocess.PIPE,
73 stdout=subprocess.PIPE,
74 stderr=stderr,
74 stderr=stderr,
75 close_fds=close_fds)
75 close_fds=close_fds)
76
76
77 try:
77 try:
78 out = callback(p)
78 out = callback(p)
79 except KeyboardInterrupt:
79 except KeyboardInterrupt:
80 print('^C')
80 print('^C')
81 sys.stdout.flush()
81 sys.stdout.flush()
82 sys.stderr.flush()
82 sys.stderr.flush()
83 out = None
83 out = None
84 finally:
84 finally:
85 # Make really sure that we don't leave processes behind, in case the
85 # Make really sure that we don't leave processes behind, in case the
86 # call above raises an exception
86 # call above raises an exception
87 # We start by assuming the subprocess finished (to avoid NameErrors
87 # We start by assuming the subprocess finished (to avoid NameErrors
88 # later depending on the path taken)
88 # later depending on the path taken)
89 if p.returncode is None:
89 if p.returncode is None:
90 try:
90 try:
91 p.terminate()
91 p.terminate()
92 p.poll()
92 p.poll()
93 except OSError:
93 except OSError:
94 pass
94 pass
95 # One last try on our way out
95 # One last try on our way out
96 if p.returncode is None:
96 if p.returncode is None:
97 try:
97 try:
98 p.kill()
98 p.kill()
99 except OSError:
99 except OSError:
100 pass
100 pass
101
101
102 return out
102 return out
103
103
104
104
105 def getoutput(cmd):
105 def getoutput(cmd):
106 """Return standard output of executing cmd in a shell.
106 """Run a command and return its stdout/stderr as a string.
107
108 Accepts the same arguments as os.system().
109
107
110 Parameters
108 Parameters
111 ----------
109 ----------
112 cmd : str
110 cmd : str
113 A command to be executed in the system shell.
111 A command to be executed in the system shell.
114
112
115 Returns
113 Returns
116 -------
114 -------
117 stdout : str
115 output : str
116 A string containing the combination of stdout and stderr from the
117 subprocess, in whatever order the subprocess originally wrote to its
118 file descriptors (so the order of the information in this string is the
119 correct order as would be seen if running the command in a terminal).
118 """
120 """
119
120 out = process_handler(cmd, lambda p: p.communicate()[0], subprocess.STDOUT)
121 out = process_handler(cmd, lambda p: p.communicate()[0], subprocess.STDOUT)
121 if out is None:
122 if out is None:
122 return ''
123 return ''
123 return py3compat.bytes_to_str(out)
124 return py3compat.bytes_to_str(out)
124
125
125
126
126 def getoutputerror(cmd):
127 def getoutputerror(cmd):
127 """Return (standard output, standard error) of executing cmd in a shell.
128 """Return (standard output, standard error) of executing cmd in a shell.
128
129
129 Accepts the same arguments as os.system().
130 Accepts the same arguments as os.system().
130
131
131 Parameters
132 Parameters
132 ----------
133 ----------
133 cmd : str
134 cmd : str
134 A command to be executed in the system shell.
135 A command to be executed in the system shell.
135
136
136 Returns
137 Returns
137 -------
138 -------
138 stdout : str
139 stdout : str
139 stderr : str
140 stderr : str
140 """
141 """
141
142
142 out_err = process_handler(cmd, lambda p: p.communicate())
143 out_err = process_handler(cmd, lambda p: p.communicate())
143 if out_err is None:
144 if out_err is None:
144 return '', ''
145 return '', ''
145 out, err = out_err
146 out, err = out_err
146 return py3compat.bytes_to_str(out), py3compat.bytes_to_str(err)
147 return py3compat.bytes_to_str(out), py3compat.bytes_to_str(err)
147
148
148
149
149 def arg_split(s, posix=False, strict=True):
150 def arg_split(s, posix=False, strict=True):
150 """Split a command line's arguments in a shell-like manner.
151 """Split a command line's arguments in a shell-like manner.
151
152
152 This is a modified version of the standard library's shlex.split()
153 This is a modified version of the standard library's shlex.split()
153 function, but with a default of posix=False for splitting, so that quotes
154 function, but with a default of posix=False for splitting, so that quotes
154 in inputs are respected.
155 in inputs are respected.
155
156
156 if strict=False, then any errors shlex.split would raise will result in the
157 if strict=False, then any errors shlex.split would raise will result in the
157 unparsed remainder being the last element of the list, rather than raising.
158 unparsed remainder being the last element of the list, rather than raising.
158 This is because we sometimes use arg_split to parse things other than
159 This is because we sometimes use arg_split to parse things other than
159 command-line args.
160 command-line args.
160 """
161 """
161
162
162 # Unfortunately, python's shlex module is buggy with unicode input:
163 # Unfortunately, python's shlex module is buggy with unicode input:
163 # http://bugs.python.org/issue1170
164 # http://bugs.python.org/issue1170
164 # At least encoding the input when it's unicode seems to help, but there
165 # At least encoding the input when it's unicode seems to help, but there
165 # may be more problems lurking. Apparently this is fixed in python3.
166 # may be more problems lurking. Apparently this is fixed in python3.
166 is_unicode = False
167 is_unicode = False
167 if (not py3compat.PY3) and isinstance(s, unicode):
168 if (not py3compat.PY3) and isinstance(s, unicode):
168 is_unicode = True
169 is_unicode = True
169 s = s.encode('utf-8')
170 s = s.encode('utf-8')
170 lex = shlex.shlex(s, posix=posix)
171 lex = shlex.shlex(s, posix=posix)
171 lex.whitespace_split = True
172 lex.whitespace_split = True
172 # Extract tokens, ensuring that things like leaving open quotes
173 # Extract tokens, ensuring that things like leaving open quotes
173 # does not cause this to raise. This is important, because we
174 # does not cause this to raise. This is important, because we
174 # sometimes pass Python source through this (e.g. %timeit f(" ")),
175 # sometimes pass Python source through this (e.g. %timeit f(" ")),
175 # and it shouldn't raise an exception.
176 # and it shouldn't raise an exception.
176 # It may be a bad idea to parse things that are not command-line args
177 # It may be a bad idea to parse things that are not command-line args
177 # through this function, but we do, so let's be safe about it.
178 # through this function, but we do, so let's be safe about it.
178 lex.commenters='' #fix for GH-1269
179 lex.commenters='' #fix for GH-1269
179 tokens = []
180 tokens = []
180 while True:
181 while True:
181 try:
182 try:
182 tokens.append(next(lex))
183 tokens.append(next(lex))
183 except StopIteration:
184 except StopIteration:
184 break
185 break
185 except ValueError:
186 except ValueError:
186 if strict:
187 if strict:
187 raise
188 raise
188 # couldn't parse, get remaining blob as last token
189 # couldn't parse, get remaining blob as last token
189 tokens.append(lex.token)
190 tokens.append(lex.token)
190 break
191 break
191
192
192 if is_unicode:
193 if is_unicode:
193 # Convert the tokens back to unicode.
194 # Convert the tokens back to unicode.
194 tokens = [x.decode('utf-8') for x in tokens]
195 tokens = [x.decode('utf-8') for x in tokens]
195 return tokens
196 return tokens
@@ -1,131 +1,135 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 Tests for platutils.py
3 Tests for platutils.py
4 """
4 """
5
5
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7 # Copyright (C) 2008-2011 The IPython Development Team
7 # Copyright (C) 2008-2011 The IPython Development Team
8 #
8 #
9 # Distributed under the terms of the BSD License. The full license is in
9 # Distributed under the terms of the BSD License. The full license is in
10 # the file COPYING, distributed as part of this software.
10 # the file COPYING, distributed as part of this software.
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16
16
17 import sys
17 import sys
18 from unittest import TestCase
18 from unittest import TestCase
19
19
20 import nose.tools as nt
20 import nose.tools as nt
21
21
22 from IPython.utils.process import (find_cmd, FindCmdError, arg_split,
22 from IPython.utils.process import (find_cmd, FindCmdError, arg_split,
23 system, getoutput, getoutputerror)
23 system, getoutput, getoutputerror)
24 from IPython.testing import decorators as dec
24 from IPython.testing import decorators as dec
25 from IPython.testing import tools as tt
25 from IPython.testing import tools as tt
26
26
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28 # Tests
28 # Tests
29 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
30
30
31 def test_find_cmd_python():
31 def test_find_cmd_python():
32 """Make sure we find sys.exectable for python."""
32 """Make sure we find sys.exectable for python."""
33 nt.assert_equal(find_cmd('python'), sys.executable)
33 nt.assert_equal(find_cmd('python'), sys.executable)
34
34
35
35
36 @dec.skip_win32
36 @dec.skip_win32
37 def test_find_cmd_ls():
37 def test_find_cmd_ls():
38 """Make sure we can find the full path to ls."""
38 """Make sure we can find the full path to ls."""
39 path = find_cmd('ls')
39 path = find_cmd('ls')
40 nt.assert_true(path.endswith('ls'))
40 nt.assert_true(path.endswith('ls'))
41
41
42
42
43 def has_pywin32():
43 def has_pywin32():
44 try:
44 try:
45 import win32api
45 import win32api
46 except ImportError:
46 except ImportError:
47 return False
47 return False
48 return True
48 return True
49
49
50
50
51 @dec.onlyif(has_pywin32, "This test requires win32api to run")
51 @dec.onlyif(has_pywin32, "This test requires win32api to run")
52 def test_find_cmd_pythonw():
52 def test_find_cmd_pythonw():
53 """Try to find pythonw on Windows."""
53 """Try to find pythonw on Windows."""
54 path = find_cmd('pythonw')
54 path = find_cmd('pythonw')
55 nt.assert_true(path.endswith('pythonw.exe'))
55 nt.assert_true(path.endswith('pythonw.exe'))
56
56
57
57
58 @dec.onlyif(lambda : sys.platform != 'win32' or has_pywin32(),
58 @dec.onlyif(lambda : sys.platform != 'win32' or has_pywin32(),
59 "This test runs on posix or in win32 with win32api installed")
59 "This test runs on posix or in win32 with win32api installed")
60 def test_find_cmd_fail():
60 def test_find_cmd_fail():
61 """Make sure that FindCmdError is raised if we can't find the cmd."""
61 """Make sure that FindCmdError is raised if we can't find the cmd."""
62 nt.assert_raises(FindCmdError,find_cmd,'asdfasdf')
62 nt.assert_raises(FindCmdError,find_cmd,'asdfasdf')
63
63
64
64
65 @dec.skip_win32
65 @dec.skip_win32
66 def test_arg_split():
66 def test_arg_split():
67 """Ensure that argument lines are correctly split like in a shell."""
67 """Ensure that argument lines are correctly split like in a shell."""
68 tests = [['hi', ['hi']],
68 tests = [['hi', ['hi']],
69 [u'hi', [u'hi']],
69 [u'hi', [u'hi']],
70 ['hello there', ['hello', 'there']],
70 ['hello there', ['hello', 'there']],
71 # \u01ce == \N{LATIN SMALL LETTER A WITH CARON}
71 # \u01ce == \N{LATIN SMALL LETTER A WITH CARON}
72 # Do not use \N because the tests crash with syntax error in
72 # Do not use \N because the tests crash with syntax error in
73 # some cases, for example windows python2.6.
73 # some cases, for example windows python2.6.
74 [u'h\u01cello', [u'h\u01cello']],
74 [u'h\u01cello', [u'h\u01cello']],
75 ['something "with quotes"', ['something', '"with quotes"']],
75 ['something "with quotes"', ['something', '"with quotes"']],
76 ]
76 ]
77 for argstr, argv in tests:
77 for argstr, argv in tests:
78 nt.assert_equal(arg_split(argstr), argv)
78 nt.assert_equal(arg_split(argstr), argv)
79
79
80 @dec.skip_if_not_win32
80 @dec.skip_if_not_win32
81 def test_arg_split_win32():
81 def test_arg_split_win32():
82 """Ensure that argument lines are correctly split like in a shell."""
82 """Ensure that argument lines are correctly split like in a shell."""
83 tests = [['hi', ['hi']],
83 tests = [['hi', ['hi']],
84 [u'hi', [u'hi']],
84 [u'hi', [u'hi']],
85 ['hello there', ['hello', 'there']],
85 ['hello there', ['hello', 'there']],
86 [u'h\u01cello', [u'h\u01cello']],
86 [u'h\u01cello', [u'h\u01cello']],
87 ['something "with quotes"', ['something', 'with quotes']],
87 ['something "with quotes"', ['something', 'with quotes']],
88 ]
88 ]
89 for argstr, argv in tests:
89 for argstr, argv in tests:
90 nt.assert_equal(arg_split(argstr), argv)
90 nt.assert_equal(arg_split(argstr), argv)
91
91
92
92
93 class SubProcessTestCase(TestCase, tt.TempFileMixin):
93 class SubProcessTestCase(TestCase, tt.TempFileMixin):
94 def setUp(self):
94 def setUp(self):
95 """Make a valid python temp file."""
95 """Make a valid python temp file."""
96 lines = ["from __future__ import print_function",
96 lines = ["from __future__ import print_function",
97 "import sys",
97 "import sys",
98 "print('on stdout', end='', file=sys.stdout)",
98 "print('on stdout', end='', file=sys.stdout)",
99 "print('on stderr', end='', file=sys.stderr)",
99 "print('on stderr', end='', file=sys.stderr)",
100 "sys.stdout.flush()",
100 "sys.stdout.flush()",
101 "sys.stderr.flush()"]
101 "sys.stderr.flush()"]
102 self.mktmp('\n'.join(lines))
102 self.mktmp('\n'.join(lines))
103
103
104 def test_system(self):
104 def test_system(self):
105 status = system('python "%s"' % self.fname)
105 status = system('python "%s"' % self.fname)
106 self.assertEqual(status, 0)
106 self.assertEqual(status, 0)
107
107
108 def test_system_quotes(self):
108 def test_system_quotes(self):
109 status = system('python -c "import sys"')
109 status = system('python -c "import sys"')
110 self.assertEqual(status, 0)
110 self.assertEqual(status, 0)
111
111
112 def test_getoutput(self):
112 def test_getoutput(self):
113 out = getoutput('python "%s"' % self.fname)
113 out = getoutput('python "%s"' % self.fname)
114 self.assertEqual(out, 'on stdout')
114 # we can't rely on the order the line buffered streams are flushed
115 try:
116 self.assertEqual(out, 'on stderron stdout')
117 except AssertionError:
118 self.assertEqual(out, 'on stdouton stderr')
115
119
116 def test_getoutput_quoted(self):
120 def test_getoutput_quoted(self):
117 out = getoutput('python -c "print (1)"')
121 out = getoutput('python -c "print (1)"')
118 self.assertEqual(out.strip(), '1')
122 self.assertEqual(out.strip(), '1')
119
123
120 #Invalid quoting on windows
124 #Invalid quoting on windows
121 @dec.skip_win32
125 @dec.skip_win32
122 def test_getoutput_quoted2(self):
126 def test_getoutput_quoted2(self):
123 out = getoutput("python -c 'print (1)'")
127 out = getoutput("python -c 'print (1)'")
124 self.assertEqual(out.strip(), '1')
128 self.assertEqual(out.strip(), '1')
125 out = getoutput("python -c 'print (\"1\")'")
129 out = getoutput("python -c 'print (\"1\")'")
126 self.assertEqual(out.strip(), '1')
130 self.assertEqual(out.strip(), '1')
127
131
128 def test_getoutput(self):
132 def test_getoutput_error(self):
129 out, err = getoutputerror('python "%s"' % self.fname)
133 out, err = getoutputerror('python "%s"' % self.fname)
130 self.assertEqual(out, 'on stdout')
134 self.assertEqual(out, 'on stdout')
131 self.assertEqual(err, 'on stderr')
135 self.assertEqual(err, 'on stderr')
General Comments 0
You need to be logged in to leave comments. Login now