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