##// END OF EJS Templates
Fix excludes for IPython.testing
Thomas Kluyver -
Show More
@@ -1,432 +1,432 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 sec = test_sections['lib']
217 sec = test_sections['testing']
218 218 # This guy is probably attic material
219 219 sec.exclude('mkdoctests')
220 # These have to be skipped on win32 because the use echo, rm, cd, etc.
220 # These have to be skipped on win32 because they 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 if not have['tornado']:
297 297 sec.exclude('nbconvert.post_processors.serve')
298 298 sec.exclude('nbconvert.post_processors.tests.test_serve')
299 299
300 300 #-----------------------------------------------------------------------------
301 301 # Functions and classes
302 302 #-----------------------------------------------------------------------------
303 303
304 304 def check_exclusions_exist():
305 305 from IPython.utils.path import get_ipython_package_dir
306 306 from IPython.utils.warn import warn
307 307 parent = os.path.dirname(get_ipython_package_dir())
308 308 for sec in test_sections:
309 309 for pattern in sec.exclusions:
310 310 fullpath = pjoin(parent, pattern)
311 311 if not os.path.exists(fullpath) and not glob.glob(fullpath + '.*'):
312 312 warn("Excluding nonexistent file: %r" % pattern)
313 313
314 314
315 315 class ExclusionPlugin(Plugin):
316 316 """A nose plugin to effect our exclusions of files and directories.
317 317 """
318 318 name = 'exclusions'
319 319 score = 3000 # Should come before any other plugins
320 320
321 321 def __init__(self, exclude_patterns=None):
322 322 """
323 323 Parameters
324 324 ----------
325 325
326 326 exclude_patterns : sequence of strings, optional
327 327 Filenames containing these patterns (as raw strings, not as regular
328 328 expressions) are excluded from the tests.
329 329 """
330 330 self.exclude_patterns = exclude_patterns or []
331 331 super(ExclusionPlugin, self).__init__()
332 332
333 333 def options(self, parser, env=os.environ):
334 334 Plugin.options(self, parser, env)
335 335
336 336 def configure(self, options, config):
337 337 Plugin.configure(self, options, config)
338 338 # Override nose trying to disable plugin.
339 339 self.enabled = True
340 340
341 341 def wantFile(self, filename):
342 342 """Return whether the given filename should be scanned for tests.
343 343 """
344 344 if any(pat in filename for pat in self.exclude_patterns):
345 345 return False
346 346 return None
347 347
348 348 def wantDirectory(self, directory):
349 349 """Return whether the given directory should be scanned for tests.
350 350 """
351 351 if any(pat in directory for pat in self.exclude_patterns):
352 352 return False
353 353 return None
354 354
355 355
356 356 def run_iptest():
357 357 """Run the IPython test suite using nose.
358 358
359 359 This function is called when this script is **not** called with the form
360 360 `iptest all`. It simply calls nose with appropriate command line flags
361 361 and accepts all of the standard nose arguments.
362 362 """
363 363 # Apply our monkeypatch to Xunit
364 364 if '--with-xunit' in sys.argv and not hasattr(Xunit, 'orig_addError'):
365 365 monkeypatch_xunit()
366 366
367 367 warnings.filterwarnings('ignore',
368 368 'This will be removed soon. Use IPython.testing.util instead')
369 369
370 370 arg1 = sys.argv[1]
371 371 if arg1 in test_sections:
372 372 section = test_sections[arg1]
373 373 sys.argv[1:2] = section.includes
374 374 elif arg1.startswith('IPython.') and arg1[8:] in test_sections:
375 375 section = test_sections[arg1[8:]]
376 376 sys.argv[1:2] = section.includes
377 377 else:
378 378 section = TestSection(arg1, includes=[arg1])
379 379
380 380
381 381 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
382 382
383 383 '--with-ipdoctest',
384 384 '--ipdoctest-tests','--ipdoctest-extension=txt',
385 385
386 386 # We add --exe because of setuptools' imbecility (it
387 387 # blindly does chmod +x on ALL files). Nose does the
388 388 # right thing and it tries to avoid executables,
389 389 # setuptools unfortunately forces our hand here. This
390 390 # has been discussed on the distutils list and the
391 391 # setuptools devs refuse to fix this problem!
392 392 '--exe',
393 393 ]
394 394 if '-a' not in argv and '-A' not in argv:
395 395 argv = argv + ['-a', '!crash']
396 396
397 397 if nose.__version__ >= '0.11':
398 398 # I don't fully understand why we need this one, but depending on what
399 399 # directory the test suite is run from, if we don't give it, 0 tests
400 400 # get run. Specifically, if the test suite is run from the source dir
401 401 # with an argument (like 'iptest.py IPython.core', 0 tests are run,
402 402 # even if the same call done in this directory works fine). It appears
403 403 # that if the requested package is in the current dir, nose bails early
404 404 # by default. Since it's otherwise harmless, leave it in by default
405 405 # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it.
406 406 argv.append('--traverse-namespace')
407 407
408 408 # use our plugin for doctesting. It will remove the standard doctest plugin
409 409 # if it finds it enabled
410 410 plugins = [ExclusionPlugin(section.excludes), IPythonDoctest(), KnownFailure()]
411 411
412 412 # Use working directory set by parent process (see iptestcontroller)
413 413 if 'IPTEST_WORKING_DIR' in os.environ:
414 414 os.chdir(os.environ['IPTEST_WORKING_DIR'])
415 415
416 416 # We need a global ipython running in this process, but the special
417 417 # in-process group spawns its own IPython kernels, so for *that* group we
418 418 # must avoid also opening the global one (otherwise there's a conflict of
419 419 # singletons). Ultimately the solution to this problem is to refactor our
420 420 # assumptions about what needs to be a singleton and what doesn't (app
421 421 # objects should, individual shells shouldn't). But for now, this
422 422 # workaround allows the test suite for the inprocess module to complete.
423 423 if 'kernel.inprocess' not in section.name:
424 424 from IPython.testing import globalipapp
425 425 globalipapp.start_ipython()
426 426
427 427 # Now nose can run
428 428 TestProgram(argv=argv, addplugins=plugins)
429 429
430 430 if __name__ == '__main__':
431 431 run_iptest()
432 432
General Comments 0
You need to be logged in to leave comments. Login now