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