##// END OF EJS Templates
fix arguments for commands in _process_posix...
MinRK -
Show More
@@ -1,193 +1,190 b''
1 1 """Posix-specific implementation of process utilities.
2 2
3 3 This file is only meant to be imported by process.py, not by end-users.
4 4 """
5 5
6 6 #-----------------------------------------------------------------------------
7 7 # Copyright (C) 2010 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 from __future__ import print_function
17 17
18 18 # Stdlib
19 19 import subprocess as sp
20 20 import sys
21 21
22 22 from IPython.external import pexpect
23 23
24 24 # Our own
25 25 from .autoattr import auto_attr
26 26 from ._process_common import getoutput
27 27 from IPython.utils import text
28 28 from IPython.utils import py3compat
29 29
30 30 #-----------------------------------------------------------------------------
31 31 # Function definitions
32 32 #-----------------------------------------------------------------------------
33 33
34 34 def _find_cmd(cmd):
35 35 """Find the full path to a command using which."""
36 36
37 37 path = sp.Popen(['/usr/bin/env', 'which', cmd],
38 38 stdout=sp.PIPE).communicate()[0]
39 39 return py3compat.bytes_to_str(path)
40 40
41 41
42 42 class ProcessHandler(object):
43 43 """Execute subprocesses under the control of pexpect.
44 44 """
45 45 # Timeout in seconds to wait on each reading of the subprocess' output.
46 46 # This should not be set too low to avoid cpu overusage from our side,
47 47 # since we read in a loop whose period is controlled by this timeout.
48 48 read_timeout = 0.05
49 49
50 50 # Timeout to give a process if we receive SIGINT, between sending the
51 51 # SIGINT to the process and forcefully terminating it.
52 52 terminate_timeout = 0.2
53 53
54 54 # File object where stdout and stderr of the subprocess will be written
55 55 logfile = None
56 56
57 57 # Shell to call for subprocesses to execute
58 58 sh = None
59 59
60 60 @auto_attr
61 61 def sh(self):
62 62 sh = pexpect.which('sh')
63 63 if sh is None:
64 64 raise OSError('"sh" shell not found')
65 65 return sh
66 66
67 67 def __init__(self, logfile=None, read_timeout=None, terminate_timeout=None):
68 68 """Arguments are used for pexpect calls."""
69 69 self.read_timeout = (ProcessHandler.read_timeout if read_timeout is
70 70 None else read_timeout)
71 71 self.terminate_timeout = (ProcessHandler.terminate_timeout if
72 72 terminate_timeout is None else
73 73 terminate_timeout)
74 74 self.logfile = sys.stdout if logfile is None else logfile
75 75
76 76 def getoutput(self, cmd):
77 77 """Run a command and return its stdout/stderr as a string.
78 78
79 79 Parameters
80 80 ----------
81 81 cmd : str
82 82 A command to be executed in the system shell.
83 83
84 84 Returns
85 85 -------
86 86 output : str
87 87 A string containing the combination of stdout and stderr from the
88 88 subprocess, in whatever order the subprocess originally wrote to its
89 89 file descriptors (so the order of the information in this string is the
90 90 correct order as would be seen if running the command in a terminal).
91 91 """
92 pcmd = self._make_cmd(cmd)
93 92 try:
94 return pexpect.run(pcmd).replace('\r\n', '\n')
93 return pexpect.run(self.sh, args=['-c', cmd]).replace('\r\n', '\n')
95 94 except KeyboardInterrupt:
96 95 print('^C', file=sys.stderr, end='')
97 96
98 97 def getoutput_pexpect(self, cmd):
99 98 """Run a command and return its stdout/stderr as a string.
100 99
101 100 Parameters
102 101 ----------
103 102 cmd : str
104 103 A command to be executed in the system shell.
105 104
106 105 Returns
107 106 -------
108 107 output : str
109 108 A string containing the combination of stdout and stderr from the
110 109 subprocess, in whatever order the subprocess originally wrote to its
111 110 file descriptors (so the order of the information in this string is the
112 111 correct order as would be seen if running the command in a terminal).
113 112 """
114 pcmd = self._make_cmd(cmd)
115 113 try:
116 return pexpect.run(pcmd).replace('\r\n', '\n')
114 return pexpect.run(self.sh, args=['-c', cmd]).replace('\r\n', '\n')
117 115 except KeyboardInterrupt:
118 116 print('^C', file=sys.stderr, end='')
119 117
120 118 def system(self, cmd):
121 119 """Execute a command in a subshell.
122 120
123 121 Parameters
124 122 ----------
125 123 cmd : str
126 124 A command to be executed in the system shell.
127 125
128 126 Returns
129 127 -------
130 128 int : child's exitstatus
131 129 """
132 130 # Get likely encoding for the output.
133 131 enc = text.getdefaultencoding()
134 132
135 pcmd = self._make_cmd(cmd)
136 133 # Patterns to match on the output, for pexpect. We read input and
137 134 # allow either a short timeout or EOF
138 135 patterns = [pexpect.TIMEOUT, pexpect.EOF]
139 136 # the index of the EOF pattern in the list.
140 EOF_index = 1 # Fix this index if you change the list!!
137 # even though we know it's 1, this call means we don't have to worry if
138 # we change the above list, and forget to change this value:
139 EOF_index = patterns.index(pexpect.EOF)
141 140 # The size of the output stored so far in the process output buffer.
142 141 # Since pexpect only appends to this buffer, each time we print we
143 142 # record how far we've printed, so that next time we only print *new*
144 143 # content from the buffer.
145 144 out_size = 0
146 145 try:
147 146 # Since we're not really searching the buffer for text patterns, we
148 147 # can set pexpect's search window to be tiny and it won't matter.
149 148 # We only search for the 'patterns' timeout or EOF, which aren't in
150 149 # the text itself.
151 #child = pexpect.spawn(pcmd, searchwindowsize=1)
152 child = pexpect.spawn(pcmd)
150 child = pexpect.spawn(self.sh, args=['-c', cmd])
153 151 flush = sys.stdout.flush
154 152 while True:
155 153 # res is the index of the pattern that caused the match, so we
156 154 # know whether we've finished (if we matched EOF) or not
157 155 res_idx = child.expect_list(patterns, self.read_timeout)
158 156 print(child.before[out_size:].decode(enc, 'replace'), end='')
159 157 flush()
160 158 if res_idx==EOF_index:
161 159 break
162 160 # Update the pointer to what we've already printed
163 161 out_size = len(child.before)
164 162 except KeyboardInterrupt:
165 163 # We need to send ^C to the process. The ascii code for '^C' is 3
166 164 # (the character is known as ETX for 'End of Text', see
167 165 # curses.ascii.ETX).
168 166 child.sendline(chr(3))
169 167 # Read and print any more output the program might produce on its
170 168 # way out.
171 169 try:
172 170 out_size = len(child.before)
173 171 child.expect_list(patterns, self.terminate_timeout)
174 172 print(child.before[out_size:].decode(enc, 'replace'), end='')
175 173 sys.stdout.flush()
176 174 except KeyboardInterrupt:
177 175 # Impatient users tend to type it multiple times
178 176 pass
179 177 finally:
180 178 # Ensure the subprocess really is terminated
181 179 child.terminate(force=True)
180 # add isalive check, to ensure exitstatus is set:
181 child.isalive()
182 182 return child.exitstatus
183 183
184 def _make_cmd(self, cmd):
185 return '%s -c "%s"' % (self.sh, cmd)
186
187 184
188 185 # Make system() with a functional interface for outside use. Note that we use
189 186 # getoutput() from the _common utils, which is built on top of popen(). Using
190 187 # pexpect to get subprocess output produces difficult to parse output, since
191 188 # programs think they are talking to a tty and produce highly formatted output
192 189 # (ls is a good example) that makes them hard.
193 190 system = ProcessHandler().system
@@ -1,98 +1,111 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-2009 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_equals(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 def test_arg_split():
66 66 """Ensure that argument lines are correctly split like in a shell."""
67 67 tests = [['hi', ['hi']],
68 68 [u'hi', [u'hi']],
69 69 ['hello there', ['hello', 'there']],
70 70 [u'h\N{LATIN SMALL LETTER A WITH CARON}llo', [u'h\N{LATIN SMALL LETTER A WITH CARON}llo']],
71 71 ['something "with quotes"', ['something', '"with quotes"']],
72 72 ]
73 73 for argstr, argv in tests:
74 74 nt.assert_equal(arg_split(argstr), argv)
75 75
76 76
77 77 class SubProcessTestCase(TestCase, tt.TempFileMixin):
78 78 def setUp(self):
79 79 """Make a valid python temp file."""
80 80 lines = ["from __future__ import print_function",
81 81 "import sys",
82 82 "print('on stdout', end='', file=sys.stdout)",
83 83 "print('on stderr', end='', file=sys.stderr)",
84 84 "sys.stdout.flush()",
85 85 "sys.stderr.flush()"]
86 86 self.mktmp('\n'.join(lines))
87 87
88 88 def test_system(self):
89 system('python "%s"' % self.fname)
89 status = system('python "%s"' % self.fname)
90 self.assertEquals(status, 0)
91
92 def test_system_quotes(self):
93 status = system('python -c "import sys"')
94 self.assertEquals(status, 0)
90 95
91 96 def test_getoutput(self):
92 97 out = getoutput('python "%s"' % self.fname)
93 98 self.assertEquals(out, 'on stdout')
94 99
100 def test_getoutput_quoted(self):
101 out = getoutput('python -c "print 1"')
102 self.assertEquals(out.strip(), '1')
103 out = getoutput("python -c 'print 1'")
104 self.assertEquals(out.strip(), '1')
105 out = getoutput("python -c 'print \"1\"'")
106 self.assertEquals(out.strip(), '1')
107
95 108 def test_getoutput(self):
96 109 out, err = getoutputerror('python "%s"' % self.fname)
97 110 self.assertEquals(out, 'on stdout')
98 111 self.assertEquals(err, 'on stderr')
General Comments 0
You need to be logged in to leave comments. Login now