##// END OF EJS Templates
Fix inline backend logic and avoid tests if mpl not available.
Fernando Perez -
Show More
@@ -1,338 +1,345 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Pylab (matplotlib) support utilities.
2 """Pylab (matplotlib) support utilities.
3
3
4 Authors
4 Authors
5 -------
5 -------
6
6
7 * Fernando Perez.
7 * Fernando Perez.
8 * Brian Granger
8 * Brian Granger
9 """
9 """
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Copyright (C) 2009-2011 The IPython Development Team
12 # Copyright (C) 2009-2011 The IPython Development Team
13 #
13 #
14 # Distributed under the terms of the BSD License. The full license is in
14 # Distributed under the terms of the BSD License. The full license is in
15 # the file COPYING, distributed as part of this software.
15 # the file COPYING, distributed as part of this software.
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19 # Imports
19 # Imports
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21
21
22 import sys
22 import sys
23 from io import BytesIO
23 from io import BytesIO
24
24
25 from IPython.utils.decorators import flag_calls
25 from IPython.utils.decorators import flag_calls
26
26
27 # If user specifies a GUI, that dictates the backend, otherwise we read the
27 # If user specifies a GUI, that dictates the backend, otherwise we read the
28 # user's mpl default from the mpl rc structure
28 # user's mpl default from the mpl rc structure
29 backends = {'tk': 'TkAgg',
29 backends = {'tk': 'TkAgg',
30 'gtk': 'GTKAgg',
30 'gtk': 'GTKAgg',
31 'wx': 'WXAgg',
31 'wx': 'WXAgg',
32 'qt': 'Qt4Agg', # qt3 not supported
32 'qt': 'Qt4Agg', # qt3 not supported
33 'qt4': 'Qt4Agg',
33 'qt4': 'Qt4Agg',
34 'osx': 'MacOSX',
34 'osx': 'MacOSX',
35 'inline' : 'module://IPython.zmq.pylab.backend_inline'}
35 'inline' : 'module://IPython.zmq.pylab.backend_inline'}
36
36
37 # We also need a reverse backends2guis mapping that will properly choose which
37 # We also need a reverse backends2guis mapping that will properly choose which
38 # GUI support to activate based on the desired matplotlib backend. For the
38 # GUI support to activate based on the desired matplotlib backend. For the
39 # most part it's just a reverse of the above dict, but we also need to add a
39 # most part it's just a reverse of the above dict, but we also need to add a
40 # few others that map to the same GUI manually:
40 # few others that map to the same GUI manually:
41 backend2gui = dict(zip(backends.values(), backends.keys()))
41 backend2gui = dict(zip(backends.values(), backends.keys()))
42 # In the reverse mapping, there are a few extra valid matplotlib backends that
42 # In the reverse mapping, there are a few extra valid matplotlib backends that
43 # map to the same GUI support
43 # map to the same GUI support
44 backend2gui['GTK'] = backend2gui['GTKCairo'] = 'gtk'
44 backend2gui['GTK'] = backend2gui['GTKCairo'] = 'gtk'
45 backend2gui['WX'] = 'wx'
45 backend2gui['WX'] = 'wx'
46 backend2gui['CocoaAgg'] = 'osx'
46 backend2gui['CocoaAgg'] = 'osx'
47
47
48 #-----------------------------------------------------------------------------
48 #-----------------------------------------------------------------------------
49 # Matplotlib utilities
49 # Matplotlib utilities
50 #-----------------------------------------------------------------------------
50 #-----------------------------------------------------------------------------
51
51
52
52
53 def getfigs(*fig_nums):
53 def getfigs(*fig_nums):
54 """Get a list of matplotlib figures by figure numbers.
54 """Get a list of matplotlib figures by figure numbers.
55
55
56 If no arguments are given, all available figures are returned. If the
56 If no arguments are given, all available figures are returned. If the
57 argument list contains references to invalid figures, a warning is printed
57 argument list contains references to invalid figures, a warning is printed
58 but the function continues pasting further figures.
58 but the function continues pasting further figures.
59
59
60 Parameters
60 Parameters
61 ----------
61 ----------
62 figs : tuple
62 figs : tuple
63 A tuple of ints giving the figure numbers of the figures to return.
63 A tuple of ints giving the figure numbers of the figures to return.
64 """
64 """
65 from matplotlib._pylab_helpers import Gcf
65 from matplotlib._pylab_helpers import Gcf
66 if not fig_nums:
66 if not fig_nums:
67 fig_managers = Gcf.get_all_fig_managers()
67 fig_managers = Gcf.get_all_fig_managers()
68 return [fm.canvas.figure for fm in fig_managers]
68 return [fm.canvas.figure for fm in fig_managers]
69 else:
69 else:
70 figs = []
70 figs = []
71 for num in fig_nums:
71 for num in fig_nums:
72 f = Gcf.figs.get(num)
72 f = Gcf.figs.get(num)
73 if f is None:
73 if f is None:
74 print('Warning: figure %s not available.' % num)
74 print('Warning: figure %s not available.' % num)
75 else:
75 else:
76 figs.append(f.canvas.figure)
76 figs.append(f.canvas.figure)
77 return figs
77 return figs
78
78
79
79
80 def figsize(sizex, sizey):
80 def figsize(sizex, sizey):
81 """Set the default figure size to be [sizex, sizey].
81 """Set the default figure size to be [sizex, sizey].
82
82
83 This is just an easy to remember, convenience wrapper that sets::
83 This is just an easy to remember, convenience wrapper that sets::
84
84
85 matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
85 matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
86 """
86 """
87 import matplotlib
87 import matplotlib
88 matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
88 matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
89
89
90
90
91 def print_figure(fig, fmt='png'):
91 def print_figure(fig, fmt='png'):
92 """Convert a figure to svg or png for inline display."""
92 """Convert a figure to svg or png for inline display."""
93 # When there's an empty figure, we shouldn't return anything, otherwise we
93 # When there's an empty figure, we shouldn't return anything, otherwise we
94 # get big blank areas in the qt console.
94 # get big blank areas in the qt console.
95 if not fig.axes:
95 if not fig.axes:
96 return
96 return
97
97
98 fc = fig.get_facecolor()
98 fc = fig.get_facecolor()
99 ec = fig.get_edgecolor()
99 ec = fig.get_edgecolor()
100 fig.set_facecolor('white')
100 fig.set_facecolor('white')
101 fig.set_edgecolor('white')
101 fig.set_edgecolor('white')
102 try:
102 try:
103 bytes_io = BytesIO()
103 bytes_io = BytesIO()
104 fig.canvas.print_figure(bytes_io, format=fmt, bbox_inches='tight')
104 fig.canvas.print_figure(bytes_io, format=fmt, bbox_inches='tight')
105 data = bytes_io.getvalue()
105 data = bytes_io.getvalue()
106 finally:
106 finally:
107 fig.set_facecolor(fc)
107 fig.set_facecolor(fc)
108 fig.set_edgecolor(ec)
108 fig.set_edgecolor(ec)
109 return data
109 return data
110
110
111
111
112 # We need a little factory function here to create the closure where
112 # We need a little factory function here to create the closure where
113 # safe_execfile can live.
113 # safe_execfile can live.
114 def mpl_runner(safe_execfile):
114 def mpl_runner(safe_execfile):
115 """Factory to return a matplotlib-enabled runner for %run.
115 """Factory to return a matplotlib-enabled runner for %run.
116
116
117 Parameters
117 Parameters
118 ----------
118 ----------
119 safe_execfile : function
119 safe_execfile : function
120 This must be a function with the same interface as the
120 This must be a function with the same interface as the
121 :meth:`safe_execfile` method of IPython.
121 :meth:`safe_execfile` method of IPython.
122
122
123 Returns
123 Returns
124 -------
124 -------
125 A function suitable for use as the ``runner`` argument of the %run magic
125 A function suitable for use as the ``runner`` argument of the %run magic
126 function.
126 function.
127 """
127 """
128
128
129 def mpl_execfile(fname,*where,**kw):
129 def mpl_execfile(fname,*where,**kw):
130 """matplotlib-aware wrapper around safe_execfile.
130 """matplotlib-aware wrapper around safe_execfile.
131
131
132 Its interface is identical to that of the :func:`execfile` builtin.
132 Its interface is identical to that of the :func:`execfile` builtin.
133
133
134 This is ultimately a call to execfile(), but wrapped in safeties to
134 This is ultimately a call to execfile(), but wrapped in safeties to
135 properly handle interactive rendering."""
135 properly handle interactive rendering."""
136
136
137 import matplotlib
137 import matplotlib
138 import matplotlib.pylab as pylab
138 import matplotlib.pylab as pylab
139
139
140 #print '*** Matplotlib runner ***' # dbg
140 #print '*** Matplotlib runner ***' # dbg
141 # turn off rendering until end of script
141 # turn off rendering until end of script
142 is_interactive = matplotlib.rcParams['interactive']
142 is_interactive = matplotlib.rcParams['interactive']
143 matplotlib.interactive(False)
143 matplotlib.interactive(False)
144 safe_execfile(fname,*where,**kw)
144 safe_execfile(fname,*where,**kw)
145 matplotlib.interactive(is_interactive)
145 matplotlib.interactive(is_interactive)
146 # make rendering call now, if the user tried to do it
146 # make rendering call now, if the user tried to do it
147 if pylab.draw_if_interactive.called:
147 if pylab.draw_if_interactive.called:
148 pylab.draw()
148 pylab.draw()
149 pylab.draw_if_interactive.called = False
149 pylab.draw_if_interactive.called = False
150
150
151 return mpl_execfile
151 return mpl_execfile
152
152
153
153
154 def select_figure_format(shell, fmt):
154 def select_figure_format(shell, fmt):
155 """Select figure format for inline backend, either 'png' or 'svg'.
155 """Select figure format for inline backend, either 'png' or 'svg'.
156
156
157 Using this method ensures only one figure format is active at a time.
157 Using this method ensures only one figure format is active at a time.
158 """
158 """
159 from matplotlib.figure import Figure
159 from matplotlib.figure import Figure
160 from IPython.zmq.pylab import backend_inline
160 from IPython.zmq.pylab import backend_inline
161
161
162 svg_formatter = shell.display_formatter.formatters['image/svg+xml']
162 svg_formatter = shell.display_formatter.formatters['image/svg+xml']
163 png_formatter = shell.display_formatter.formatters['image/png']
163 png_formatter = shell.display_formatter.formatters['image/png']
164
164
165 if fmt=='png':
165 if fmt=='png':
166 svg_formatter.type_printers.pop(Figure, None)
166 svg_formatter.type_printers.pop(Figure, None)
167 png_formatter.for_type(Figure, lambda fig: print_figure(fig, 'png'))
167 png_formatter.for_type(Figure, lambda fig: print_figure(fig, 'png'))
168 elif fmt=='svg':
168 elif fmt=='svg':
169 png_formatter.type_printers.pop(Figure, None)
169 png_formatter.type_printers.pop(Figure, None)
170 svg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'svg'))
170 svg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'svg'))
171 else:
171 else:
172 raise ValueError("supported formats are: 'png', 'svg', not %r"%fmt)
172 raise ValueError("supported formats are: 'png', 'svg', not %r"%fmt)
173
173
174 # set the format to be used in the backend()
174 # set the format to be used in the backend()
175 backend_inline._figure_format = fmt
175 backend_inline._figure_format = fmt
176
176
177 #-----------------------------------------------------------------------------
177 #-----------------------------------------------------------------------------
178 # Code for initializing matplotlib and importing pylab
178 # Code for initializing matplotlib and importing pylab
179 #-----------------------------------------------------------------------------
179 #-----------------------------------------------------------------------------
180
180
181
181
182 def find_gui_and_backend(gui=None):
182 def find_gui_and_backend(gui=None):
183 """Given a gui string return the gui and mpl backend.
183 """Given a gui string return the gui and mpl backend.
184
184
185 Parameters
185 Parameters
186 ----------
186 ----------
187 gui : str
187 gui : str
188 Can be one of ('tk','gtk','wx','qt','qt4','inline').
188 Can be one of ('tk','gtk','wx','qt','qt4','inline').
189
189
190 Returns
190 Returns
191 -------
191 -------
192 A tuple of (gui, backend) where backend is one of ('TkAgg','GTKAgg',
192 A tuple of (gui, backend) where backend is one of ('TkAgg','GTKAgg',
193 'WXAgg','Qt4Agg','module://IPython.zmq.pylab.backend_inline').
193 'WXAgg','Qt4Agg','module://IPython.zmq.pylab.backend_inline').
194 """
194 """
195
195
196 import matplotlib
196 import matplotlib
197
197
198 if gui and gui != 'auto':
198 if gui and gui != 'auto':
199 # select backend based on requested gui
199 # select backend based on requested gui
200 backend = backends[gui]
200 backend = backends[gui]
201 else:
201 else:
202 backend = matplotlib.rcParams['backend']
202 backend = matplotlib.rcParams['backend']
203 # In this case, we need to find what the appropriate gui selection call
203 # In this case, we need to find what the appropriate gui selection call
204 # should be for IPython, so we can activate inputhook accordingly
204 # should be for IPython, so we can activate inputhook accordingly
205 gui = backend2gui.get(backend, None)
205 gui = backend2gui.get(backend, None)
206 return gui, backend
206 return gui, backend
207
207
208
208
209 def activate_matplotlib(backend):
209 def activate_matplotlib(backend):
210 """Activate the given backend and set interactive to True."""
210 """Activate the given backend and set interactive to True."""
211
211
212 import matplotlib
212 import matplotlib
213 if backend.startswith('module://'):
213 if backend.startswith('module://'):
214 # Work around bug in matplotlib: matplotlib.use converts the
214 # Work around bug in matplotlib: matplotlib.use converts the
215 # backend_id to lowercase even if a module name is specified!
215 # backend_id to lowercase even if a module name is specified!
216 matplotlib.rcParams['backend'] = backend
216 matplotlib.rcParams['backend'] = backend
217 else:
217 else:
218 matplotlib.use(backend)
218 matplotlib.use(backend)
219 matplotlib.interactive(True)
219 matplotlib.interactive(True)
220
220
221 # This must be imported last in the matplotlib series, after
221 # This must be imported last in the matplotlib series, after
222 # backend/interactivity choices have been made
222 # backend/interactivity choices have been made
223 import matplotlib.pylab as pylab
223 import matplotlib.pylab as pylab
224
224
225 # XXX For now leave this commented out, but depending on discussions with
225 # XXX For now leave this commented out, but depending on discussions with
226 # mpl-dev, we may be able to allow interactive switching...
226 # mpl-dev, we may be able to allow interactive switching...
227 #import matplotlib.pyplot
227 #import matplotlib.pyplot
228 #matplotlib.pyplot.switch_backend(backend)
228 #matplotlib.pyplot.switch_backend(backend)
229
229
230 pylab.show._needmain = False
230 pylab.show._needmain = False
231 # We need to detect at runtime whether show() is called by the user.
231 # We need to detect at runtime whether show() is called by the user.
232 # For this, we wrap it into a decorator which adds a 'called' flag.
232 # For this, we wrap it into a decorator which adds a 'called' flag.
233 pylab.draw_if_interactive = flag_calls(pylab.draw_if_interactive)
233 pylab.draw_if_interactive = flag_calls(pylab.draw_if_interactive)
234
234
235
235
236 def import_pylab(user_ns, import_all=True):
236 def import_pylab(user_ns, import_all=True):
237 """Import the standard pylab symbols into user_ns."""
237 """Import the standard pylab symbols into user_ns."""
238
238
239 # Import numpy as np/pyplot as plt are conventions we're trying to
239 # Import numpy as np/pyplot as plt are conventions we're trying to
240 # somewhat standardize on. Making them available to users by default
240 # somewhat standardize on. Making them available to users by default
241 # will greatly help this.
241 # will greatly help this.
242 s = ("import numpy\n"
242 s = ("import numpy\n"
243 "import matplotlib\n"
243 "import matplotlib\n"
244 "from matplotlib import pylab, mlab, pyplot\n"
244 "from matplotlib import pylab, mlab, pyplot\n"
245 "np = numpy\n"
245 "np = numpy\n"
246 "plt = pyplot\n"
246 "plt = pyplot\n"
247 )
247 )
248 exec s in user_ns
248 exec s in user_ns
249
249
250 if import_all:
250 if import_all:
251 s = ("from matplotlib.pylab import *\n"
251 s = ("from matplotlib.pylab import *\n"
252 "from numpy import *\n")
252 "from numpy import *\n")
253 exec s in user_ns
253 exec s in user_ns
254
254
255
255
256 def configure_inline_backend(shell, user_ns=None):
256 def configure_inline_support(shell, backend, user_ns=None):
257 """Configure an IPython shell object for matplotlib use.
257 """Configure an IPython shell object for matplotlib use.
258
258
259 Parameters
259 Parameters
260 ----------
260 ----------
261 shell : InteractiveShell instance
261 shell : InteractiveShell instance
262 If None, this function returns immediately.
262 If None, this function returns immediately.
263
263
264 backend : matplotlib backend
265
264 user_ns : dict
266 user_ns : dict
265 A namespace where all configured variables will be placed. If not given,
267 A namespace where all configured variables will be placed. If not given,
266 the `user_ns` attribute of the shell object is used.
268 the `user_ns` attribute of the shell object is used.
267 """
269 """
268 if shell is None:
270 if shell is None:
269 return
271 return
270
272
271 user_ns = shell.user_ns if user_ns is None else user_ns
272
273 # If using our svg payload backend, register the post-execution
273 # If using our svg payload backend, register the post-execution
274 # function that will pick up the results for display. This can only be
274 # function that will pick up the results for display. This can only be
275 # done with access to the real shell object.
275 # done with access to the real shell object.
276 from IPython.zmq.pylab.backend_inline import InlineBackend
277
276
277 # Note: if we can't load the inline backend, then there's no point
278 # continuing (such as in terminal-only shells in environments without
279 # zeromq available).
280 try:
281 from IPython.zmq.pylab.backend_inline import InlineBackend
282 except ImportError:
283 return
284
285 user_ns = shell.user_ns if user_ns is None else user_ns
286
278 cfg = InlineBackend.instance(config=shell.config)
287 cfg = InlineBackend.instance(config=shell.config)
279 cfg.shell = shell
288 cfg.shell = shell
280 if cfg not in shell.configurables:
289 if cfg not in shell.configurables:
281 shell.configurables.append(cfg)
290 shell.configurables.append(cfg)
282
291
283 from IPython.zmq.pylab.backend_inline import flush_figures
292 if backend == backends['inline']:
284 from matplotlib import pyplot
293 from IPython.zmq.pylab.backend_inline import flush_figures
285 shell.register_post_execute(flush_figures)
294 from matplotlib import pyplot
286 # load inline_rc
295 shell.register_post_execute(flush_figures)
287 pyplot.rcParams.update(cfg.rc)
296 # load inline_rc
288 # Add 'figsize' to pyplot and to the user's namespace
297 pyplot.rcParams.update(cfg.rc)
289 user_ns['figsize'] = pyplot.figsize = figsize
298 # Add 'figsize' to pyplot and to the user's namespace
299 user_ns['figsize'] = pyplot.figsize = figsize
290
300
291 # Setup the default figure format
301 # Setup the default figure format
292 fmt = cfg.figure_format
302 fmt = cfg.figure_format
293 select_figure_format(shell, fmt)
303 select_figure_format(shell, fmt)
294
304
295 # The old pastefig function has been replaced by display
305 # The old pastefig function has been replaced by display
296 from IPython.core.display import display
306 from IPython.core.display import display
297 # Add display and getfigs to the user's namespace
307 # Add display and getfigs to the user's namespace
298 user_ns['display'] = display
308 user_ns['display'] = display
299 user_ns['getfigs'] = getfigs
309 user_ns['getfigs'] = getfigs
300
310
301
311
302 def pylab_activate(user_ns, gui=None, import_all=True, shell=None):
312 def pylab_activate(user_ns, gui=None, import_all=True, shell=None):
303 """Activate pylab mode in the user's namespace.
313 """Activate pylab mode in the user's namespace.
304
314
305 Loads and initializes numpy, matplotlib and friends for interactive use.
315 Loads and initializes numpy, matplotlib and friends for interactive use.
306
316
307 Parameters
317 Parameters
308 ----------
318 ----------
309 user_ns : dict
319 user_ns : dict
310 Namespace where the imports will occur.
320 Namespace where the imports will occur.
311
321
312 gui : optional, string
322 gui : optional, string
313 A valid gui name following the conventions of the %gui magic.
323 A valid gui name following the conventions of the %gui magic.
314
324
315 import_all : optional, boolean
325 import_all : optional, boolean
316 If true, an 'import *' is done from numpy and pylab.
326 If true, an 'import *' is done from numpy and pylab.
317
327
318 Returns
328 Returns
319 -------
329 -------
320 The actual gui used (if not given as input, it was obtained from matplotlib
330 The actual gui used (if not given as input, it was obtained from matplotlib
321 itself, and will be needed next to configure IPython's gui integration.
331 itself, and will be needed next to configure IPython's gui integration.
322 """
332 """
323 gui, backend = find_gui_and_backend(gui)
333 gui, backend = find_gui_and_backend(gui)
324 activate_matplotlib(backend)
334 activate_matplotlib(backend)
325 import_pylab(user_ns, import_all)
335 import_pylab(user_ns, import_all)
326
336 configure_inline_support(shell, backend, user_ns)
327 # The inline backend is only used by GUI shells
328 if backend == backends['inline']:
329 configure_inline_backend(shell, backend, user_ns)
330
337
331 print """
338 print """
332 Welcome to pylab, a matplotlib-based Python environment [backend: %s].
339 Welcome to pylab, a matplotlib-based Python environment [backend: %s].
333 For more information, type 'help(pylab)'.""" % backend
340 For more information, type 'help(pylab)'.""" % backend
334 # flush stdout, just to be safe
341 # flush stdout, just to be safe
335 sys.stdout.flush()
342 sys.stdout.flush()
336
343
337 return gui
344 return gui
338
345
@@ -1,476 +1,476 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 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18 # Copyright (C) 2009-2011 The IPython Development Team
18 # Copyright (C) 2009-2011 The IPython Development Team
19 #
19 #
20 # Distributed under the terms of the BSD License. The full license is in
20 # Distributed under the terms of the BSD License. The full license is in
21 # the file COPYING, distributed as part of this software.
21 # the file COPYING, distributed as part of this software.
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23
23
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25 # Imports
25 # Imports
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27
27
28 # Stdlib
28 # Stdlib
29 import os
29 import os
30 import os.path as path
30 import os.path as path
31 import signal
31 import signal
32 import sys
32 import sys
33 import subprocess
33 import subprocess
34 import tempfile
34 import tempfile
35 import time
35 import time
36 import warnings
36 import warnings
37
37
38 # Note: monkeypatch!
38 # Note: monkeypatch!
39 # We need to monkeypatch a small problem in nose itself first, before importing
39 # We need to monkeypatch a small problem in nose itself first, before importing
40 # it for actual use. This should get into nose upstream, but its release cycle
40 # it for actual use. This should get into nose upstream, but its release cycle
41 # is slow and we need it for our parametric tests to work correctly.
41 # is slow and we need it for our parametric tests to work correctly.
42 from IPython.testing import nosepatch
42 from IPython.testing import nosepatch
43 # Now, proceed to import nose itself
43 # Now, proceed to import nose itself
44 import nose.plugins.builtin
44 import nose.plugins.builtin
45 from nose.core import TestProgram
45 from nose.core import TestProgram
46
46
47 # Our own imports
47 # Our own imports
48 from IPython.utils.importstring import import_item
48 from IPython.utils.importstring import import_item
49 from IPython.utils.path import get_ipython_module_path
49 from IPython.utils.path import get_ipython_module_path
50 from IPython.utils.process import find_cmd, pycmd2argv
50 from IPython.utils.process import find_cmd, pycmd2argv
51 from IPython.utils.sysinfo import sys_info
51 from IPython.utils.sysinfo import sys_info
52
52
53 from IPython.testing import globalipapp
53 from IPython.testing import globalipapp
54 from IPython.testing.plugin.ipdoctest import IPythonDoctest
54 from IPython.testing.plugin.ipdoctest import IPythonDoctest
55 from IPython.external.decorators import KnownFailure
55 from IPython.external.decorators import KnownFailure
56
56
57 pjoin = path.join
57 pjoin = path.join
58
58
59
59
60 #-----------------------------------------------------------------------------
60 #-----------------------------------------------------------------------------
61 # Globals
61 # Globals
62 #-----------------------------------------------------------------------------
62 #-----------------------------------------------------------------------------
63
63
64
64
65 #-----------------------------------------------------------------------------
65 #-----------------------------------------------------------------------------
66 # Warnings control
66 # Warnings control
67 #-----------------------------------------------------------------------------
67 #-----------------------------------------------------------------------------
68
68
69 # Twisted generates annoying warnings with Python 2.6, as will do other code
69 # Twisted generates annoying warnings with Python 2.6, as will do other code
70 # that imports 'sets' as of today
70 # that imports 'sets' as of today
71 warnings.filterwarnings('ignore', 'the sets module is deprecated',
71 warnings.filterwarnings('ignore', 'the sets module is deprecated',
72 DeprecationWarning )
72 DeprecationWarning )
73
73
74 # This one also comes from Twisted
74 # This one also comes from Twisted
75 warnings.filterwarnings('ignore', 'the sha module is deprecated',
75 warnings.filterwarnings('ignore', 'the sha module is deprecated',
76 DeprecationWarning)
76 DeprecationWarning)
77
77
78 # Wx on Fedora11 spits these out
78 # Wx on Fedora11 spits these out
79 warnings.filterwarnings('ignore', 'wxPython/wxWidgets release number mismatch',
79 warnings.filterwarnings('ignore', 'wxPython/wxWidgets release number mismatch',
80 UserWarning)
80 UserWarning)
81
81
82 #-----------------------------------------------------------------------------
82 #-----------------------------------------------------------------------------
83 # Logic for skipping doctests
83 # Logic for skipping doctests
84 #-----------------------------------------------------------------------------
84 #-----------------------------------------------------------------------------
85 def extract_version(mod):
85 def extract_version(mod):
86 return mod.__version__
86 return mod.__version__
87
87
88 def test_for(item, min_version=None, callback=extract_version):
88 def test_for(item, min_version=None, callback=extract_version):
89 """Test to see if item is importable, and optionally check against a minimum
89 """Test to see if item is importable, and optionally check against a minimum
90 version.
90 version.
91
91
92 If min_version is given, the default behavior is to check against the
92 If min_version is given, the default behavior is to check against the
93 `__version__` attribute of the item, but specifying `callback` allows you to
93 `__version__` attribute of the item, but specifying `callback` allows you to
94 extract the value you are interested in. e.g::
94 extract the value you are interested in. e.g::
95
95
96 In [1]: import sys
96 In [1]: import sys
97
97
98 In [2]: from IPython.testing.iptest import test_for
98 In [2]: from IPython.testing.iptest import test_for
99
99
100 In [3]: test_for('sys', (2,6), callback=lambda sys: sys.version_info)
100 In [3]: test_for('sys', (2,6), callback=lambda sys: sys.version_info)
101 Out[3]: True
101 Out[3]: True
102
102
103 """
103 """
104 try:
104 try:
105 check = import_item(item)
105 check = import_item(item)
106 except (ImportError, RuntimeError):
106 except (ImportError, RuntimeError):
107 # GTK reports Runtime error if it can't be initialized even if it's
107 # GTK reports Runtime error if it can't be initialized even if it's
108 # importable.
108 # importable.
109 return False
109 return False
110 else:
110 else:
111 if min_version:
111 if min_version:
112 if callback:
112 if callback:
113 # extra processing step to get version to compare
113 # extra processing step to get version to compare
114 check = callback(check)
114 check = callback(check)
115
115
116 return check >= min_version
116 return check >= min_version
117 else:
117 else:
118 return True
118 return True
119
119
120 # Global dict where we can store information on what we have and what we don't
120 # Global dict where we can store information on what we have and what we don't
121 # have available at test run time
121 # have available at test run time
122 have = {}
122 have = {}
123
123
124 have['curses'] = test_for('_curses')
124 have['curses'] = test_for('_curses')
125 have['matplotlib'] = test_for('matplotlib')
125 have['matplotlib'] = test_for('matplotlib')
126 have['pexpect'] = test_for('IPython.external.pexpect')
126 have['pexpect'] = test_for('IPython.external.pexpect')
127 have['pymongo'] = test_for('pymongo')
127 have['pymongo'] = test_for('pymongo')
128 have['wx'] = test_for('wx')
128 have['wx'] = test_for('wx')
129 have['wx.aui'] = test_for('wx.aui')
129 have['wx.aui'] = test_for('wx.aui')
130 have['qt'] = test_for('IPython.external.qt')
130 have['qt'] = test_for('IPython.external.qt')
131 have['sqlite3'] = test_for('sqlite3')
131 have['sqlite3'] = test_for('sqlite3')
132
132
133 have['tornado'] = test_for('tornado.version_info', (2,1,0), callback=None)
133 have['tornado'] = test_for('tornado.version_info', (2,1,0), callback=None)
134
134
135 if os.name == 'nt':
135 if os.name == 'nt':
136 min_zmq = (2,1,7)
136 min_zmq = (2,1,7)
137 else:
137 else:
138 min_zmq = (2,1,4)
138 min_zmq = (2,1,4)
139
139
140 def version_tuple(mod):
140 def version_tuple(mod):
141 "turn '2.1.9' into (2,1,9), and '2.1dev' into (2,1,999)"
141 "turn '2.1.9' into (2,1,9), and '2.1dev' into (2,1,999)"
142 # turn 'dev' into 999, because Python3 rejects str-int comparisons
142 # turn 'dev' into 999, because Python3 rejects str-int comparisons
143 vs = mod.__version__.replace('dev', '.999')
143 vs = mod.__version__.replace('dev', '.999')
144 tup = tuple([int(v) for v in vs.split('.') ])
144 tup = tuple([int(v) for v in vs.split('.') ])
145 return tup
145 return tup
146
146
147 have['zmq'] = test_for('zmq', min_zmq, version_tuple)
147 have['zmq'] = test_for('zmq', min_zmq, version_tuple)
148
148
149 #-----------------------------------------------------------------------------
149 #-----------------------------------------------------------------------------
150 # Functions and classes
150 # Functions and classes
151 #-----------------------------------------------------------------------------
151 #-----------------------------------------------------------------------------
152
152
153 def report():
153 def report():
154 """Return a string with a summary report of test-related variables."""
154 """Return a string with a summary report of test-related variables."""
155
155
156 out = [ sys_info(), '\n']
156 out = [ sys_info(), '\n']
157
157
158 avail = []
158 avail = []
159 not_avail = []
159 not_avail = []
160
160
161 for k, is_avail in have.items():
161 for k, is_avail in have.items():
162 if is_avail:
162 if is_avail:
163 avail.append(k)
163 avail.append(k)
164 else:
164 else:
165 not_avail.append(k)
165 not_avail.append(k)
166
166
167 if avail:
167 if avail:
168 out.append('\nTools and libraries available at test time:\n')
168 out.append('\nTools and libraries available at test time:\n')
169 avail.sort()
169 avail.sort()
170 out.append(' ' + ' '.join(avail)+'\n')
170 out.append(' ' + ' '.join(avail)+'\n')
171
171
172 if not_avail:
172 if not_avail:
173 out.append('\nTools and libraries NOT available at test time:\n')
173 out.append('\nTools and libraries NOT available at test time:\n')
174 not_avail.sort()
174 not_avail.sort()
175 out.append(' ' + ' '.join(not_avail)+'\n')
175 out.append(' ' + ' '.join(not_avail)+'\n')
176
176
177 return ''.join(out)
177 return ''.join(out)
178
178
179
179
180 def make_exclude():
180 def make_exclude():
181 """Make patterns of modules and packages to exclude from testing.
181 """Make patterns of modules and packages to exclude from testing.
182
182
183 For the IPythonDoctest plugin, we need to exclude certain patterns that
183 For the IPythonDoctest plugin, we need to exclude certain patterns that
184 cause testing problems. We should strive to minimize the number of
184 cause testing problems. We should strive to minimize the number of
185 skipped modules, since this means untested code.
185 skipped modules, since this means untested code.
186
186
187 These modules and packages will NOT get scanned by nose at all for tests.
187 These modules and packages will NOT get scanned by nose at all for tests.
188 """
188 """
189 # Simple utility to make IPython paths more readably, we need a lot of
189 # Simple utility to make IPython paths more readably, we need a lot of
190 # these below
190 # these below
191 ipjoin = lambda *paths: pjoin('IPython', *paths)
191 ipjoin = lambda *paths: pjoin('IPython', *paths)
192
192
193 exclusions = [ipjoin('external'),
193 exclusions = [ipjoin('external'),
194 pjoin('IPython_doctest_plugin'),
194 pjoin('IPython_doctest_plugin'),
195 ipjoin('quarantine'),
195 ipjoin('quarantine'),
196 ipjoin('deathrow'),
196 ipjoin('deathrow'),
197 ipjoin('testing', 'attic'),
197 ipjoin('testing', 'attic'),
198 # This guy is probably attic material
198 # This guy is probably attic material
199 ipjoin('testing', 'mkdoctests'),
199 ipjoin('testing', 'mkdoctests'),
200 # Testing inputhook will need a lot of thought, to figure out
200 # Testing inputhook will need a lot of thought, to figure out
201 # how to have tests that don't lock up with the gui event
201 # how to have tests that don't lock up with the gui event
202 # loops in the picture
202 # loops in the picture
203 ipjoin('lib', 'inputhook'),
203 ipjoin('lib', 'inputhook'),
204 # Config files aren't really importable stand-alone
204 # Config files aren't really importable stand-alone
205 ipjoin('config', 'default'),
205 ipjoin('config', 'default'),
206 ipjoin('config', 'profile'),
206 ipjoin('config', 'profile'),
207 ]
207 ]
208 if not have['sqlite3']:
208 if not have['sqlite3']:
209 exclusions.append(ipjoin('core', 'tests', 'test_history'))
209 exclusions.append(ipjoin('core', 'tests', 'test_history'))
210 exclusions.append(ipjoin('core', 'history'))
210 exclusions.append(ipjoin('core', 'history'))
211 if not have['wx']:
211 if not have['wx']:
212 exclusions.append(ipjoin('lib', 'inputhookwx'))
212 exclusions.append(ipjoin('lib', 'inputhookwx'))
213
213
214 # We do this unconditionally, so that the test suite doesn't import
214 # We do this unconditionally, so that the test suite doesn't import
215 # gtk, changing the default encoding and masking some unicode bugs.
215 # gtk, changing the default encoding and masking some unicode bugs.
216 exclusions.append(ipjoin('lib', 'inputhookgtk'))
216 exclusions.append(ipjoin('lib', 'inputhookgtk'))
217
217
218 # These have to be skipped on win32 because the use echo, rm, cd, etc.
218 # These have to be skipped on win32 because the use echo, rm, cd, etc.
219 # See ticket https://github.com/ipython/ipython/issues/87
219 # See ticket https://github.com/ipython/ipython/issues/87
220 if sys.platform == 'win32':
220 if sys.platform == 'win32':
221 exclusions.append(ipjoin('testing', 'plugin', 'test_exampleip'))
221 exclusions.append(ipjoin('testing', 'plugin', 'test_exampleip'))
222 exclusions.append(ipjoin('testing', 'plugin', 'dtexample'))
222 exclusions.append(ipjoin('testing', 'plugin', 'dtexample'))
223
223
224 if not have['pexpect']:
224 if not have['pexpect']:
225 exclusions.extend([ipjoin('scripts', 'irunner'),
225 exclusions.extend([ipjoin('scripts', 'irunner'),
226 ipjoin('lib', 'irunner'),
226 ipjoin('lib', 'irunner'),
227 ipjoin('lib', 'tests', 'test_irunner')])
227 ipjoin('lib', 'tests', 'test_irunner')])
228
228
229 if not have['zmq']:
229 if not have['zmq']:
230 exclusions.append(ipjoin('zmq'))
230 exclusions.append(ipjoin('zmq'))
231 exclusions.append(ipjoin('frontend', 'qt'))
231 exclusions.append(ipjoin('frontend', 'qt'))
232 exclusions.append(ipjoin('parallel'))
232 exclusions.append(ipjoin('parallel'))
233 elif not have['qt']:
233 elif not have['qt']:
234 exclusions.append(ipjoin('frontend', 'qt'))
234 exclusions.append(ipjoin('frontend', 'qt'))
235
235
236 if not have['pymongo']:
236 if not have['pymongo']:
237 exclusions.append(ipjoin('parallel', 'controller', 'mongodb'))
237 exclusions.append(ipjoin('parallel', 'controller', 'mongodb'))
238 exclusions.append(ipjoin('parallel', 'tests', 'test_mongodb'))
238 exclusions.append(ipjoin('parallel', 'tests', 'test_mongodb'))
239
239
240 if not have['matplotlib']:
240 if not have['matplotlib']:
241 exclusions.extend([ipjoin('lib', 'pylabtools'),
241 exclusions.extend([ipjoin('core', 'pylabtools'),
242 ipjoin('lib', 'tests', 'test_pylabtools')])
242 ipjoin('core', 'tests', 'test_pylabtools')])
243
243
244 if not have['tornado']:
244 if not have['tornado']:
245 exclusions.append(ipjoin('frontend', 'html'))
245 exclusions.append(ipjoin('frontend', 'html'))
246
246
247 # This is needed for the reg-exp to match on win32 in the ipdoctest plugin.
247 # This is needed for the reg-exp to match on win32 in the ipdoctest plugin.
248 if sys.platform == 'win32':
248 if sys.platform == 'win32':
249 exclusions = [s.replace('\\','\\\\') for s in exclusions]
249 exclusions = [s.replace('\\','\\\\') for s in exclusions]
250
250
251 return exclusions
251 return exclusions
252
252
253
253
254 class IPTester(object):
254 class IPTester(object):
255 """Call that calls iptest or trial in a subprocess.
255 """Call that calls iptest or trial in a subprocess.
256 """
256 """
257 #: string, name of test runner that will be called
257 #: string, name of test runner that will be called
258 runner = None
258 runner = None
259 #: list, parameters for test runner
259 #: list, parameters for test runner
260 params = None
260 params = None
261 #: list, arguments of system call to be made to call test runner
261 #: list, arguments of system call to be made to call test runner
262 call_args = None
262 call_args = None
263 #: list, process ids of subprocesses we start (for cleanup)
263 #: list, process ids of subprocesses we start (for cleanup)
264 pids = None
264 pids = None
265
265
266 def __init__(self, runner='iptest', params=None):
266 def __init__(self, runner='iptest', params=None):
267 """Create new test runner."""
267 """Create new test runner."""
268 p = os.path
268 p = os.path
269 if runner == 'iptest':
269 if runner == 'iptest':
270 iptest_app = get_ipython_module_path('IPython.testing.iptest')
270 iptest_app = get_ipython_module_path('IPython.testing.iptest')
271 self.runner = pycmd2argv(iptest_app) + sys.argv[1:]
271 self.runner = pycmd2argv(iptest_app) + sys.argv[1:]
272 else:
272 else:
273 raise Exception('Not a valid test runner: %s' % repr(runner))
273 raise Exception('Not a valid test runner: %s' % repr(runner))
274 if params is None:
274 if params is None:
275 params = []
275 params = []
276 if isinstance(params, str):
276 if isinstance(params, str):
277 params = [params]
277 params = [params]
278 self.params = params
278 self.params = params
279
279
280 # Assemble call
280 # Assemble call
281 self.call_args = self.runner+self.params
281 self.call_args = self.runner+self.params
282
282
283 # Store pids of anything we start to clean up on deletion, if possible
283 # Store pids of anything we start to clean up on deletion, if possible
284 # (on posix only, since win32 has no os.kill)
284 # (on posix only, since win32 has no os.kill)
285 self.pids = []
285 self.pids = []
286
286
287 if sys.platform == 'win32':
287 if sys.platform == 'win32':
288 def _run_cmd(self):
288 def _run_cmd(self):
289 # On Windows, use os.system instead of subprocess.call, because I
289 # On Windows, use os.system instead of subprocess.call, because I
290 # was having problems with subprocess and I just don't know enough
290 # was having problems with subprocess and I just don't know enough
291 # about win32 to debug this reliably. Os.system may be the 'old
291 # about win32 to debug this reliably. Os.system may be the 'old
292 # fashioned' way to do it, but it works just fine. If someone
292 # fashioned' way to do it, but it works just fine. If someone
293 # later can clean this up that's fine, as long as the tests run
293 # later can clean this up that's fine, as long as the tests run
294 # reliably in win32.
294 # reliably in win32.
295 # What types of problems are you having. They may be related to
295 # What types of problems are you having. They may be related to
296 # running Python in unboffered mode. BG.
296 # running Python in unboffered mode. BG.
297 return os.system(' '.join(self.call_args))
297 return os.system(' '.join(self.call_args))
298 else:
298 else:
299 def _run_cmd(self):
299 def _run_cmd(self):
300 # print >> sys.stderr, '*** CMD:', ' '.join(self.call_args) # dbg
300 # print >> sys.stderr, '*** CMD:', ' '.join(self.call_args) # dbg
301 subp = subprocess.Popen(self.call_args)
301 subp = subprocess.Popen(self.call_args)
302 self.pids.append(subp.pid)
302 self.pids.append(subp.pid)
303 # If this fails, the pid will be left in self.pids and cleaned up
303 # If this fails, the pid will be left in self.pids and cleaned up
304 # later, but if the wait call succeeds, then we can clear the
304 # later, but if the wait call succeeds, then we can clear the
305 # stored pid.
305 # stored pid.
306 retcode = subp.wait()
306 retcode = subp.wait()
307 self.pids.pop()
307 self.pids.pop()
308 return retcode
308 return retcode
309
309
310 def run(self):
310 def run(self):
311 """Run the stored commands"""
311 """Run the stored commands"""
312 try:
312 try:
313 return self._run_cmd()
313 return self._run_cmd()
314 except:
314 except:
315 import traceback
315 import traceback
316 traceback.print_exc()
316 traceback.print_exc()
317 return 1 # signal failure
317 return 1 # signal failure
318
318
319 def __del__(self):
319 def __del__(self):
320 """Cleanup on exit by killing any leftover processes."""
320 """Cleanup on exit by killing any leftover processes."""
321
321
322 if not hasattr(os, 'kill'):
322 if not hasattr(os, 'kill'):
323 return
323 return
324
324
325 for pid in self.pids:
325 for pid in self.pids:
326 try:
326 try:
327 print 'Cleaning stale PID:', pid
327 print 'Cleaning stale PID:', pid
328 os.kill(pid, signal.SIGKILL)
328 os.kill(pid, signal.SIGKILL)
329 except OSError:
329 except OSError:
330 # This is just a best effort, if we fail or the process was
330 # This is just a best effort, if we fail or the process was
331 # really gone, ignore it.
331 # really gone, ignore it.
332 pass
332 pass
333
333
334
334
335 def make_runners():
335 def make_runners():
336 """Define the top-level packages that need to be tested.
336 """Define the top-level packages that need to be tested.
337 """
337 """
338
338
339 # Packages to be tested via nose, that only depend on the stdlib
339 # Packages to be tested via nose, that only depend on the stdlib
340 nose_pkg_names = ['config', 'core', 'extensions', 'frontend', 'lib',
340 nose_pkg_names = ['config', 'core', 'extensions', 'frontend', 'lib',
341 'scripts', 'testing', 'utils', 'nbformat' ]
341 'scripts', 'testing', 'utils', 'nbformat' ]
342
342
343 if have['zmq']:
343 if have['zmq']:
344 nose_pkg_names.append('parallel')
344 nose_pkg_names.append('parallel')
345
345
346 # For debugging this code, only load quick stuff
346 # For debugging this code, only load quick stuff
347 #nose_pkg_names = ['core', 'extensions'] # dbg
347 #nose_pkg_names = ['core', 'extensions'] # dbg
348
348
349 # Make fully qualified package names prepending 'IPython.' to our name lists
349 # Make fully qualified package names prepending 'IPython.' to our name lists
350 nose_packages = ['IPython.%s' % m for m in nose_pkg_names ]
350 nose_packages = ['IPython.%s' % m for m in nose_pkg_names ]
351
351
352 # Make runners
352 # Make runners
353 runners = [ (v, IPTester('iptest', params=v)) for v in nose_packages ]
353 runners = [ (v, IPTester('iptest', params=v)) for v in nose_packages ]
354
354
355 return runners
355 return runners
356
356
357
357
358 def run_iptest():
358 def run_iptest():
359 """Run the IPython test suite using nose.
359 """Run the IPython test suite using nose.
360
360
361 This function is called when this script is **not** called with the form
361 This function is called when this script is **not** called with the form
362 `iptest all`. It simply calls nose with appropriate command line flags
362 `iptest all`. It simply calls nose with appropriate command line flags
363 and accepts all of the standard nose arguments.
363 and accepts all of the standard nose arguments.
364 """
364 """
365
365
366 warnings.filterwarnings('ignore',
366 warnings.filterwarnings('ignore',
367 'This will be removed soon. Use IPython.testing.util instead')
367 'This will be removed soon. Use IPython.testing.util instead')
368
368
369 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
369 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
370
370
371 # Loading ipdoctest causes problems with Twisted, but
371 # Loading ipdoctest causes problems with Twisted, but
372 # our test suite runner now separates things and runs
372 # our test suite runner now separates things and runs
373 # all Twisted tests with trial.
373 # all Twisted tests with trial.
374 '--with-ipdoctest',
374 '--with-ipdoctest',
375 '--ipdoctest-tests','--ipdoctest-extension=txt',
375 '--ipdoctest-tests','--ipdoctest-extension=txt',
376
376
377 # We add --exe because of setuptools' imbecility (it
377 # We add --exe because of setuptools' imbecility (it
378 # blindly does chmod +x on ALL files). Nose does the
378 # blindly does chmod +x on ALL files). Nose does the
379 # right thing and it tries to avoid executables,
379 # right thing and it tries to avoid executables,
380 # setuptools unfortunately forces our hand here. This
380 # setuptools unfortunately forces our hand here. This
381 # has been discussed on the distutils list and the
381 # has been discussed on the distutils list and the
382 # setuptools devs refuse to fix this problem!
382 # setuptools devs refuse to fix this problem!
383 '--exe',
383 '--exe',
384 ]
384 ]
385
385
386 if nose.__version__ >= '0.11':
386 if nose.__version__ >= '0.11':
387 # I don't fully understand why we need this one, but depending on what
387 # I don't fully understand why we need this one, but depending on what
388 # directory the test suite is run from, if we don't give it, 0 tests
388 # directory the test suite is run from, if we don't give it, 0 tests
389 # get run. Specifically, if the test suite is run from the source dir
389 # get run. Specifically, if the test suite is run from the source dir
390 # with an argument (like 'iptest.py IPython.core', 0 tests are run,
390 # with an argument (like 'iptest.py IPython.core', 0 tests are run,
391 # even if the same call done in this directory works fine). It appears
391 # even if the same call done in this directory works fine). It appears
392 # that if the requested package is in the current dir, nose bails early
392 # that if the requested package is in the current dir, nose bails early
393 # by default. Since it's otherwise harmless, leave it in by default
393 # by default. Since it's otherwise harmless, leave it in by default
394 # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it.
394 # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it.
395 argv.append('--traverse-namespace')
395 argv.append('--traverse-namespace')
396
396
397 # use our plugin for doctesting. It will remove the standard doctest plugin
397 # use our plugin for doctesting. It will remove the standard doctest plugin
398 # if it finds it enabled
398 # if it finds it enabled
399 plugins = [IPythonDoctest(make_exclude()), KnownFailure()]
399 plugins = [IPythonDoctest(make_exclude()), KnownFailure()]
400 # We need a global ipython running in this process
400 # We need a global ipython running in this process
401 globalipapp.start_ipython()
401 globalipapp.start_ipython()
402 # Now nose can run
402 # Now nose can run
403 TestProgram(argv=argv, addplugins=plugins)
403 TestProgram(argv=argv, addplugins=plugins)
404
404
405
405
406 def run_iptestall():
406 def run_iptestall():
407 """Run the entire IPython test suite by calling nose and trial.
407 """Run the entire IPython test suite by calling nose and trial.
408
408
409 This function constructs :class:`IPTester` instances for all IPython
409 This function constructs :class:`IPTester` instances for all IPython
410 modules and package and then runs each of them. This causes the modules
410 modules and package and then runs each of them. This causes the modules
411 and packages of IPython to be tested each in their own subprocess using
411 and packages of IPython to be tested each in their own subprocess using
412 nose or twisted.trial appropriately.
412 nose or twisted.trial appropriately.
413 """
413 """
414
414
415 runners = make_runners()
415 runners = make_runners()
416
416
417 # Run the test runners in a temporary dir so we can nuke it when finished
417 # Run the test runners in a temporary dir so we can nuke it when finished
418 # to clean up any junk files left over by accident. This also makes it
418 # to clean up any junk files left over by accident. This also makes it
419 # robust against being run in non-writeable directories by mistake, as the
419 # robust against being run in non-writeable directories by mistake, as the
420 # temp dir will always be user-writeable.
420 # temp dir will always be user-writeable.
421 curdir = os.getcwdu()
421 curdir = os.getcwdu()
422 testdir = tempfile.gettempdir()
422 testdir = tempfile.gettempdir()
423 os.chdir(testdir)
423 os.chdir(testdir)
424
424
425 # Run all test runners, tracking execution time
425 # Run all test runners, tracking execution time
426 failed = []
426 failed = []
427 t_start = time.time()
427 t_start = time.time()
428 try:
428 try:
429 for (name, runner) in runners:
429 for (name, runner) in runners:
430 print '*'*70
430 print '*'*70
431 print 'IPython test group:',name
431 print 'IPython test group:',name
432 res = runner.run()
432 res = runner.run()
433 if res:
433 if res:
434 failed.append( (name, runner) )
434 failed.append( (name, runner) )
435 finally:
435 finally:
436 os.chdir(curdir)
436 os.chdir(curdir)
437 t_end = time.time()
437 t_end = time.time()
438 t_tests = t_end - t_start
438 t_tests = t_end - t_start
439 nrunners = len(runners)
439 nrunners = len(runners)
440 nfail = len(failed)
440 nfail = len(failed)
441 # summarize results
441 # summarize results
442 print
442 print
443 print '*'*70
443 print '*'*70
444 print 'Test suite completed for system with the following information:'
444 print 'Test suite completed for system with the following information:'
445 print report()
445 print report()
446 print 'Ran %s test groups in %.3fs' % (nrunners, t_tests)
446 print 'Ran %s test groups in %.3fs' % (nrunners, t_tests)
447 print
447 print
448 print 'Status:'
448 print 'Status:'
449 if not failed:
449 if not failed:
450 print 'OK'
450 print 'OK'
451 else:
451 else:
452 # If anything went wrong, point out what command to rerun manually to
452 # If anything went wrong, point out what command to rerun manually to
453 # see the actual errors and individual summary
453 # see the actual errors and individual summary
454 print 'ERROR - %s out of %s test groups failed.' % (nfail, nrunners)
454 print 'ERROR - %s out of %s test groups failed.' % (nfail, nrunners)
455 for name, failed_runner in failed:
455 for name, failed_runner in failed:
456 print '-'*40
456 print '-'*40
457 print 'Runner failed:',name
457 print 'Runner failed:',name
458 print 'You may wish to rerun this one individually, with:'
458 print 'You may wish to rerun this one individually, with:'
459 print ' '.join(failed_runner.call_args)
459 print ' '.join(failed_runner.call_args)
460 print
460 print
461 # Ensure that our exit code indicates failure
461 # Ensure that our exit code indicates failure
462 sys.exit(1)
462 sys.exit(1)
463
463
464
464
465 def main():
465 def main():
466 for arg in sys.argv[1:]:
466 for arg in sys.argv[1:]:
467 if arg.startswith('IPython'):
467 if arg.startswith('IPython'):
468 # This is in-process
468 # This is in-process
469 run_iptest()
469 run_iptest()
470 else:
470 else:
471 # This starts subprocesses
471 # This starts subprocesses
472 run_iptestall()
472 run_iptestall()
473
473
474
474
475 if __name__ == '__main__':
475 if __name__ == '__main__':
476 main()
476 main()
General Comments 0
You need to be logged in to leave comments. Login now