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