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