##// END OF EJS Templates
Readd warning at import time.
Matthias Bussonnier -
Show More
@@ -1,659 +1,664 b''
1 # coding: utf-8
1 # coding: utf-8
2 """
2 """
3 Deprecated since IPython 5.0
3 Deprecated since IPython 5.0
4
4
5 Inputhook management for GUI event loop integration.
5 Inputhook management for GUI event loop integration.
6 """
6 """
7
7
8 # Copyright (c) IPython Development Team.
8 # Copyright (c) IPython Development Team.
9 # Distributed under the terms of the Modified BSD License.
9 # Distributed under the terms of the Modified BSD License.
10
10
11 try:
11 try:
12 import ctypes
12 import ctypes
13 except ImportError:
13 except ImportError:
14 ctypes = None
14 ctypes = None
15 except SystemError: # IronPython issue, 2/8/2014
15 except SystemError: # IronPython issue, 2/8/2014
16 ctypes = None
16 ctypes = None
17 import os
17 import os
18 import platform
18 import platform
19 import sys
19 import sys
20 from distutils.version import LooseVersion as V
20 from distutils.version import LooseVersion as V
21
21
22 from warnings import warn
22 from warnings import warn
23
23
24
25 warn("`IPython.lib.inputhook` is deprecated since IPython 5.0 and will be removed in future versions.",
26 DeprecationWarning, stacklevel=2)
27
28
24 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
25 # Constants
30 # Constants
26 #-----------------------------------------------------------------------------
31 #-----------------------------------------------------------------------------
27
32
28 # Constants for identifying the GUI toolkits.
33 # Constants for identifying the GUI toolkits.
29 GUI_WX = 'wx'
34 GUI_WX = 'wx'
30 GUI_QT = 'qt'
35 GUI_QT = 'qt'
31 GUI_QT4 = 'qt4'
36 GUI_QT4 = 'qt4'
32 GUI_GTK = 'gtk'
37 GUI_GTK = 'gtk'
33 GUI_TK = 'tk'
38 GUI_TK = 'tk'
34 GUI_OSX = 'osx'
39 GUI_OSX = 'osx'
35 GUI_GLUT = 'glut'
40 GUI_GLUT = 'glut'
36 GUI_PYGLET = 'pyglet'
41 GUI_PYGLET = 'pyglet'
37 GUI_GTK3 = 'gtk3'
42 GUI_GTK3 = 'gtk3'
38 GUI_NONE = 'none' # i.e. disable
43 GUI_NONE = 'none' # i.e. disable
39
44
40 #-----------------------------------------------------------------------------
45 #-----------------------------------------------------------------------------
41 # Utilities
46 # Utilities
42 #-----------------------------------------------------------------------------
47 #-----------------------------------------------------------------------------
43
48
44 def _stdin_ready_posix():
49 def _stdin_ready_posix():
45 """Return True if there's something to read on stdin (posix version)."""
50 """Return True if there's something to read on stdin (posix version)."""
46 infds, outfds, erfds = select.select([sys.stdin],[],[],0)
51 infds, outfds, erfds = select.select([sys.stdin],[],[],0)
47 return bool(infds)
52 return bool(infds)
48
53
49 def _stdin_ready_nt():
54 def _stdin_ready_nt():
50 """Return True if there's something to read on stdin (nt version)."""
55 """Return True if there's something to read on stdin (nt version)."""
51 return msvcrt.kbhit()
56 return msvcrt.kbhit()
52
57
53 def _stdin_ready_other():
58 def _stdin_ready_other():
54 """Return True, assuming there's something to read on stdin."""
59 """Return True, assuming there's something to read on stdin."""
55 return True
60 return True
56
61
57 def _use_appnope():
62 def _use_appnope():
58 """Should we use appnope for dealing with OS X app nap?
63 """Should we use appnope for dealing with OS X app nap?
59
64
60 Checks if we are on OS X 10.9 or greater.
65 Checks if we are on OS X 10.9 or greater.
61 """
66 """
62 return sys.platform == 'darwin' and V(platform.mac_ver()[0]) >= V('10.9')
67 return sys.platform == 'darwin' and V(platform.mac_ver()[0]) >= V('10.9')
63
68
64 def _ignore_CTRL_C_posix():
69 def _ignore_CTRL_C_posix():
65 """Ignore CTRL+C (SIGINT)."""
70 """Ignore CTRL+C (SIGINT)."""
66 signal.signal(signal.SIGINT, signal.SIG_IGN)
71 signal.signal(signal.SIGINT, signal.SIG_IGN)
67
72
68 def _allow_CTRL_C_posix():
73 def _allow_CTRL_C_posix():
69 """Take CTRL+C into account (SIGINT)."""
74 """Take CTRL+C into account (SIGINT)."""
70 signal.signal(signal.SIGINT, signal.default_int_handler)
75 signal.signal(signal.SIGINT, signal.default_int_handler)
71
76
72 def _ignore_CTRL_C_other():
77 def _ignore_CTRL_C_other():
73 """Ignore CTRL+C (not implemented)."""
78 """Ignore CTRL+C (not implemented)."""
74 pass
79 pass
75
80
76 def _allow_CTRL_C_other():
81 def _allow_CTRL_C_other():
77 """Take CTRL+C into account (not implemented)."""
82 """Take CTRL+C into account (not implemented)."""
78 pass
83 pass
79
84
80 if os.name == 'posix':
85 if os.name == 'posix':
81 import select
86 import select
82 import signal
87 import signal
83 stdin_ready = _stdin_ready_posix
88 stdin_ready = _stdin_ready_posix
84 ignore_CTRL_C = _ignore_CTRL_C_posix
89 ignore_CTRL_C = _ignore_CTRL_C_posix
85 allow_CTRL_C = _allow_CTRL_C_posix
90 allow_CTRL_C = _allow_CTRL_C_posix
86 elif os.name == 'nt':
91 elif os.name == 'nt':
87 import msvcrt
92 import msvcrt
88 stdin_ready = _stdin_ready_nt
93 stdin_ready = _stdin_ready_nt
89 ignore_CTRL_C = _ignore_CTRL_C_other
94 ignore_CTRL_C = _ignore_CTRL_C_other
90 allow_CTRL_C = _allow_CTRL_C_other
95 allow_CTRL_C = _allow_CTRL_C_other
91 else:
96 else:
92 stdin_ready = _stdin_ready_other
97 stdin_ready = _stdin_ready_other
93 ignore_CTRL_C = _ignore_CTRL_C_other
98 ignore_CTRL_C = _ignore_CTRL_C_other
94 allow_CTRL_C = _allow_CTRL_C_other
99 allow_CTRL_C = _allow_CTRL_C_other
95
100
96
101
97 #-----------------------------------------------------------------------------
102 #-----------------------------------------------------------------------------
98 # Main InputHookManager class
103 # Main InputHookManager class
99 #-----------------------------------------------------------------------------
104 #-----------------------------------------------------------------------------
100
105
101
106
102 class InputHookManager(object):
107 class InputHookManager(object):
103 """DEPRECATED since IPython 5.0
108 """DEPRECATED since IPython 5.0
104
109
105 Manage PyOS_InputHook for different GUI toolkits.
110 Manage PyOS_InputHook for different GUI toolkits.
106
111
107 This class installs various hooks under ``PyOSInputHook`` to handle
112 This class installs various hooks under ``PyOSInputHook`` to handle
108 GUI event loop integration.
113 GUI event loop integration.
109 """
114 """
110
115
111 def __init__(self):
116 def __init__(self):
112 if ctypes is None:
117 if ctypes is None:
113 warn("IPython GUI event loop requires ctypes, %gui will not be available")
118 warn("IPython GUI event loop requires ctypes, %gui will not be available")
114 else:
119 else:
115 self.PYFUNC = ctypes.PYFUNCTYPE(ctypes.c_int)
120 self.PYFUNC = ctypes.PYFUNCTYPE(ctypes.c_int)
116 self.guihooks = {}
121 self.guihooks = {}
117 self.aliases = {}
122 self.aliases = {}
118 self.apps = {}
123 self.apps = {}
119 self._reset()
124 self._reset()
120
125
121 def _reset(self):
126 def _reset(self):
122 self._callback_pyfunctype = None
127 self._callback_pyfunctype = None
123 self._callback = None
128 self._callback = None
124 self._installed = False
129 self._installed = False
125 self._current_gui = None
130 self._current_gui = None
126
131
127 def get_pyos_inputhook(self):
132 def get_pyos_inputhook(self):
128 """DEPRECATED since IPython 5.0
133 """DEPRECATED since IPython 5.0
129
134
130 Return the current PyOS_InputHook as a ctypes.c_void_p."""
135 Return the current PyOS_InputHook as a ctypes.c_void_p."""
131 warn("`get_pyos_inputhook` is deprecated since IPython 5.0 and will be removed in future versions.",
136 warn("`get_pyos_inputhook` is deprecated since IPython 5.0 and will be removed in future versions.",
132 DeprecationWarning, stacklevel=2)
137 DeprecationWarning, stacklevel=2)
133 return ctypes.c_void_p.in_dll(ctypes.pythonapi,"PyOS_InputHook")
138 return ctypes.c_void_p.in_dll(ctypes.pythonapi,"PyOS_InputHook")
134
139
135 def get_pyos_inputhook_as_func(self):
140 def get_pyos_inputhook_as_func(self):
136 """DEPRECATED since IPython 5.0
141 """DEPRECATED since IPython 5.0
137
142
138 Return the current PyOS_InputHook as a ctypes.PYFUNCYPE."""
143 Return the current PyOS_InputHook as a ctypes.PYFUNCYPE."""
139 warn("`get_pyos_inputhook_as_func` is deprecated since IPython 5.0 and will be removed in future versions.",
144 warn("`get_pyos_inputhook_as_func` is deprecated since IPython 5.0 and will be removed in future versions.",
140 DeprecationWarning, stacklevel=2)
145 DeprecationWarning, stacklevel=2)
141 return self.PYFUNC.in_dll(ctypes.pythonapi,"PyOS_InputHook")
146 return self.PYFUNC.in_dll(ctypes.pythonapi,"PyOS_InputHook")
142
147
143 def set_inputhook(self, callback):
148 def set_inputhook(self, callback):
144 """DEPRECATED since IPython 5.0
149 """DEPRECATED since IPython 5.0
145
150
146 Set PyOS_InputHook to callback and return the previous one."""
151 Set PyOS_InputHook to callback and return the previous one."""
147 # On platforms with 'readline' support, it's all too likely to
152 # On platforms with 'readline' support, it's all too likely to
148 # have a KeyboardInterrupt signal delivered *even before* an
153 # have a KeyboardInterrupt signal delivered *even before* an
149 # initial ``try:`` clause in the callback can be executed, so
154 # initial ``try:`` clause in the callback can be executed, so
150 # we need to disable CTRL+C in this situation.
155 # we need to disable CTRL+C in this situation.
151 ignore_CTRL_C()
156 ignore_CTRL_C()
152 self._callback = callback
157 self._callback = callback
153 self._callback_pyfunctype = self.PYFUNC(callback)
158 self._callback_pyfunctype = self.PYFUNC(callback)
154 pyos_inputhook_ptr = self.get_pyos_inputhook()
159 pyos_inputhook_ptr = self.get_pyos_inputhook()
155 original = self.get_pyos_inputhook_as_func()
160 original = self.get_pyos_inputhook_as_func()
156 pyos_inputhook_ptr.value = \
161 pyos_inputhook_ptr.value = \
157 ctypes.cast(self._callback_pyfunctype, ctypes.c_void_p).value
162 ctypes.cast(self._callback_pyfunctype, ctypes.c_void_p).value
158 self._installed = True
163 self._installed = True
159 return original
164 return original
160
165
161 def clear_inputhook(self, app=None):
166 def clear_inputhook(self, app=None):
162 """DEPRECATED since IPython 5.0
167 """DEPRECATED since IPython 5.0
163
168
164 Set PyOS_InputHook to NULL and return the previous one.
169 Set PyOS_InputHook to NULL and return the previous one.
165
170
166 Parameters
171 Parameters
167 ----------
172 ----------
168 app : optional, ignored
173 app : optional, ignored
169 This parameter is allowed only so that clear_inputhook() can be
174 This parameter is allowed only so that clear_inputhook() can be
170 called with a similar interface as all the ``enable_*`` methods. But
175 called with a similar interface as all the ``enable_*`` methods. But
171 the actual value of the parameter is ignored. This uniform interface
176 the actual value of the parameter is ignored. This uniform interface
172 makes it easier to have user-level entry points in the main IPython
177 makes it easier to have user-level entry points in the main IPython
173 app like :meth:`enable_gui`."""
178 app like :meth:`enable_gui`."""
174 warn("`clear_inputhook` is deprecated since IPython 5.0 and will be removed in future versions.",
179 warn("`clear_inputhook` is deprecated since IPython 5.0 and will be removed in future versions.",
175 DeprecationWarning, stacklevel=2)
180 DeprecationWarning, stacklevel=2)
176 pyos_inputhook_ptr = self.get_pyos_inputhook()
181 pyos_inputhook_ptr = self.get_pyos_inputhook()
177 original = self.get_pyos_inputhook_as_func()
182 original = self.get_pyos_inputhook_as_func()
178 pyos_inputhook_ptr.value = ctypes.c_void_p(None).value
183 pyos_inputhook_ptr.value = ctypes.c_void_p(None).value
179 allow_CTRL_C()
184 allow_CTRL_C()
180 self._reset()
185 self._reset()
181 return original
186 return original
182
187
183 def clear_app_refs(self, gui=None):
188 def clear_app_refs(self, gui=None):
184 """DEPRECATED since IPython 5.0
189 """DEPRECATED since IPython 5.0
185
190
186 Clear IPython's internal reference to an application instance.
191 Clear IPython's internal reference to an application instance.
187
192
188 Whenever we create an app for a user on qt4 or wx, we hold a
193 Whenever we create an app for a user on qt4 or wx, we hold a
189 reference to the app. This is needed because in some cases bad things
194 reference to the app. This is needed because in some cases bad things
190 can happen if a user doesn't hold a reference themselves. This
195 can happen if a user doesn't hold a reference themselves. This
191 method is provided to clear the references we are holding.
196 method is provided to clear the references we are holding.
192
197
193 Parameters
198 Parameters
194 ----------
199 ----------
195 gui : None or str
200 gui : None or str
196 If None, clear all app references. If ('wx', 'qt4') clear
201 If None, clear all app references. If ('wx', 'qt4') clear
197 the app for that toolkit. References are not held for gtk or tk
202 the app for that toolkit. References are not held for gtk or tk
198 as those toolkits don't have the notion of an app.
203 as those toolkits don't have the notion of an app.
199 """
204 """
200 warn("`clear_app_refs` is deprecated since IPython 5.0 and will be removed in future versions.",
205 warn("`clear_app_refs` is deprecated since IPython 5.0 and will be removed in future versions.",
201 DeprecationWarning, stacklevel=2)
206 DeprecationWarning, stacklevel=2)
202 if gui is None:
207 if gui is None:
203 self.apps = {}
208 self.apps = {}
204 elif gui in self.apps:
209 elif gui in self.apps:
205 del self.apps[gui]
210 del self.apps[gui]
206
211
207 def register(self, toolkitname, *aliases):
212 def register(self, toolkitname, *aliases):
208 """DEPRECATED since IPython 5.0
213 """DEPRECATED since IPython 5.0
209
214
210 Register a class to provide the event loop for a given GUI.
215 Register a class to provide the event loop for a given GUI.
211
216
212 This is intended to be used as a class decorator. It should be passed
217 This is intended to be used as a class decorator. It should be passed
213 the names with which to register this GUI integration. The classes
218 the names with which to register this GUI integration. The classes
214 themselves should subclass :class:`InputHookBase`.
219 themselves should subclass :class:`InputHookBase`.
215
220
216 ::
221 ::
217
222
218 @inputhook_manager.register('qt')
223 @inputhook_manager.register('qt')
219 class QtInputHook(InputHookBase):
224 class QtInputHook(InputHookBase):
220 def enable(self, app=None):
225 def enable(self, app=None):
221 ...
226 ...
222 """
227 """
223 warn("`register` is deprecated since IPython 5.0 and will be removed in future versions.",
228 warn("`register` is deprecated since IPython 5.0 and will be removed in future versions.",
224 DeprecationWarning, stacklevel=2)
229 DeprecationWarning, stacklevel=2)
225 def decorator(cls):
230 def decorator(cls):
226 if ctypes is not None:
231 if ctypes is not None:
227 inst = cls(self)
232 inst = cls(self)
228 self.guihooks[toolkitname] = inst
233 self.guihooks[toolkitname] = inst
229 for a in aliases:
234 for a in aliases:
230 self.aliases[a] = toolkitname
235 self.aliases[a] = toolkitname
231 return cls
236 return cls
232 return decorator
237 return decorator
233
238
234 def current_gui(self):
239 def current_gui(self):
235 """DEPRECATED since IPython 5.0
240 """DEPRECATED since IPython 5.0
236
241
237 Return a string indicating the currently active GUI or None."""
242 Return a string indicating the currently active GUI or None."""
238 warn("`current_gui` is deprecated since IPython 5.0 and will be removed in future versions.",
243 warn("`current_gui` is deprecated since IPython 5.0 and will be removed in future versions.",
239 DeprecationWarning, stacklevel=2)
244 DeprecationWarning, stacklevel=2)
240 return self._current_gui
245 return self._current_gui
241
246
242 def enable_gui(self, gui=None, app=None):
247 def enable_gui(self, gui=None, app=None):
243 """DEPRECATED since IPython 5.0
248 """DEPRECATED since IPython 5.0
244
249
245 Switch amongst GUI input hooks by name.
250 Switch amongst GUI input hooks by name.
246
251
247 This is a higher level method than :meth:`set_inputhook` - it uses the
252 This is a higher level method than :meth:`set_inputhook` - it uses the
248 GUI name to look up a registered object which enables the input hook
253 GUI name to look up a registered object which enables the input hook
249 for that GUI.
254 for that GUI.
250
255
251 Parameters
256 Parameters
252 ----------
257 ----------
253 gui : optional, string or None
258 gui : optional, string or None
254 If None (or 'none'), clears input hook, otherwise it must be one
259 If None (or 'none'), clears input hook, otherwise it must be one
255 of the recognized GUI names (see ``GUI_*`` constants in module).
260 of the recognized GUI names (see ``GUI_*`` constants in module).
256
261
257 app : optional, existing application object.
262 app : optional, existing application object.
258 For toolkits that have the concept of a global app, you can supply an
263 For toolkits that have the concept of a global app, you can supply an
259 existing one. If not given, the toolkit will be probed for one, and if
264 existing one. If not given, the toolkit will be probed for one, and if
260 none is found, a new one will be created. Note that GTK does not have
265 none is found, a new one will be created. Note that GTK does not have
261 this concept, and passing an app if ``gui=="GTK"`` will raise an error.
266 this concept, and passing an app if ``gui=="GTK"`` will raise an error.
262
267
263 Returns
268 Returns
264 -------
269 -------
265 The output of the underlying gui switch routine, typically the actual
270 The output of the underlying gui switch routine, typically the actual
266 PyOS_InputHook wrapper object or the GUI toolkit app created, if there was
271 PyOS_InputHook wrapper object or the GUI toolkit app created, if there was
267 one.
272 one.
268 """
273 """
269 warn("`enable_gui` is deprecated since IPython 5.0 and will be removed in future versions.",
274 warn("`enable_gui` is deprecated since IPython 5.0 and will be removed in future versions.",
270 DeprecationWarning, stacklevel=2)
275 DeprecationWarning, stacklevel=2)
271 if gui in (None, GUI_NONE):
276 if gui in (None, GUI_NONE):
272 return self.disable_gui()
277 return self.disable_gui()
273
278
274 if gui in self.aliases:
279 if gui in self.aliases:
275 return self.enable_gui(self.aliases[gui], app)
280 return self.enable_gui(self.aliases[gui], app)
276
281
277 try:
282 try:
278 gui_hook = self.guihooks[gui]
283 gui_hook = self.guihooks[gui]
279 except KeyError:
284 except KeyError:
280 e = "Invalid GUI request {!r}, valid ones are: {}"
285 e = "Invalid GUI request {!r}, valid ones are: {}"
281 raise ValueError(e.format(gui, ', '.join(self.guihooks)))
286 raise ValueError(e.format(gui, ', '.join(self.guihooks)))
282 self._current_gui = gui
287 self._current_gui = gui
283
288
284 app = gui_hook.enable(app)
289 app = gui_hook.enable(app)
285 if app is not None:
290 if app is not None:
286 app._in_event_loop = True
291 app._in_event_loop = True
287 self.apps[gui] = app
292 self.apps[gui] = app
288 return app
293 return app
289
294
290 def disable_gui(self):
295 def disable_gui(self):
291 """DEPRECATED since IPython 5.0
296 """DEPRECATED since IPython 5.0
292
297
293 Disable GUI event loop integration.
298 Disable GUI event loop integration.
294
299
295 If an application was registered, this sets its ``_in_event_loop``
300 If an application was registered, this sets its ``_in_event_loop``
296 attribute to False. It then calls :meth:`clear_inputhook`.
301 attribute to False. It then calls :meth:`clear_inputhook`.
297 """
302 """
298 warn("`disable_gui` is deprecated since IPython 5.0 and will be removed in future versions.",
303 warn("`disable_gui` is deprecated since IPython 5.0 and will be removed in future versions.",
299 DeprecationWarning, stacklevel=2)
304 DeprecationWarning, stacklevel=2)
300 gui = self._current_gui
305 gui = self._current_gui
301 if gui in self.apps:
306 if gui in self.apps:
302 self.apps[gui]._in_event_loop = False
307 self.apps[gui]._in_event_loop = False
303 return self.clear_inputhook()
308 return self.clear_inputhook()
304
309
305 class InputHookBase(object):
310 class InputHookBase(object):
306 """DEPRECATED since IPython 5.0
311 """DEPRECATED since IPython 5.0
307
312
308 Base class for input hooks for specific toolkits.
313 Base class for input hooks for specific toolkits.
309
314
310 Subclasses should define an :meth:`enable` method with one argument, ``app``,
315 Subclasses should define an :meth:`enable` method with one argument, ``app``,
311 which will either be an instance of the toolkit's application class, or None.
316 which will either be an instance of the toolkit's application class, or None.
312 They may also define a :meth:`disable` method with no arguments.
317 They may also define a :meth:`disable` method with no arguments.
313 """
318 """
314 def __init__(self, manager):
319 def __init__(self, manager):
315 self.manager = manager
320 self.manager = manager
316
321
317 def disable(self):
322 def disable(self):
318 pass
323 pass
319
324
320 inputhook_manager = InputHookManager()
325 inputhook_manager = InputHookManager()
321
326
322 @inputhook_manager.register('osx')
327 @inputhook_manager.register('osx')
323 class NullInputHook(InputHookBase):
328 class NullInputHook(InputHookBase):
324 """DEPRECATED since IPython 5.0
329 """DEPRECATED since IPython 5.0
325
330
326 A null inputhook that doesn't need to do anything"""
331 A null inputhook that doesn't need to do anything"""
327 def enable(self, app=None):
332 def enable(self, app=None):
328 warn("This function is deprecated since IPython 5.0 and will be removed in future versions.",
333 warn("This function is deprecated since IPython 5.0 and will be removed in future versions.",
329 DeprecationWarning, stacklevel=2)
334 DeprecationWarning, stacklevel=2)
330
335
331 @inputhook_manager.register('wx')
336 @inputhook_manager.register('wx')
332 class WxInputHook(InputHookBase):
337 class WxInputHook(InputHookBase):
333 def enable(self, app=None):
338 def enable(self, app=None):
334 """DEPRECATED since IPython 5.0
339 """DEPRECATED since IPython 5.0
335
340
336 Enable event loop integration with wxPython.
341 Enable event loop integration with wxPython.
337
342
338 Parameters
343 Parameters
339 ----------
344 ----------
340 app : WX Application, optional.
345 app : WX Application, optional.
341 Running application to use. If not given, we probe WX for an
346 Running application to use. If not given, we probe WX for an
342 existing application object, and create a new one if none is found.
347 existing application object, and create a new one if none is found.
343
348
344 Notes
349 Notes
345 -----
350 -----
346 This methods sets the ``PyOS_InputHook`` for wxPython, which allows
351 This methods sets the ``PyOS_InputHook`` for wxPython, which allows
347 the wxPython to integrate with terminal based applications like
352 the wxPython to integrate with terminal based applications like
348 IPython.
353 IPython.
349
354
350 If ``app`` is not given we probe for an existing one, and return it if
355 If ``app`` is not given we probe for an existing one, and return it if
351 found. If no existing app is found, we create an :class:`wx.App` as
356 found. If no existing app is found, we create an :class:`wx.App` as
352 follows::
357 follows::
353
358
354 import wx
359 import wx
355 app = wx.App(redirect=False, clearSigInt=False)
360 app = wx.App(redirect=False, clearSigInt=False)
356 """
361 """
357 warn("This function is deprecated since IPython 5.0 and will be removed in future versions.",
362 warn("This function is deprecated since IPython 5.0 and will be removed in future versions.",
358 DeprecationWarning, stacklevel=2)
363 DeprecationWarning, stacklevel=2)
359 import wx
364 import wx
360
365
361 wx_version = V(wx.__version__).version
366 wx_version = V(wx.__version__).version
362
367
363 if wx_version < [2, 8]:
368 if wx_version < [2, 8]:
364 raise ValueError("requires wxPython >= 2.8, but you have %s" % wx.__version__)
369 raise ValueError("requires wxPython >= 2.8, but you have %s" % wx.__version__)
365
370
366 from IPython.lib.inputhookwx import inputhook_wx
371 from IPython.lib.inputhookwx import inputhook_wx
367 self.manager.set_inputhook(inputhook_wx)
372 self.manager.set_inputhook(inputhook_wx)
368 if _use_appnope():
373 if _use_appnope():
369 from appnope import nope
374 from appnope import nope
370 nope()
375 nope()
371
376
372 import wx
377 import wx
373 if app is None:
378 if app is None:
374 app = wx.GetApp()
379 app = wx.GetApp()
375 if app is None:
380 if app is None:
376 app = wx.App(redirect=False, clearSigInt=False)
381 app = wx.App(redirect=False, clearSigInt=False)
377
382
378 return app
383 return app
379
384
380 def disable(self):
385 def disable(self):
381 """DEPRECATED since IPython 5.0
386 """DEPRECATED since IPython 5.0
382
387
383 Disable event loop integration with wxPython.
388 Disable event loop integration with wxPython.
384
389
385 This restores appnapp on OS X
390 This restores appnapp on OS X
386 """
391 """
387 warn("This function is deprecated since IPython 5.0 and will be removed in future versions.",
392 warn("This function is deprecated since IPython 5.0 and will be removed in future versions.",
388 DeprecationWarning, stacklevel=2)
393 DeprecationWarning, stacklevel=2)
389 if _use_appnope():
394 if _use_appnope():
390 from appnope import nap
395 from appnope import nap
391 nap()
396 nap()
392
397
393 @inputhook_manager.register('qt', 'qt4')
398 @inputhook_manager.register('qt', 'qt4')
394 class Qt4InputHook(InputHookBase):
399 class Qt4InputHook(InputHookBase):
395 def enable(self, app=None):
400 def enable(self, app=None):
396 """DEPRECATED since IPython 5.0
401 """DEPRECATED since IPython 5.0
397
402
398 Enable event loop integration with PyQt4.
403 Enable event loop integration with PyQt4.
399
404
400 Parameters
405 Parameters
401 ----------
406 ----------
402 app : Qt Application, optional.
407 app : Qt Application, optional.
403 Running application to use. If not given, we probe Qt for an
408 Running application to use. If not given, we probe Qt for an
404 existing application object, and create a new one if none is found.
409 existing application object, and create a new one if none is found.
405
410
406 Notes
411 Notes
407 -----
412 -----
408 This methods sets the PyOS_InputHook for PyQt4, which allows
413 This methods sets the PyOS_InputHook for PyQt4, which allows
409 the PyQt4 to integrate with terminal based applications like
414 the PyQt4 to integrate with terminal based applications like
410 IPython.
415 IPython.
411
416
412 If ``app`` is not given we probe for an existing one, and return it if
417 If ``app`` is not given we probe for an existing one, and return it if
413 found. If no existing app is found, we create an :class:`QApplication`
418 found. If no existing app is found, we create an :class:`QApplication`
414 as follows::
419 as follows::
415
420
416 from PyQt4 import QtCore
421 from PyQt4 import QtCore
417 app = QtGui.QApplication(sys.argv)
422 app = QtGui.QApplication(sys.argv)
418 """
423 """
419 warn("This function is deprecated since IPython 5.0 and will be removed in future versions.",
424 warn("This function is deprecated since IPython 5.0 and will be removed in future versions.",
420 DeprecationWarning, stacklevel=2)
425 DeprecationWarning, stacklevel=2)
421 from IPython.lib.inputhookqt4 import create_inputhook_qt4
426 from IPython.lib.inputhookqt4 import create_inputhook_qt4
422 app, inputhook_qt4 = create_inputhook_qt4(self.manager, app)
427 app, inputhook_qt4 = create_inputhook_qt4(self.manager, app)
423 self.manager.set_inputhook(inputhook_qt4)
428 self.manager.set_inputhook(inputhook_qt4)
424 if _use_appnope():
429 if _use_appnope():
425 from appnope import nope
430 from appnope import nope
426 nope()
431 nope()
427
432
428 return app
433 return app
429
434
430 def disable_qt4(self):
435 def disable_qt4(self):
431 """DEPRECATED since IPython 5.0
436 """DEPRECATED since IPython 5.0
432
437
433 Disable event loop integration with PyQt4.
438 Disable event loop integration with PyQt4.
434
439
435 This restores appnapp on OS X
440 This restores appnapp on OS X
436 """
441 """
437 warn("This function is deprecated since IPython 5.0 and will be removed in future versions.",
442 warn("This function is deprecated since IPython 5.0 and will be removed in future versions.",
438 DeprecationWarning, stacklevel=2)
443 DeprecationWarning, stacklevel=2)
439 if _use_appnope():
444 if _use_appnope():
440 from appnope import nap
445 from appnope import nap
441 nap()
446 nap()
442
447
443
448
444 @inputhook_manager.register('qt5')
449 @inputhook_manager.register('qt5')
445 class Qt5InputHook(Qt4InputHook):
450 class Qt5InputHook(Qt4InputHook):
446 def enable(self, app=None):
451 def enable(self, app=None):
447 warn("This function is deprecated since IPython 5.0 and will be removed in future versions.",
452 warn("This function is deprecated since IPython 5.0 and will be removed in future versions.",
448 DeprecationWarning, stacklevel=2)
453 DeprecationWarning, stacklevel=2)
449 os.environ['QT_API'] = 'pyqt5'
454 os.environ['QT_API'] = 'pyqt5'
450 return Qt4InputHook.enable(self, app)
455 return Qt4InputHook.enable(self, app)
451
456
452
457
453 @inputhook_manager.register('gtk')
458 @inputhook_manager.register('gtk')
454 class GtkInputHook(InputHookBase):
459 class GtkInputHook(InputHookBase):
455 def enable(self, app=None):
460 def enable(self, app=None):
456 """DEPRECATED since IPython 5.0
461 """DEPRECATED since IPython 5.0
457
462
458 Enable event loop integration with PyGTK.
463 Enable event loop integration with PyGTK.
459
464
460 Parameters
465 Parameters
461 ----------
466 ----------
462 app : ignored
467 app : ignored
463 Ignored, it's only a placeholder to keep the call signature of all
468 Ignored, it's only a placeholder to keep the call signature of all
464 gui activation methods consistent, which simplifies the logic of
469 gui activation methods consistent, which simplifies the logic of
465 supporting magics.
470 supporting magics.
466
471
467 Notes
472 Notes
468 -----
473 -----
469 This methods sets the PyOS_InputHook for PyGTK, which allows
474 This methods sets the PyOS_InputHook for PyGTK, which allows
470 the PyGTK to integrate with terminal based applications like
475 the PyGTK to integrate with terminal based applications like
471 IPython.
476 IPython.
472 """
477 """
473 warn("This function is deprecated since IPython 5.0 and will be removed in future versions.",
478 warn("This function is deprecated since IPython 5.0 and will be removed in future versions.",
474 DeprecationWarning, stacklevel=2)
479 DeprecationWarning, stacklevel=2)
475 import gtk
480 import gtk
476 try:
481 try:
477 gtk.set_interactive(True)
482 gtk.set_interactive(True)
478 except AttributeError:
483 except AttributeError:
479 # For older versions of gtk, use our own ctypes version
484 # For older versions of gtk, use our own ctypes version
480 from IPython.lib.inputhookgtk import inputhook_gtk
485 from IPython.lib.inputhookgtk import inputhook_gtk
481 self.manager.set_inputhook(inputhook_gtk)
486 self.manager.set_inputhook(inputhook_gtk)
482
487
483
488
484 @inputhook_manager.register('tk')
489 @inputhook_manager.register('tk')
485 class TkInputHook(InputHookBase):
490 class TkInputHook(InputHookBase):
486 def enable(self, app=None):
491 def enable(self, app=None):
487 """DEPRECATED since IPython 5.0
492 """DEPRECATED since IPython 5.0
488
493
489 Enable event loop integration with Tk.
494 Enable event loop integration with Tk.
490
495
491 Parameters
496 Parameters
492 ----------
497 ----------
493 app : toplevel :class:`Tkinter.Tk` widget, optional.
498 app : toplevel :class:`Tkinter.Tk` widget, optional.
494 Running toplevel widget to use. If not given, we probe Tk for an
499 Running toplevel widget to use. If not given, we probe Tk for an
495 existing one, and create a new one if none is found.
500 existing one, and create a new one if none is found.
496
501
497 Notes
502 Notes
498 -----
503 -----
499 If you have already created a :class:`Tkinter.Tk` object, the only
504 If you have already created a :class:`Tkinter.Tk` object, the only
500 thing done by this method is to register with the
505 thing done by this method is to register with the
501 :class:`InputHookManager`, since creating that object automatically
506 :class:`InputHookManager`, since creating that object automatically
502 sets ``PyOS_InputHook``.
507 sets ``PyOS_InputHook``.
503 """
508 """
504 warn("This function is deprecated since IPython 5.0 and will be removed in future versions.",
509 warn("This function is deprecated since IPython 5.0 and will be removed in future versions.",
505 DeprecationWarning, stacklevel=2)
510 DeprecationWarning, stacklevel=2)
506 if app is None:
511 if app is None:
507 try:
512 try:
508 from tkinter import Tk # Py 3
513 from tkinter import Tk # Py 3
509 except ImportError:
514 except ImportError:
510 from Tkinter import Tk # Py 2
515 from Tkinter import Tk # Py 2
511 app = Tk()
516 app = Tk()
512 app.withdraw()
517 app.withdraw()
513 self.manager.apps[GUI_TK] = app
518 self.manager.apps[GUI_TK] = app
514 return app
519 return app
515
520
516
521
517 @inputhook_manager.register('glut')
522 @inputhook_manager.register('glut')
518 class GlutInputHook(InputHookBase):
523 class GlutInputHook(InputHookBase):
519 def enable(self, app=None):
524 def enable(self, app=None):
520 """DEPRECATED since IPython 5.0
525 """DEPRECATED since IPython 5.0
521
526
522 Enable event loop integration with GLUT.
527 Enable event loop integration with GLUT.
523
528
524 Parameters
529 Parameters
525 ----------
530 ----------
526
531
527 app : ignored
532 app : ignored
528 Ignored, it's only a placeholder to keep the call signature of all
533 Ignored, it's only a placeholder to keep the call signature of all
529 gui activation methods consistent, which simplifies the logic of
534 gui activation methods consistent, which simplifies the logic of
530 supporting magics.
535 supporting magics.
531
536
532 Notes
537 Notes
533 -----
538 -----
534
539
535 This methods sets the PyOS_InputHook for GLUT, which allows the GLUT to
540 This methods sets the PyOS_InputHook for GLUT, which allows the GLUT to
536 integrate with terminal based applications like IPython. Due to GLUT
541 integrate with terminal based applications like IPython. Due to GLUT
537 limitations, it is currently not possible to start the event loop
542 limitations, it is currently not possible to start the event loop
538 without first creating a window. You should thus not create another
543 without first creating a window. You should thus not create another
539 window but use instead the created one. See 'gui-glut.py' in the
544 window but use instead the created one. See 'gui-glut.py' in the
540 docs/examples/lib directory.
545 docs/examples/lib directory.
541
546
542 The default screen mode is set to:
547 The default screen mode is set to:
543 glut.GLUT_DOUBLE | glut.GLUT_RGBA | glut.GLUT_DEPTH
548 glut.GLUT_DOUBLE | glut.GLUT_RGBA | glut.GLUT_DEPTH
544 """
549 """
545 warn("This function is deprecated since IPython 5.0 and will be removed in future versions.",
550 warn("This function is deprecated since IPython 5.0 and will be removed in future versions.",
546 DeprecationWarning, stacklevel=2)
551 DeprecationWarning, stacklevel=2)
547
552
548 import OpenGL.GLUT as glut
553 import OpenGL.GLUT as glut
549 from IPython.lib.inputhookglut import glut_display_mode, \
554 from IPython.lib.inputhookglut import glut_display_mode, \
550 glut_close, glut_display, \
555 glut_close, glut_display, \
551 glut_idle, inputhook_glut
556 glut_idle, inputhook_glut
552
557
553 if GUI_GLUT not in self.manager.apps:
558 if GUI_GLUT not in self.manager.apps:
554 glut.glutInit( sys.argv )
559 glut.glutInit( sys.argv )
555 glut.glutInitDisplayMode( glut_display_mode )
560 glut.glutInitDisplayMode( glut_display_mode )
556 # This is specific to freeglut
561 # This is specific to freeglut
557 if bool(glut.glutSetOption):
562 if bool(glut.glutSetOption):
558 glut.glutSetOption( glut.GLUT_ACTION_ON_WINDOW_CLOSE,
563 glut.glutSetOption( glut.GLUT_ACTION_ON_WINDOW_CLOSE,
559 glut.GLUT_ACTION_GLUTMAINLOOP_RETURNS )
564 glut.GLUT_ACTION_GLUTMAINLOOP_RETURNS )
560 glut.glutCreateWindow( sys.argv[0] )
565 glut.glutCreateWindow( sys.argv[0] )
561 glut.glutReshapeWindow( 1, 1 )
566 glut.glutReshapeWindow( 1, 1 )
562 glut.glutHideWindow( )
567 glut.glutHideWindow( )
563 glut.glutWMCloseFunc( glut_close )
568 glut.glutWMCloseFunc( glut_close )
564 glut.glutDisplayFunc( glut_display )
569 glut.glutDisplayFunc( glut_display )
565 glut.glutIdleFunc( glut_idle )
570 glut.glutIdleFunc( glut_idle )
566 else:
571 else:
567 glut.glutWMCloseFunc( glut_close )
572 glut.glutWMCloseFunc( glut_close )
568 glut.glutDisplayFunc( glut_display )
573 glut.glutDisplayFunc( glut_display )
569 glut.glutIdleFunc( glut_idle)
574 glut.glutIdleFunc( glut_idle)
570 self.manager.set_inputhook( inputhook_glut )
575 self.manager.set_inputhook( inputhook_glut )
571
576
572
577
573 def disable(self):
578 def disable(self):
574 """DEPRECATED since IPython 5.0
579 """DEPRECATED since IPython 5.0
575
580
576 Disable event loop integration with glut.
581 Disable event loop integration with glut.
577
582
578 This sets PyOS_InputHook to NULL and set the display function to a
583 This sets PyOS_InputHook to NULL and set the display function to a
579 dummy one and set the timer to a dummy timer that will be triggered
584 dummy one and set the timer to a dummy timer that will be triggered
580 very far in the future.
585 very far in the future.
581 """
586 """
582 warn("This function is deprecated since IPython 5.0 and will be removed in future versions.",
587 warn("This function is deprecated since IPython 5.0 and will be removed in future versions.",
583 DeprecationWarning, stacklevel=2)
588 DeprecationWarning, stacklevel=2)
584 import OpenGL.GLUT as glut
589 import OpenGL.GLUT as glut
585 from glut_support import glutMainLoopEvent
590 from glut_support import glutMainLoopEvent
586
591
587 glut.glutHideWindow() # This is an event to be processed below
592 glut.glutHideWindow() # This is an event to be processed below
588 glutMainLoopEvent()
593 glutMainLoopEvent()
589 super(GlutInputHook, self).disable()
594 super(GlutInputHook, self).disable()
590
595
591 @inputhook_manager.register('pyglet')
596 @inputhook_manager.register('pyglet')
592 class PygletInputHook(InputHookBase):
597 class PygletInputHook(InputHookBase):
593 def enable(self, app=None):
598 def enable(self, app=None):
594 """DEPRECATED since IPython 5.0
599 """DEPRECATED since IPython 5.0
595
600
596 Enable event loop integration with pyglet.
601 Enable event loop integration with pyglet.
597
602
598 Parameters
603 Parameters
599 ----------
604 ----------
600 app : ignored
605 app : ignored
601 Ignored, it's only a placeholder to keep the call signature of all
606 Ignored, it's only a placeholder to keep the call signature of all
602 gui activation methods consistent, which simplifies the logic of
607 gui activation methods consistent, which simplifies the logic of
603 supporting magics.
608 supporting magics.
604
609
605 Notes
610 Notes
606 -----
611 -----
607 This methods sets the ``PyOS_InputHook`` for pyglet, which allows
612 This methods sets the ``PyOS_InputHook`` for pyglet, which allows
608 pyglet to integrate with terminal based applications like
613 pyglet to integrate with terminal based applications like
609 IPython.
614 IPython.
610
615
611 """
616 """
612 warn("This function is deprecated since IPython 5.0 and will be removed in future versions.",
617 warn("This function is deprecated since IPython 5.0 and will be removed in future versions.",
613 DeprecationWarning, stacklevel=2)
618 DeprecationWarning, stacklevel=2)
614 from IPython.lib.inputhookpyglet import inputhook_pyglet
619 from IPython.lib.inputhookpyglet import inputhook_pyglet
615 self.manager.set_inputhook(inputhook_pyglet)
620 self.manager.set_inputhook(inputhook_pyglet)
616 return app
621 return app
617
622
618
623
619 @inputhook_manager.register('gtk3')
624 @inputhook_manager.register('gtk3')
620 class Gtk3InputHook(InputHookBase):
625 class Gtk3InputHook(InputHookBase):
621 def enable(self, app=None):
626 def enable(self, app=None):
622 """DEPRECATED since IPython 5.0
627 """DEPRECATED since IPython 5.0
623
628
624 Enable event loop integration with Gtk3 (gir bindings).
629 Enable event loop integration with Gtk3 (gir bindings).
625
630
626 Parameters
631 Parameters
627 ----------
632 ----------
628 app : ignored
633 app : ignored
629 Ignored, it's only a placeholder to keep the call signature of all
634 Ignored, it's only a placeholder to keep the call signature of all
630 gui activation methods consistent, which simplifies the logic of
635 gui activation methods consistent, which simplifies the logic of
631 supporting magics.
636 supporting magics.
632
637
633 Notes
638 Notes
634 -----
639 -----
635 This methods sets the PyOS_InputHook for Gtk3, which allows
640 This methods sets the PyOS_InputHook for Gtk3, which allows
636 the Gtk3 to integrate with terminal based applications like
641 the Gtk3 to integrate with terminal based applications like
637 IPython.
642 IPython.
638 """
643 """
639 warn("This function is deprecated since IPython 5.0 and will be removed in future versions.",
644 warn("This function is deprecated since IPython 5.0 and will be removed in future versions.",
640 DeprecationWarning, stacklevel=2)
645 DeprecationWarning, stacklevel=2)
641 from IPython.lib.inputhookgtk3 import inputhook_gtk3
646 from IPython.lib.inputhookgtk3 import inputhook_gtk3
642 self.manager.set_inputhook(inputhook_gtk3)
647 self.manager.set_inputhook(inputhook_gtk3)
643
648
644
649
645 clear_inputhook = inputhook_manager.clear_inputhook
650 clear_inputhook = inputhook_manager.clear_inputhook
646 set_inputhook = inputhook_manager.set_inputhook
651 set_inputhook = inputhook_manager.set_inputhook
647 current_gui = inputhook_manager.current_gui
652 current_gui = inputhook_manager.current_gui
648 clear_app_refs = inputhook_manager.clear_app_refs
653 clear_app_refs = inputhook_manager.clear_app_refs
649 enable_gui = inputhook_manager.enable_gui
654 enable_gui = inputhook_manager.enable_gui
650 disable_gui = inputhook_manager.disable_gui
655 disable_gui = inputhook_manager.disable_gui
651 register = inputhook_manager.register
656 register = inputhook_manager.register
652 guis = inputhook_manager.guihooks
657 guis = inputhook_manager.guihooks
653
658
654
659
655 def _deprecated_disable():
660 def _deprecated_disable():
656 warn("This function is deprecated since IPython 4.0 use disable_gui() instead", DeprecationWarning)
661 warn("This function is deprecated since IPython 4.0 use disable_gui() instead", DeprecationWarning)
657 inputhook_manager.disable_gui()
662 inputhook_manager.disable_gui()
658 disable_wx = disable_qt4 = disable_gtk = disable_gtk3 = disable_glut = \
663 disable_wx = disable_qt4 = disable_gtk = disable_gtk3 = disable_glut = \
659 disable_pyglet = disable_osx = _deprecated_disable
664 disable_pyglet = disable_osx = _deprecated_disable
@@ -1,436 +1,433 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """IPython Test Suite Runner.
2 """IPython Test Suite Runner.
3
3
4 This module provides a main entry point to a user script to test IPython
4 This module provides a main entry point to a user script to test IPython
5 itself from the command line. There are two ways of running this script:
5 itself from the command line. There are two ways of running this script:
6
6
7 1. With the syntax `iptest all`. This runs our entire test suite by
7 1. With the syntax `iptest all`. This runs our entire test suite by
8 calling this script (with different arguments) recursively. This
8 calling this script (with different arguments) recursively. This
9 causes modules and package to be tested in different processes, using nose
9 causes modules and package to be tested in different processes, using nose
10 or trial where appropriate.
10 or trial where appropriate.
11 2. With the regular nose syntax, like `iptest -vvs IPython`. In this form
11 2. With the regular nose syntax, like `iptest -vvs IPython`. In this form
12 the script simply calls nose, but with special command line flags and
12 the script simply calls nose, but with special command line flags and
13 plugins loaded.
13 plugins loaded.
14
14
15 """
15 """
16
16
17 # Copyright (c) IPython Development Team.
17 # Copyright (c) IPython Development Team.
18 # Distributed under the terms of the Modified BSD License.
18 # Distributed under the terms of the Modified BSD License.
19
19
20 from __future__ import print_function
20 from __future__ import print_function
21
21
22 import glob
22 import glob
23 from io import BytesIO
23 from io import BytesIO
24 import os
24 import os
25 import os.path as path
25 import os.path as path
26 import sys
26 import sys
27 from threading import Thread, Lock, Event
27 from threading import Thread, Lock, Event
28 import warnings
28 import warnings
29
29
30 import nose.plugins.builtin
30 import nose.plugins.builtin
31 from nose.plugins.xunit import Xunit
31 from nose.plugins.xunit import Xunit
32 from nose import SkipTest
32 from nose import SkipTest
33 from nose.core import TestProgram
33 from nose.core import TestProgram
34 from nose.plugins import Plugin
34 from nose.plugins import Plugin
35 from nose.util import safe_str
35 from nose.util import safe_str
36
36
37 from IPython import version_info
37 from IPython import version_info
38 from IPython.utils.py3compat import bytes_to_str
38 from IPython.utils.py3compat import bytes_to_str
39 from IPython.utils.importstring import import_item
39 from IPython.utils.importstring import import_item
40 from IPython.testing.plugin.ipdoctest import IPythonDoctest
40 from IPython.testing.plugin.ipdoctest import IPythonDoctest
41 from IPython.external.decorators import KnownFailure, knownfailureif
41 from IPython.external.decorators import KnownFailure, knownfailureif
42
42
43 pjoin = path.join
43 pjoin = path.join
44
44
45
45
46 # Enable printing all warnings raise by IPython's modules
46 # Enable printing all warnings raise by IPython's modules
47 warnings.filterwarnings('ignore', message='.*Matplotlib is building the font cache.*', category=UserWarning, module='.*')
47 warnings.filterwarnings('ignore', message='.*Matplotlib is building the font cache.*', category=UserWarning, module='.*')
48 if sys.version_info > (3,0):
48 if sys.version_info > (3,0):
49 warnings.filterwarnings('error', message='.*', category=ResourceWarning, module='.*')
49 warnings.filterwarnings('error', message='.*', category=ResourceWarning, module='.*')
50 warnings.filterwarnings('error', message=".*{'config': True}.*", category=DeprecationWarning, module='IPy.*')
50 warnings.filterwarnings('error', message=".*{'config': True}.*", category=DeprecationWarning, module='IPy.*')
51 warnings.filterwarnings('default', message='.*', category=Warning, module='IPy.*')
51 warnings.filterwarnings('default', message='.*', category=Warning, module='IPy.*')
52
52
53 if version_info < (6,):
53 if version_info < (6,):
54 # nose.tools renames all things from `camelCase` to `snake_case` which raise an
54 # nose.tools renames all things from `camelCase` to `snake_case` which raise an
55 # warning with the runner they also import from standard import library. (as of Dec 2015)
55 # warning with the runner they also import from standard import library. (as of Dec 2015)
56 # Ignore, let's revisit that in a couple of years for IPython 6.
56 # Ignore, let's revisit that in a couple of years for IPython 6.
57 warnings.filterwarnings('ignore', message='.*Please use assertEqual instead', category=Warning, module='IPython.*')
57 warnings.filterwarnings('ignore', message='.*Please use assertEqual instead', category=Warning, module='IPython.*')
58
58
59
59
60 # ------------------------------------------------------------------------------
60 # ------------------------------------------------------------------------------
61 # Monkeypatch Xunit to count known failures as skipped.
61 # Monkeypatch Xunit to count known failures as skipped.
62 # ------------------------------------------------------------------------------
62 # ------------------------------------------------------------------------------
63 def monkeypatch_xunit():
63 def monkeypatch_xunit():
64 try:
64 try:
65 knownfailureif(True)(lambda: None)()
65 knownfailureif(True)(lambda: None)()
66 except Exception as e:
66 except Exception as e:
67 KnownFailureTest = type(e)
67 KnownFailureTest = type(e)
68
68
69 def addError(self, test, err, capt=None):
69 def addError(self, test, err, capt=None):
70 if issubclass(err[0], KnownFailureTest):
70 if issubclass(err[0], KnownFailureTest):
71 err = (SkipTest,) + err[1:]
71 err = (SkipTest,) + err[1:]
72 return self.orig_addError(test, err, capt)
72 return self.orig_addError(test, err, capt)
73
73
74 Xunit.orig_addError = Xunit.addError
74 Xunit.orig_addError = Xunit.addError
75 Xunit.addError = addError
75 Xunit.addError = addError
76
76
77 #-----------------------------------------------------------------------------
77 #-----------------------------------------------------------------------------
78 # Check which dependencies are installed and greater than minimum version.
78 # Check which dependencies are installed and greater than minimum version.
79 #-----------------------------------------------------------------------------
79 #-----------------------------------------------------------------------------
80 def extract_version(mod):
80 def extract_version(mod):
81 return mod.__version__
81 return mod.__version__
82
82
83 def test_for(item, min_version=None, callback=extract_version):
83 def test_for(item, min_version=None, callback=extract_version):
84 """Test to see if item is importable, and optionally check against a minimum
84 """Test to see if item is importable, and optionally check against a minimum
85 version.
85 version.
86
86
87 If min_version is given, the default behavior is to check against the
87 If min_version is given, the default behavior is to check against the
88 `__version__` attribute of the item, but specifying `callback` allows you to
88 `__version__` attribute of the item, but specifying `callback` allows you to
89 extract the value you are interested in. e.g::
89 extract the value you are interested in. e.g::
90
90
91 In [1]: import sys
91 In [1]: import sys
92
92
93 In [2]: from IPython.testing.iptest import test_for
93 In [2]: from IPython.testing.iptest import test_for
94
94
95 In [3]: test_for('sys', (2,6), callback=lambda sys: sys.version_info)
95 In [3]: test_for('sys', (2,6), callback=lambda sys: sys.version_info)
96 Out[3]: True
96 Out[3]: True
97
97
98 """
98 """
99 try:
99 try:
100 check = import_item(item)
100 check = import_item(item)
101 except (ImportError, RuntimeError):
101 except (ImportError, RuntimeError):
102 # GTK reports Runtime error if it can't be initialized even if it's
102 # GTK reports Runtime error if it can't be initialized even if it's
103 # importable.
103 # importable.
104 return False
104 return False
105 else:
105 else:
106 if min_version:
106 if min_version:
107 if callback:
107 if callback:
108 # extra processing step to get version to compare
108 # extra processing step to get version to compare
109 check = callback(check)
109 check = callback(check)
110
110
111 return check >= min_version
111 return check >= min_version
112 else:
112 else:
113 return True
113 return True
114
114
115 # Global dict where we can store information on what we have and what we don't
115 # Global dict where we can store information on what we have and what we don't
116 # have available at test run time
116 # have available at test run time
117 have = {'matplotlib': test_for('matplotlib'),
117 have = {'matplotlib': test_for('matplotlib'),
118 'pygments': test_for('pygments'),
118 'pygments': test_for('pygments'),
119 'sqlite3': test_for('sqlite3')}
119 'sqlite3': test_for('sqlite3')}
120
120
121 #-----------------------------------------------------------------------------
121 #-----------------------------------------------------------------------------
122 # Test suite definitions
122 # Test suite definitions
123 #-----------------------------------------------------------------------------
123 #-----------------------------------------------------------------------------
124
124
125 test_group_names = ['core',
125 test_group_names = ['core',
126 'extensions', 'lib', 'terminal', 'testing', 'utils',
126 'extensions', 'lib', 'terminal', 'testing', 'utils',
127 ]
127 ]
128
128
129 class TestSection(object):
129 class TestSection(object):
130 def __init__(self, name, includes):
130 def __init__(self, name, includes):
131 self.name = name
131 self.name = name
132 self.includes = includes
132 self.includes = includes
133 self.excludes = []
133 self.excludes = []
134 self.dependencies = []
134 self.dependencies = []
135 self.enabled = True
135 self.enabled = True
136
136
137 def exclude(self, module):
137 def exclude(self, module):
138 if not module.startswith('IPython'):
138 if not module.startswith('IPython'):
139 module = self.includes[0] + "." + module
139 module = self.includes[0] + "." + module
140 self.excludes.append(module.replace('.', os.sep))
140 self.excludes.append(module.replace('.', os.sep))
141
141
142 def requires(self, *packages):
142 def requires(self, *packages):
143 self.dependencies.extend(packages)
143 self.dependencies.extend(packages)
144
144
145 @property
145 @property
146 def will_run(self):
146 def will_run(self):
147 return self.enabled and all(have[p] for p in self.dependencies)
147 return self.enabled and all(have[p] for p in self.dependencies)
148
148
149 # Name -> (include, exclude, dependencies_met)
149 # Name -> (include, exclude, dependencies_met)
150 test_sections = {n:TestSection(n, ['IPython.%s' % n]) for n in test_group_names}
150 test_sections = {n:TestSection(n, ['IPython.%s' % n]) for n in test_group_names}
151
151
152
152
153 # Exclusions and dependencies
153 # Exclusions and dependencies
154 # ---------------------------
154 # ---------------------------
155
155
156 # core:
156 # core:
157 sec = test_sections['core']
157 sec = test_sections['core']
158 if not have['sqlite3']:
158 if not have['sqlite3']:
159 sec.exclude('tests.test_history')
159 sec.exclude('tests.test_history')
160 sec.exclude('history')
160 sec.exclude('history')
161 if not have['matplotlib']:
161 if not have['matplotlib']:
162 sec.exclude('pylabtools'),
162 sec.exclude('pylabtools'),
163 sec.exclude('tests.test_pylabtools')
163 sec.exclude('tests.test_pylabtools')
164
164
165 # lib:
165 # lib:
166 sec = test_sections['lib']
166 sec = test_sections['lib']
167 sec.exclude('kernel')
167 sec.exclude('kernel')
168 if not have['pygments']:
168 if not have['pygments']:
169 sec.exclude('tests.test_lexers')
169 sec.exclude('tests.test_lexers')
170 # We do this unconditionally, so that the test suite doesn't import
170 # We do this unconditionally, so that the test suite doesn't import
171 # gtk, changing the default encoding and masking some unicode bugs.
171 # gtk, changing the default encoding and masking some unicode bugs.
172 sec.exclude('inputhookgtk')
172 sec.exclude('inputhookgtk')
173 # We also do this unconditionally, because wx can interfere with Unix signals.
173 # We also do this unconditionally, because wx can interfere with Unix signals.
174 # There are currently no tests for it anyway.
174 # There are currently no tests for it anyway.
175 sec.exclude('inputhookwx')
175 sec.exclude('inputhookwx')
176 # Testing inputhook will need a lot of thought, to figure out
176 # Testing inputhook will need a lot of thought, to figure out
177 # how to have tests that don't lock up with the gui event
177 # how to have tests that don't lock up with the gui event
178 # loops in the picture
178 # loops in the picture
179 sec.exclude('inputhook')
179 sec.exclude('inputhook')
180
180
181 # testing:
181 # testing:
182 sec = test_sections['testing']
182 sec = test_sections['testing']
183 # These have to be skipped on win32 because they use echo, rm, cd, etc.
183 # These have to be skipped on win32 because they use echo, rm, cd, etc.
184 # See ticket https://github.com/ipython/ipython/issues/87
184 # See ticket https://github.com/ipython/ipython/issues/87
185 if sys.platform == 'win32':
185 if sys.platform == 'win32':
186 sec.exclude('plugin.test_exampleip')
186 sec.exclude('plugin.test_exampleip')
187 sec.exclude('plugin.dtexample')
187 sec.exclude('plugin.dtexample')
188
188
189 # don't run jupyter_console tests found via shim
189 # don't run jupyter_console tests found via shim
190 test_sections['terminal'].exclude('console')
190 test_sections['terminal'].exclude('console')
191
191
192 # extensions:
192 # extensions:
193 sec = test_sections['extensions']
193 sec = test_sections['extensions']
194 # This is deprecated in favour of rpy2
194 # This is deprecated in favour of rpy2
195 sec.exclude('rmagic')
195 sec.exclude('rmagic')
196 # autoreload does some strange stuff, so move it to its own test section
196 # autoreload does some strange stuff, so move it to its own test section
197 sec.exclude('autoreload')
197 sec.exclude('autoreload')
198 sec.exclude('tests.test_autoreload')
198 sec.exclude('tests.test_autoreload')
199 test_sections['autoreload'] = TestSection('autoreload',
199 test_sections['autoreload'] = TestSection('autoreload',
200 ['IPython.extensions.autoreload', 'IPython.extensions.tests.test_autoreload'])
200 ['IPython.extensions.autoreload', 'IPython.extensions.tests.test_autoreload'])
201 test_group_names.append('autoreload')
201 test_group_names.append('autoreload')
202
202
203
203
204 #-----------------------------------------------------------------------------
204 #-----------------------------------------------------------------------------
205 # Functions and classes
205 # Functions and classes
206 #-----------------------------------------------------------------------------
206 #-----------------------------------------------------------------------------
207
207
208 def check_exclusions_exist():
208 def check_exclusions_exist():
209 from IPython.paths import get_ipython_package_dir
209 from IPython.paths import get_ipython_package_dir
210 from warnings import warn
210 from warnings import warn
211 parent = os.path.dirname(get_ipython_package_dir())
211 parent = os.path.dirname(get_ipython_package_dir())
212 for sec in test_sections:
212 for sec in test_sections:
213 for pattern in sec.exclusions:
213 for pattern in sec.exclusions:
214 fullpath = pjoin(parent, pattern)
214 fullpath = pjoin(parent, pattern)
215 if not os.path.exists(fullpath) and not glob.glob(fullpath + '.*'):
215 if not os.path.exists(fullpath) and not glob.glob(fullpath + '.*'):
216 warn("Excluding nonexistent file: %r" % pattern)
216 warn("Excluding nonexistent file: %r" % pattern)
217
217
218
218
219 class ExclusionPlugin(Plugin):
219 class ExclusionPlugin(Plugin):
220 """A nose plugin to effect our exclusions of files and directories.
220 """A nose plugin to effect our exclusions of files and directories.
221 """
221 """
222 name = 'exclusions'
222 name = 'exclusions'
223 score = 3000 # Should come before any other plugins
223 score = 3000 # Should come before any other plugins
224
224
225 def __init__(self, exclude_patterns=None):
225 def __init__(self, exclude_patterns=None):
226 """
226 """
227 Parameters
227 Parameters
228 ----------
228 ----------
229
229
230 exclude_patterns : sequence of strings, optional
230 exclude_patterns : sequence of strings, optional
231 Filenames containing these patterns (as raw strings, not as regular
231 Filenames containing these patterns (as raw strings, not as regular
232 expressions) are excluded from the tests.
232 expressions) are excluded from the tests.
233 """
233 """
234 self.exclude_patterns = exclude_patterns or []
234 self.exclude_patterns = exclude_patterns or []
235 super(ExclusionPlugin, self).__init__()
235 super(ExclusionPlugin, self).__init__()
236
236
237 def options(self, parser, env=os.environ):
237 def options(self, parser, env=os.environ):
238 Plugin.options(self, parser, env)
238 Plugin.options(self, parser, env)
239
239
240 def configure(self, options, config):
240 def configure(self, options, config):
241 Plugin.configure(self, options, config)
241 Plugin.configure(self, options, config)
242 # Override nose trying to disable plugin.
242 # Override nose trying to disable plugin.
243 self.enabled = True
243 self.enabled = True
244
244
245 def wantFile(self, filename):
245 def wantFile(self, filename):
246 """Return whether the given filename should be scanned for tests.
246 """Return whether the given filename should be scanned for tests.
247 """
247 """
248 if any(pat in filename for pat in self.exclude_patterns):
248 if any(pat in filename for pat in self.exclude_patterns):
249 return False
249 return False
250 return None
250 return None
251
251
252 def wantDirectory(self, directory):
252 def wantDirectory(self, directory):
253 """Return whether the given directory should be scanned for tests.
253 """Return whether the given directory should be scanned for tests.
254 """
254 """
255 if any(pat in directory for pat in self.exclude_patterns):
255 if any(pat in directory for pat in self.exclude_patterns):
256 return False
256 return False
257 return None
257 return None
258
258
259
259
260 class StreamCapturer(Thread):
260 class StreamCapturer(Thread):
261 daemon = True # Don't hang if main thread crashes
261 daemon = True # Don't hang if main thread crashes
262 started = False
262 started = False
263 def __init__(self, echo=False):
263 def __init__(self, echo=False):
264 super(StreamCapturer, self).__init__()
264 super(StreamCapturer, self).__init__()
265 self.echo = echo
265 self.echo = echo
266 self.streams = []
266 self.streams = []
267 self.buffer = BytesIO()
267 self.buffer = BytesIO()
268 self.readfd, self.writefd = os.pipe()
268 self.readfd, self.writefd = os.pipe()
269 self.buffer_lock = Lock()
269 self.buffer_lock = Lock()
270 self.stop = Event()
270 self.stop = Event()
271
271
272 def run(self):
272 def run(self):
273 self.started = True
273 self.started = True
274
274
275 while not self.stop.is_set():
275 while not self.stop.is_set():
276 chunk = os.read(self.readfd, 1024)
276 chunk = os.read(self.readfd, 1024)
277
277
278 with self.buffer_lock:
278 with self.buffer_lock:
279 self.buffer.write(chunk)
279 self.buffer.write(chunk)
280 if self.echo:
280 if self.echo:
281 sys.stdout.write(bytes_to_str(chunk))
281 sys.stdout.write(bytes_to_str(chunk))
282
282
283 os.close(self.readfd)
283 os.close(self.readfd)
284 os.close(self.writefd)
284 os.close(self.writefd)
285
285
286 def reset_buffer(self):
286 def reset_buffer(self):
287 with self.buffer_lock:
287 with self.buffer_lock:
288 self.buffer.truncate(0)
288 self.buffer.truncate(0)
289 self.buffer.seek(0)
289 self.buffer.seek(0)
290
290
291 def get_buffer(self):
291 def get_buffer(self):
292 with self.buffer_lock:
292 with self.buffer_lock:
293 return self.buffer.getvalue()
293 return self.buffer.getvalue()
294
294
295 def ensure_started(self):
295 def ensure_started(self):
296 if not self.started:
296 if not self.started:
297 self.start()
297 self.start()
298
298
299 def halt(self):
299 def halt(self):
300 """Safely stop the thread."""
300 """Safely stop the thread."""
301 if not self.started:
301 if not self.started:
302 return
302 return
303
303
304 self.stop.set()
304 self.stop.set()
305 os.write(self.writefd, b'\0') # Ensure we're not locked in a read()
305 os.write(self.writefd, b'\0') # Ensure we're not locked in a read()
306 self.join()
306 self.join()
307
307
308 class SubprocessStreamCapturePlugin(Plugin):
308 class SubprocessStreamCapturePlugin(Plugin):
309 name='subprocstreams'
309 name='subprocstreams'
310 def __init__(self):
310 def __init__(self):
311 Plugin.__init__(self)
311 Plugin.__init__(self)
312 self.stream_capturer = StreamCapturer()
312 self.stream_capturer = StreamCapturer()
313 self.destination = os.environ.get('IPTEST_SUBPROC_STREAMS', 'capture')
313 self.destination = os.environ.get('IPTEST_SUBPROC_STREAMS', 'capture')
314 # This is ugly, but distant parts of the test machinery need to be able
314 # This is ugly, but distant parts of the test machinery need to be able
315 # to redirect streams, so we make the object globally accessible.
315 # to redirect streams, so we make the object globally accessible.
316 nose.iptest_stdstreams_fileno = self.get_write_fileno
316 nose.iptest_stdstreams_fileno = self.get_write_fileno
317
317
318 def get_write_fileno(self):
318 def get_write_fileno(self):
319 if self.destination == 'capture':
319 if self.destination == 'capture':
320 self.stream_capturer.ensure_started()
320 self.stream_capturer.ensure_started()
321 return self.stream_capturer.writefd
321 return self.stream_capturer.writefd
322 elif self.destination == 'discard':
322 elif self.destination == 'discard':
323 return os.open(os.devnull, os.O_WRONLY)
323 return os.open(os.devnull, os.O_WRONLY)
324 else:
324 else:
325 return sys.__stdout__.fileno()
325 return sys.__stdout__.fileno()
326
326
327 def configure(self, options, config):
327 def configure(self, options, config):
328 Plugin.configure(self, options, config)
328 Plugin.configure(self, options, config)
329 # Override nose trying to disable plugin.
329 # Override nose trying to disable plugin.
330 if self.destination == 'capture':
330 if self.destination == 'capture':
331 self.enabled = True
331 self.enabled = True
332
332
333 def startTest(self, test):
333 def startTest(self, test):
334 # Reset log capture
334 # Reset log capture
335 self.stream_capturer.reset_buffer()
335 self.stream_capturer.reset_buffer()
336
336
337 def formatFailure(self, test, err):
337 def formatFailure(self, test, err):
338 # Show output
338 # Show output
339 ec, ev, tb = err
339 ec, ev, tb = err
340 captured = self.stream_capturer.get_buffer().decode('utf-8', 'replace')
340 captured = self.stream_capturer.get_buffer().decode('utf-8', 'replace')
341 if captured.strip():
341 if captured.strip():
342 ev = safe_str(ev)
342 ev = safe_str(ev)
343 out = [ev, '>> begin captured subprocess output <<',
343 out = [ev, '>> begin captured subprocess output <<',
344 captured,
344 captured,
345 '>> end captured subprocess output <<']
345 '>> end captured subprocess output <<']
346 return ec, '\n'.join(out), tb
346 return ec, '\n'.join(out), tb
347
347
348 return err
348 return err
349
349
350 formatError = formatFailure
350 formatError = formatFailure
351
351
352 def finalize(self, result):
352 def finalize(self, result):
353 self.stream_capturer.halt()
353 self.stream_capturer.halt()
354
354
355
355
356 def run_iptest():
356 def run_iptest():
357 """Run the IPython test suite using nose.
357 """Run the IPython test suite using nose.
358
358
359 This function is called when this script is **not** called with the form
359 This function is called when this script is **not** called with the form
360 `iptest all`. It simply calls nose with appropriate command line flags
360 `iptest all`. It simply calls nose with appropriate command line flags
361 and accepts all of the standard nose arguments.
361 and accepts all of the standard nose arguments.
362 """
362 """
363 # Apply our monkeypatch to Xunit
363 # Apply our monkeypatch to Xunit
364 if '--with-xunit' in sys.argv and not hasattr(Xunit, 'orig_addError'):
364 if '--with-xunit' in sys.argv and not hasattr(Xunit, 'orig_addError'):
365 monkeypatch_xunit()
365 monkeypatch_xunit()
366
366
367 warnings.filterwarnings('ignore',
368 'This will be removed soon. Use IPython.testing.util instead')
369
370 arg1 = sys.argv[1]
367 arg1 = sys.argv[1]
371 if arg1 in test_sections:
368 if arg1 in test_sections:
372 section = test_sections[arg1]
369 section = test_sections[arg1]
373 sys.argv[1:2] = section.includes
370 sys.argv[1:2] = section.includes
374 elif arg1.startswith('IPython.') and arg1[8:] in test_sections:
371 elif arg1.startswith('IPython.') and arg1[8:] in test_sections:
375 section = test_sections[arg1[8:]]
372 section = test_sections[arg1[8:]]
376 sys.argv[1:2] = section.includes
373 sys.argv[1:2] = section.includes
377 else:
374 else:
378 section = TestSection(arg1, includes=[arg1])
375 section = TestSection(arg1, includes=[arg1])
379
376
380
377
381 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
378 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
382 # We add --exe because of setuptools' imbecility (it
379 # We add --exe because of setuptools' imbecility (it
383 # blindly does chmod +x on ALL files). Nose does the
380 # blindly does chmod +x on ALL files). Nose does the
384 # right thing and it tries to avoid executables,
381 # right thing and it tries to avoid executables,
385 # setuptools unfortunately forces our hand here. This
382 # setuptools unfortunately forces our hand here. This
386 # has been discussed on the distutils list and the
383 # has been discussed on the distutils list and the
387 # setuptools devs refuse to fix this problem!
384 # setuptools devs refuse to fix this problem!
388 '--exe',
385 '--exe',
389 ]
386 ]
390 if '-a' not in argv and '-A' not in argv:
387 if '-a' not in argv and '-A' not in argv:
391 argv = argv + ['-a', '!crash']
388 argv = argv + ['-a', '!crash']
392
389
393 if nose.__version__ >= '0.11':
390 if nose.__version__ >= '0.11':
394 # I don't fully understand why we need this one, but depending on what
391 # I don't fully understand why we need this one, but depending on what
395 # directory the test suite is run from, if we don't give it, 0 tests
392 # directory the test suite is run from, if we don't give it, 0 tests
396 # get run. Specifically, if the test suite is run from the source dir
393 # get run. Specifically, if the test suite is run from the source dir
397 # with an argument (like 'iptest.py IPython.core', 0 tests are run,
394 # with an argument (like 'iptest.py IPython.core', 0 tests are run,
398 # even if the same call done in this directory works fine). It appears
395 # even if the same call done in this directory works fine). It appears
399 # that if the requested package is in the current dir, nose bails early
396 # that if the requested package is in the current dir, nose bails early
400 # by default. Since it's otherwise harmless, leave it in by default
397 # by default. Since it's otherwise harmless, leave it in by default
401 # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it.
398 # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it.
402 argv.append('--traverse-namespace')
399 argv.append('--traverse-namespace')
403
400
404 plugins = [ ExclusionPlugin(section.excludes), KnownFailure(),
401 plugins = [ ExclusionPlugin(section.excludes), KnownFailure(),
405 SubprocessStreamCapturePlugin() ]
402 SubprocessStreamCapturePlugin() ]
406
403
407 # we still have some vestigial doctests in core
404 # we still have some vestigial doctests in core
408 if (section.name.startswith(('core', 'IPython.core'))):
405 if (section.name.startswith(('core', 'IPython.core'))):
409 plugins.append(IPythonDoctest())
406 plugins.append(IPythonDoctest())
410 argv.extend([
407 argv.extend([
411 '--with-ipdoctest',
408 '--with-ipdoctest',
412 '--ipdoctest-tests',
409 '--ipdoctest-tests',
413 '--ipdoctest-extension=txt',
410 '--ipdoctest-extension=txt',
414 ])
411 ])
415
412
416
413
417 # Use working directory set by parent process (see iptestcontroller)
414 # Use working directory set by parent process (see iptestcontroller)
418 if 'IPTEST_WORKING_DIR' in os.environ:
415 if 'IPTEST_WORKING_DIR' in os.environ:
419 os.chdir(os.environ['IPTEST_WORKING_DIR'])
416 os.chdir(os.environ['IPTEST_WORKING_DIR'])
420
417
421 # We need a global ipython running in this process, but the special
418 # We need a global ipython running in this process, but the special
422 # in-process group spawns its own IPython kernels, so for *that* group we
419 # in-process group spawns its own IPython kernels, so for *that* group we
423 # must avoid also opening the global one (otherwise there's a conflict of
420 # must avoid also opening the global one (otherwise there's a conflict of
424 # singletons). Ultimately the solution to this problem is to refactor our
421 # singletons). Ultimately the solution to this problem is to refactor our
425 # assumptions about what needs to be a singleton and what doesn't (app
422 # assumptions about what needs to be a singleton and what doesn't (app
426 # objects should, individual shells shouldn't). But for now, this
423 # objects should, individual shells shouldn't). But for now, this
427 # workaround allows the test suite for the inprocess module to complete.
424 # workaround allows the test suite for the inprocess module to complete.
428 if 'kernel.inprocess' not in section.name:
425 if 'kernel.inprocess' not in section.name:
429 from IPython.testing import globalipapp
426 from IPython.testing import globalipapp
430 globalipapp.start_ipython()
427 globalipapp.start_ipython()
431
428
432 # Now nose can run
429 # Now nose can run
433 TestProgram(argv=argv, addplugins=plugins)
430 TestProgram(argv=argv, addplugins=plugins)
434
431
435 if __name__ == '__main__':
432 if __name__ == '__main__':
436 run_iptest()
433 run_iptest()
General Comments 0
You need to be logged in to leave comments. Login now