##// END OF EJS Templates
Merge pull request #4149 from minrk/fewer-kernels...
Paul Ivanov -
r12449:cd4bd811 merge
parent child Browse files
Show More
@@ -0,0 +1,167 b''
1 """utilities for testing IPython kernels"""
2
3 #-------------------------------------------------------------------------------
4 # Copyright (C) 2013 The IPython Development Team
5 #
6 # Distributed under the terms of the BSD License. The full license is in
7 # the file COPYING, distributed as part of this software.
8 #-------------------------------------------------------------------------------
9
10 #-------------------------------------------------------------------------------
11 # Imports
12 #-------------------------------------------------------------------------------
13
14 import atexit
15
16 from contextlib import contextmanager
17 from subprocess import PIPE
18 from Queue import Empty
19
20 import nose.tools as nt
21
22 from IPython.kernel import KernelManager
23
24 #-------------------------------------------------------------------------------
25 # Globals
26 #-------------------------------------------------------------------------------
27
28 STARTUP_TIMEOUT = 60
29 TIMEOUT = 15
30
31 KM = None
32 KC = None
33
34 #-------------------------------------------------------------------------------
35 # code
36 #-------------------------------------------------------------------------------
37
38
39 def start_new_kernel():
40 """start a new kernel, and return its Manager and Client"""
41 km = KernelManager()
42 km.start_kernel(stdout=PIPE, stderr=PIPE)
43 kc = km.client()
44 kc.start_channels()
45
46 msg_id = kc.kernel_info()
47 kc.get_shell_msg(block=True, timeout=STARTUP_TIMEOUT)
48 flush_channels(kc)
49 return km, kc
50
51 def flush_channels(kc=None):
52 """flush any messages waiting on the queue"""
53 from .test_message_spec import validate_message
54
55 if kc is None:
56 kc = KC
57 for channel in (kc.shell_channel, kc.iopub_channel):
58 while True:
59 try:
60 msg = channel.get_msg(block=True, timeout=0.1)
61 except Empty:
62 break
63 else:
64 validate_message(msg)
65
66
67 def execute(code='', kc=None, **kwargs):
68 """wrapper for doing common steps for validating an execution request"""
69 from .test_message_spec import validate_message
70 if kc is None:
71 kc = KC
72 msg_id = kc.execute(code=code, **kwargs)
73 reply = kc.get_shell_msg(timeout=TIMEOUT)
74 validate_message(reply, 'execute_reply', msg_id)
75 busy = kc.get_iopub_msg(timeout=TIMEOUT)
76 validate_message(busy, 'status', msg_id)
77 nt.assert_equal(busy['content']['execution_state'], 'busy')
78
79 if not kwargs.get('silent'):
80 pyin = kc.get_iopub_msg(timeout=TIMEOUT)
81 validate_message(pyin, 'pyin', msg_id)
82 nt.assert_equal(pyin['content']['code'], code)
83
84 return msg_id, reply['content']
85
86 def start_global_kernel():
87 """start the global kernel (if it isn't running) and return its client"""
88 global KM, KC
89 if KM is None:
90 KM, KC = start_new_kernel()
91 atexit.register(stop_global_kernel)
92 return KC
93
94 @contextmanager
95 def kernel():
96 """Context manager for the global kernel instance
97
98 Should be used for most kernel tests
99
100 Returns
101 -------
102 kernel_client: connected KernelClient instance
103 """
104 yield start_global_kernel()
105
106 def uses_kernel(test_f):
107 """Decorator for tests that use the global kernel"""
108 def wrapped_test():
109 with kernel() as kc:
110 test_f(kc)
111 wrapped_test.__doc__ = test_f.__doc__
112 wrapped_test.__name__ = test_f.__name__
113 return wrapped_test
114
115 def stop_global_kernel():
116 """Stop the global shared kernel instance, if it exists"""
117 global KM, KC
118 KC.stop_channels()
119 KC = None
120 if KM is None:
121 return
122 KM.shutdown_kernel(now=True)
123 KM = None
124
125 @contextmanager
126 def new_kernel():
127 """Context manager for a new kernel in a subprocess
128
129 Should only be used for tests where the kernel must not be re-used.
130
131 Returns
132 -------
133 kernel_client: connected KernelClient instance
134 """
135 km, kc = start_new_kernel()
136 try:
137 yield kc
138 finally:
139 kc.stop_channels()
140 km.shutdown_kernel(now=True)
141
142
143 def assemble_output(iopub):
144 """assemble stdout/err from an execution"""
145 stdout = ''
146 stderr = ''
147 while True:
148 msg = iopub.get_msg(block=True, timeout=1)
149 msg_type = msg['msg_type']
150 content = msg['content']
151 if msg_type == 'status' and content['execution_state'] == 'idle':
152 # idle message signals end of output
153 break
154 elif msg['msg_type'] == 'stream':
155 if content['name'] == 'stdout':
156 stdout += content['data']
157 elif content['name'] == 'stderr':
158 stderr += content['data']
159 else:
160 raise KeyError("bad stream: %r" % content['name'])
161 else:
162 # other output, ignored
163 pass
164 return stdout, stderr
165
166
167
@@ -1,301 +1,301 b''
1 """A kernel manager for multiple kernels
1 """A kernel manager for multiple kernels
2
2
3 Authors:
3 Authors:
4
4
5 * Brian Granger
5 * Brian Granger
6 """
6 """
7
7
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2013 The IPython Development Team
9 # Copyright (C) 2013 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
18
19 from __future__ import absolute_import
19 from __future__ import absolute_import
20
20
21 import os
21 import os
22 import uuid
22 import uuid
23
23
24 import zmq
24 import zmq
25
25
26 from IPython.config.configurable import LoggingConfigurable
26 from IPython.config.configurable import LoggingConfigurable
27 from IPython.utils.importstring import import_item
27 from IPython.utils.importstring import import_item
28 from IPython.utils.traitlets import (
28 from IPython.utils.traitlets import (
29 Instance, Dict, Unicode, Any, DottedObjectName
29 Instance, Dict, Unicode, Any, DottedObjectName
30 )
30 )
31
31
32 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
33 # Classes
33 # Classes
34 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
35
35
36 class DuplicateKernelError(Exception):
36 class DuplicateKernelError(Exception):
37 pass
37 pass
38
38
39
39
40
40
41 def kernel_method(f):
41 def kernel_method(f):
42 """decorator for proxying MKM.method(kernel_id) to individual KMs by ID"""
42 """decorator for proxying MKM.method(kernel_id) to individual KMs by ID"""
43 def wrapped(self, kernel_id, *args, **kwargs):
43 def wrapped(self, kernel_id, *args, **kwargs):
44 # get the kernel
44 # get the kernel
45 km = self.get_kernel(kernel_id)
45 km = self.get_kernel(kernel_id)
46 method = getattr(km, f.__name__)
46 method = getattr(km, f.__name__)
47 # call the kernel's method
47 # call the kernel's method
48 r = method(*args, **kwargs)
48 r = method(*args, **kwargs)
49 # last thing, call anything defined in the actual class method
49 # last thing, call anything defined in the actual class method
50 # such as logging messages
50 # such as logging messages
51 f(self, kernel_id, *args, **kwargs)
51 f(self, kernel_id, *args, **kwargs)
52 # return the method result
52 # return the method result
53 return r
53 return r
54 return wrapped
54 return wrapped
55
55
56
56
57 class MultiKernelManager(LoggingConfigurable):
57 class MultiKernelManager(LoggingConfigurable):
58 """A class for managing multiple kernels."""
58 """A class for managing multiple kernels."""
59
59
60 kernel_manager_class = DottedObjectName(
60 kernel_manager_class = DottedObjectName(
61 "IPython.kernel.ioloop.IOLoopKernelManager", config=True,
61 "IPython.kernel.ioloop.IOLoopKernelManager", config=True,
62 help="""The kernel manager class. This is configurable to allow
62 help="""The kernel manager class. This is configurable to allow
63 subclassing of the KernelManager for customized behavior.
63 subclassing of the KernelManager for customized behavior.
64 """
64 """
65 )
65 )
66 def _kernel_manager_class_changed(self, name, old, new):
66 def _kernel_manager_class_changed(self, name, old, new):
67 self.kernel_manager_factory = import_item(new)
67 self.kernel_manager_factory = import_item(new)
68
68
69 kernel_manager_factory = Any(help="this is kernel_manager_class after import")
69 kernel_manager_factory = Any(help="this is kernel_manager_class after import")
70 def _kernel_manager_factory_default(self):
70 def _kernel_manager_factory_default(self):
71 return import_item(self.kernel_manager_class)
71 return import_item(self.kernel_manager_class)
72
72
73 context = Instance('zmq.Context')
73 context = Instance('zmq.Context')
74 def _context_default(self):
74 def _context_default(self):
75 return zmq.Context.instance()
75 return zmq.Context.instance()
76
76
77 connection_dir = Unicode('')
77 connection_dir = Unicode('')
78
78
79 _kernels = Dict()
79 _kernels = Dict()
80
80
81 def list_kernel_ids(self):
81 def list_kernel_ids(self):
82 """Return a list of the kernel ids of the active kernels."""
82 """Return a list of the kernel ids of the active kernels."""
83 # Create a copy so we can iterate over kernels in operations
83 # Create a copy so we can iterate over kernels in operations
84 # that delete keys.
84 # that delete keys.
85 return list(self._kernels.keys())
85 return list(self._kernels.keys())
86
86
87 def __len__(self):
87 def __len__(self):
88 """Return the number of running kernels."""
88 """Return the number of running kernels."""
89 return len(self.list_kernel_ids())
89 return len(self.list_kernel_ids())
90
90
91 def __contains__(self, kernel_id):
91 def __contains__(self, kernel_id):
92 return kernel_id in self._kernels
92 return kernel_id in self._kernels
93
93
94 def start_kernel(self, **kwargs):
94 def start_kernel(self, **kwargs):
95 """Start a new kernel.
95 """Start a new kernel.
96
96
97 The caller can pick a kernel_id by passing one in as a keyword arg,
97 The caller can pick a kernel_id by passing one in as a keyword arg,
98 otherwise one will be picked using a uuid.
98 otherwise one will be picked using a uuid.
99
99
100 To silence the kernel's stdout/stderr, call this using::
100 To silence the kernel's stdout/stderr, call this using::
101
101
102 km.start_kernel(stdout=PIPE, stderr=PIPE)
102 km.start_kernel(stdout=PIPE, stderr=PIPE)
103
103
104 """
104 """
105 kernel_id = kwargs.pop('kernel_id', unicode(uuid.uuid4()))
105 kernel_id = kwargs.pop('kernel_id', unicode(uuid.uuid4()))
106 if kernel_id in self:
106 if kernel_id in self:
107 raise DuplicateKernelError('Kernel already exists: %s' % kernel_id)
107 raise DuplicateKernelError('Kernel already exists: %s' % kernel_id)
108 # kernel_manager_factory is the constructor for the KernelManager
108 # kernel_manager_factory is the constructor for the KernelManager
109 # subclass we are using. It can be configured as any Configurable,
109 # subclass we are using. It can be configured as any Configurable,
110 # including things like its transport and ip.
110 # including things like its transport and ip.
111 km = self.kernel_manager_factory(connection_file=os.path.join(
111 km = self.kernel_manager_factory(connection_file=os.path.join(
112 self.connection_dir, "kernel-%s.json" % kernel_id),
112 self.connection_dir, "kernel-%s.json" % kernel_id),
113 parent=self, autorestart=True, log=self.log
113 parent=self, autorestart=True, log=self.log
114 )
114 )
115 km.start_kernel(**kwargs)
115 km.start_kernel(**kwargs)
116 self._kernels[kernel_id] = km
116 self._kernels[kernel_id] = km
117 return kernel_id
117 return kernel_id
118
118
119 @kernel_method
119 @kernel_method
120 def shutdown_kernel(self, kernel_id, now=False):
120 def shutdown_kernel(self, kernel_id, now=False):
121 """Shutdown a kernel by its kernel uuid.
121 """Shutdown a kernel by its kernel uuid.
122
122
123 Parameters
123 Parameters
124 ==========
124 ==========
125 kernel_id : uuid
125 kernel_id : uuid
126 The id of the kernel to shutdown.
126 The id of the kernel to shutdown.
127 now : bool
127 now : bool
128 Should the kernel be shutdown forcibly using a signal.
128 Should the kernel be shutdown forcibly using a signal.
129 """
129 """
130 self.log.info("Kernel shutdown: %s" % kernel_id)
130 self.log.info("Kernel shutdown: %s" % kernel_id)
131 self.remove_kernel(kernel_id)
131 self.remove_kernel(kernel_id)
132
132
133 def remove_kernel(self, kernel_id):
133 def remove_kernel(self, kernel_id):
134 """remove a kernel from our mapping.
134 """remove a kernel from our mapping.
135
135
136 Mainly so that a kernel can be removed if it is already dead,
136 Mainly so that a kernel can be removed if it is already dead,
137 without having to call shutdown_kernel.
137 without having to call shutdown_kernel.
138
138
139 The kernel object is returned.
139 The kernel object is returned.
140 """
140 """
141 return self._kernels.pop(kernel_id)
141 return self._kernels.pop(kernel_id)
142
142
143 def shutdown_all(self, now=False):
143 def shutdown_all(self, now=False):
144 """Shutdown all kernels."""
144 """Shutdown all kernels."""
145 for kid in self.list_kernel_ids():
145 for kid in self.list_kernel_ids():
146 self.shutdown_kernel(kid, now=now)
146 self.shutdown_kernel(kid, now=now)
147
147
148 @kernel_method
148 @kernel_method
149 def interrupt_kernel(self, kernel_id):
149 def interrupt_kernel(self, kernel_id):
150 """Interrupt (SIGINT) the kernel by its uuid.
150 """Interrupt (SIGINT) the kernel by its uuid.
151
151
152 Parameters
152 Parameters
153 ==========
153 ==========
154 kernel_id : uuid
154 kernel_id : uuid
155 The id of the kernel to interrupt.
155 The id of the kernel to interrupt.
156 """
156 """
157 self.log.info("Kernel interrupted: %s" % kernel_id)
157 self.log.info("Kernel interrupted: %s" % kernel_id)
158
158
159 @kernel_method
159 @kernel_method
160 def signal_kernel(self, kernel_id, signum):
160 def signal_kernel(self, kernel_id, signum):
161 """Sends a signal to the kernel by its uuid.
161 """Sends a signal to the kernel by its uuid.
162
162
163 Note that since only SIGTERM is supported on Windows, this function
163 Note that since only SIGTERM is supported on Windows, this function
164 is only useful on Unix systems.
164 is only useful on Unix systems.
165
165
166 Parameters
166 Parameters
167 ==========
167 ==========
168 kernel_id : uuid
168 kernel_id : uuid
169 The id of the kernel to signal.
169 The id of the kernel to signal.
170 """
170 """
171 self.log.info("Signaled Kernel %s with %s" % (kernel_id, signum))
171 self.log.info("Signaled Kernel %s with %s" % (kernel_id, signum))
172
172
173 @kernel_method
173 @kernel_method
174 def restart_kernel(self, kernel_id):
174 def restart_kernel(self, kernel_id, now=False):
175 """Restart a kernel by its uuid, keeping the same ports.
175 """Restart a kernel by its uuid, keeping the same ports.
176
176
177 Parameters
177 Parameters
178 ==========
178 ==========
179 kernel_id : uuid
179 kernel_id : uuid
180 The id of the kernel to interrupt.
180 The id of the kernel to interrupt.
181 """
181 """
182 self.log.info("Kernel restarted: %s" % kernel_id)
182 self.log.info("Kernel restarted: %s" % kernel_id)
183
183
184 @kernel_method
184 @kernel_method
185 def is_alive(self, kernel_id):
185 def is_alive(self, kernel_id):
186 """Is the kernel alive.
186 """Is the kernel alive.
187
187
188 This calls KernelManager.is_alive() which calls Popen.poll on the
188 This calls KernelManager.is_alive() which calls Popen.poll on the
189 actual kernel subprocess.
189 actual kernel subprocess.
190
190
191 Parameters
191 Parameters
192 ==========
192 ==========
193 kernel_id : uuid
193 kernel_id : uuid
194 The id of the kernel.
194 The id of the kernel.
195 """
195 """
196
196
197 def _check_kernel_id(self, kernel_id):
197 def _check_kernel_id(self, kernel_id):
198 """check that a kernel id is valid"""
198 """check that a kernel id is valid"""
199 if kernel_id not in self:
199 if kernel_id not in self:
200 raise KeyError("Kernel with id not found: %s" % kernel_id)
200 raise KeyError("Kernel with id not found: %s" % kernel_id)
201
201
202 def get_kernel(self, kernel_id):
202 def get_kernel(self, kernel_id):
203 """Get the single KernelManager object for a kernel by its uuid.
203 """Get the single KernelManager object for a kernel by its uuid.
204
204
205 Parameters
205 Parameters
206 ==========
206 ==========
207 kernel_id : uuid
207 kernel_id : uuid
208 The id of the kernel.
208 The id of the kernel.
209 """
209 """
210 self._check_kernel_id(kernel_id)
210 self._check_kernel_id(kernel_id)
211 return self._kernels[kernel_id]
211 return self._kernels[kernel_id]
212
212
213 @kernel_method
213 @kernel_method
214 def add_restart_callback(self, kernel_id, callback, event='restart'):
214 def add_restart_callback(self, kernel_id, callback, event='restart'):
215 """add a callback for the KernelRestarter"""
215 """add a callback for the KernelRestarter"""
216
216
217 @kernel_method
217 @kernel_method
218 def remove_restart_callback(self, kernel_id, callback, event='restart'):
218 def remove_restart_callback(self, kernel_id, callback, event='restart'):
219 """remove a callback for the KernelRestarter"""
219 """remove a callback for the KernelRestarter"""
220
220
221 @kernel_method
221 @kernel_method
222 def get_connection_info(self, kernel_id):
222 def get_connection_info(self, kernel_id):
223 """Return a dictionary of connection data for a kernel.
223 """Return a dictionary of connection data for a kernel.
224
224
225 Parameters
225 Parameters
226 ==========
226 ==========
227 kernel_id : uuid
227 kernel_id : uuid
228 The id of the kernel.
228 The id of the kernel.
229
229
230 Returns
230 Returns
231 =======
231 =======
232 connection_dict : dict
232 connection_dict : dict
233 A dict of the information needed to connect to a kernel.
233 A dict of the information needed to connect to a kernel.
234 This includes the ip address and the integer port
234 This includes the ip address and the integer port
235 numbers of the different channels (stdin_port, iopub_port,
235 numbers of the different channels (stdin_port, iopub_port,
236 shell_port, hb_port).
236 shell_port, hb_port).
237 """
237 """
238
238
239 @kernel_method
239 @kernel_method
240 def connect_iopub(self, kernel_id, identity=None):
240 def connect_iopub(self, kernel_id, identity=None):
241 """Return a zmq Socket connected to the iopub channel.
241 """Return a zmq Socket connected to the iopub channel.
242
242
243 Parameters
243 Parameters
244 ==========
244 ==========
245 kernel_id : uuid
245 kernel_id : uuid
246 The id of the kernel
246 The id of the kernel
247 identity : bytes (optional)
247 identity : bytes (optional)
248 The zmq identity of the socket
248 The zmq identity of the socket
249
249
250 Returns
250 Returns
251 =======
251 =======
252 stream : zmq Socket or ZMQStream
252 stream : zmq Socket or ZMQStream
253 """
253 """
254
254
255 @kernel_method
255 @kernel_method
256 def connect_shell(self, kernel_id, identity=None):
256 def connect_shell(self, kernel_id, identity=None):
257 """Return a zmq Socket connected to the shell channel.
257 """Return a zmq Socket connected to the shell channel.
258
258
259 Parameters
259 Parameters
260 ==========
260 ==========
261 kernel_id : uuid
261 kernel_id : uuid
262 The id of the kernel
262 The id of the kernel
263 identity : bytes (optional)
263 identity : bytes (optional)
264 The zmq identity of the socket
264 The zmq identity of the socket
265
265
266 Returns
266 Returns
267 =======
267 =======
268 stream : zmq Socket or ZMQStream
268 stream : zmq Socket or ZMQStream
269 """
269 """
270
270
271 @kernel_method
271 @kernel_method
272 def connect_stdin(self, kernel_id, identity=None):
272 def connect_stdin(self, kernel_id, identity=None):
273 """Return a zmq Socket connected to the stdin channel.
273 """Return a zmq Socket connected to the stdin channel.
274
274
275 Parameters
275 Parameters
276 ==========
276 ==========
277 kernel_id : uuid
277 kernel_id : uuid
278 The id of the kernel
278 The id of the kernel
279 identity : bytes (optional)
279 identity : bytes (optional)
280 The zmq identity of the socket
280 The zmq identity of the socket
281
281
282 Returns
282 Returns
283 =======
283 =======
284 stream : zmq Socket or ZMQStream
284 stream : zmq Socket or ZMQStream
285 """
285 """
286
286
287 @kernel_method
287 @kernel_method
288 def connect_hb(self, kernel_id, identity=None):
288 def connect_hb(self, kernel_id, identity=None):
289 """Return a zmq Socket connected to the hb channel.
289 """Return a zmq Socket connected to the hb channel.
290
290
291 Parameters
291 Parameters
292 ==========
292 ==========
293 kernel_id : uuid
293 kernel_id : uuid
294 The id of the kernel
294 The id of the kernel
295 identity : bytes (optional)
295 identity : bytes (optional)
296 The zmq identity of the socket
296 The zmq identity of the socket
297
297
298 Returns
298 Returns
299 =======
299 =======
300 stream : zmq Socket or ZMQStream
300 stream : zmq Socket or ZMQStream
301 """
301 """
@@ -1,256 +1,170 b''
1 """test the IPython Kernel"""
1 """test the IPython Kernel"""
2
2
3 #-------------------------------------------------------------------------------
3 #-------------------------------------------------------------------------------
4 # Copyright (C) 2013 The IPython Development Team
4 # Copyright (C) 2013 The IPython Development Team
5 #
5 #
6 # Distributed under the terms of the BSD License. The full license is in
6 # Distributed under the terms of the BSD License. The full license is in
7 # the file COPYING, distributed as part of this software.
7 # the file COPYING, distributed as part of this software.
8 #-------------------------------------------------------------------------------
8 #-------------------------------------------------------------------------------
9
9
10 #-------------------------------------------------------------------------------
10 #-------------------------------------------------------------------------------
11 # Imports
11 # Imports
12 #-------------------------------------------------------------------------------
12 #-------------------------------------------------------------------------------
13
13
14 import os
15 import shutil
16 import sys
14 import sys
17 import tempfile
18
19 from contextlib import contextmanager
20 from subprocess import PIPE
21
15
22 import nose.tools as nt
16 import nose.tools as nt
23
17
24 from IPython.kernel import KernelManager
25 from IPython.kernel.tests.test_message_spec import execute, flush_channels
26 from IPython.testing import decorators as dec, tools as tt
18 from IPython.testing import decorators as dec, tools as tt
27 from IPython.utils import path, py3compat
19 from IPython.utils import py3compat
20
21 from .utils import new_kernel, kernel, TIMEOUT, assemble_output, execute, flush_channels
28
22
29 #-------------------------------------------------------------------------------
23 #-------------------------------------------------------------------------------
30 # Tests
24 # Tests
31 #-------------------------------------------------------------------------------
25 #-------------------------------------------------------------------------------
32 IPYTHONDIR = None
33 save_env = None
34 save_get_ipython_dir = None
35
36 STARTUP_TIMEOUT = 60
37 TIMEOUT = 15
38
39 def setup():
40 """setup temporary IPYTHONDIR for tests"""
41 global IPYTHONDIR
42 global save_env
43 global save_get_ipython_dir
44
45 IPYTHONDIR = tempfile.mkdtemp()
46
47 save_env = os.environ.copy()
48 os.environ["IPYTHONDIR"] = IPYTHONDIR
49
50 save_get_ipython_dir = path.get_ipython_dir
51 path.get_ipython_dir = lambda : IPYTHONDIR
52
53
54 def teardown():
55 path.get_ipython_dir = save_get_ipython_dir
56 os.environ = save_env
57
58 try:
59 shutil.rmtree(IPYTHONDIR)
60 except (OSError, IOError):
61 # no such file
62 pass
63
64
65 @contextmanager
66 def new_kernel():
67 """start a kernel in a subprocess, and wait for it to be ready
68
69 Returns
70 -------
71 kernel_manager: connected KernelManager instance
72 """
73 KM = KernelManager()
74
75 KM.start_kernel(stdout=PIPE, stderr=PIPE)
76 KC = KM.client()
77 KC.start_channels()
78
79 # wait for kernel to be ready
80 KC.shell_channel.execute("import sys")
81 KC.shell_channel.get_msg(block=True, timeout=STARTUP_TIMEOUT)
82 flush_channels(KC)
83 try:
84 yield KC
85 finally:
86 KC.stop_channels()
87 KM.shutdown_kernel()
88
89
90 def assemble_output(iopub):
91 """assemble stdout/err from an execution"""
92 stdout = ''
93 stderr = ''
94 while True:
95 msg = iopub.get_msg(block=True, timeout=1)
96 msg_type = msg['msg_type']
97 content = msg['content']
98 if msg_type == 'status' and content['execution_state'] == 'idle':
99 # idle message signals end of output
100 break
101 elif msg['msg_type'] == 'stream':
102 if content['name'] == 'stdout':
103 stdout = stdout + content['data']
104 elif content['name'] == 'stderr':
105 stderr = stderr + content['data']
106 else:
107 raise KeyError("bad stream: %r" % content['name'])
108 else:
109 # other output, ignored
110 pass
111 return stdout, stderr
112
26
113
27
114 def _check_mp_mode(kc, expected=False, stream="stdout"):
28 def _check_mp_mode(kc, expected=False, stream="stdout"):
115 execute(kc=kc, code="import sys")
29 execute(kc=kc, code="import sys")
116 flush_channels(kc)
30 flush_channels(kc)
117 msg_id, content = execute(kc=kc, code="print (sys.%s._check_mp_mode())" % stream)
31 msg_id, content = execute(kc=kc, code="print (sys.%s._check_mp_mode())" % stream)
118 stdout, stderr = assemble_output(kc.iopub_channel)
32 stdout, stderr = assemble_output(kc.iopub_channel)
119 nt.assert_equal(eval(stdout.strip()), expected)
33 nt.assert_equal(eval(stdout.strip()), expected)
120
34
121
35
122 # printing tests
36 # printing tests
123
37
124 def test_simple_print():
38 def test_simple_print():
125 """simple print statement in kernel"""
39 """simple print statement in kernel"""
126 with new_kernel() as kc:
40 with kernel() as kc:
127 iopub = kc.iopub_channel
41 iopub = kc.iopub_channel
128 msg_id, content = execute(kc=kc, code="print ('hi')")
42 msg_id, content = execute(kc=kc, code="print ('hi')")
129 stdout, stderr = assemble_output(iopub)
43 stdout, stderr = assemble_output(iopub)
130 nt.assert_equal(stdout, 'hi\n')
44 nt.assert_equal(stdout, 'hi\n')
131 nt.assert_equal(stderr, '')
45 nt.assert_equal(stderr, '')
132 _check_mp_mode(kc, expected=False)
46 _check_mp_mode(kc, expected=False)
133
47
134
48
135 @dec.knownfailureif(sys.platform == 'win32', "subprocess prints fail on Windows")
49 @dec.knownfailureif(sys.platform == 'win32', "subprocess prints fail on Windows")
136 def test_subprocess_print():
50 def test_subprocess_print():
137 """printing from forked mp.Process"""
51 """printing from forked mp.Process"""
138 with new_kernel() as kc:
52 with new_kernel() as kc:
139 iopub = kc.iopub_channel
53 iopub = kc.iopub_channel
140
54
141 _check_mp_mode(kc, expected=False)
55 _check_mp_mode(kc, expected=False)
142 flush_channels(kc)
56 flush_channels(kc)
143 np = 5
57 np = 5
144 code = '\n'.join([
58 code = '\n'.join([
145 "from __future__ import print_function",
59 "from __future__ import print_function",
146 "import multiprocessing as mp",
60 "import multiprocessing as mp",
147 "pool = [mp.Process(target=print, args=('hello', i,)) for i in range(%i)]" % np,
61 "pool = [mp.Process(target=print, args=('hello', i,)) for i in range(%i)]" % np,
148 "for p in pool: p.start()",
62 "for p in pool: p.start()",
149 "for p in pool: p.join()"
63 "for p in pool: p.join()"
150 ])
64 ])
151
65
152 expected = '\n'.join([
66 expected = '\n'.join([
153 "hello %s" % i for i in range(np)
67 "hello %s" % i for i in range(np)
154 ]) + '\n'
68 ]) + '\n'
155
69
156 msg_id, content = execute(kc=kc, code=code)
70 msg_id, content = execute(kc=kc, code=code)
157 stdout, stderr = assemble_output(iopub)
71 stdout, stderr = assemble_output(iopub)
158 nt.assert_equal(stdout.count("hello"), np, stdout)
72 nt.assert_equal(stdout.count("hello"), np, stdout)
159 for n in range(np):
73 for n in range(np):
160 nt.assert_equal(stdout.count(str(n)), 1, stdout)
74 nt.assert_equal(stdout.count(str(n)), 1, stdout)
161 nt.assert_equal(stderr, '')
75 nt.assert_equal(stderr, '')
162 _check_mp_mode(kc, expected=False)
76 _check_mp_mode(kc, expected=False)
163 _check_mp_mode(kc, expected=False, stream="stderr")
77 _check_mp_mode(kc, expected=False, stream="stderr")
164
78
165
79
166 def test_subprocess_noprint():
80 def test_subprocess_noprint():
167 """mp.Process without print doesn't trigger iostream mp_mode"""
81 """mp.Process without print doesn't trigger iostream mp_mode"""
168 with new_kernel() as kc:
82 with kernel() as kc:
169 iopub = kc.iopub_channel
83 iopub = kc.iopub_channel
170
84
171 np = 5
85 np = 5
172 code = '\n'.join([
86 code = '\n'.join([
173 "import multiprocessing as mp",
87 "import multiprocessing as mp",
174 "pool = [mp.Process(target=range, args=(i,)) for i in range(%i)]" % np,
88 "pool = [mp.Process(target=range, args=(i,)) for i in range(%i)]" % np,
175 "for p in pool: p.start()",
89 "for p in pool: p.start()",
176 "for p in pool: p.join()"
90 "for p in pool: p.join()"
177 ])
91 ])
178
92
179 msg_id, content = execute(kc=kc, code=code)
93 msg_id, content = execute(kc=kc, code=code)
180 stdout, stderr = assemble_output(iopub)
94 stdout, stderr = assemble_output(iopub)
181 nt.assert_equal(stdout, '')
95 nt.assert_equal(stdout, '')
182 nt.assert_equal(stderr, '')
96 nt.assert_equal(stderr, '')
183
97
184 _check_mp_mode(kc, expected=False)
98 _check_mp_mode(kc, expected=False)
185 _check_mp_mode(kc, expected=False, stream="stderr")
99 _check_mp_mode(kc, expected=False, stream="stderr")
186
100
187
101
188 @dec.knownfailureif(sys.platform == 'win32', "subprocess prints fail on Windows")
102 @dec.knownfailureif(sys.platform == 'win32', "subprocess prints fail on Windows")
189 def test_subprocess_error():
103 def test_subprocess_error():
190 """error in mp.Process doesn't crash"""
104 """error in mp.Process doesn't crash"""
191 with new_kernel() as kc:
105 with new_kernel() as kc:
192 iopub = kc.iopub_channel
106 iopub = kc.iopub_channel
193
107
194 code = '\n'.join([
108 code = '\n'.join([
195 "import multiprocessing as mp",
109 "import multiprocessing as mp",
196 "p = mp.Process(target=int, args=('hi',))",
110 "p = mp.Process(target=int, args=('hi',))",
197 "p.start()",
111 "p.start()",
198 "p.join()",
112 "p.join()",
199 ])
113 ])
200
114
201 msg_id, content = execute(kc=kc, code=code)
115 msg_id, content = execute(kc=kc, code=code)
202 stdout, stderr = assemble_output(iopub)
116 stdout, stderr = assemble_output(iopub)
203 nt.assert_equal(stdout, '')
117 nt.assert_equal(stdout, '')
204 nt.assert_true("ValueError" in stderr, stderr)
118 nt.assert_true("ValueError" in stderr, stderr)
205
119
206 _check_mp_mode(kc, expected=False)
120 _check_mp_mode(kc, expected=False)
207 _check_mp_mode(kc, expected=False, stream="stderr")
121 _check_mp_mode(kc, expected=False, stream="stderr")
208
122
209 # raw_input tests
123 # raw_input tests
210
124
211 def test_raw_input():
125 def test_raw_input():
212 """test [raw_]input"""
126 """test [raw_]input"""
213 with new_kernel() as kc:
127 with kernel() as kc:
214 iopub = kc.iopub_channel
128 iopub = kc.iopub_channel
215
129
216 input_f = "input" if py3compat.PY3 else "raw_input"
130 input_f = "input" if py3compat.PY3 else "raw_input"
217 theprompt = "prompt> "
131 theprompt = "prompt> "
218 code = 'print({input_f}("{theprompt}"))'.format(**locals())
132 code = 'print({input_f}("{theprompt}"))'.format(**locals())
219 msg_id = kc.execute(code, allow_stdin=True)
133 msg_id = kc.execute(code, allow_stdin=True)
220 msg = kc.get_stdin_msg(block=True, timeout=TIMEOUT)
134 msg = kc.get_stdin_msg(block=True, timeout=TIMEOUT)
221 nt.assert_equal(msg['header']['msg_type'], u'input_request')
135 nt.assert_equal(msg['header']['msg_type'], u'input_request')
222 content = msg['content']
136 content = msg['content']
223 nt.assert_equal(content['prompt'], theprompt)
137 nt.assert_equal(content['prompt'], theprompt)
224 text = "some text"
138 text = "some text"
225 kc.input(text)
139 kc.input(text)
226 reply = kc.get_shell_msg(block=True, timeout=TIMEOUT)
140 reply = kc.get_shell_msg(block=True, timeout=TIMEOUT)
227 nt.assert_equal(reply['content']['status'], 'ok')
141 nt.assert_equal(reply['content']['status'], 'ok')
228 stdout, stderr = assemble_output(iopub)
142 stdout, stderr = assemble_output(iopub)
229 nt.assert_equal(stdout, text + "\n")
143 nt.assert_equal(stdout, text + "\n")
230
144
231
145
232 @dec.skipif(py3compat.PY3)
146 @dec.skipif(py3compat.PY3)
233 def test_eval_input():
147 def test_eval_input():
234 """test input() on Python 2"""
148 """test input() on Python 2"""
235 with new_kernel() as kc:
149 with kernel() as kc:
236 iopub = kc.iopub_channel
150 iopub = kc.iopub_channel
237
151
238 input_f = "input" if py3compat.PY3 else "raw_input"
152 input_f = "input" if py3compat.PY3 else "raw_input"
239 theprompt = "prompt> "
153 theprompt = "prompt> "
240 code = 'print(input("{theprompt}"))'.format(**locals())
154 code = 'print(input("{theprompt}"))'.format(**locals())
241 msg_id = kc.execute(code, allow_stdin=True)
155 msg_id = kc.execute(code, allow_stdin=True)
242 msg = kc.get_stdin_msg(block=True, timeout=TIMEOUT)
156 msg = kc.get_stdin_msg(block=True, timeout=TIMEOUT)
243 nt.assert_equal(msg['header']['msg_type'], u'input_request')
157 nt.assert_equal(msg['header']['msg_type'], u'input_request')
244 content = msg['content']
158 content = msg['content']
245 nt.assert_equal(content['prompt'], theprompt)
159 nt.assert_equal(content['prompt'], theprompt)
246 kc.input("1+1")
160 kc.input("1+1")
247 reply = kc.get_shell_msg(block=True, timeout=TIMEOUT)
161 reply = kc.get_shell_msg(block=True, timeout=TIMEOUT)
248 nt.assert_equal(reply['content']['status'], 'ok')
162 nt.assert_equal(reply['content']['status'], 'ok')
249 stdout, stderr = assemble_output(iopub)
163 stdout, stderr = assemble_output(iopub)
250 nt.assert_equal(stdout, "2\n")
164 nt.assert_equal(stdout, "2\n")
251
165
252
166
253 def test_help_output():
167 def test_help_output():
254 """ipython kernel --help-all works"""
168 """ipython kernel --help-all works"""
255 tt.help_all_output_test('kernel')
169 tt.help_all_output_test('kernel')
256
170
@@ -1,61 +1,54 b''
1 """Tests for the notebook kernel and session manager"""
1 """Tests for the notebook kernel and session manager"""
2
2
3 from subprocess import PIPE
3 from subprocess import PIPE
4 import time
4 import time
5 from unittest import TestCase
5 from unittest import TestCase
6
6
7 from IPython.testing import decorators as dec
7 from IPython.testing import decorators as dec
8
8
9 from IPython.config.loader import Config
9 from IPython.config.loader import Config
10 from IPython.kernel import KernelManager
10 from IPython.kernel import KernelManager
11
11
12 class TestKernelManager(TestCase):
12 class TestKernelManager(TestCase):
13
13
14 def _get_tcp_km(self):
14 def _get_tcp_km(self):
15 c = Config()
15 c = Config()
16 km = KernelManager(config=c)
16 km = KernelManager(config=c)
17 return km
17 return km
18
18
19 def _get_ipc_km(self):
19 def _get_ipc_km(self):
20 c = Config()
20 c = Config()
21 c.KernelManager.transport = 'ipc'
21 c.KernelManager.transport = 'ipc'
22 c.KernelManager.ip = 'test'
22 c.KernelManager.ip = 'test'
23 km = KernelManager(config=c)
23 km = KernelManager(config=c)
24 return km
24 return km
25
25
26 def _run_lifecycle(self, km):
26 def _run_lifecycle(self, km):
27 km.start_kernel(stdout=PIPE, stderr=PIPE)
27 km.start_kernel(stdout=PIPE, stderr=PIPE)
28 self.assertTrue(km.is_alive())
28 self.assertTrue(km.is_alive())
29 km.restart_kernel()
29 km.restart_kernel(now=True)
30 self.assertTrue(km.is_alive())
30 self.assertTrue(km.is_alive())
31 # We need a delay here to give the restarting kernel a chance to
32 # restart. Otherwise, the interrupt will kill it, causing the test
33 # suite to hang. The reason it *hangs* is that the shutdown
34 # message for the restart sometimes hasn't been sent to the kernel.
35 # Because linger is oo on the shell channel, the context can't
36 # close until the message is sent to the kernel, which is not dead.
37 time.sleep(1.0)
38 km.interrupt_kernel()
31 km.interrupt_kernel()
39 self.assertTrue(isinstance(km, KernelManager))
32 self.assertTrue(isinstance(km, KernelManager))
40 km.shutdown_kernel()
33 km.shutdown_kernel(now=True)
41
34
42 def test_tcp_lifecycle(self):
35 def test_tcp_lifecycle(self):
43 km = self._get_tcp_km()
36 km = self._get_tcp_km()
44 self._run_lifecycle(km)
37 self._run_lifecycle(km)
45
38
46 @dec.skip_win32
39 @dec.skip_win32
47 def test_ipc_lifecycle(self):
40 def test_ipc_lifecycle(self):
48 km = self._get_ipc_km()
41 km = self._get_ipc_km()
49 self._run_lifecycle(km)
42 self._run_lifecycle(km)
50
43
51 def test_get_connect_info(self):
44 def test_get_connect_info(self):
52 km = self._get_tcp_km()
45 km = self._get_tcp_km()
53 cinfo = km.get_connection_info()
46 cinfo = km.get_connection_info()
54 keys = sorted(cinfo.keys())
47 keys = sorted(cinfo.keys())
55 expected = sorted([
48 expected = sorted([
56 'ip', 'transport',
49 'ip', 'transport',
57 'hb_port', 'shell_port', 'stdin_port', 'iopub_port', 'control_port',
50 'hb_port', 'shell_port', 'stdin_port', 'iopub_port', 'control_port',
58 'key', 'signature_scheme',
51 'key', 'signature_scheme',
59 ])
52 ])
60 self.assertEqual(keys, expected)
53 self.assertEqual(keys, expected)
61
54
@@ -1,484 +1,433 b''
1 """Test suite for our zeromq-based messaging specification.
1 """Test suite for our zeromq-based message specification.
2 """
2 """
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Copyright (C) 2010-2011 The IPython Development Team
4 # Copyright (C) 2010 The IPython Development Team
5 #
5 #
6 # Distributed under the terms of the BSD License. The full license is in
6 # Distributed under the terms of the BSD License. The full license is in
7 # the file COPYING.txt, distributed as part of this software.
7 # the file COPYING.txt, distributed as part of this software.
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9
9
10 import re
10 import re
11 from subprocess import PIPE
11 from subprocess import PIPE
12 from Queue import Empty
12 from Queue import Empty
13
13
14 import nose.tools as nt
14 import nose.tools as nt
15
15
16 from IPython.kernel import KernelManager
16 from IPython.kernel import KernelManager
17
17
18 from IPython.utils.traitlets import (
18 from IPython.utils.traitlets import (
19 HasTraits, TraitError, Bool, Unicode, Dict, Integer, List, Enum, Any,
19 HasTraits, TraitError, Bool, Unicode, Dict, Integer, List, Enum, Any,
20 )
20 )
21
21
22 from .utils import TIMEOUT, start_global_kernel, flush_channels, execute
23
22 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
23 # Global setup and utilities
25 # Globals
24 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
25
27 KC = None
26 STARTUP_TIMEOUT = 60
27 TIMEOUT = 15
28
28
29 def setup():
29 def setup():
30 global KM, KC
30 global KC
31 KM = KernelManager()
31 KC = start_global_kernel()
32 KM.start_kernel(stdout=PIPE, stderr=PIPE)
33 KC = KM.client()
34 KC.start_channels()
35
36 # wait for kernel to be ready
37 try:
38 msg = KC.iopub_channel.get_msg(block=True, timeout=STARTUP_TIMEOUT)
39 except Empty:
40 pass
41 msg_id = KC.kernel_info()
42 KC.get_shell_msg(block=True, timeout=STARTUP_TIMEOUT)
43 flush_channels()
44
45
46 def teardown():
47 KC.stop_channels()
48 KM.shutdown_kernel()
49
50
51 def flush_channels(kc=None):
52 """flush any messages waiting on the queue"""
53 if kc is None:
54 kc = KC
55 for channel in (kc.shell_channel, kc.iopub_channel):
56 while True:
57 try:
58 msg = channel.get_msg(block=True, timeout=0.1)
59 except Empty:
60 break
61 else:
62 validate_message(msg)
63
64
65 def execute(code='', kc=None, **kwargs):
66 """wrapper for doing common steps for validating an execution request"""
67 if kc is None:
68 kc = KC
69 msg_id = kc.execute(code=code, **kwargs)
70 reply = kc.get_shell_msg(timeout=TIMEOUT)
71 validate_message(reply, 'execute_reply', msg_id)
72 busy = kc.get_iopub_msg(timeout=TIMEOUT)
73 validate_message(busy, 'status', msg_id)
74 nt.assert_equal(busy['content']['execution_state'], 'busy')
75
76 if not kwargs.get('silent'):
77 pyin = kc.get_iopub_msg(timeout=TIMEOUT)
78 validate_message(pyin, 'pyin', msg_id)
79 nt.assert_equal(pyin['content']['code'], code)
80
81 return msg_id, reply['content']
82
32
83 #-----------------------------------------------------------------------------
33 #-----------------------------------------------------------------------------
84 # MSG Spec References
34 # Message Spec References
85 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
86
36
87
88 class Reference(HasTraits):
37 class Reference(HasTraits):
89
38
90 """
39 """
91 Base class for message spec specification testing.
40 Base class for message spec specification testing.
92
41
93 This class is the core of the message specification test. The
42 This class is the core of the message specification test. The
94 idea is that child classes implement trait attributes for each
43 idea is that child classes implement trait attributes for each
95 message keys, so that message keys can be tested against these
44 message keys, so that message keys can be tested against these
96 traits using :meth:`check` method.
45 traits using :meth:`check` method.
97
46
98 """
47 """
99
48
100 def check(self, d):
49 def check(self, d):
101 """validate a dict against our traits"""
50 """validate a dict against our traits"""
102 for key in self.trait_names():
51 for key in self.trait_names():
103 nt.assert_in(key, d)
52 nt.assert_in(key, d)
104 # FIXME: always allow None, probably not a good idea
53 # FIXME: always allow None, probably not a good idea
105 if d[key] is None:
54 if d[key] is None:
106 continue
55 continue
107 try:
56 try:
108 setattr(self, key, d[key])
57 setattr(self, key, d[key])
109 except TraitError as e:
58 except TraitError as e:
110 nt.assert_true(False, str(e))
59 nt.assert_true(False, str(e))
111
60
112
61
113 class RMessage(Reference):
62 class RMessage(Reference):
114 msg_id = Unicode()
63 msg_id = Unicode()
115 msg_type = Unicode()
64 msg_type = Unicode()
116 header = Dict()
65 header = Dict()
117 parent_header = Dict()
66 parent_header = Dict()
118 content = Dict()
67 content = Dict()
119
68
120 class RHeader(Reference):
69 class RHeader(Reference):
121 msg_id = Unicode()
70 msg_id = Unicode()
122 msg_type = Unicode()
71 msg_type = Unicode()
123 session = Unicode()
72 session = Unicode()
124 username = Unicode()
73 username = Unicode()
125
74
126 class RContent(Reference):
75 class RContent(Reference):
127 status = Enum((u'ok', u'error'))
76 status = Enum((u'ok', u'error'))
128
77
129
78
130 class ExecuteReply(Reference):
79 class ExecuteReply(Reference):
131 execution_count = Integer()
80 execution_count = Integer()
132 status = Enum((u'ok', u'error'))
81 status = Enum((u'ok', u'error'))
133
82
134 def check(self, d):
83 def check(self, d):
135 Reference.check(self, d)
84 Reference.check(self, d)
136 if d['status'] == 'ok':
85 if d['status'] == 'ok':
137 ExecuteReplyOkay().check(d)
86 ExecuteReplyOkay().check(d)
138 elif d['status'] == 'error':
87 elif d['status'] == 'error':
139 ExecuteReplyError().check(d)
88 ExecuteReplyError().check(d)
140
89
141
90
142 class ExecuteReplyOkay(Reference):
91 class ExecuteReplyOkay(Reference):
143 payload = List(Dict)
92 payload = List(Dict)
144 user_variables = Dict()
93 user_variables = Dict()
145 user_expressions = Dict()
94 user_expressions = Dict()
146
95
147
96
148 class ExecuteReplyError(Reference):
97 class ExecuteReplyError(Reference):
149 ename = Unicode()
98 ename = Unicode()
150 evalue = Unicode()
99 evalue = Unicode()
151 traceback = List(Unicode)
100 traceback = List(Unicode)
152
101
153
102
154 class OInfoReply(Reference):
103 class OInfoReply(Reference):
155 name = Unicode()
104 name = Unicode()
156 found = Bool()
105 found = Bool()
157 ismagic = Bool()
106 ismagic = Bool()
158 isalias = Bool()
107 isalias = Bool()
159 namespace = Enum((u'builtin', u'magics', u'alias', u'Interactive'))
108 namespace = Enum((u'builtin', u'magics', u'alias', u'Interactive'))
160 type_name = Unicode()
109 type_name = Unicode()
161 string_form = Unicode()
110 string_form = Unicode()
162 base_class = Unicode()
111 base_class = Unicode()
163 length = Integer()
112 length = Integer()
164 file = Unicode()
113 file = Unicode()
165 definition = Unicode()
114 definition = Unicode()
166 argspec = Dict()
115 argspec = Dict()
167 init_definition = Unicode()
116 init_definition = Unicode()
168 docstring = Unicode()
117 docstring = Unicode()
169 init_docstring = Unicode()
118 init_docstring = Unicode()
170 class_docstring = Unicode()
119 class_docstring = Unicode()
171 call_def = Unicode()
120 call_def = Unicode()
172 call_docstring = Unicode()
121 call_docstring = Unicode()
173 source = Unicode()
122 source = Unicode()
174
123
175 def check(self, d):
124 def check(self, d):
176 Reference.check(self, d)
125 Reference.check(self, d)
177 if d['argspec'] is not None:
126 if d['argspec'] is not None:
178 ArgSpec().check(d['argspec'])
127 ArgSpec().check(d['argspec'])
179
128
180
129
181 class ArgSpec(Reference):
130 class ArgSpec(Reference):
182 args = List(Unicode)
131 args = List(Unicode)
183 varargs = Unicode()
132 varargs = Unicode()
184 varkw = Unicode()
133 varkw = Unicode()
185 defaults = List()
134 defaults = List()
186
135
187
136
188 class Status(Reference):
137 class Status(Reference):
189 execution_state = Enum((u'busy', u'idle', u'starting'))
138 execution_state = Enum((u'busy', u'idle', u'starting'))
190
139
191
140
192 class CompleteReply(Reference):
141 class CompleteReply(Reference):
193 matches = List(Unicode)
142 matches = List(Unicode)
194
143
195
144
196 def Version(num, trait=Integer):
145 def Version(num, trait=Integer):
197 return List(trait, default_value=[0] * num, minlen=num, maxlen=num)
146 return List(trait, default_value=[0] * num, minlen=num, maxlen=num)
198
147
199
148
200 class KernelInfoReply(Reference):
149 class KernelInfoReply(Reference):
201
150
202 protocol_version = Version(2)
151 protocol_version = Version(2)
203 ipython_version = Version(4, Any)
152 ipython_version = Version(4, Any)
204 language_version = Version(3)
153 language_version = Version(3)
205 language = Unicode()
154 language = Unicode()
206
155
207 def _ipython_version_changed(self, name, old, new):
156 def _ipython_version_changed(self, name, old, new):
208 for v in new:
157 for v in new:
209 assert isinstance(v, int) or isinstance(v, basestring), \
158 assert isinstance(v, int) or isinstance(v, basestring), \
210 'expected int or string as version component, got {0!r}'.format(v)
159 'expected int or string as version component, got {0!r}'.format(v)
211
160
212
161
213 # IOPub messages
162 # IOPub messages
214
163
215 class PyIn(Reference):
164 class PyIn(Reference):
216 code = Unicode()
165 code = Unicode()
217 execution_count = Integer()
166 execution_count = Integer()
218
167
219
168
220 PyErr = ExecuteReplyError
169 PyErr = ExecuteReplyError
221
170
222
171
223 class Stream(Reference):
172 class Stream(Reference):
224 name = Enum((u'stdout', u'stderr'))
173 name = Enum((u'stdout', u'stderr'))
225 data = Unicode()
174 data = Unicode()
226
175
227
176
228 mime_pat = re.compile(r'\w+/\w+')
177 mime_pat = re.compile(r'\w+/\w+')
229
178
230 class DisplayData(Reference):
179 class DisplayData(Reference):
231 source = Unicode()
180 source = Unicode()
232 metadata = Dict()
181 metadata = Dict()
233 data = Dict()
182 data = Dict()
234 def _data_changed(self, name, old, new):
183 def _data_changed(self, name, old, new):
235 for k,v in new.iteritems():
184 for k,v in new.iteritems():
236 assert mime_pat.match(k)
185 assert mime_pat.match(k)
237 nt.assert_is_instance(v, basestring)
186 nt.assert_is_instance(v, basestring)
238
187
239
188
240 class PyOut(Reference):
189 class PyOut(Reference):
241 execution_count = Integer()
190 execution_count = Integer()
242 data = Dict()
191 data = Dict()
243 def _data_changed(self, name, old, new):
192 def _data_changed(self, name, old, new):
244 for k,v in new.iteritems():
193 for k,v in new.iteritems():
245 assert mime_pat.match(k)
194 assert mime_pat.match(k)
246 nt.assert_is_instance(v, basestring)
195 nt.assert_is_instance(v, basestring)
247
196
248
197
249 references = {
198 references = {
250 'execute_reply' : ExecuteReply(),
199 'execute_reply' : ExecuteReply(),
251 'object_info_reply' : OInfoReply(),
200 'object_info_reply' : OInfoReply(),
252 'status' : Status(),
201 'status' : Status(),
253 'complete_reply' : CompleteReply(),
202 'complete_reply' : CompleteReply(),
254 'kernel_info_reply': KernelInfoReply(),
203 'kernel_info_reply': KernelInfoReply(),
255 'pyin' : PyIn(),
204 'pyin' : PyIn(),
256 'pyout' : PyOut(),
205 'pyout' : PyOut(),
257 'pyerr' : PyErr(),
206 'pyerr' : PyErr(),
258 'stream' : Stream(),
207 'stream' : Stream(),
259 'display_data' : DisplayData(),
208 'display_data' : DisplayData(),
260 }
209 }
261 """
210 """
262 Specifications of `content` part of the reply messages.
211 Specifications of `content` part of the reply messages.
263 """
212 """
264
213
265
214
266 def validate_message(msg, msg_type=None, parent=None):
215 def validate_message(msg, msg_type=None, parent=None):
267 """validate a message
216 """validate a message
268
217
269 This is a generator, and must be iterated through to actually
218 This is a generator, and must be iterated through to actually
270 trigger each test.
219 trigger each test.
271
220
272 If msg_type and/or parent are given, the msg_type and/or parent msg_id
221 If msg_type and/or parent are given, the msg_type and/or parent msg_id
273 are compared with the given values.
222 are compared with the given values.
274 """
223 """
275 RMessage().check(msg)
224 RMessage().check(msg)
276 if msg_type:
225 if msg_type:
277 nt.assert_equal(msg['msg_type'], msg_type)
226 nt.assert_equal(msg['msg_type'], msg_type)
278 if parent:
227 if parent:
279 nt.assert_equal(msg['parent_header']['msg_id'], parent)
228 nt.assert_equal(msg['parent_header']['msg_id'], parent)
280 content = msg['content']
229 content = msg['content']
281 ref = references[msg['msg_type']]
230 ref = references[msg['msg_type']]
282 ref.check(content)
231 ref.check(content)
283
232
284
233
285 #-----------------------------------------------------------------------------
234 #-----------------------------------------------------------------------------
286 # Tests
235 # Tests
287 #-----------------------------------------------------------------------------
236 #-----------------------------------------------------------------------------
288
237
289 # Shell channel
238 # Shell channel
290
239
291 def test_execute():
240 def test_execute():
292 flush_channels()
241 flush_channels()
293
242
294 msg_id = KC.execute(code='x=1')
243 msg_id = KC.execute(code='x=1')
295 reply = KC.get_shell_msg(timeout=TIMEOUT)
244 reply = KC.get_shell_msg(timeout=TIMEOUT)
296 validate_message(reply, 'execute_reply', msg_id)
245 validate_message(reply, 'execute_reply', msg_id)
297
246
298
247
299 def test_execute_silent():
248 def test_execute_silent():
300 flush_channels()
249 flush_channels()
301 msg_id, reply = execute(code='x=1', silent=True)
250 msg_id, reply = execute(code='x=1', silent=True)
302
251
303 # flush status=idle
252 # flush status=idle
304 status = KC.iopub_channel.get_msg(timeout=TIMEOUT)
253 status = KC.iopub_channel.get_msg(timeout=TIMEOUT)
305 validate_message(status, 'status', msg_id)
254 validate_message(status, 'status', msg_id)
306 nt.assert_equal(status['content']['execution_state'], 'idle')
255 nt.assert_equal(status['content']['execution_state'], 'idle')
307
256
308 nt.assert_raises(Empty, KC.iopub_channel.get_msg, timeout=0.1)
257 nt.assert_raises(Empty, KC.iopub_channel.get_msg, timeout=0.1)
309 count = reply['execution_count']
258 count = reply['execution_count']
310
259
311 msg_id, reply = execute(code='x=2', silent=True)
260 msg_id, reply = execute(code='x=2', silent=True)
312
261
313 # flush status=idle
262 # flush status=idle
314 status = KC.iopub_channel.get_msg(timeout=TIMEOUT)
263 status = KC.iopub_channel.get_msg(timeout=TIMEOUT)
315 validate_message(status, 'status', msg_id)
264 validate_message(status, 'status', msg_id)
316 nt.assert_equal(status['content']['execution_state'], 'idle')
265 nt.assert_equal(status['content']['execution_state'], 'idle')
317
266
318 nt.assert_raises(Empty, KC.iopub_channel.get_msg, timeout=0.1)
267 nt.assert_raises(Empty, KC.iopub_channel.get_msg, timeout=0.1)
319 count_2 = reply['execution_count']
268 count_2 = reply['execution_count']
320 nt.assert_equal(count_2, count)
269 nt.assert_equal(count_2, count)
321
270
322
271
323 def test_execute_error():
272 def test_execute_error():
324 flush_channels()
273 flush_channels()
325
274
326 msg_id, reply = execute(code='1/0')
275 msg_id, reply = execute(code='1/0')
327 nt.assert_equal(reply['status'], 'error')
276 nt.assert_equal(reply['status'], 'error')
328 nt.assert_equal(reply['ename'], 'ZeroDivisionError')
277 nt.assert_equal(reply['ename'], 'ZeroDivisionError')
329
278
330 pyerr = KC.iopub_channel.get_msg(timeout=TIMEOUT)
279 pyerr = KC.iopub_channel.get_msg(timeout=TIMEOUT)
331 validate_message(pyerr, 'pyerr', msg_id)
280 validate_message(pyerr, 'pyerr', msg_id)
332
281
333
282
334 def test_execute_inc():
283 def test_execute_inc():
335 """execute request should increment execution_count"""
284 """execute request should increment execution_count"""
336 flush_channels()
285 flush_channels()
337
286
338 msg_id, reply = execute(code='x=1')
287 msg_id, reply = execute(code='x=1')
339 count = reply['execution_count']
288 count = reply['execution_count']
340
289
341 flush_channels()
290 flush_channels()
342
291
343 msg_id, reply = execute(code='x=2')
292 msg_id, reply = execute(code='x=2')
344 count_2 = reply['execution_count']
293 count_2 = reply['execution_count']
345 nt.assert_equal(count_2, count+1)
294 nt.assert_equal(count_2, count+1)
346
295
347
296
348 def test_user_variables():
297 def test_user_variables():
349 flush_channels()
298 flush_channels()
350
299
351 msg_id, reply = execute(code='x=1', user_variables=['x'])
300 msg_id, reply = execute(code='x=1', user_variables=['x'])
352 user_variables = reply['user_variables']
301 user_variables = reply['user_variables']
353 nt.assert_equal(user_variables, {u'x': {
302 nt.assert_equal(user_variables, {u'x': {
354 u'status': u'ok',
303 u'status': u'ok',
355 u'data': {u'text/plain': u'1'},
304 u'data': {u'text/plain': u'1'},
356 u'metadata': {},
305 u'metadata': {},
357 }})
306 }})
358
307
359
308
360 def test_user_variables_fail():
309 def test_user_variables_fail():
361 flush_channels()
310 flush_channels()
362
311
363 msg_id, reply = execute(code='x=1', user_variables=['nosuchname'])
312 msg_id, reply = execute(code='x=1', user_variables=['nosuchname'])
364 user_variables = reply['user_variables']
313 user_variables = reply['user_variables']
365 foo = user_variables['nosuchname']
314 foo = user_variables['nosuchname']
366 nt.assert_equal(foo['status'], 'error')
315 nt.assert_equal(foo['status'], 'error')
367 nt.assert_equal(foo['ename'], 'KeyError')
316 nt.assert_equal(foo['ename'], 'KeyError')
368
317
369
318
370 def test_user_expressions():
319 def test_user_expressions():
371 flush_channels()
320 flush_channels()
372
321
373 msg_id, reply = execute(code='x=1', user_expressions=dict(foo='x+1'))
322 msg_id, reply = execute(code='x=1', user_expressions=dict(foo='x+1'))
374 user_expressions = reply['user_expressions']
323 user_expressions = reply['user_expressions']
375 nt.assert_equal(user_expressions, {u'foo': {
324 nt.assert_equal(user_expressions, {u'foo': {
376 u'status': u'ok',
325 u'status': u'ok',
377 u'data': {u'text/plain': u'2'},
326 u'data': {u'text/plain': u'2'},
378 u'metadata': {},
327 u'metadata': {},
379 }})
328 }})
380
329
381
330
382 def test_user_expressions_fail():
331 def test_user_expressions_fail():
383 flush_channels()
332 flush_channels()
384
333
385 msg_id, reply = execute(code='x=0', user_expressions=dict(foo='nosuchname'))
334 msg_id, reply = execute(code='x=0', user_expressions=dict(foo='nosuchname'))
386 user_expressions = reply['user_expressions']
335 user_expressions = reply['user_expressions']
387 foo = user_expressions['foo']
336 foo = user_expressions['foo']
388 nt.assert_equal(foo['status'], 'error')
337 nt.assert_equal(foo['status'], 'error')
389 nt.assert_equal(foo['ename'], 'NameError')
338 nt.assert_equal(foo['ename'], 'NameError')
390
339
391
340
392 def test_oinfo():
341 def test_oinfo():
393 flush_channels()
342 flush_channels()
394
343
395 msg_id = KC.object_info('a')
344 msg_id = KC.object_info('a')
396 reply = KC.get_shell_msg(timeout=TIMEOUT)
345 reply = KC.get_shell_msg(timeout=TIMEOUT)
397 validate_message(reply, 'object_info_reply', msg_id)
346 validate_message(reply, 'object_info_reply', msg_id)
398
347
399
348
400 def test_oinfo_found():
349 def test_oinfo_found():
401 flush_channels()
350 flush_channels()
402
351
403 msg_id, reply = execute(code='a=5')
352 msg_id, reply = execute(code='a=5')
404
353
405 msg_id = KC.object_info('a')
354 msg_id = KC.object_info('a')
406 reply = KC.get_shell_msg(timeout=TIMEOUT)
355 reply = KC.get_shell_msg(timeout=TIMEOUT)
407 validate_message(reply, 'object_info_reply', msg_id)
356 validate_message(reply, 'object_info_reply', msg_id)
408 content = reply['content']
357 content = reply['content']
409 assert content['found']
358 assert content['found']
410 argspec = content['argspec']
359 argspec = content['argspec']
411 nt.assert_is(argspec, None)
360 nt.assert_is(argspec, None)
412
361
413
362
414 def test_oinfo_detail():
363 def test_oinfo_detail():
415 flush_channels()
364 flush_channels()
416
365
417 msg_id, reply = execute(code='ip=get_ipython()')
366 msg_id, reply = execute(code='ip=get_ipython()')
418
367
419 msg_id = KC.object_info('ip.object_inspect', detail_level=2)
368 msg_id = KC.object_info('ip.object_inspect', detail_level=2)
420 reply = KC.get_shell_msg(timeout=TIMEOUT)
369 reply = KC.get_shell_msg(timeout=TIMEOUT)
421 validate_message(reply, 'object_info_reply', msg_id)
370 validate_message(reply, 'object_info_reply', msg_id)
422 content = reply['content']
371 content = reply['content']
423 assert content['found']
372 assert content['found']
424 argspec = content['argspec']
373 argspec = content['argspec']
425 nt.assert_is_instance(argspec, dict, "expected non-empty argspec dict, got %r" % argspec)
374 nt.assert_is_instance(argspec, dict, "expected non-empty argspec dict, got %r" % argspec)
426 nt.assert_equal(argspec['defaults'], [0])
375 nt.assert_equal(argspec['defaults'], [0])
427
376
428
377
429 def test_oinfo_not_found():
378 def test_oinfo_not_found():
430 flush_channels()
379 flush_channels()
431
380
432 msg_id = KC.object_info('dne')
381 msg_id = KC.object_info('dne')
433 reply = KC.get_shell_msg(timeout=TIMEOUT)
382 reply = KC.get_shell_msg(timeout=TIMEOUT)
434 validate_message(reply, 'object_info_reply', msg_id)
383 validate_message(reply, 'object_info_reply', msg_id)
435 content = reply['content']
384 content = reply['content']
436 nt.assert_false(content['found'])
385 nt.assert_false(content['found'])
437
386
438
387
439 def test_complete():
388 def test_complete():
440 flush_channels()
389 flush_channels()
441
390
442 msg_id, reply = execute(code="alpha = albert = 5")
391 msg_id, reply = execute(code="alpha = albert = 5")
443
392
444 msg_id = KC.complete('al', 'al', 2)
393 msg_id = KC.complete('al', 'al', 2)
445 reply = KC.get_shell_msg(timeout=TIMEOUT)
394 reply = KC.get_shell_msg(timeout=TIMEOUT)
446 validate_message(reply, 'complete_reply', msg_id)
395 validate_message(reply, 'complete_reply', msg_id)
447 matches = reply['content']['matches']
396 matches = reply['content']['matches']
448 for name in ('alpha', 'albert'):
397 for name in ('alpha', 'albert'):
449 nt.assert_in(name, matches)
398 nt.assert_in(name, matches)
450
399
451
400
452 def test_kernel_info_request():
401 def test_kernel_info_request():
453 flush_channels()
402 flush_channels()
454
403
455 msg_id = KC.kernel_info()
404 msg_id = KC.kernel_info()
456 reply = KC.get_shell_msg(timeout=TIMEOUT)
405 reply = KC.get_shell_msg(timeout=TIMEOUT)
457 validate_message(reply, 'kernel_info_reply', msg_id)
406 validate_message(reply, 'kernel_info_reply', msg_id)
458
407
459
408
460 # IOPub channel
409 # IOPub channel
461
410
462
411
463 def test_stream():
412 def test_stream():
464 flush_channels()
413 flush_channels()
465
414
466 msg_id, reply = execute("print('hi')")
415 msg_id, reply = execute("print('hi')")
467
416
468 stdout = KC.iopub_channel.get_msg(timeout=TIMEOUT)
417 stdout = KC.iopub_channel.get_msg(timeout=TIMEOUT)
469 validate_message(stdout, 'stream', msg_id)
418 validate_message(stdout, 'stream', msg_id)
470 content = stdout['content']
419 content = stdout['content']
471 nt.assert_equal(content['name'], u'stdout')
420 nt.assert_equal(content['name'], u'stdout')
472 nt.assert_equal(content['data'], u'hi\n')
421 nt.assert_equal(content['data'], u'hi\n')
473
422
474
423
475 def test_display_data():
424 def test_display_data():
476 flush_channels()
425 flush_channels()
477
426
478 msg_id, reply = execute("from IPython.core.display import display; display(1)")
427 msg_id, reply = execute("from IPython.core.display import display; display(1)")
479
428
480 display = KC.iopub_channel.get_msg(timeout=TIMEOUT)
429 display = KC.iopub_channel.get_msg(timeout=TIMEOUT)
481 validate_message(display, 'display_data', parent=msg_id)
430 validate_message(display, 'display_data', parent=msg_id)
482 data = display['content']['data']
431 data = display['content']['data']
483 nt.assert_equal(data['text/plain'], u'1')
432 nt.assert_equal(data['text/plain'], u'1')
484
433
@@ -1,85 +1,78 b''
1 """Tests for the notebook kernel and session manager."""
1 """Tests for the notebook kernel and session manager."""
2
2
3 from subprocess import PIPE
3 from subprocess import PIPE
4 import time
4 import time
5 from unittest import TestCase
5 from unittest import TestCase
6
6
7 from IPython.testing import decorators as dec
7 from IPython.testing import decorators as dec
8
8
9 from IPython.config.loader import Config
9 from IPython.config.loader import Config
10 from IPython.utils.localinterfaces import LOCALHOST
10 from IPython.utils.localinterfaces import LOCALHOST
11 from IPython.kernel import KernelManager
11 from IPython.kernel import KernelManager
12 from IPython.kernel.multikernelmanager import MultiKernelManager
12 from IPython.kernel.multikernelmanager import MultiKernelManager
13
13
14 class TestKernelManager(TestCase):
14 class TestKernelManager(TestCase):
15
15
16 def _get_tcp_km(self):
16 def _get_tcp_km(self):
17 c = Config()
17 c = Config()
18 km = MultiKernelManager(config=c)
18 km = MultiKernelManager(config=c)
19 return km
19 return km
20
20
21 def _get_ipc_km(self):
21 def _get_ipc_km(self):
22 c = Config()
22 c = Config()
23 c.KernelManager.transport = 'ipc'
23 c.KernelManager.transport = 'ipc'
24 c.KernelManager.ip = 'test'
24 c.KernelManager.ip = 'test'
25 km = MultiKernelManager(config=c)
25 km = MultiKernelManager(config=c)
26 return km
26 return km
27
27
28 def _run_lifecycle(self, km):
28 def _run_lifecycle(self, km):
29 kid = km.start_kernel(stdout=PIPE, stderr=PIPE)
29 kid = km.start_kernel(stdout=PIPE, stderr=PIPE)
30 self.assertTrue(km.is_alive(kid))
30 self.assertTrue(km.is_alive(kid))
31 self.assertTrue(kid in km)
31 self.assertTrue(kid in km)
32 self.assertTrue(kid in km.list_kernel_ids())
32 self.assertTrue(kid in km.list_kernel_ids())
33 self.assertEqual(len(km),1)
33 self.assertEqual(len(km),1)
34 km.restart_kernel(kid)
34 km.restart_kernel(kid, now=True)
35 self.assertTrue(km.is_alive(kid))
35 self.assertTrue(km.is_alive(kid))
36 self.assertTrue(kid in km.list_kernel_ids())
36 self.assertTrue(kid in km.list_kernel_ids())
37 # We need a delay here to give the restarting kernel a chance to
38 # restart. Otherwise, the interrupt will kill it, causing the test
39 # suite to hang. The reason it *hangs* is that the shutdown
40 # message for the restart sometimes hasn't been sent to the kernel.
41 # Because linger is oo on the shell channel, the context can't
42 # close until the message is sent to the kernel, which is not dead.
43 time.sleep(1.0)
44 km.interrupt_kernel(kid)
37 km.interrupt_kernel(kid)
45 k = km.get_kernel(kid)
38 k = km.get_kernel(kid)
46 self.assertTrue(isinstance(k, KernelManager))
39 self.assertTrue(isinstance(k, KernelManager))
47 km.shutdown_kernel(kid)
40 km.shutdown_kernel(kid, now=True)
48 self.assertTrue(not kid in km)
41 self.assertTrue(not kid in km)
49
42
50 def _run_cinfo(self, km, transport, ip):
43 def _run_cinfo(self, km, transport, ip):
51 kid = km.start_kernel(stdout=PIPE, stderr=PIPE)
44 kid = km.start_kernel(stdout=PIPE, stderr=PIPE)
52 k = km.get_kernel(kid)
45 k = km.get_kernel(kid)
53 cinfo = km.get_connection_info(kid)
46 cinfo = km.get_connection_info(kid)
54 self.assertEqual(transport, cinfo['transport'])
47 self.assertEqual(transport, cinfo['transport'])
55 self.assertEqual(ip, cinfo['ip'])
48 self.assertEqual(ip, cinfo['ip'])
56 self.assertTrue('stdin_port' in cinfo)
49 self.assertTrue('stdin_port' in cinfo)
57 self.assertTrue('iopub_port' in cinfo)
50 self.assertTrue('iopub_port' in cinfo)
58 stream = km.connect_iopub(kid)
51 stream = km.connect_iopub(kid)
59 stream.close()
52 stream.close()
60 self.assertTrue('shell_port' in cinfo)
53 self.assertTrue('shell_port' in cinfo)
61 stream = km.connect_shell(kid)
54 stream = km.connect_shell(kid)
62 stream.close()
55 stream.close()
63 self.assertTrue('hb_port' in cinfo)
56 self.assertTrue('hb_port' in cinfo)
64 stream = km.connect_hb(kid)
57 stream = km.connect_hb(kid)
65 stream.close()
58 stream.close()
66 km.shutdown_kernel(kid)
59 km.shutdown_kernel(kid, now=True)
67
60
68 def test_tcp_lifecycle(self):
61 def test_tcp_lifecycle(self):
69 km = self._get_tcp_km()
62 km = self._get_tcp_km()
70 self._run_lifecycle(km)
63 self._run_lifecycle(km)
71
64
72 def test_tcp_cinfo(self):
65 def test_tcp_cinfo(self):
73 km = self._get_tcp_km()
66 km = self._get_tcp_km()
74 self._run_cinfo(km, 'tcp', LOCALHOST)
67 self._run_cinfo(km, 'tcp', LOCALHOST)
75
68
76 @dec.skip_win32
69 @dec.skip_win32
77 def test_ipc_lifecycle(self):
70 def test_ipc_lifecycle(self):
78 km = self._get_ipc_km()
71 km = self._get_ipc_km()
79 self._run_lifecycle(km)
72 self._run_lifecycle(km)
80
73
81 @dec.skip_win32
74 @dec.skip_win32
82 def test_ipc_cinfo(self):
75 def test_ipc_cinfo(self):
83 km = self._get_ipc_km()
76 km = self._get_ipc_km()
84 self._run_cinfo(km, 'ipc', 'test')
77 self._run_cinfo(km, 'ipc', 'test')
85
78
General Comments 0
You need to be logged in to leave comments. Login now