##// END OF EJS Templates
Fix iptest under windows vista.
gvaroquaux -
Show More
@@ -1,182 +1,177 b''
1 # Addapted from killableprocess.py.
1 # Addapted from killableprocess.py.
2 #______________________________________________________________________________
2 #______________________________________________________________________________
3 #
3 #
4 # killableprocess - subprocesses which can be reliably killed
4 # killableprocess - subprocesses which can be reliably killed
5 #
5 #
6 # Parts of this module are copied from the subprocess.py file contained
6 # Parts of this module are copied from the subprocess.py file contained
7 # in the Python distribution.
7 # in the Python distribution.
8 #
8 #
9 # Copyright (c) 2003-2004 by Peter Astrand <astrand@lysator.liu.se>
9 # Copyright (c) 2003-2004 by Peter Astrand <astrand@lysator.liu.se>
10 #
10 #
11 # Additions and modifications written by Benjamin Smedberg
11 # Additions and modifications written by Benjamin Smedberg
12 # <benjamin@smedbergs.us> are Copyright (c) 2006 by the Mozilla Foundation
12 # <benjamin@smedbergs.us> are Copyright (c) 2006 by the Mozilla Foundation
13 # <http://www.mozilla.org/>
13 # <http://www.mozilla.org/>
14 #
14 #
15 # By obtaining, using, and/or copying this software and/or its
15 # By obtaining, using, and/or copying this software and/or its
16 # associated documentation, you agree that you have read, understood,
16 # associated documentation, you agree that you have read, understood,
17 # and will comply with the following terms and conditions:
17 # and will comply with the following terms and conditions:
18 #
18 #
19 # Permission to use, copy, modify, and distribute this software and
19 # Permission to use, copy, modify, and distribute this software and
20 # its associated documentation for any purpose and without fee is
20 # its associated documentation for any purpose and without fee is
21 # hereby granted, provided that the above copyright notice appears in
21 # hereby granted, provided that the above copyright notice appears in
22 # all copies, and that both that copyright notice and this permission
22 # all copies, and that both that copyright notice and this permission
23 # notice appear in supporting documentation, and that the name of the
23 # notice appear in supporting documentation, and that the name of the
24 # author not be used in advertising or publicity pertaining to
24 # author not be used in advertising or publicity pertaining to
25 # distribution of the software without specific, written prior
25 # distribution of the software without specific, written prior
26 # permission.
26 # permission.
27 #
27 #
28 # THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
28 # THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
29 # INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
29 # INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
30 # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR
30 # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR
31 # CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
31 # CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
32 # OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
32 # OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
33 # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
33 # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
34 # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
34 # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
35
35
36 r"""killableprocess - Subprocesses which can be reliably killed
36 r"""killableprocess - Subprocesses which can be reliably killed
37
37
38 This module is a subclass of the builtin "subprocess" module. It allows
38 This module is a subclass of the builtin "subprocess" module. It allows
39 processes that launch subprocesses to be reliably killed on Windows (via the Popen.kill() method.
39 processes that launch subprocesses to be reliably killed on Windows (via the Popen.kill() method.
40
40
41 It also adds a timeout argument to Wait() for a limited period of time before
41 It also adds a timeout argument to Wait() for a limited period of time before
42 forcefully killing the process.
42 forcefully killing the process.
43
43
44 Note: On Windows, this module requires Windows 2000 or higher (no support for
44 Note: On Windows, this module requires Windows 2000 or higher (no support for
45 Windows 95, 98, or NT 4.0). It also requires ctypes, which is bundled with
45 Windows 95, 98, or NT 4.0). It also requires ctypes, which is bundled with
46 Python 2.5+ or available from http://python.net/crew/theller/ctypes/
46 Python 2.5+ or available from http://python.net/crew/theller/ctypes/
47 """
47 """
48
48
49 import subprocess
49 import subprocess
50 from subprocess import PIPE
50 from subprocess import PIPE
51 import sys
51 import sys
52 import os
52 import os
53 import types
53 import types
54
54
55 try:
55 try:
56 from subprocess import CalledProcessError
56 from subprocess import CalledProcessError
57 except ImportError:
57 except ImportError:
58 # Python 2.4 doesn't implement CalledProcessError
58 # Python 2.4 doesn't implement CalledProcessError
59 class CalledProcessError(Exception):
59 class CalledProcessError(Exception):
60 """This exception is raised when a process run by check_call() returns
60 """This exception is raised when a process run by check_call() returns
61 a non-zero exit status. The exit status will be stored in the
61 a non-zero exit status. The exit status will be stored in the
62 returncode attribute."""
62 returncode attribute."""
63 def __init__(self, returncode, cmd):
63 def __init__(self, returncode, cmd):
64 self.returncode = returncode
64 self.returncode = returncode
65 self.cmd = cmd
65 self.cmd = cmd
66 def __str__(self):
66 def __str__(self):
67 return "Command '%s' returned non-zero exit status %d" % (self.cmd, self.returncode)
67 return "Command '%s' returned non-zero exit status %d" % (self.cmd, self.returncode)
68
68
69 mswindows = (sys.platform == "win32")
69 mswindows = (sys.platform == "win32")
70
70
71 skip = False
71 skip = False
72
72
73 if mswindows:
73 if mswindows:
74 # Some versions of windows, in some strange corner cases, give
74 import platform
75 # errror with the killableprocess.
75 if platform.uname()[3] == '' or platform.uname()[3] > '6.0.6000':
76 if sys.version_info[:2] > (2, 4):
77 import platform
78 if platform.uname()[3] == '' or platform.uname()[3] > '6.0.6000':
79 skip = True
80 else:
81 import winprocess
82 else:
83 skip = True
76 skip = True
77 else:
78 import winprocess
84 else:
79 else:
85 import signal
80 import signal
86
81
87 if not mswindows:
82 if not mswindows:
88 def DoNothing(*args):
83 def DoNothing(*args):
89 pass
84 pass
90
85
91
86
92 if skip:
87 if skip:
93 Popen = subprocess.Popen
88 Popen = subprocess.Popen
94 else:
89 else:
95 class Popen(subprocess.Popen):
90 class Popen(subprocess.Popen):
96 if not mswindows:
91 if not mswindows:
97 # Override __init__ to set a preexec_fn
92 # Override __init__ to set a preexec_fn
98 def __init__(self, *args, **kwargs):
93 def __init__(self, *args, **kwargs):
99 if len(args) >= 7:
94 if len(args) >= 7:
100 raise Exception("Arguments preexec_fn and after must be passed by keyword.")
95 raise Exception("Arguments preexec_fn and after must be passed by keyword.")
101
96
102 real_preexec_fn = kwargs.pop("preexec_fn", None)
97 real_preexec_fn = kwargs.pop("preexec_fn", None)
103 def setpgid_preexec_fn():
98 def setpgid_preexec_fn():
104 os.setpgid(0, 0)
99 os.setpgid(0, 0)
105 if real_preexec_fn:
100 if real_preexec_fn:
106 apply(real_preexec_fn)
101 apply(real_preexec_fn)
107
102
108 kwargs['preexec_fn'] = setpgid_preexec_fn
103 kwargs['preexec_fn'] = setpgid_preexec_fn
109
104
110 subprocess.Popen.__init__(self, *args, **kwargs)
105 subprocess.Popen.__init__(self, *args, **kwargs)
111
106
112 if mswindows:
107 if mswindows:
113 def _execute_child(self, args, executable, preexec_fn, close_fds,
108 def _execute_child(self, args, executable, preexec_fn, close_fds,
114 cwd, env, universal_newlines, startupinfo,
109 cwd, env, universal_newlines, startupinfo,
115 creationflags, shell,
110 creationflags, shell,
116 p2cread, p2cwrite,
111 p2cread, p2cwrite,
117 c2pread, c2pwrite,
112 c2pread, c2pwrite,
118 errread, errwrite):
113 errread, errwrite):
119 if not isinstance(args, types.StringTypes):
114 if not isinstance(args, types.StringTypes):
120 args = subprocess.list2cmdline(args)
115 args = subprocess.list2cmdline(args)
121
116
122 if startupinfo is None:
117 if startupinfo is None:
123 startupinfo = winprocess.STARTUPINFO()
118 startupinfo = winprocess.STARTUPINFO()
124
119
125 if None not in (p2cread, c2pwrite, errwrite):
120 if None not in (p2cread, c2pwrite, errwrite):
126 startupinfo.dwFlags |= winprocess.STARTF_USESTDHANDLES
121 startupinfo.dwFlags |= winprocess.STARTF_USESTDHANDLES
127
122
128 startupinfo.hStdInput = int(p2cread)
123 startupinfo.hStdInput = int(p2cread)
129 startupinfo.hStdOutput = int(c2pwrite)
124 startupinfo.hStdOutput = int(c2pwrite)
130 startupinfo.hStdError = int(errwrite)
125 startupinfo.hStdError = int(errwrite)
131 if shell:
126 if shell:
132 startupinfo.dwFlags |= winprocess.STARTF_USESHOWWINDOW
127 startupinfo.dwFlags |= winprocess.STARTF_USESHOWWINDOW
133 startupinfo.wShowWindow = winprocess.SW_HIDE
128 startupinfo.wShowWindow = winprocess.SW_HIDE
134 comspec = os.environ.get("COMSPEC", "cmd.exe")
129 comspec = os.environ.get("COMSPEC", "cmd.exe")
135 args = comspec + " /c " + args
130 args = comspec + " /c " + args
136
131
137 # We create a new job for this process, so that we can kill
132 # We create a new job for this process, so that we can kill
138 # the process and any sub-processes
133 # the process and any sub-processes
139 self._job = winprocess.CreateJobObject()
134 self._job = winprocess.CreateJobObject()
140
135
141 creationflags |= winprocess.CREATE_SUSPENDED
136 creationflags |= winprocess.CREATE_SUSPENDED
142 creationflags |= winprocess.CREATE_UNICODE_ENVIRONMENT
137 creationflags |= winprocess.CREATE_UNICODE_ENVIRONMENT
143
138
144 hp, ht, pid, tid = winprocess.CreateProcess(
139 hp, ht, pid, tid = winprocess.CreateProcess(
145 executable, args,
140 executable, args,
146 None, None, # No special security
141 None, None, # No special security
147 1, # Must inherit handles!
142 1, # Must inherit handles!
148 creationflags,
143 creationflags,
149 winprocess.EnvironmentBlock(env),
144 winprocess.EnvironmentBlock(env),
150 cwd, startupinfo)
145 cwd, startupinfo)
151
146
152 self._child_created = True
147 self._child_created = True
153 self._handle = hp
148 self._handle = hp
154 self._thread = ht
149 self._thread = ht
155 self.pid = pid
150 self.pid = pid
156
151
157 winprocess.AssignProcessToJobObject(self._job, hp)
152 winprocess.AssignProcessToJobObject(self._job, hp)
158 winprocess.ResumeThread(ht)
153 winprocess.ResumeThread(ht)
159
154
160 if p2cread is not None:
155 if p2cread is not None:
161 p2cread.Close()
156 p2cread.Close()
162 if c2pwrite is not None:
157 if c2pwrite is not None:
163 c2pwrite.Close()
158 c2pwrite.Close()
164 if errwrite is not None:
159 if errwrite is not None:
165 errwrite.Close()
160 errwrite.Close()
166
161
167 def kill(self, group=True):
162 def kill(self, group=True):
168 """Kill the process. If group=True, all sub-processes will also be killed."""
163 """Kill the process. If group=True, all sub-processes will also be killed."""
169 if mswindows:
164 if mswindows:
170 if group:
165 if group:
171 winprocess.TerminateJobObject(self._job, 127)
166 winprocess.TerminateJobObject(self._job, 127)
172 else:
167 else:
173 winprocess.TerminateProcess(self._handle, 127)
168 winprocess.TerminateProcess(self._handle, 127)
174 self.returncode = 127
169 self.returncode = 127
175 else:
170 else:
176 if group:
171 if group:
177 os.killpg(self.pid, signal.SIGKILL)
172 os.killpg(self.pid, signal.SIGKILL)
178 else:
173 else:
179 os.kill(self.pid, signal.SIGKILL)
174 os.kill(self.pid, signal.SIGKILL)
180 self.returncode = -9
175 self.returncode = -9
181
176
182
177
@@ -1,74 +1,74 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 Object for encapsulating process execution by using callbacks for stdout,
3 Object for encapsulating process execution by using callbacks for stdout,
4 stderr and stdin.
4 stderr and stdin.
5 """
5 """
6 __docformat__ = "restructuredtext en"
6 __docformat__ = "restructuredtext en"
7
7
8 #-------------------------------------------------------------------------------
8 #-------------------------------------------------------------------------------
9 # Copyright (C) 2008 The IPython Development Team
9 # Copyright (C) 2008 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-------------------------------------------------------------------------------
13 #-------------------------------------------------------------------------------
14
14
15 #-------------------------------------------------------------------------------
15 #-------------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-------------------------------------------------------------------------------
17 #-------------------------------------------------------------------------------
18 from killableprocess import Popen, PIPE
18 from killableprocess import Popen, PIPE
19 from threading import Thread
19 from threading import Thread
20 from time import sleep
20 from time import sleep
21 import os
21 import os
22
22
23 class PipedProcess(Thread):
23 class PipedProcess(Thread):
24 """ Class that encapsulates process execution by using callbacks for
24 """ Class that encapsulates process execution by using callbacks for
25 stdout, stderr and stdin, and providing a reliable way of
25 stdout, stderr and stdin, and providing a reliable way of
26 killing it.
26 killing it.
27 """
27 """
28
28
29 def __init__(self, command_string, out_callback,
29 def __init__(self, command_string, out_callback,
30 end_callback=None,):
30 end_callback=None,):
31 """ command_string: the command line executed to start the
31 """ command_string: the command line executed to start the
32 process.
32 process.
33
33
34 out_callback: the python callable called on stdout/stderr.
34 out_callback: the python callable called on stdout/stderr.
35
35
36 end_callback: an optional callable called when the process
36 end_callback: an optional callable called when the process
37 finishes.
37 finishes.
38
38
39 These callbacks are called from a different thread as the
39 These callbacks are called from a different thread as the
40 thread from which is started.
40 thread from which is started.
41 """
41 """
42 self.command_string = command_string
42 self.command_string = command_string
43 self.out_callback = out_callback
43 self.out_callback = out_callback
44 self.end_callback = end_callback
44 self.end_callback = end_callback
45 Thread.__init__(self)
45 Thread.__init__(self)
46
46
47
47
48 def run(self):
48 def run(self):
49 """ Start the process and hook up the callbacks.
49 """ Start the process and hook up the callbacks.
50 """
50 """
51 env = os.environ
51 env = os.environ
52 env['TERM'] = 'xterm'
52 env['TERM'] = 'xterm'
53 process = Popen((self.command_string + ' 2>&1', ), shell=True,
53 process = Popen(self.command_string + ' 2>&1', shell=True,
54 env=env,
54 env=env,
55 universal_newlines=True,
55 universal_newlines=True,
56 stdout=PIPE, stdin=PIPE, )
56 stdout=PIPE, stdin=PIPE, )
57 self.process = process
57 self.process = process
58 while True:
58 while True:
59 out_char = process.stdout.read(1)
59 out_char = process.stdout.read(1)
60 if out_char == '':
60 if out_char == '':
61 if process.poll() is not None:
61 if process.poll() is not None:
62 # The process has finished
62 # The process has finished
63 break
63 break
64 else:
64 else:
65 # The process is not giving any interesting
65 # The process is not giving any interesting
66 # output. No use polling it immediatly.
66 # output. No use polling it immediatly.
67 sleep(0.1)
67 sleep(0.1)
68 else:
68 else:
69 self.out_callback(out_char)
69 self.out_callback(out_char)
70
70
71 if self.end_callback is not None:
71 if self.end_callback is not None:
72 self.end_callback()
72 self.end_callback()
73
73
74
74
@@ -1,73 +1,67 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 Test process execution and IO redirection.
3 Test process execution and IO redirection.
4 """
4 """
5
5
6 __docformat__ = "restructuredtext en"
6 __docformat__ = "restructuredtext en"
7
7
8 #-------------------------------------------------------------------------------
8 #-------------------------------------------------------------------------------
9 # Copyright (C) 2008 The IPython Development Team
9 # Copyright (C) 2008 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is
11 # Distributed under the terms of the BSD License. The full license is
12 # in the file COPYING, distributed as part of this software.
12 # in the file COPYING, distributed as part of this software.
13 #-------------------------------------------------------------------------------
13 #-------------------------------------------------------------------------------
14
14
15 from cStringIO import StringIO
15 from cStringIO import StringIO
16 from time import sleep
16 from time import sleep
17 import sys
17 import sys
18
18
19 from IPython.frontend._process import PipedProcess
19 from IPython.frontend._process import PipedProcess
20 from IPython.testing import decorators as testdec
20 from IPython.testing import decorators as testdec
21
21
22
22
23 # FIXME
24 @testdec.skip("This doesn't work under Windows")
25 def test_capture_out():
23 def test_capture_out():
26 """ A simple test to see if we can execute a process and get the output.
24 """ A simple test to see if we can execute a process and get the output.
27 """
25 """
28 s = StringIO()
26 s = StringIO()
29 p = PipedProcess('echo 1', out_callback=s.write, )
27 p = PipedProcess('echo 1', out_callback=s.write, )
30 p.start()
28 p.start()
31 p.join()
29 p.join()
32 result = s.getvalue().rstrip()
30 result = s.getvalue().rstrip()
33 assert result == '1'
31 assert result == '1'
34
32
35
33
36 # FIXME
37 @testdec.skip("This doesn't work under Windows")
38 def test_io():
34 def test_io():
39 """ Checks that we can send characters on stdin to the process.
35 """ Checks that we can send characters on stdin to the process.
40 """
36 """
41 s = StringIO()
37 s = StringIO()
42 p = PipedProcess(sys.executable + ' -c "a = raw_input(); print a"',
38 p = PipedProcess(sys.executable + ' -c "a = raw_input(); print a"',
43 out_callback=s.write, )
39 out_callback=s.write, )
44 p.start()
40 p.start()
45 test_string = '12345\n'
41 test_string = '12345\n'
46 while not hasattr(p, 'process'):
42 while not hasattr(p, 'process'):
47 sleep(0.1)
43 sleep(0.1)
48 p.process.stdin.write(test_string)
44 p.process.stdin.write(test_string)
49 p.join()
45 p.join()
50 result = s.getvalue()
46 result = s.getvalue()
51 assert result == test_string
47 assert result == test_string
52
48
53
49
54 # FIXME
55 @testdec.skip("This doesn't work under Windows")
56 def test_kill():
50 def test_kill():
57 """ Check that we can kill a process, and its subprocess.
51 """ Check that we can kill a process, and its subprocess.
58 """
52 """
59 s = StringIO()
53 s = StringIO()
60 p = PipedProcess(sys.executable + ' -c "a = raw_input();"',
54 p = PipedProcess(sys.executable + ' -c "a = raw_input();"',
61 out_callback=s.write, )
55 out_callback=s.write, )
62 p.start()
56 p.start()
63 while not hasattr(p, 'process'):
57 while not hasattr(p, 'process'):
64 sleep(0.1)
58 sleep(0.1)
65 p.process.kill()
59 p.process.kill()
66 assert p.process.poll() is not None
60 assert p.process.poll() is not None
67
61
68
62
69 if __name__ == '__main__':
63 if __name__ == '__main__':
70 test_capture_out()
64 test_capture_out()
71 test_io()
65 test_io()
72 test_kill()
66 test_kill()
73
67
General Comments 0
You need to be logged in to leave comments. Login now