##// END OF EJS Templates
Add filter warnings for Traitlets 4.1 now that it's out.
Matthias Bussonnier -
Show More
@@ -1,441 +1,442 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 # Copyright (c) IPython Development Team.
18 18 # Distributed under the terms of the Modified BSD License.
19 19
20 20 from __future__ import print_function
21 21
22 22 import glob
23 23 from io import BytesIO
24 24 import os
25 25 import os.path as path
26 26 import sys
27 27 from threading import Thread, Lock, Event
28 28 import warnings
29 29
30 30 import nose.plugins.builtin
31 31 from nose.plugins.xunit import Xunit
32 32 from nose import SkipTest
33 33 from nose.core import TestProgram
34 34 from nose.plugins import Plugin
35 35 from nose.util import safe_str
36 36
37 37 from IPython import version_info
38 38 from IPython.utils.py3compat import bytes_to_str
39 39 from IPython.utils.importstring import import_item
40 40 from IPython.testing.plugin.ipdoctest import IPythonDoctest
41 41 from IPython.external.decorators import KnownFailure, knownfailureif
42 42
43 43 pjoin = path.join
44 44
45 45
46 46 # Enable printing all warnings raise by IPython's modules
47 47 warnings.filterwarnings('default', message='.*', category=Warning, module='IPy.*')
48 48
49 49
50 50 if version_info < (4,2):
51 51 # ignore some warnings from traitlets until 6.0
52 52 warnings.filterwarnings('ignore', message='.*on_trait_change is deprecated: use observe instead.*')
53 53 warnings.filterwarnings('ignore', message='.*was set from the constructor.*', category=Warning, module='IPython.*')
54 warnings.filterwarnings('ignore', message='.*use the instance .help string directly, like x.help.*', category=DeprecationWarning, module='IPython.*')
54 55 else :
55 warnings.warn('iptest has been filtering out for Traitlets warnings messages, for 2 major versions (since 4.x), please consider updating to use new API')
56 warnings.warn('iptest has been filtering out for Traitlets warnings messages, for 2 minor versions (since 4.x), please consider updating to use new API')
56 57
57 58 if version_info < (6,):
58 59 # nose.tools renames all things from `camelCase` to `snake_case` which raise an
59 60 # warning with the runner they also import from standard import library. (as of Dec 2015)
60 61 # Ignore, let's revisit that in a couple of years for IPython 6.
61 62 warnings.filterwarnings('ignore', message='.*Please use assertEqual instead', category=Warning, module='IPython.*')
62 63
63 64
64 65 # ------------------------------------------------------------------------------
65 66 # Monkeypatch Xunit to count known failures as skipped.
66 67 # ------------------------------------------------------------------------------
67 68 def monkeypatch_xunit():
68 69 try:
69 70 knownfailureif(True)(lambda: None)()
70 71 except Exception as e:
71 72 KnownFailureTest = type(e)
72 73
73 74 def addError(self, test, err, capt=None):
74 75 if issubclass(err[0], KnownFailureTest):
75 76 err = (SkipTest,) + err[1:]
76 77 return self.orig_addError(test, err, capt)
77 78
78 79 Xunit.orig_addError = Xunit.addError
79 80 Xunit.addError = addError
80 81
81 82 #-----------------------------------------------------------------------------
82 83 # Check which dependencies are installed and greater than minimum version.
83 84 #-----------------------------------------------------------------------------
84 85 def extract_version(mod):
85 86 return mod.__version__
86 87
87 88 def test_for(item, min_version=None, callback=extract_version):
88 89 """Test to see if item is importable, and optionally check against a minimum
89 90 version.
90 91
91 92 If min_version is given, the default behavior is to check against the
92 93 `__version__` attribute of the item, but specifying `callback` allows you to
93 94 extract the value you are interested in. e.g::
94 95
95 96 In [1]: import sys
96 97
97 98 In [2]: from IPython.testing.iptest import test_for
98 99
99 100 In [3]: test_for('sys', (2,6), callback=lambda sys: sys.version_info)
100 101 Out[3]: True
101 102
102 103 """
103 104 try:
104 105 check = import_item(item)
105 106 except (ImportError, RuntimeError):
106 107 # GTK reports Runtime error if it can't be initialized even if it's
107 108 # importable.
108 109 return False
109 110 else:
110 111 if min_version:
111 112 if callback:
112 113 # extra processing step to get version to compare
113 114 check = callback(check)
114 115
115 116 return check >= min_version
116 117 else:
117 118 return True
118 119
119 120 # Global dict where we can store information on what we have and what we don't
120 121 # have available at test run time
121 122 have = {'matplotlib': test_for('matplotlib'),
122 123 'pygments': test_for('pygments'),
123 124 'sqlite3': test_for('sqlite3')}
124 125
125 126 #-----------------------------------------------------------------------------
126 127 # Test suite definitions
127 128 #-----------------------------------------------------------------------------
128 129
129 130 test_group_names = ['core',
130 131 'extensions', 'lib', 'terminal', 'testing', 'utils',
131 132 ]
132 133
133 134 class TestSection(object):
134 135 def __init__(self, name, includes):
135 136 self.name = name
136 137 self.includes = includes
137 138 self.excludes = []
138 139 self.dependencies = []
139 140 self.enabled = True
140 141
141 142 def exclude(self, module):
142 143 if not module.startswith('IPython'):
143 144 module = self.includes[0] + "." + module
144 145 self.excludes.append(module.replace('.', os.sep))
145 146
146 147 def requires(self, *packages):
147 148 self.dependencies.extend(packages)
148 149
149 150 @property
150 151 def will_run(self):
151 152 return self.enabled and all(have[p] for p in self.dependencies)
152 153
153 154 # Name -> (include, exclude, dependencies_met)
154 155 test_sections = {n:TestSection(n, ['IPython.%s' % n]) for n in test_group_names}
155 156
156 157
157 158 # Exclusions and dependencies
158 159 # ---------------------------
159 160
160 161 # core:
161 162 sec = test_sections['core']
162 163 if not have['sqlite3']:
163 164 sec.exclude('tests.test_history')
164 165 sec.exclude('history')
165 166 if not have['matplotlib']:
166 167 sec.exclude('pylabtools'),
167 168 sec.exclude('tests.test_pylabtools')
168 169
169 170 # lib:
170 171 sec = test_sections['lib']
171 172 sec.exclude('kernel')
172 173 if not have['pygments']:
173 174 sec.exclude('tests.test_lexers')
174 175 # We do this unconditionally, so that the test suite doesn't import
175 176 # gtk, changing the default encoding and masking some unicode bugs.
176 177 sec.exclude('inputhookgtk')
177 178 # We also do this unconditionally, because wx can interfere with Unix signals.
178 179 # There are currently no tests for it anyway.
179 180 sec.exclude('inputhookwx')
180 181 # Testing inputhook will need a lot of thought, to figure out
181 182 # how to have tests that don't lock up with the gui event
182 183 # loops in the picture
183 184 sec.exclude('inputhook')
184 185
185 186 # testing:
186 187 sec = test_sections['testing']
187 188 # These have to be skipped on win32 because they use echo, rm, cd, etc.
188 189 # See ticket https://github.com/ipython/ipython/issues/87
189 190 if sys.platform == 'win32':
190 191 sec.exclude('plugin.test_exampleip')
191 192 sec.exclude('plugin.dtexample')
192 193
193 194 # don't run jupyter_console tests found via shim
194 195 test_sections['terminal'].exclude('console')
195 196
196 197 # extensions:
197 198 sec = test_sections['extensions']
198 199 # This is deprecated in favour of rpy2
199 200 sec.exclude('rmagic')
200 201 # autoreload does some strange stuff, so move it to its own test section
201 202 sec.exclude('autoreload')
202 203 sec.exclude('tests.test_autoreload')
203 204 test_sections['autoreload'] = TestSection('autoreload',
204 205 ['IPython.extensions.autoreload', 'IPython.extensions.tests.test_autoreload'])
205 206 test_group_names.append('autoreload')
206 207
207 208
208 209 #-----------------------------------------------------------------------------
209 210 # Functions and classes
210 211 #-----------------------------------------------------------------------------
211 212
212 213 def check_exclusions_exist():
213 214 from IPython.paths import get_ipython_package_dir
214 215 from IPython.utils.warn import warn
215 216 parent = os.path.dirname(get_ipython_package_dir())
216 217 for sec in test_sections:
217 218 for pattern in sec.exclusions:
218 219 fullpath = pjoin(parent, pattern)
219 220 if not os.path.exists(fullpath) and not glob.glob(fullpath + '.*'):
220 221 warn("Excluding nonexistent file: %r" % pattern)
221 222
222 223
223 224 class ExclusionPlugin(Plugin):
224 225 """A nose plugin to effect our exclusions of files and directories.
225 226 """
226 227 name = 'exclusions'
227 228 score = 3000 # Should come before any other plugins
228 229
229 230 def __init__(self, exclude_patterns=None):
230 231 """
231 232 Parameters
232 233 ----------
233 234
234 235 exclude_patterns : sequence of strings, optional
235 236 Filenames containing these patterns (as raw strings, not as regular
236 237 expressions) are excluded from the tests.
237 238 """
238 239 self.exclude_patterns = exclude_patterns or []
239 240 super(ExclusionPlugin, self).__init__()
240 241
241 242 def options(self, parser, env=os.environ):
242 243 Plugin.options(self, parser, env)
243 244
244 245 def configure(self, options, config):
245 246 Plugin.configure(self, options, config)
246 247 # Override nose trying to disable plugin.
247 248 self.enabled = True
248 249
249 250 def wantFile(self, filename):
250 251 """Return whether the given filename should be scanned for tests.
251 252 """
252 253 if any(pat in filename for pat in self.exclude_patterns):
253 254 return False
254 255 return None
255 256
256 257 def wantDirectory(self, directory):
257 258 """Return whether the given directory should be scanned for tests.
258 259 """
259 260 if any(pat in directory for pat in self.exclude_patterns):
260 261 return False
261 262 return None
262 263
263 264
264 265 class StreamCapturer(Thread):
265 266 daemon = True # Don't hang if main thread crashes
266 267 started = False
267 268 def __init__(self, echo=False):
268 269 super(StreamCapturer, self).__init__()
269 270 self.echo = echo
270 271 self.streams = []
271 272 self.buffer = BytesIO()
272 273 self.readfd, self.writefd = os.pipe()
273 274 self.buffer_lock = Lock()
274 275 self.stop = Event()
275 276
276 277 def run(self):
277 278 self.started = True
278 279
279 280 while not self.stop.is_set():
280 281 chunk = os.read(self.readfd, 1024)
281 282
282 283 with self.buffer_lock:
283 284 self.buffer.write(chunk)
284 285 if self.echo:
285 286 sys.stdout.write(bytes_to_str(chunk))
286 287
287 288 os.close(self.readfd)
288 289 os.close(self.writefd)
289 290
290 291 def reset_buffer(self):
291 292 with self.buffer_lock:
292 293 self.buffer.truncate(0)
293 294 self.buffer.seek(0)
294 295
295 296 def get_buffer(self):
296 297 with self.buffer_lock:
297 298 return self.buffer.getvalue()
298 299
299 300 def ensure_started(self):
300 301 if not self.started:
301 302 self.start()
302 303
303 304 def halt(self):
304 305 """Safely stop the thread."""
305 306 if not self.started:
306 307 return
307 308
308 309 self.stop.set()
309 310 os.write(self.writefd, b'\0') # Ensure we're not locked in a read()
310 311 self.join()
311 312
312 313 class SubprocessStreamCapturePlugin(Plugin):
313 314 name='subprocstreams'
314 315 def __init__(self):
315 316 Plugin.__init__(self)
316 317 self.stream_capturer = StreamCapturer()
317 318 self.destination = os.environ.get('IPTEST_SUBPROC_STREAMS', 'capture')
318 319 # This is ugly, but distant parts of the test machinery need to be able
319 320 # to redirect streams, so we make the object globally accessible.
320 321 nose.iptest_stdstreams_fileno = self.get_write_fileno
321 322
322 323 def get_write_fileno(self):
323 324 if self.destination == 'capture':
324 325 self.stream_capturer.ensure_started()
325 326 return self.stream_capturer.writefd
326 327 elif self.destination == 'discard':
327 328 return os.open(os.devnull, os.O_WRONLY)
328 329 else:
329 330 return sys.__stdout__.fileno()
330 331
331 332 def configure(self, options, config):
332 333 Plugin.configure(self, options, config)
333 334 # Override nose trying to disable plugin.
334 335 if self.destination == 'capture':
335 336 self.enabled = True
336 337
337 338 def startTest(self, test):
338 339 # Reset log capture
339 340 self.stream_capturer.reset_buffer()
340 341
341 342 def formatFailure(self, test, err):
342 343 # Show output
343 344 ec, ev, tb = err
344 345 captured = self.stream_capturer.get_buffer().decode('utf-8', 'replace')
345 346 if captured.strip():
346 347 ev = safe_str(ev)
347 348 out = [ev, '>> begin captured subprocess output <<',
348 349 captured,
349 350 '>> end captured subprocess output <<']
350 351 return ec, '\n'.join(out), tb
351 352
352 353 return err
353 354
354 355 formatError = formatFailure
355 356
356 357 def finalize(self, result):
357 358 self.stream_capturer.halt()
358 359
359 360
360 361 def run_iptest():
361 362 """Run the IPython test suite using nose.
362 363
363 364 This function is called when this script is **not** called with the form
364 365 `iptest all`. It simply calls nose with appropriate command line flags
365 366 and accepts all of the standard nose arguments.
366 367 """
367 368 # Apply our monkeypatch to Xunit
368 369 if '--with-xunit' in sys.argv and not hasattr(Xunit, 'orig_addError'):
369 370 monkeypatch_xunit()
370 371
371 372 warnings.filterwarnings('ignore',
372 373 'This will be removed soon. Use IPython.testing.util instead')
373 374
374 375 arg1 = sys.argv[1]
375 376 if arg1 in test_sections:
376 377 section = test_sections[arg1]
377 378 sys.argv[1:2] = section.includes
378 379 elif arg1.startswith('IPython.') and arg1[8:] in test_sections:
379 380 section = test_sections[arg1[8:]]
380 381 sys.argv[1:2] = section.includes
381 382 else:
382 383 section = TestSection(arg1, includes=[arg1])
383 384
384 385
385 386 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
386 387 # We add --exe because of setuptools' imbecility (it
387 388 # blindly does chmod +x on ALL files). Nose does the
388 389 # right thing and it tries to avoid executables,
389 390 # setuptools unfortunately forces our hand here. This
390 391 # has been discussed on the distutils list and the
391 392 # setuptools devs refuse to fix this problem!
392 393 '--exe',
393 394 ]
394 395 if '-a' not in argv and '-A' not in argv:
395 396 argv = argv + ['-a', '!crash']
396 397
397 398 if nose.__version__ >= '0.11':
398 399 # I don't fully understand why we need this one, but depending on what
399 400 # directory the test suite is run from, if we don't give it, 0 tests
400 401 # get run. Specifically, if the test suite is run from the source dir
401 402 # with an argument (like 'iptest.py IPython.core', 0 tests are run,
402 403 # even if the same call done in this directory works fine). It appears
403 404 # that if the requested package is in the current dir, nose bails early
404 405 # by default. Since it's otherwise harmless, leave it in by default
405 406 # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it.
406 407 argv.append('--traverse-namespace')
407 408
408 409 plugins = [ ExclusionPlugin(section.excludes), KnownFailure(),
409 410 SubprocessStreamCapturePlugin() ]
410 411
411 412 # we still have some vestigial doctests in core
412 413 if (section.name.startswith(('core', 'IPython.core'))):
413 414 plugins.append(IPythonDoctest())
414 415 argv.extend([
415 416 '--with-ipdoctest',
416 417 '--ipdoctest-tests',
417 418 '--ipdoctest-extension=txt',
418 419 ])
419 420
420 421
421 422 # Use working directory set by parent process (see iptestcontroller)
422 423 if 'IPTEST_WORKING_DIR' in os.environ:
423 424 os.chdir(os.environ['IPTEST_WORKING_DIR'])
424 425
425 426 # We need a global ipython running in this process, but the special
426 427 # in-process group spawns its own IPython kernels, so for *that* group we
427 428 # must avoid also opening the global one (otherwise there's a conflict of
428 429 # singletons). Ultimately the solution to this problem is to refactor our
429 430 # assumptions about what needs to be a singleton and what doesn't (app
430 431 # objects should, individual shells shouldn't). But for now, this
431 432 # workaround allows the test suite for the inprocess module to complete.
432 433 if 'kernel.inprocess' not in section.name:
433 434 from IPython.testing import globalipapp
434 435 globalipapp.start_ipython()
435 436
436 437 # Now nose can run
437 438 TestProgram(argv=argv, addplugins=plugins)
438 439
439 440 if __name__ == '__main__':
440 441 run_iptest()
441 442
General Comments 0
You need to be logged in to leave comments. Login now