Show More
@@ -18,10 +18,12 b' This file is only meant to be imported by process.py, not by end-users.' | |||
|
18 | 18 | import os |
|
19 | 19 | import sys |
|
20 | 20 | import ctypes |
|
21 | import time | |
|
21 | 22 | |
|
22 | 23 | from ctypes import c_int, POINTER |
|
23 | 24 | from ctypes.wintypes import LPCWSTR, HLOCAL |
|
24 | from subprocess import STDOUT | |
|
25 | from subprocess import STDOUT, TimeoutExpired | |
|
26 | from threading import Thread | |
|
25 | 27 | |
|
26 | 28 | # our own imports |
|
27 | 29 | from ._process_common import read_no_interrupt, process_handler, arg_split as py_arg_split |
@@ -93,15 +95,29 b' def _find_cmd(cmd):' | |||
|
93 | 95 | def _system_body(p): |
|
94 | 96 | """Callback for _system.""" |
|
95 | 97 | enc = DEFAULT_ENCODING |
|
98 | ||
|
99 | def stdout_read(): | |
|
96 | 100 | for line in read_no_interrupt(p.stdout).splitlines(): |
|
97 | 101 | line = line.decode(enc, 'replace') |
|
98 | 102 | print(line, file=sys.stdout) |
|
103 | ||
|
104 | def stderr_read(): | |
|
99 | 105 | for line in read_no_interrupt(p.stderr).splitlines(): |
|
100 | 106 | line = line.decode(enc, 'replace') |
|
101 | 107 | print(line, file=sys.stderr) |
|
102 | 108 | |
|
103 | # Wait to finish for returncode | |
|
104 | return p.wait() | |
|
109 | Thread(target=stdout_read).start() | |
|
110 | Thread(target=stderr_read).start() | |
|
111 | ||
|
112 | # Wait to finish for returncode. Unfortunately, Python has a bug where | |
|
113 | # wait() isn't interruptible (https://bugs.python.org/issue28168) so poll in | |
|
114 | # a loop instead of just doing `return p.wait()`. | |
|
115 | while True: | |
|
116 | result = p.poll() | |
|
117 | if result is None: | |
|
118 | time.sleep(0.01) | |
|
119 | else: | |
|
120 | return result | |
|
105 | 121 | |
|
106 | 122 | |
|
107 | 123 | def system(cmd): |
@@ -116,9 +132,7 b' def system(cmd):' | |||
|
116 | 132 | |
|
117 | 133 | Returns |
|
118 | 134 | ------- |
|
119 | None : we explicitly do NOT return the subprocess status code, as this | |
|
120 | utility is meant to be used extensively in IPython, where any return value | |
|
121 | would trigger :func:`sys.displayhook` calls. | |
|
135 | int : child process' exit code. | |
|
122 | 136 | """ |
|
123 | 137 | # The controller provides interactivity with both |
|
124 | 138 | # stdin and stdout |
@@ -15,13 +15,19 b' Tests for platutils.py' | |||
|
15 | 15 | #----------------------------------------------------------------------------- |
|
16 | 16 | |
|
17 | 17 | import sys |
|
18 | import signal | |
|
18 | 19 | import os |
|
20 | import time | |
|
21 | from _thread import interrupt_main # Py 3 | |
|
22 | import threading | |
|
23 | from unittest import SkipTest | |
|
19 | 24 | |
|
20 | 25 | import nose.tools as nt |
|
21 | 26 | |
|
22 | 27 | from IPython.utils.process import (find_cmd, FindCmdError, arg_split, |
|
23 | 28 | system, getoutput, getoutputerror, |
|
24 | 29 | get_output_error_code) |
|
30 | from IPython.utils.capture import capture_output | |
|
25 | 31 | from IPython.testing import decorators as dec |
|
26 | 32 | from IPython.testing import tools as tt |
|
27 | 33 | |
@@ -107,6 +113,49 b' class SubProcessTestCase(tt.TempFileMixin):' | |||
|
107 | 113 | status = system('%s -c "import sys"' % python) |
|
108 | 114 | self.assertEqual(status, 0) |
|
109 | 115 | |
|
116 | def assert_interrupts(self, command): | |
|
117 | """ | |
|
118 | Interrupt a subprocess after a second. | |
|
119 | """ | |
|
120 | if threading.main_thread() != threading.current_thread(): | |
|
121 | raise nt.SkipTest("Can't run this test if not in main thread.") | |
|
122 | ||
|
123 | # Some tests can overwrite SIGINT handler (by using pdb for example), | |
|
124 | # which then breaks this test, so just make sure it's operating | |
|
125 | # normally. | |
|
126 | signal.signal(signal.SIGINT, signal.default_int_handler) | |
|
127 | ||
|
128 | def interrupt(): | |
|
129 | # Wait for subprocess to start: | |
|
130 | time.sleep(0.5) | |
|
131 | interrupt_main() | |
|
132 | ||
|
133 | threading.Thread(target=interrupt).start() | |
|
134 | start = time.time() | |
|
135 | try: | |
|
136 | result = command() | |
|
137 | except KeyboardInterrupt: | |
|
138 | # Success! | |
|
139 | pass | |
|
140 | end = time.time() | |
|
141 | self.assertTrue( | |
|
142 | end - start < 2, "Process didn't die quickly: %s" % (end - start) | |
|
143 | ) | |
|
144 | return result | |
|
145 | ||
|
146 | def test_system_interrupt(self): | |
|
147 | """ | |
|
148 | When interrupted in the way ipykernel interrupts IPython, the | |
|
149 | subprocess is interrupted. | |
|
150 | """ | |
|
151 | def command(): | |
|
152 | return system('%s -c "import time; time.sleep(5)"' % python) | |
|
153 | ||
|
154 | status = self.assert_interrupts(command) | |
|
155 | self.assertNotEqual( | |
|
156 | status, 0, "The process wasn't interrupted. Status: %s" % (status,) | |
|
157 | ) | |
|
158 | ||
|
110 | 159 | def test_getoutput(self): |
|
111 | 160 | out = getoutput('%s "%s"' % (python, self.fname)) |
|
112 | 161 | # we can't rely on the order the line buffered streams are flushed |
@@ -142,3 +191,5 b' class SubProcessTestCase(tt.TempFileMixin):' | |||
|
142 | 191 | self.assertEqual(out, 'on stdout') |
|
143 | 192 | self.assertEqual(err, 'on stderr') |
|
144 | 193 | self.assertEqual(code, 0) |
|
194 | ||
|
195 |
General Comments 0
You need to be logged in to leave comments.
Login now