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