##// END OF EJS Templates
Allow 'iptest IPython.lib' as well as 'iptest lib'
Thomas Kluyver -
Show More
@@ -1,430 +1,433 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 from __future__ import print_function
28 28
29 29 # Stdlib
30 30 import glob
31 31 import os
32 32 import os.path as path
33 33 import re
34 34 import sys
35 35 import warnings
36 36
37 37 # Now, proceed to import nose itself
38 38 import nose.plugins.builtin
39 39 from nose.plugins.xunit import Xunit
40 40 from nose import SkipTest
41 41 from nose.core import TestProgram
42 42 from nose.plugins import Plugin
43 43
44 44 # Our own imports
45 45 from IPython.utils.importstring import import_item
46 46 from IPython.testing.plugin.ipdoctest import IPythonDoctest
47 47 from IPython.external.decorators import KnownFailure, knownfailureif
48 48
49 49 pjoin = path.join
50 50
51 51
52 52 #-----------------------------------------------------------------------------
53 53 # Globals
54 54 #-----------------------------------------------------------------------------
55 55
56 56
57 57 #-----------------------------------------------------------------------------
58 58 # Warnings control
59 59 #-----------------------------------------------------------------------------
60 60
61 61 # Twisted generates annoying warnings with Python 2.6, as will do other code
62 62 # that imports 'sets' as of today
63 63 warnings.filterwarnings('ignore', 'the sets module is deprecated',
64 64 DeprecationWarning )
65 65
66 66 # This one also comes from Twisted
67 67 warnings.filterwarnings('ignore', 'the sha module is deprecated',
68 68 DeprecationWarning)
69 69
70 70 # Wx on Fedora11 spits these out
71 71 warnings.filterwarnings('ignore', 'wxPython/wxWidgets release number mismatch',
72 72 UserWarning)
73 73
74 74 # ------------------------------------------------------------------------------
75 75 # Monkeypatch Xunit to count known failures as skipped.
76 76 # ------------------------------------------------------------------------------
77 77 def monkeypatch_xunit():
78 78 try:
79 79 knownfailureif(True)(lambda: None)()
80 80 except Exception as e:
81 81 KnownFailureTest = type(e)
82 82
83 83 def addError(self, test, err, capt=None):
84 84 if issubclass(err[0], KnownFailureTest):
85 85 err = (SkipTest,) + err[1:]
86 86 return self.orig_addError(test, err, capt)
87 87
88 88 Xunit.orig_addError = Xunit.addError
89 89 Xunit.addError = addError
90 90
91 91 #-----------------------------------------------------------------------------
92 92 # Check which dependencies are installed and greater than minimum version.
93 93 #-----------------------------------------------------------------------------
94 94 def extract_version(mod):
95 95 return mod.__version__
96 96
97 97 def test_for(item, min_version=None, callback=extract_version):
98 98 """Test to see if item is importable, and optionally check against a minimum
99 99 version.
100 100
101 101 If min_version is given, the default behavior is to check against the
102 102 `__version__` attribute of the item, but specifying `callback` allows you to
103 103 extract the value you are interested in. e.g::
104 104
105 105 In [1]: import sys
106 106
107 107 In [2]: from IPython.testing.iptest import test_for
108 108
109 109 In [3]: test_for('sys', (2,6), callback=lambda sys: sys.version_info)
110 110 Out[3]: True
111 111
112 112 """
113 113 try:
114 114 check = import_item(item)
115 115 except (ImportError, RuntimeError):
116 116 # GTK reports Runtime error if it can't be initialized even if it's
117 117 # importable.
118 118 return False
119 119 else:
120 120 if min_version:
121 121 if callback:
122 122 # extra processing step to get version to compare
123 123 check = callback(check)
124 124
125 125 return check >= min_version
126 126 else:
127 127 return True
128 128
129 129 # Global dict where we can store information on what we have and what we don't
130 130 # have available at test run time
131 131 have = {}
132 132
133 133 have['curses'] = test_for('_curses')
134 134 have['matplotlib'] = test_for('matplotlib')
135 135 have['numpy'] = test_for('numpy')
136 136 have['pexpect'] = test_for('IPython.external.pexpect')
137 137 have['pymongo'] = test_for('pymongo')
138 138 have['pygments'] = test_for('pygments')
139 139 have['qt'] = test_for('IPython.external.qt')
140 140 have['rpy2'] = test_for('rpy2')
141 141 have['sqlite3'] = test_for('sqlite3')
142 142 have['cython'] = test_for('Cython')
143 143 have['oct2py'] = test_for('oct2py')
144 144 have['tornado'] = test_for('tornado.version_info', (2,1,0), callback=None)
145 145 have['jinja2'] = test_for('jinja2')
146 146 have['wx'] = test_for('wx')
147 147 have['wx.aui'] = test_for('wx.aui')
148 148 have['azure'] = test_for('azure')
149 149 have['sphinx'] = test_for('sphinx')
150 150
151 151 min_zmq = (2,1,11)
152 152
153 153 have['zmq'] = test_for('zmq.pyzmq_version_info', min_zmq, callback=lambda x: x())
154 154
155 155 #-----------------------------------------------------------------------------
156 156 # Test suite definitions
157 157 #-----------------------------------------------------------------------------
158 158
159 159 test_group_names = ['parallel', 'kernel', 'kernel.inprocess', 'config', 'core',
160 160 'extensions', 'lib', 'terminal', 'testing', 'utils',
161 161 'nbformat', 'qt', 'html', 'nbconvert'
162 162 ]
163 163
164 164 class TestSection(object):
165 165 def __init__(self, name, includes):
166 166 self.name = name
167 167 self.includes = includes
168 168 self.excludes = []
169 169 self.dependencies = []
170 170 self.enabled = True
171 171
172 172 def exclude(self, module):
173 173 if not module.startswith('IPython'):
174 174 module = self.includes[0] + "." + module
175 175 self.excludes.append(module.replace('.', os.sep))
176 176
177 177 def requires(self, *packages):
178 178 self.dependencies.extend(packages)
179 179
180 180 @property
181 181 def will_run(self):
182 182 return self.enabled and all(have[p] for p in self.dependencies)
183 183
184 184 # Name -> (include, exclude, dependencies_met)
185 185 test_sections = {n:TestSection(n, ['IPython.%s' % n]) for n in test_group_names}
186 186
187 187 # Exclusions and dependencies
188 188 # ---------------------------
189 189
190 190 # core:
191 191 sec = test_sections['core']
192 192 if not have['sqlite3']:
193 193 sec.exclude('tests.test_history')
194 194 sec.exclude('history')
195 195 if not have['matplotlib']:
196 196 sec.exclude('pylabtools'),
197 197 sec.exclude('tests.test_pylabtools')
198 198
199 199 # lib:
200 200 sec = test_sections['lib']
201 201 if not have['wx']:
202 202 sec.exclude('inputhookwx')
203 203 if not have['pexpect']:
204 204 sec.exclude('irunner')
205 205 sec.exclude('tests.test_irunner')
206 206 if not have['zmq']:
207 207 sec.exclude('kernel')
208 208 # We do this unconditionally, so that the test suite doesn't import
209 209 # gtk, changing the default encoding and masking some unicode bugs.
210 210 sec.exclude('inputhookgtk')
211 211 # Testing inputhook will need a lot of thought, to figure out
212 212 # how to have tests that don't lock up with the gui event
213 213 # loops in the picture
214 214 sec.exclude('inputhook')
215 215
216 216 # testing:
217 217 sec = test_sections['lib']
218 218 # This guy is probably attic material
219 219 sec.exclude('mkdoctests')
220 220 # These have to be skipped on win32 because the use echo, rm, cd, etc.
221 221 # See ticket https://github.com/ipython/ipython/issues/87
222 222 if sys.platform == 'win32':
223 223 sec.exclude('plugin.test_exampleip')
224 224 sec.exclude('plugin.dtexample')
225 225
226 226 # terminal:
227 227 if (not have['pexpect']) or (not have['zmq']):
228 228 test_sections['terminal'].exclude('console')
229 229
230 230 # parallel
231 231 sec = test_sections['parallel']
232 232 sec.requires('zmq')
233 233 if not have['pymongo']:
234 234 sec.exclude('controller.mongodb')
235 235 sec.exclude('tests.test_mongodb')
236 236
237 237 # kernel:
238 238 sec = test_sections['kernel']
239 239 sec.requires('zmq')
240 240 # The in-process kernel tests are done in a separate section
241 241 sec.exclude('inprocess')
242 242 # importing gtk sets the default encoding, which we want to avoid
243 243 sec.exclude('zmq.gui.gtkembed')
244 244 if not have['matplotlib']:
245 245 sec.exclude('zmq.pylab')
246 246
247 247 # kernel.inprocess:
248 248 test_sections['kernel.inprocess'].requires('zmq')
249 249
250 250 # extensions:
251 251 sec = test_sections['extensions']
252 252 if not have['cython']:
253 253 sec.exclude('cythonmagic')
254 254 sec.exclude('tests.test_cythonmagic')
255 255 if not have['oct2py']:
256 256 sec.exclude('octavemagic')
257 257 sec.exclude('tests.test_octavemagic')
258 258 if not have['rpy2'] or not have['numpy']:
259 259 sec.exclude('rmagic')
260 260 sec.exclude('tests.test_rmagic')
261 261 # autoreload does some strange stuff, so move it to its own test section
262 262 sec.exclude('autoreload')
263 263 sec.exclude('tests.test_autoreload')
264 264 test_sections['autoreload'] = TestSection('autoreload',
265 265 ['IPython.extensions.autoreload', 'IPython.extensions.tests.test_autoreload'])
266 266 test_group_names.append('autoreload')
267 267
268 268 # qt:
269 269 test_sections['qt'].requires('zmq', 'qt', 'pygments')
270 270
271 271 # html:
272 272 sec = test_sections['html']
273 273 sec.requires('zmq', 'tornado')
274 274 # The notebook 'static' directory contains JS, css and other
275 275 # files for web serving. Occasionally projects may put a .py
276 276 # file in there (MathJax ships a conf.py), so we might as
277 277 # well play it safe and skip the whole thing.
278 278 sec.exclude('static')
279 279 sec.exclude('fabfile')
280 280 if not have['jinja2']:
281 281 sec.exclude('notebookapp')
282 282 if not have['azure']:
283 283 sec.exclude('services.notebooks.azurenbmanager')
284 284
285 285 # config:
286 286 # Config files aren't really importable stand-alone
287 287 test_sections['config'].exclude('profile')
288 288
289 289 # nbconvert:
290 290 sec = test_sections['nbconvert']
291 291 sec.requires('pygments', 'jinja2', 'sphinx')
292 292 # Exclude nbconvert directories containing config files used to test.
293 293 # Executing the config files with iptest would cause an exception.
294 294 sec.exclude('tests.files')
295 295 sec.exclude('exporters.tests.files')
296 296
297 297 #-----------------------------------------------------------------------------
298 298 # Functions and classes
299 299 #-----------------------------------------------------------------------------
300 300
301 301 def check_exclusions_exist():
302 302 from IPython.utils.path import get_ipython_package_dir
303 303 from IPython.utils.warn import warn
304 304 parent = os.path.dirname(get_ipython_package_dir())
305 305 for sec in test_sections:
306 306 for pattern in sec.exclusions:
307 307 fullpath = pjoin(parent, pattern)
308 308 if not os.path.exists(fullpath) and not glob.glob(fullpath + '.*'):
309 309 warn("Excluding nonexistent file: %r" % pattern)
310 310
311 311
312 312 class ExclusionPlugin(Plugin):
313 313 """A nose plugin to effect our exclusions of files and directories.
314 314 """
315 315 name = 'exclusions'
316 316 score = 3000 # Should come before any other plugins
317 317
318 318 def __init__(self, exclude_patterns=None):
319 319 """
320 320 Parameters
321 321 ----------
322 322
323 323 exclude_patterns : sequence of strings, optional
324 324 These patterns are compiled as regular expressions, subsequently used
325 325 to exclude any filename which matches them from inclusion in the test
326 326 suite (using pattern.search(), NOT pattern.match() ).
327 327 """
328 328
329 329 if exclude_patterns is None:
330 330 exclude_patterns = []
331 331 self.exclude_patterns = [re.compile(p) for p in exclude_patterns]
332 332 super(ExclusionPlugin, self).__init__()
333 333
334 334 def options(self, parser, env=os.environ):
335 335 Plugin.options(self, parser, env)
336 336
337 337 def configure(self, options, config):
338 338 Plugin.configure(self, options, config)
339 339 # Override nose trying to disable plugin.
340 340 self.enabled = True
341 341
342 342 def wantFile(self, filename):
343 343 """Return whether the given filename should be scanned for tests.
344 344 """
345 345 if any(pat.search(filename) for pat in self.exclude_patterns):
346 346 return False
347 347 return None
348 348
349 349 def wantDirectory(self, directory):
350 350 """Return whether the given directory should be scanned for tests.
351 351 """
352 352 if any(pat.search(directory) for pat in self.exclude_patterns):
353 353 return False
354 354 return None
355 355
356 356
357 357 def run_iptest():
358 358 """Run the IPython test suite using nose.
359 359
360 360 This function is called when this script is **not** called with the form
361 361 `iptest all`. It simply calls nose with appropriate command line flags
362 362 and accepts all of the standard nose arguments.
363 363 """
364 364 # Apply our monkeypatch to Xunit
365 365 if '--with-xunit' in sys.argv and not hasattr(Xunit, 'orig_addError'):
366 366 monkeypatch_xunit()
367 367
368 368 warnings.filterwarnings('ignore',
369 369 'This will be removed soon. Use IPython.testing.util instead')
370 370
371 if sys.argv[1] in test_sections:
372 section = test_sections[sys.argv[1]]
371 arg1 = sys.argv[1]
372 if arg1 in test_sections:
373 section = test_sections[arg1]
374 sys.argv[1:2] = section.includes
375 elif arg1.startswith('IPython.') and arg1[8:] in test_sections:
376 section = test_sections[arg1[8:]]
373 377 sys.argv[1:2] = section.includes
374 378 else:
375 arg1 = sys.argv[1]
376 379 section = TestSection(arg1, includes=[arg1])
377 380
378 381
379 382 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
380 383
381 384 '--with-ipdoctest',
382 385 '--ipdoctest-tests','--ipdoctest-extension=txt',
383 386
384 387 # We add --exe because of setuptools' imbecility (it
385 388 # blindly does chmod +x on ALL files). Nose does the
386 389 # right thing and it tries to avoid executables,
387 390 # setuptools unfortunately forces our hand here. This
388 391 # has been discussed on the distutils list and the
389 392 # setuptools devs refuse to fix this problem!
390 393 '--exe',
391 394 ]
392 395 if '-a' not in argv and '-A' not in argv:
393 396 argv = argv + ['-a', '!crash']
394 397
395 398 if nose.__version__ >= '0.11':
396 399 # I don't fully understand why we need this one, but depending on what
397 400 # directory the test suite is run from, if we don't give it, 0 tests
398 401 # get run. Specifically, if the test suite is run from the source dir
399 402 # with an argument (like 'iptest.py IPython.core', 0 tests are run,
400 403 # even if the same call done in this directory works fine). It appears
401 404 # that if the requested package is in the current dir, nose bails early
402 405 # by default. Since it's otherwise harmless, leave it in by default
403 406 # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it.
404 407 argv.append('--traverse-namespace')
405 408
406 409 # use our plugin for doctesting. It will remove the standard doctest plugin
407 410 # if it finds it enabled
408 411 plugins = [ExclusionPlugin(section.excludes), IPythonDoctest(), KnownFailure()]
409 412
410 413 # Use working directory set by parent process (see iptestcontroller)
411 414 if 'IPTEST_WORKING_DIR' in os.environ:
412 415 os.chdir(os.environ['IPTEST_WORKING_DIR'])
413 416
414 417 # We need a global ipython running in this process, but the special
415 418 # in-process group spawns its own IPython kernels, so for *that* group we
416 419 # must avoid also opening the global one (otherwise there's a conflict of
417 420 # singletons). Ultimately the solution to this problem is to refactor our
418 421 # assumptions about what needs to be a singleton and what doesn't (app
419 422 # objects should, individual shells shouldn't). But for now, this
420 423 # workaround allows the test suite for the inprocess module to complete.
421 424 if 'kernel.inprocess' not in section.name:
422 425 from IPython.testing import globalipapp
423 426 globalipapp.start_ipython()
424 427
425 428 # Now nose can run
426 429 TestProgram(argv=argv, addplugins=plugins)
427 430
428 431 if __name__ == '__main__':
429 432 run_iptest()
430 433
General Comments 0
You need to be logged in to leave comments. Login now