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