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