##// END OF EJS Templates
html and js tests require sqlite3 (session manager)
MinRK -
Show More
@@ -1,524 +1,524 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """IPython Test Suite Runner.
2 """IPython Test Suite Runner.
3
3
4 This module provides a main entry point to a user script to test IPython
4 This module provides a main entry point to a user script to test IPython
5 itself from the command line. There are two ways of running this script:
5 itself from the command line. There are two ways of running this script:
6
6
7 1. With the syntax `iptest all`. This runs our entire test suite by
7 1. With the syntax `iptest all`. This runs our entire test suite by
8 calling this script (with different arguments) recursively. This
8 calling this script (with different arguments) recursively. This
9 causes modules and package to be tested in different processes, using nose
9 causes modules and package to be tested in different processes, using nose
10 or trial where appropriate.
10 or trial where appropriate.
11 2. With the regular nose syntax, like `iptest -vvs IPython`. In this form
11 2. With the regular nose syntax, like `iptest -vvs IPython`. In this form
12 the script simply calls nose, but with special command line flags and
12 the script simply calls nose, but with special command line flags and
13 plugins loaded.
13 plugins loaded.
14
14
15 """
15 """
16
16
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18 # Copyright (C) 2009-2011 The IPython Development Team
18 # Copyright (C) 2009-2011 The IPython Development Team
19 #
19 #
20 # Distributed under the terms of the BSD License. The full license is in
20 # Distributed under the terms of the BSD License. The full license is in
21 # the file COPYING, distributed as part of this software.
21 # the file COPYING, distributed as part of this software.
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23
23
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25 # Imports
25 # Imports
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27 from __future__ import print_function
27 from __future__ import print_function
28
28
29 # Stdlib
29 # Stdlib
30 import glob
30 import glob
31 from io import BytesIO
31 from io import BytesIO
32 import os
32 import os
33 import os.path as path
33 import os.path as path
34 import sys
34 import sys
35 from threading import Thread, Lock, Event
35 from threading import Thread, Lock, Event
36 import warnings
36 import warnings
37
37
38 # Now, proceed to import nose itself
38 # Now, proceed to import nose itself
39 import nose.plugins.builtin
39 import nose.plugins.builtin
40 from nose.plugins.xunit import Xunit
40 from nose.plugins.xunit import Xunit
41 from nose import SkipTest
41 from nose import SkipTest
42 from nose.core import TestProgram
42 from nose.core import TestProgram
43 from nose.plugins import Plugin
43 from nose.plugins import Plugin
44 from nose.util import safe_str
44 from nose.util import safe_str
45
45
46 # Our own imports
46 # Our own imports
47 from IPython.utils.process import is_cmd_found
47 from IPython.utils.process import is_cmd_found
48 from IPython.utils.importstring import import_item
48 from IPython.utils.importstring import import_item
49 from IPython.testing.plugin.ipdoctest import IPythonDoctest
49 from IPython.testing.plugin.ipdoctest import IPythonDoctest
50 from IPython.external.decorators import KnownFailure, knownfailureif
50 from IPython.external.decorators import KnownFailure, knownfailureif
51
51
52 pjoin = path.join
52 pjoin = path.join
53
53
54
54
55 #-----------------------------------------------------------------------------
55 #-----------------------------------------------------------------------------
56 # Globals
56 # Globals
57 #-----------------------------------------------------------------------------
57 #-----------------------------------------------------------------------------
58
58
59
59
60 #-----------------------------------------------------------------------------
60 #-----------------------------------------------------------------------------
61 # Warnings control
61 # Warnings control
62 #-----------------------------------------------------------------------------
62 #-----------------------------------------------------------------------------
63
63
64 # Twisted generates annoying warnings with Python 2.6, as will do other code
64 # Twisted generates annoying warnings with Python 2.6, as will do other code
65 # that imports 'sets' as of today
65 # that imports 'sets' as of today
66 warnings.filterwarnings('ignore', 'the sets module is deprecated',
66 warnings.filterwarnings('ignore', 'the sets module is deprecated',
67 DeprecationWarning )
67 DeprecationWarning )
68
68
69 # This one also comes from Twisted
69 # This one also comes from Twisted
70 warnings.filterwarnings('ignore', 'the sha module is deprecated',
70 warnings.filterwarnings('ignore', 'the sha module is deprecated',
71 DeprecationWarning)
71 DeprecationWarning)
72
72
73 # Wx on Fedora11 spits these out
73 # Wx on Fedora11 spits these out
74 warnings.filterwarnings('ignore', 'wxPython/wxWidgets release number mismatch',
74 warnings.filterwarnings('ignore', 'wxPython/wxWidgets release number mismatch',
75 UserWarning)
75 UserWarning)
76
76
77 # ------------------------------------------------------------------------------
77 # ------------------------------------------------------------------------------
78 # Monkeypatch Xunit to count known failures as skipped.
78 # Monkeypatch Xunit to count known failures as skipped.
79 # ------------------------------------------------------------------------------
79 # ------------------------------------------------------------------------------
80 def monkeypatch_xunit():
80 def monkeypatch_xunit():
81 try:
81 try:
82 knownfailureif(True)(lambda: None)()
82 knownfailureif(True)(lambda: None)()
83 except Exception as e:
83 except Exception as e:
84 KnownFailureTest = type(e)
84 KnownFailureTest = type(e)
85
85
86 def addError(self, test, err, capt=None):
86 def addError(self, test, err, capt=None):
87 if issubclass(err[0], KnownFailureTest):
87 if issubclass(err[0], KnownFailureTest):
88 err = (SkipTest,) + err[1:]
88 err = (SkipTest,) + err[1:]
89 return self.orig_addError(test, err, capt)
89 return self.orig_addError(test, err, capt)
90
90
91 Xunit.orig_addError = Xunit.addError
91 Xunit.orig_addError = Xunit.addError
92 Xunit.addError = addError
92 Xunit.addError = addError
93
93
94 #-----------------------------------------------------------------------------
94 #-----------------------------------------------------------------------------
95 # Check which dependencies are installed and greater than minimum version.
95 # Check which dependencies are installed and greater than minimum version.
96 #-----------------------------------------------------------------------------
96 #-----------------------------------------------------------------------------
97 def extract_version(mod):
97 def extract_version(mod):
98 return mod.__version__
98 return mod.__version__
99
99
100 def test_for(item, min_version=None, callback=extract_version):
100 def test_for(item, min_version=None, callback=extract_version):
101 """Test to see if item is importable, and optionally check against a minimum
101 """Test to see if item is importable, and optionally check against a minimum
102 version.
102 version.
103
103
104 If min_version is given, the default behavior is to check against the
104 If min_version is given, the default behavior is to check against the
105 `__version__` attribute of the item, but specifying `callback` allows you to
105 `__version__` attribute of the item, but specifying `callback` allows you to
106 extract the value you are interested in. e.g::
106 extract the value you are interested in. e.g::
107
107
108 In [1]: import sys
108 In [1]: import sys
109
109
110 In [2]: from IPython.testing.iptest import test_for
110 In [2]: from IPython.testing.iptest import test_for
111
111
112 In [3]: test_for('sys', (2,6), callback=lambda sys: sys.version_info)
112 In [3]: test_for('sys', (2,6), callback=lambda sys: sys.version_info)
113 Out[3]: True
113 Out[3]: True
114
114
115 """
115 """
116 try:
116 try:
117 check = import_item(item)
117 check = import_item(item)
118 except (ImportError, RuntimeError):
118 except (ImportError, RuntimeError):
119 # GTK reports Runtime error if it can't be initialized even if it's
119 # GTK reports Runtime error if it can't be initialized even if it's
120 # importable.
120 # importable.
121 return False
121 return False
122 else:
122 else:
123 if min_version:
123 if min_version:
124 if callback:
124 if callback:
125 # extra processing step to get version to compare
125 # extra processing step to get version to compare
126 check = callback(check)
126 check = callback(check)
127
127
128 return check >= min_version
128 return check >= min_version
129 else:
129 else:
130 return True
130 return True
131
131
132 # Global dict where we can store information on what we have and what we don't
132 # Global dict where we can store information on what we have and what we don't
133 # have available at test run time
133 # have available at test run time
134 have = {}
134 have = {}
135
135
136 have['curses'] = test_for('_curses')
136 have['curses'] = test_for('_curses')
137 have['matplotlib'] = test_for('matplotlib')
137 have['matplotlib'] = test_for('matplotlib')
138 have['numpy'] = test_for('numpy')
138 have['numpy'] = test_for('numpy')
139 have['pexpect'] = test_for('IPython.external.pexpect')
139 have['pexpect'] = test_for('IPython.external.pexpect')
140 have['pymongo'] = test_for('pymongo')
140 have['pymongo'] = test_for('pymongo')
141 have['pygments'] = test_for('pygments')
141 have['pygments'] = test_for('pygments')
142 have['qt'] = test_for('IPython.external.qt')
142 have['qt'] = test_for('IPython.external.qt')
143 have['rpy2'] = test_for('rpy2')
143 have['rpy2'] = test_for('rpy2')
144 have['sqlite3'] = test_for('sqlite3')
144 have['sqlite3'] = test_for('sqlite3')
145 have['cython'] = test_for('Cython')
145 have['cython'] = test_for('Cython')
146 have['oct2py'] = test_for('oct2py')
146 have['oct2py'] = test_for('oct2py')
147 have['tornado'] = test_for('tornado.version_info', (3,1,0), callback=None)
147 have['tornado'] = test_for('tornado.version_info', (3,1,0), callback=None)
148 have['jinja2'] = test_for('jinja2')
148 have['jinja2'] = test_for('jinja2')
149 have['requests'] = test_for('requests')
149 have['requests'] = test_for('requests')
150 have['sphinx'] = test_for('sphinx')
150 have['sphinx'] = test_for('sphinx')
151 have['casperjs'] = is_cmd_found('casperjs')
151 have['casperjs'] = is_cmd_found('casperjs')
152
152
153 min_zmq = (2,1,11)
153 min_zmq = (2,1,11)
154
154
155 have['zmq'] = test_for('zmq.pyzmq_version_info', min_zmq, callback=lambda x: x())
155 have['zmq'] = test_for('zmq.pyzmq_version_info', min_zmq, callback=lambda x: x())
156
156
157 #-----------------------------------------------------------------------------
157 #-----------------------------------------------------------------------------
158 # Test suite definitions
158 # Test suite definitions
159 #-----------------------------------------------------------------------------
159 #-----------------------------------------------------------------------------
160
160
161 test_group_names = ['parallel', 'kernel', 'kernel.inprocess', 'config', 'core',
161 test_group_names = ['parallel', 'kernel', 'kernel.inprocess', 'config', 'core',
162 'extensions', 'lib', 'terminal', 'testing', 'utils',
162 'extensions', 'lib', 'terminal', 'testing', 'utils',
163 'nbformat', 'qt', 'html', 'nbconvert'
163 'nbformat', 'qt', 'html', 'nbconvert'
164 ]
164 ]
165
165
166 class TestSection(object):
166 class TestSection(object):
167 def __init__(self, name, includes):
167 def __init__(self, name, includes):
168 self.name = name
168 self.name = name
169 self.includes = includes
169 self.includes = includes
170 self.excludes = []
170 self.excludes = []
171 self.dependencies = []
171 self.dependencies = []
172 self.enabled = True
172 self.enabled = True
173
173
174 def exclude(self, module):
174 def exclude(self, module):
175 if not module.startswith('IPython'):
175 if not module.startswith('IPython'):
176 module = self.includes[0] + "." + module
176 module = self.includes[0] + "." + module
177 self.excludes.append(module.replace('.', os.sep))
177 self.excludes.append(module.replace('.', os.sep))
178
178
179 def requires(self, *packages):
179 def requires(self, *packages):
180 self.dependencies.extend(packages)
180 self.dependencies.extend(packages)
181
181
182 @property
182 @property
183 def will_run(self):
183 def will_run(self):
184 return self.enabled and all(have[p] for p in self.dependencies)
184 return self.enabled and all(have[p] for p in self.dependencies)
185
185
186 # Name -> (include, exclude, dependencies_met)
186 # Name -> (include, exclude, dependencies_met)
187 test_sections = {n:TestSection(n, ['IPython.%s' % n]) for n in test_group_names}
187 test_sections = {n:TestSection(n, ['IPython.%s' % n]) for n in test_group_names}
188
188
189 # Exclusions and dependencies
189 # Exclusions and dependencies
190 # ---------------------------
190 # ---------------------------
191
191
192 # core:
192 # core:
193 sec = test_sections['core']
193 sec = test_sections['core']
194 if not have['sqlite3']:
194 if not have['sqlite3']:
195 sec.exclude('tests.test_history')
195 sec.exclude('tests.test_history')
196 sec.exclude('history')
196 sec.exclude('history')
197 if not have['matplotlib']:
197 if not have['matplotlib']:
198 sec.exclude('pylabtools'),
198 sec.exclude('pylabtools'),
199 sec.exclude('tests.test_pylabtools')
199 sec.exclude('tests.test_pylabtools')
200
200
201 # lib:
201 # lib:
202 sec = test_sections['lib']
202 sec = test_sections['lib']
203 if not have['zmq']:
203 if not have['zmq']:
204 sec.exclude('kernel')
204 sec.exclude('kernel')
205 # We do this unconditionally, so that the test suite doesn't import
205 # We do this unconditionally, so that the test suite doesn't import
206 # gtk, changing the default encoding and masking some unicode bugs.
206 # gtk, changing the default encoding and masking some unicode bugs.
207 sec.exclude('inputhookgtk')
207 sec.exclude('inputhookgtk')
208 # We also do this unconditionally, because wx can interfere with Unix signals.
208 # We also do this unconditionally, because wx can interfere with Unix signals.
209 # There are currently no tests for it anyway.
209 # There are currently no tests for it anyway.
210 sec.exclude('inputhookwx')
210 sec.exclude('inputhookwx')
211 # Testing inputhook will need a lot of thought, to figure out
211 # Testing inputhook will need a lot of thought, to figure out
212 # how to have tests that don't lock up with the gui event
212 # how to have tests that don't lock up with the gui event
213 # loops in the picture
213 # loops in the picture
214 sec.exclude('inputhook')
214 sec.exclude('inputhook')
215
215
216 # testing:
216 # testing:
217 sec = test_sections['testing']
217 sec = test_sections['testing']
218 # These have to be skipped on win32 because they use echo, rm, cd, etc.
218 # These have to be skipped on win32 because they use echo, rm, cd, etc.
219 # See ticket https://github.com/ipython/ipython/issues/87
219 # See ticket https://github.com/ipython/ipython/issues/87
220 if sys.platform == 'win32':
220 if sys.platform == 'win32':
221 sec.exclude('plugin.test_exampleip')
221 sec.exclude('plugin.test_exampleip')
222 sec.exclude('plugin.dtexample')
222 sec.exclude('plugin.dtexample')
223
223
224 # terminal:
224 # terminal:
225 if (not have['pexpect']) or (not have['zmq']):
225 if (not have['pexpect']) or (not have['zmq']):
226 test_sections['terminal'].exclude('console')
226 test_sections['terminal'].exclude('console')
227
227
228 # parallel
228 # parallel
229 sec = test_sections['parallel']
229 sec = test_sections['parallel']
230 sec.requires('zmq')
230 sec.requires('zmq')
231 if not have['pymongo']:
231 if not have['pymongo']:
232 sec.exclude('controller.mongodb')
232 sec.exclude('controller.mongodb')
233 sec.exclude('tests.test_mongodb')
233 sec.exclude('tests.test_mongodb')
234
234
235 # kernel:
235 # kernel:
236 sec = test_sections['kernel']
236 sec = test_sections['kernel']
237 sec.requires('zmq')
237 sec.requires('zmq')
238 # The in-process kernel tests are done in a separate section
238 # The in-process kernel tests are done in a separate section
239 sec.exclude('inprocess')
239 sec.exclude('inprocess')
240 # importing gtk sets the default encoding, which we want to avoid
240 # importing gtk sets the default encoding, which we want to avoid
241 sec.exclude('zmq.gui.gtkembed')
241 sec.exclude('zmq.gui.gtkembed')
242 if not have['matplotlib']:
242 if not have['matplotlib']:
243 sec.exclude('zmq.pylab')
243 sec.exclude('zmq.pylab')
244
244
245 # kernel.inprocess:
245 # kernel.inprocess:
246 test_sections['kernel.inprocess'].requires('zmq')
246 test_sections['kernel.inprocess'].requires('zmq')
247
247
248 # extensions:
248 # extensions:
249 sec = test_sections['extensions']
249 sec = test_sections['extensions']
250 if not have['cython']:
250 if not have['cython']:
251 sec.exclude('cythonmagic')
251 sec.exclude('cythonmagic')
252 sec.exclude('tests.test_cythonmagic')
252 sec.exclude('tests.test_cythonmagic')
253 if not have['oct2py']:
253 if not have['oct2py']:
254 sec.exclude('octavemagic')
254 sec.exclude('octavemagic')
255 sec.exclude('tests.test_octavemagic')
255 sec.exclude('tests.test_octavemagic')
256 if not have['rpy2'] or not have['numpy']:
256 if not have['rpy2'] or not have['numpy']:
257 sec.exclude('rmagic')
257 sec.exclude('rmagic')
258 sec.exclude('tests.test_rmagic')
258 sec.exclude('tests.test_rmagic')
259 # autoreload does some strange stuff, so move it to its own test section
259 # autoreload does some strange stuff, so move it to its own test section
260 sec.exclude('autoreload')
260 sec.exclude('autoreload')
261 sec.exclude('tests.test_autoreload')
261 sec.exclude('tests.test_autoreload')
262 test_sections['autoreload'] = TestSection('autoreload',
262 test_sections['autoreload'] = TestSection('autoreload',
263 ['IPython.extensions.autoreload', 'IPython.extensions.tests.test_autoreload'])
263 ['IPython.extensions.autoreload', 'IPython.extensions.tests.test_autoreload'])
264 test_group_names.append('autoreload')
264 test_group_names.append('autoreload')
265
265
266 # qt:
266 # qt:
267 test_sections['qt'].requires('zmq', 'qt', 'pygments')
267 test_sections['qt'].requires('zmq', 'qt', 'pygments')
268
268
269 # html:
269 # html:
270 sec = test_sections['html']
270 sec = test_sections['html']
271 sec.requires('zmq', 'tornado', 'requests')
271 sec.requires('zmq', 'tornado', 'requests', 'sqlite3')
272 # The notebook 'static' directory contains JS, css and other
272 # The notebook 'static' directory contains JS, css and other
273 # files for web serving. Occasionally projects may put a .py
273 # files for web serving. Occasionally projects may put a .py
274 # file in there (MathJax ships a conf.py), so we might as
274 # file in there (MathJax ships a conf.py), so we might as
275 # well play it safe and skip the whole thing.
275 # well play it safe and skip the whole thing.
276 sec.exclude('static')
276 sec.exclude('static')
277 sec.exclude('fabfile')
277 sec.exclude('fabfile')
278 if not have['jinja2']:
278 if not have['jinja2']:
279 sec.exclude('notebookapp')
279 sec.exclude('notebookapp')
280 if not have['pygments'] or not have['jinja2']:
280 if not have['pygments'] or not have['jinja2']:
281 sec.exclude('nbconvert')
281 sec.exclude('nbconvert')
282
282
283 # config:
283 # config:
284 # Config files aren't really importable stand-alone
284 # Config files aren't really importable stand-alone
285 test_sections['config'].exclude('profile')
285 test_sections['config'].exclude('profile')
286
286
287 # nbconvert:
287 # nbconvert:
288 sec = test_sections['nbconvert']
288 sec = test_sections['nbconvert']
289 sec.requires('pygments', 'jinja2')
289 sec.requires('pygments', 'jinja2')
290 # Exclude nbconvert directories containing config files used to test.
290 # Exclude nbconvert directories containing config files used to test.
291 # Executing the config files with iptest would cause an exception.
291 # Executing the config files with iptest would cause an exception.
292 sec.exclude('tests.files')
292 sec.exclude('tests.files')
293 sec.exclude('exporters.tests.files')
293 sec.exclude('exporters.tests.files')
294 if not have['tornado']:
294 if not have['tornado']:
295 sec.exclude('nbconvert.post_processors.serve')
295 sec.exclude('nbconvert.post_processors.serve')
296 sec.exclude('nbconvert.post_processors.tests.test_serve')
296 sec.exclude('nbconvert.post_processors.tests.test_serve')
297
297
298 #-----------------------------------------------------------------------------
298 #-----------------------------------------------------------------------------
299 # Functions and classes
299 # Functions and classes
300 #-----------------------------------------------------------------------------
300 #-----------------------------------------------------------------------------
301
301
302 def check_exclusions_exist():
302 def check_exclusions_exist():
303 from IPython.utils.path import get_ipython_package_dir
303 from IPython.utils.path import get_ipython_package_dir
304 from IPython.utils.warn import warn
304 from IPython.utils.warn import warn
305 parent = os.path.dirname(get_ipython_package_dir())
305 parent = os.path.dirname(get_ipython_package_dir())
306 for sec in test_sections:
306 for sec in test_sections:
307 for pattern in sec.exclusions:
307 for pattern in sec.exclusions:
308 fullpath = pjoin(parent, pattern)
308 fullpath = pjoin(parent, pattern)
309 if not os.path.exists(fullpath) and not glob.glob(fullpath + '.*'):
309 if not os.path.exists(fullpath) and not glob.glob(fullpath + '.*'):
310 warn("Excluding nonexistent file: %r" % pattern)
310 warn("Excluding nonexistent file: %r" % pattern)
311
311
312
312
313 class ExclusionPlugin(Plugin):
313 class ExclusionPlugin(Plugin):
314 """A nose plugin to effect our exclusions of files and directories.
314 """A nose plugin to effect our exclusions of files and directories.
315 """
315 """
316 name = 'exclusions'
316 name = 'exclusions'
317 score = 3000 # Should come before any other plugins
317 score = 3000 # Should come before any other plugins
318
318
319 def __init__(self, exclude_patterns=None):
319 def __init__(self, exclude_patterns=None):
320 """
320 """
321 Parameters
321 Parameters
322 ----------
322 ----------
323
323
324 exclude_patterns : sequence of strings, optional
324 exclude_patterns : sequence of strings, optional
325 Filenames containing these patterns (as raw strings, not as regular
325 Filenames containing these patterns (as raw strings, not as regular
326 expressions) are excluded from the tests.
326 expressions) are excluded from the tests.
327 """
327 """
328 self.exclude_patterns = exclude_patterns or []
328 self.exclude_patterns = exclude_patterns or []
329 super(ExclusionPlugin, self).__init__()
329 super(ExclusionPlugin, self).__init__()
330
330
331 def options(self, parser, env=os.environ):
331 def options(self, parser, env=os.environ):
332 Plugin.options(self, parser, env)
332 Plugin.options(self, parser, env)
333
333
334 def configure(self, options, config):
334 def configure(self, options, config):
335 Plugin.configure(self, options, config)
335 Plugin.configure(self, options, config)
336 # Override nose trying to disable plugin.
336 # Override nose trying to disable plugin.
337 self.enabled = True
337 self.enabled = True
338
338
339 def wantFile(self, filename):
339 def wantFile(self, filename):
340 """Return whether the given filename should be scanned for tests.
340 """Return whether the given filename should be scanned for tests.
341 """
341 """
342 if any(pat in filename for pat in self.exclude_patterns):
342 if any(pat in filename for pat in self.exclude_patterns):
343 return False
343 return False
344 return None
344 return None
345
345
346 def wantDirectory(self, directory):
346 def wantDirectory(self, directory):
347 """Return whether the given directory should be scanned for tests.
347 """Return whether the given directory should be scanned for tests.
348 """
348 """
349 if any(pat in directory for pat in self.exclude_patterns):
349 if any(pat in directory for pat in self.exclude_patterns):
350 return False
350 return False
351 return None
351 return None
352
352
353
353
354 class StreamCapturer(Thread):
354 class StreamCapturer(Thread):
355 daemon = True # Don't hang if main thread crashes
355 daemon = True # Don't hang if main thread crashes
356 started = False
356 started = False
357 def __init__(self):
357 def __init__(self):
358 super(StreamCapturer, self).__init__()
358 super(StreamCapturer, self).__init__()
359 self.streams = []
359 self.streams = []
360 self.buffer = BytesIO()
360 self.buffer = BytesIO()
361 self.readfd, self.writefd = os.pipe()
361 self.readfd, self.writefd = os.pipe()
362 self.buffer_lock = Lock()
362 self.buffer_lock = Lock()
363 self.stop = Event()
363 self.stop = Event()
364
364
365 def run(self):
365 def run(self):
366 self.started = True
366 self.started = True
367
367
368 while not self.stop.is_set():
368 while not self.stop.is_set():
369 chunk = os.read(self.readfd, 1024)
369 chunk = os.read(self.readfd, 1024)
370
370
371 with self.buffer_lock:
371 with self.buffer_lock:
372 self.buffer.write(chunk)
372 self.buffer.write(chunk)
373
373
374 os.close(self.readfd)
374 os.close(self.readfd)
375 os.close(self.writefd)
375 os.close(self.writefd)
376
376
377 def reset_buffer(self):
377 def reset_buffer(self):
378 with self.buffer_lock:
378 with self.buffer_lock:
379 self.buffer.truncate(0)
379 self.buffer.truncate(0)
380 self.buffer.seek(0)
380 self.buffer.seek(0)
381
381
382 def get_buffer(self):
382 def get_buffer(self):
383 with self.buffer_lock:
383 with self.buffer_lock:
384 return self.buffer.getvalue()
384 return self.buffer.getvalue()
385
385
386 def ensure_started(self):
386 def ensure_started(self):
387 if not self.started:
387 if not self.started:
388 self.start()
388 self.start()
389
389
390 def halt(self):
390 def halt(self):
391 """Safely stop the thread."""
391 """Safely stop the thread."""
392 if not self.started:
392 if not self.started:
393 return
393 return
394
394
395 self.stop.set()
395 self.stop.set()
396 os.write(self.writefd, b'wake up') # Ensure we're not locked in a read()
396 os.write(self.writefd, b'wake up') # Ensure we're not locked in a read()
397 self.join()
397 self.join()
398
398
399 class SubprocessStreamCapturePlugin(Plugin):
399 class SubprocessStreamCapturePlugin(Plugin):
400 name='subprocstreams'
400 name='subprocstreams'
401 def __init__(self):
401 def __init__(self):
402 Plugin.__init__(self)
402 Plugin.__init__(self)
403 self.stream_capturer = StreamCapturer()
403 self.stream_capturer = StreamCapturer()
404 self.destination = os.environ.get('IPTEST_SUBPROC_STREAMS', 'capture')
404 self.destination = os.environ.get('IPTEST_SUBPROC_STREAMS', 'capture')
405 # This is ugly, but distant parts of the test machinery need to be able
405 # This is ugly, but distant parts of the test machinery need to be able
406 # to redirect streams, so we make the object globally accessible.
406 # to redirect streams, so we make the object globally accessible.
407 nose.iptest_stdstreams_fileno = self.get_write_fileno
407 nose.iptest_stdstreams_fileno = self.get_write_fileno
408
408
409 def get_write_fileno(self):
409 def get_write_fileno(self):
410 if self.destination == 'capture':
410 if self.destination == 'capture':
411 self.stream_capturer.ensure_started()
411 self.stream_capturer.ensure_started()
412 return self.stream_capturer.writefd
412 return self.stream_capturer.writefd
413 elif self.destination == 'discard':
413 elif self.destination == 'discard':
414 return os.open(os.devnull, os.O_WRONLY)
414 return os.open(os.devnull, os.O_WRONLY)
415 else:
415 else:
416 return sys.__stdout__.fileno()
416 return sys.__stdout__.fileno()
417
417
418 def configure(self, options, config):
418 def configure(self, options, config):
419 Plugin.configure(self, options, config)
419 Plugin.configure(self, options, config)
420 # Override nose trying to disable plugin.
420 # Override nose trying to disable plugin.
421 if self.destination == 'capture':
421 if self.destination == 'capture':
422 self.enabled = True
422 self.enabled = True
423
423
424 def startTest(self, test):
424 def startTest(self, test):
425 # Reset log capture
425 # Reset log capture
426 self.stream_capturer.reset_buffer()
426 self.stream_capturer.reset_buffer()
427
427
428 def formatFailure(self, test, err):
428 def formatFailure(self, test, err):
429 # Show output
429 # Show output
430 ec, ev, tb = err
430 ec, ev, tb = err
431 captured = self.stream_capturer.get_buffer().decode('utf-8', 'replace')
431 captured = self.stream_capturer.get_buffer().decode('utf-8', 'replace')
432 if captured.strip():
432 if captured.strip():
433 ev = safe_str(ev)
433 ev = safe_str(ev)
434 out = [ev, '>> begin captured subprocess output <<',
434 out = [ev, '>> begin captured subprocess output <<',
435 captured,
435 captured,
436 '>> end captured subprocess output <<']
436 '>> end captured subprocess output <<']
437 return ec, '\n'.join(out), tb
437 return ec, '\n'.join(out), tb
438
438
439 return err
439 return err
440
440
441 formatError = formatFailure
441 formatError = formatFailure
442
442
443 def finalize(self, result):
443 def finalize(self, result):
444 self.stream_capturer.halt()
444 self.stream_capturer.halt()
445
445
446
446
447 def run_iptest():
447 def run_iptest():
448 """Run the IPython test suite using nose.
448 """Run the IPython test suite using nose.
449
449
450 This function is called when this script is **not** called with the form
450 This function is called when this script is **not** called with the form
451 `iptest all`. It simply calls nose with appropriate command line flags
451 `iptest all`. It simply calls nose with appropriate command line flags
452 and accepts all of the standard nose arguments.
452 and accepts all of the standard nose arguments.
453 """
453 """
454 # Apply our monkeypatch to Xunit
454 # Apply our monkeypatch to Xunit
455 if '--with-xunit' in sys.argv and not hasattr(Xunit, 'orig_addError'):
455 if '--with-xunit' in sys.argv and not hasattr(Xunit, 'orig_addError'):
456 monkeypatch_xunit()
456 monkeypatch_xunit()
457
457
458 warnings.filterwarnings('ignore',
458 warnings.filterwarnings('ignore',
459 'This will be removed soon. Use IPython.testing.util instead')
459 'This will be removed soon. Use IPython.testing.util instead')
460
460
461 arg1 = sys.argv[1]
461 arg1 = sys.argv[1]
462 if arg1 in test_sections:
462 if arg1 in test_sections:
463 section = test_sections[arg1]
463 section = test_sections[arg1]
464 sys.argv[1:2] = section.includes
464 sys.argv[1:2] = section.includes
465 elif arg1.startswith('IPython.') and arg1[8:] in test_sections:
465 elif arg1.startswith('IPython.') and arg1[8:] in test_sections:
466 section = test_sections[arg1[8:]]
466 section = test_sections[arg1[8:]]
467 sys.argv[1:2] = section.includes
467 sys.argv[1:2] = section.includes
468 else:
468 else:
469 section = TestSection(arg1, includes=[arg1])
469 section = TestSection(arg1, includes=[arg1])
470
470
471
471
472 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
472 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
473
473
474 '--with-ipdoctest',
474 '--with-ipdoctest',
475 '--ipdoctest-tests','--ipdoctest-extension=txt',
475 '--ipdoctest-tests','--ipdoctest-extension=txt',
476
476
477 # We add --exe because of setuptools' imbecility (it
477 # We add --exe because of setuptools' imbecility (it
478 # blindly does chmod +x on ALL files). Nose does the
478 # blindly does chmod +x on ALL files). Nose does the
479 # right thing and it tries to avoid executables,
479 # right thing and it tries to avoid executables,
480 # setuptools unfortunately forces our hand here. This
480 # setuptools unfortunately forces our hand here. This
481 # has been discussed on the distutils list and the
481 # has been discussed on the distutils list and the
482 # setuptools devs refuse to fix this problem!
482 # setuptools devs refuse to fix this problem!
483 '--exe',
483 '--exe',
484 ]
484 ]
485 if '-a' not in argv and '-A' not in argv:
485 if '-a' not in argv and '-A' not in argv:
486 argv = argv + ['-a', '!crash']
486 argv = argv + ['-a', '!crash']
487
487
488 if nose.__version__ >= '0.11':
488 if nose.__version__ >= '0.11':
489 # I don't fully understand why we need this one, but depending on what
489 # I don't fully understand why we need this one, but depending on what
490 # directory the test suite is run from, if we don't give it, 0 tests
490 # directory the test suite is run from, if we don't give it, 0 tests
491 # get run. Specifically, if the test suite is run from the source dir
491 # get run. Specifically, if the test suite is run from the source dir
492 # with an argument (like 'iptest.py IPython.core', 0 tests are run,
492 # with an argument (like 'iptest.py IPython.core', 0 tests are run,
493 # even if the same call done in this directory works fine). It appears
493 # even if the same call done in this directory works fine). It appears
494 # that if the requested package is in the current dir, nose bails early
494 # that if the requested package is in the current dir, nose bails early
495 # by default. Since it's otherwise harmless, leave it in by default
495 # by default. Since it's otherwise harmless, leave it in by default
496 # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it.
496 # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it.
497 argv.append('--traverse-namespace')
497 argv.append('--traverse-namespace')
498
498
499 # use our plugin for doctesting. It will remove the standard doctest plugin
499 # use our plugin for doctesting. It will remove the standard doctest plugin
500 # if it finds it enabled
500 # if it finds it enabled
501 plugins = [ExclusionPlugin(section.excludes), IPythonDoctest(), KnownFailure(),
501 plugins = [ExclusionPlugin(section.excludes), IPythonDoctest(), KnownFailure(),
502 SubprocessStreamCapturePlugin() ]
502 SubprocessStreamCapturePlugin() ]
503
503
504 # Use working directory set by parent process (see iptestcontroller)
504 # Use working directory set by parent process (see iptestcontroller)
505 if 'IPTEST_WORKING_DIR' in os.environ:
505 if 'IPTEST_WORKING_DIR' in os.environ:
506 os.chdir(os.environ['IPTEST_WORKING_DIR'])
506 os.chdir(os.environ['IPTEST_WORKING_DIR'])
507
507
508 # We need a global ipython running in this process, but the special
508 # We need a global ipython running in this process, but the special
509 # in-process group spawns its own IPython kernels, so for *that* group we
509 # in-process group spawns its own IPython kernels, so for *that* group we
510 # must avoid also opening the global one (otherwise there's a conflict of
510 # must avoid also opening the global one (otherwise there's a conflict of
511 # singletons). Ultimately the solution to this problem is to refactor our
511 # singletons). Ultimately the solution to this problem is to refactor our
512 # assumptions about what needs to be a singleton and what doesn't (app
512 # assumptions about what needs to be a singleton and what doesn't (app
513 # objects should, individual shells shouldn't). But for now, this
513 # objects should, individual shells shouldn't). But for now, this
514 # workaround allows the test suite for the inprocess module to complete.
514 # workaround allows the test suite for the inprocess module to complete.
515 if 'kernel.inprocess' not in section.name:
515 if 'kernel.inprocess' not in section.name:
516 from IPython.testing import globalipapp
516 from IPython.testing import globalipapp
517 globalipapp.start_ipython()
517 globalipapp.start_ipython()
518
518
519 # Now nose can run
519 # Now nose can run
520 TestProgram(argv=argv, addplugins=plugins)
520 TestProgram(argv=argv, addplugins=plugins)
521
521
522 if __name__ == '__main__':
522 if __name__ == '__main__':
523 run_iptest()
523 run_iptest()
524
524
@@ -1,595 +1,595 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """IPython Test Process Controller
2 """IPython Test Process Controller
3
3
4 This module runs one or more subprocesses which will actually run the IPython
4 This module runs one or more subprocesses which will actually run the IPython
5 test suite.
5 test suite.
6
6
7 """
7 """
8
8
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10 # Copyright (C) 2009-2011 The IPython Development Team
10 # Copyright (C) 2009-2011 The IPython Development Team
11 #
11 #
12 # Distributed under the terms of the BSD License. The full license is in
12 # Distributed under the terms of the BSD License. The full license is in
13 # the file COPYING, distributed as part of this software.
13 # the file COPYING, distributed as part of this software.
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15
15
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 # Imports
17 # Imports
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19 from __future__ import print_function
19 from __future__ import print_function
20
20
21 import argparse
21 import argparse
22 import json
22 import json
23 import multiprocessing.pool
23 import multiprocessing.pool
24 import os
24 import os
25 import shutil
25 import shutil
26 import signal
26 import signal
27 import sys
27 import sys
28 import subprocess
28 import subprocess
29 import time
29 import time
30
30
31 from .iptest import have, test_group_names as py_test_group_names, test_sections, StreamCapturer
31 from .iptest import have, test_group_names as py_test_group_names, test_sections, StreamCapturer
32 from IPython.utils.path import compress_user
32 from IPython.utils.path import compress_user
33 from IPython.utils.py3compat import bytes_to_str
33 from IPython.utils.py3compat import bytes_to_str
34 from IPython.utils.sysinfo import get_sys_info
34 from IPython.utils.sysinfo import get_sys_info
35 from IPython.utils.tempdir import TemporaryDirectory
35 from IPython.utils.tempdir import TemporaryDirectory
36
36
37
37
38 class TestController(object):
38 class TestController(object):
39 """Run tests in a subprocess
39 """Run tests in a subprocess
40 """
40 """
41 #: str, IPython test suite to be executed.
41 #: str, IPython test suite to be executed.
42 section = None
42 section = None
43 #: list, command line arguments to be executed
43 #: list, command line arguments to be executed
44 cmd = None
44 cmd = None
45 #: dict, extra environment variables to set for the subprocess
45 #: dict, extra environment variables to set for the subprocess
46 env = None
46 env = None
47 #: list, TemporaryDirectory instances to clear up when the process finishes
47 #: list, TemporaryDirectory instances to clear up when the process finishes
48 dirs = None
48 dirs = None
49 #: subprocess.Popen instance
49 #: subprocess.Popen instance
50 process = None
50 process = None
51 #: str, process stdout+stderr
51 #: str, process stdout+stderr
52 stdout = None
52 stdout = None
53
53
54 def __init__(self):
54 def __init__(self):
55 self.cmd = []
55 self.cmd = []
56 self.env = {}
56 self.env = {}
57 self.dirs = []
57 self.dirs = []
58
58
59 def setup(self):
59 def setup(self):
60 """Create temporary directories etc.
60 """Create temporary directories etc.
61
61
62 This is only called when we know the test group will be run. Things
62 This is only called when we know the test group will be run. Things
63 created here may be cleaned up by self.cleanup().
63 created here may be cleaned up by self.cleanup().
64 """
64 """
65 pass
65 pass
66
66
67 def launch(self, buffer_output=False):
67 def launch(self, buffer_output=False):
68 # print('*** ENV:', self.env) # dbg
68 # print('*** ENV:', self.env) # dbg
69 # print('*** CMD:', self.cmd) # dbg
69 # print('*** CMD:', self.cmd) # dbg
70 env = os.environ.copy()
70 env = os.environ.copy()
71 env.update(self.env)
71 env.update(self.env)
72 output = subprocess.PIPE if buffer_output else None
72 output = subprocess.PIPE if buffer_output else None
73 stdout = subprocess.STDOUT if buffer_output else None
73 stdout = subprocess.STDOUT if buffer_output else None
74 self.process = subprocess.Popen(self.cmd, stdout=output,
74 self.process = subprocess.Popen(self.cmd, stdout=output,
75 stderr=stdout, env=env)
75 stderr=stdout, env=env)
76
76
77 def wait(self):
77 def wait(self):
78 self.stdout, _ = self.process.communicate()
78 self.stdout, _ = self.process.communicate()
79 return self.process.returncode
79 return self.process.returncode
80
80
81 def print_extra_info(self):
81 def print_extra_info(self):
82 """Print extra information about this test run.
82 """Print extra information about this test run.
83
83
84 If we're running in parallel and showing the concise view, this is only
84 If we're running in parallel and showing the concise view, this is only
85 called if the test group fails. Otherwise, it's called before the test
85 called if the test group fails. Otherwise, it's called before the test
86 group is started.
86 group is started.
87
87
88 The base implementation does nothing, but it can be overridden by
88 The base implementation does nothing, but it can be overridden by
89 subclasses.
89 subclasses.
90 """
90 """
91 return
91 return
92
92
93 def cleanup_process(self):
93 def cleanup_process(self):
94 """Cleanup on exit by killing any leftover processes."""
94 """Cleanup on exit by killing any leftover processes."""
95 subp = self.process
95 subp = self.process
96 if subp is None or (subp.poll() is not None):
96 if subp is None or (subp.poll() is not None):
97 return # Process doesn't exist, or is already dead.
97 return # Process doesn't exist, or is already dead.
98
98
99 try:
99 try:
100 print('Cleaning up stale PID: %d' % subp.pid)
100 print('Cleaning up stale PID: %d' % subp.pid)
101 subp.kill()
101 subp.kill()
102 except: # (OSError, WindowsError) ?
102 except: # (OSError, WindowsError) ?
103 # This is just a best effort, if we fail or the process was
103 # This is just a best effort, if we fail or the process was
104 # really gone, ignore it.
104 # really gone, ignore it.
105 pass
105 pass
106 else:
106 else:
107 for i in range(10):
107 for i in range(10):
108 if subp.poll() is None:
108 if subp.poll() is None:
109 time.sleep(0.1)
109 time.sleep(0.1)
110 else:
110 else:
111 break
111 break
112
112
113 if subp.poll() is None:
113 if subp.poll() is None:
114 # The process did not die...
114 # The process did not die...
115 print('... failed. Manual cleanup may be required.')
115 print('... failed. Manual cleanup may be required.')
116
116
117 def cleanup(self):
117 def cleanup(self):
118 "Kill process if it's still alive, and clean up temporary directories"
118 "Kill process if it's still alive, and clean up temporary directories"
119 self.cleanup_process()
119 self.cleanup_process()
120 for td in self.dirs:
120 for td in self.dirs:
121 td.cleanup()
121 td.cleanup()
122
122
123 __del__ = cleanup
123 __del__ = cleanup
124
124
125 class PyTestController(TestController):
125 class PyTestController(TestController):
126 """Run Python tests using IPython.testing.iptest"""
126 """Run Python tests using IPython.testing.iptest"""
127 #: str, Python command to execute in subprocess
127 #: str, Python command to execute in subprocess
128 pycmd = None
128 pycmd = None
129
129
130 def __init__(self, section):
130 def __init__(self, section):
131 """Create new test runner."""
131 """Create new test runner."""
132 TestController.__init__(self)
132 TestController.__init__(self)
133 self.section = section
133 self.section = section
134 # pycmd is put into cmd[2] in PyTestController.launch()
134 # pycmd is put into cmd[2] in PyTestController.launch()
135 self.cmd = [sys.executable, '-c', None, section]
135 self.cmd = [sys.executable, '-c', None, section]
136 self.pycmd = "from IPython.testing.iptest import run_iptest; run_iptest()"
136 self.pycmd = "from IPython.testing.iptest import run_iptest; run_iptest()"
137
137
138 def setup(self):
138 def setup(self):
139 ipydir = TemporaryDirectory()
139 ipydir = TemporaryDirectory()
140 self.dirs.append(ipydir)
140 self.dirs.append(ipydir)
141 self.env['IPYTHONDIR'] = ipydir.name
141 self.env['IPYTHONDIR'] = ipydir.name
142 self.workingdir = workingdir = TemporaryDirectory()
142 self.workingdir = workingdir = TemporaryDirectory()
143 self.dirs.append(workingdir)
143 self.dirs.append(workingdir)
144 self.env['IPTEST_WORKING_DIR'] = workingdir.name
144 self.env['IPTEST_WORKING_DIR'] = workingdir.name
145 # This means we won't get odd effects from our own matplotlib config
145 # This means we won't get odd effects from our own matplotlib config
146 self.env['MPLCONFIGDIR'] = workingdir.name
146 self.env['MPLCONFIGDIR'] = workingdir.name
147
147
148 @property
148 @property
149 def will_run(self):
149 def will_run(self):
150 try:
150 try:
151 return test_sections[self.section].will_run
151 return test_sections[self.section].will_run
152 except KeyError:
152 except KeyError:
153 return True
153 return True
154
154
155 def add_xunit(self):
155 def add_xunit(self):
156 xunit_file = os.path.abspath(self.section + '.xunit.xml')
156 xunit_file = os.path.abspath(self.section + '.xunit.xml')
157 self.cmd.extend(['--with-xunit', '--xunit-file', xunit_file])
157 self.cmd.extend(['--with-xunit', '--xunit-file', xunit_file])
158
158
159 def add_coverage(self):
159 def add_coverage(self):
160 try:
160 try:
161 sources = test_sections[self.section].includes
161 sources = test_sections[self.section].includes
162 except KeyError:
162 except KeyError:
163 sources = ['IPython']
163 sources = ['IPython']
164
164
165 coverage_rc = ("[run]\n"
165 coverage_rc = ("[run]\n"
166 "data_file = {data_file}\n"
166 "data_file = {data_file}\n"
167 "source =\n"
167 "source =\n"
168 " {source}\n"
168 " {source}\n"
169 ).format(data_file=os.path.abspath('.coverage.'+self.section),
169 ).format(data_file=os.path.abspath('.coverage.'+self.section),
170 source="\n ".join(sources))
170 source="\n ".join(sources))
171 config_file = os.path.join(self.workingdir.name, '.coveragerc')
171 config_file = os.path.join(self.workingdir.name, '.coveragerc')
172 with open(config_file, 'w') as f:
172 with open(config_file, 'w') as f:
173 f.write(coverage_rc)
173 f.write(coverage_rc)
174
174
175 self.env['COVERAGE_PROCESS_START'] = config_file
175 self.env['COVERAGE_PROCESS_START'] = config_file
176 self.pycmd = "import coverage; coverage.process_startup(); " + self.pycmd
176 self.pycmd = "import coverage; coverage.process_startup(); " + self.pycmd
177
177
178 def launch(self, buffer_output=False):
178 def launch(self, buffer_output=False):
179 self.cmd[2] = self.pycmd
179 self.cmd[2] = self.pycmd
180 super(PyTestController, self).launch(buffer_output=buffer_output)
180 super(PyTestController, self).launch(buffer_output=buffer_output)
181
181
182 js_prefix = 'js/'
182 js_prefix = 'js/'
183
183
184 def get_js_test_dir():
184 def get_js_test_dir():
185 import IPython.html.tests as t
185 import IPython.html.tests as t
186 return os.path.join(os.path.dirname(t.__file__), '')
186 return os.path.join(os.path.dirname(t.__file__), '')
187
187
188 def all_js_groups():
188 def all_js_groups():
189 import glob
189 import glob
190 test_dir = get_js_test_dir()
190 test_dir = get_js_test_dir()
191 all_subdirs = glob.glob(test_dir + '*/')
191 all_subdirs = glob.glob(test_dir + '*/')
192 return [js_prefix+os.path.relpath(x, test_dir) for x in all_subdirs if os.path.relpath(x, test_dir) != '__pycache__']
192 return [js_prefix+os.path.relpath(x, test_dir) for x in all_subdirs if os.path.relpath(x, test_dir) != '__pycache__']
193
193
194 class JSController(TestController):
194 class JSController(TestController):
195 """Run CasperJS tests """
195 """Run CasperJS tests """
196 def __init__(self, section):
196 def __init__(self, section):
197 """Create new test runner."""
197 """Create new test runner."""
198 TestController.__init__(self)
198 TestController.__init__(self)
199 self.section = section
199 self.section = section
200 js_test_dir = get_js_test_dir()
200 js_test_dir = get_js_test_dir()
201 includes = '--includes=' + os.path.join(js_test_dir,'util.js')
201 includes = '--includes=' + os.path.join(js_test_dir,'util.js')
202 test_cases = os.path.join(js_test_dir, self.section[len(js_prefix):])
202 test_cases = os.path.join(js_test_dir, self.section[len(js_prefix):])
203 self.cmd = ['casperjs', 'test', includes, test_cases]
203 self.cmd = ['casperjs', 'test', includes, test_cases]
204
204
205 def setup(self):
205 def setup(self):
206 self.ipydir = TemporaryDirectory()
206 self.ipydir = TemporaryDirectory()
207 self.nbdir = TemporaryDirectory()
207 self.nbdir = TemporaryDirectory()
208 self.dirs.append(self.ipydir)
208 self.dirs.append(self.ipydir)
209 self.dirs.append(self.nbdir)
209 self.dirs.append(self.nbdir)
210 os.makedirs(os.path.join(self.nbdir.name, os.path.join(u'sub βˆ‚ir1', u'sub βˆ‚ir 1a')))
210 os.makedirs(os.path.join(self.nbdir.name, os.path.join(u'sub βˆ‚ir1', u'sub βˆ‚ir 1a')))
211 os.makedirs(os.path.join(self.nbdir.name, os.path.join(u'sub βˆ‚ir2', u'sub βˆ‚ir 1b')))
211 os.makedirs(os.path.join(self.nbdir.name, os.path.join(u'sub βˆ‚ir2', u'sub βˆ‚ir 1b')))
212
212
213 # start the ipython notebook, so we get the port number
213 # start the ipython notebook, so we get the port number
214 self.server_port = 0
214 self.server_port = 0
215 self._init_server()
215 self._init_server()
216 if self.server_port:
216 if self.server_port:
217 self.cmd.append("--port=%i" % self.server_port)
217 self.cmd.append("--port=%i" % self.server_port)
218
218
219 def print_extra_info(self):
219 def print_extra_info(self):
220 print("Running tests with notebook directory %r" % self.nbdir.name)
220 print("Running tests with notebook directory %r" % self.nbdir.name)
221
221
222 @property
222 @property
223 def will_run(self):
223 def will_run(self):
224 return all(have[a] for a in ['zmq', 'tornado', 'jinja2', 'casperjs'])
224 return all(have[a] for a in ['zmq', 'tornado', 'jinja2', 'casperjs', 'sqlite3'])
225
225
226 def _init_server(self):
226 def _init_server(self):
227 "Start the notebook server in a separate process"
227 "Start the notebook server in a separate process"
228 self.server_command = command = [sys.executable,
228 self.server_command = command = [sys.executable,
229 '-m', 'IPython.html',
229 '-m', 'IPython.html',
230 '--no-browser',
230 '--no-browser',
231 '--ipython-dir', self.ipydir.name,
231 '--ipython-dir', self.ipydir.name,
232 '--notebook-dir', self.nbdir.name,
232 '--notebook-dir', self.nbdir.name,
233 ]
233 ]
234 # ipc doesn't work on Windows, and darwin has crazy-long temp paths,
234 # ipc doesn't work on Windows, and darwin has crazy-long temp paths,
235 # which run afoul of ipc's maximum path length.
235 # which run afoul of ipc's maximum path length.
236 if sys.platform.startswith('linux'):
236 if sys.platform.startswith('linux'):
237 command.append('--KernelManager.transport=ipc')
237 command.append('--KernelManager.transport=ipc')
238 self.stream_capturer = c = StreamCapturer()
238 self.stream_capturer = c = StreamCapturer()
239 c.start()
239 c.start()
240 self.server = subprocess.Popen(command, stdout=c.writefd, stderr=subprocess.STDOUT)
240 self.server = subprocess.Popen(command, stdout=c.writefd, stderr=subprocess.STDOUT)
241 self.server_info_file = os.path.join(self.ipydir.name,
241 self.server_info_file = os.path.join(self.ipydir.name,
242 'profile_default', 'security', 'nbserver-%i.json' % self.server.pid
242 'profile_default', 'security', 'nbserver-%i.json' % self.server.pid
243 )
243 )
244 self._wait_for_server()
244 self._wait_for_server()
245
245
246 def _wait_for_server(self):
246 def _wait_for_server(self):
247 """Wait 30 seconds for the notebook server to start"""
247 """Wait 30 seconds for the notebook server to start"""
248 for i in range(300):
248 for i in range(300):
249 if self.server.poll() is not None:
249 if self.server.poll() is not None:
250 return self._failed_to_start()
250 return self._failed_to_start()
251 if os.path.exists(self.server_info_file):
251 if os.path.exists(self.server_info_file):
252 self._load_server_info()
252 self._load_server_info()
253 return
253 return
254 time.sleep(0.1)
254 time.sleep(0.1)
255 print("Notebook server-info file never arrived: %s" % self.server_info_file,
255 print("Notebook server-info file never arrived: %s" % self.server_info_file,
256 file=sys.stderr
256 file=sys.stderr
257 )
257 )
258
258
259 def _failed_to_start(self):
259 def _failed_to_start(self):
260 """Notebook server exited prematurely"""
260 """Notebook server exited prematurely"""
261 captured = self.stream_capturer.get_buffer().decode('utf-8', 'replace')
261 captured = self.stream_capturer.get_buffer().decode('utf-8', 'replace')
262 print("Notebook failed to start: ", file=sys.stderr)
262 print("Notebook failed to start: ", file=sys.stderr)
263 print(self.server_command)
263 print(self.server_command)
264 print(captured, file=sys.stderr)
264 print(captured, file=sys.stderr)
265
265
266 def _load_server_info(self):
266 def _load_server_info(self):
267 """Notebook server started, load connection info from JSON"""
267 """Notebook server started, load connection info from JSON"""
268 with open(self.server_info_file) as f:
268 with open(self.server_info_file) as f:
269 info = json.load(f)
269 info = json.load(f)
270 self.server_port = info['port']
270 self.server_port = info['port']
271
271
272 def cleanup(self):
272 def cleanup(self):
273 self.stream_capturer.halt()
273 self.stream_capturer.halt()
274 try:
274 try:
275 self.server.terminate()
275 self.server.terminate()
276 except OSError:
276 except OSError:
277 # already dead
277 # already dead
278 pass
278 pass
279 self.server.wait()
279 self.server.wait()
280 TestController.cleanup(self)
280 TestController.cleanup(self)
281
281
282
282
283 def prepare_controllers(options):
283 def prepare_controllers(options):
284 """Returns two lists of TestController instances, those to run, and those
284 """Returns two lists of TestController instances, those to run, and those
285 not to run."""
285 not to run."""
286 testgroups = options.testgroups
286 testgroups = options.testgroups
287
287
288 if testgroups:
288 if testgroups:
289 py_testgroups = [g for g in testgroups if (g in py_test_group_names) \
289 py_testgroups = [g for g in testgroups if (g in py_test_group_names) \
290 or g.startswith('IPython.')]
290 or g.startswith('IPython.')]
291 if 'js' in testgroups:
291 if 'js' in testgroups:
292 js_testgroups = all_js_groups()
292 js_testgroups = all_js_groups()
293 else:
293 else:
294 js_testgroups = [g for g in testgroups if g not in py_testgroups]
294 js_testgroups = [g for g in testgroups if g not in py_testgroups]
295 else:
295 else:
296 py_testgroups = py_test_group_names
296 py_testgroups = py_test_group_names
297 js_testgroups = all_js_groups()
297 js_testgroups = all_js_groups()
298 if not options.all:
298 if not options.all:
299 test_sections['parallel'].enabled = False
299 test_sections['parallel'].enabled = False
300
300
301 c_js = [JSController(name) for name in js_testgroups]
301 c_js = [JSController(name) for name in js_testgroups]
302 c_py = [PyTestController(name) for name in py_testgroups]
302 c_py = [PyTestController(name) for name in py_testgroups]
303
303
304 configure_py_controllers(c_py, xunit=options.xunit,
304 configure_py_controllers(c_py, xunit=options.xunit,
305 coverage=options.coverage, subproc_streams=options.subproc_streams,
305 coverage=options.coverage, subproc_streams=options.subproc_streams,
306 extra_args=options.extra_args)
306 extra_args=options.extra_args)
307
307
308 controllers = c_py + c_js
308 controllers = c_py + c_js
309 to_run = [c for c in controllers if c.will_run]
309 to_run = [c for c in controllers if c.will_run]
310 not_run = [c for c in controllers if not c.will_run]
310 not_run = [c for c in controllers if not c.will_run]
311 return to_run, not_run
311 return to_run, not_run
312
312
313 def configure_py_controllers(controllers, xunit=False, coverage=False,
313 def configure_py_controllers(controllers, xunit=False, coverage=False,
314 subproc_streams='capture', extra_args=()):
314 subproc_streams='capture', extra_args=()):
315 """Apply options for a collection of TestController objects."""
315 """Apply options for a collection of TestController objects."""
316 for controller in controllers:
316 for controller in controllers:
317 if xunit:
317 if xunit:
318 controller.add_xunit()
318 controller.add_xunit()
319 if coverage:
319 if coverage:
320 controller.add_coverage()
320 controller.add_coverage()
321 controller.env['IPTEST_SUBPROC_STREAMS'] = subproc_streams
321 controller.env['IPTEST_SUBPROC_STREAMS'] = subproc_streams
322 controller.cmd.extend(extra_args)
322 controller.cmd.extend(extra_args)
323
323
324 def do_run(controller, buffer_output=True):
324 def do_run(controller, buffer_output=True):
325 """Setup and run a test controller.
325 """Setup and run a test controller.
326
326
327 If buffer_output is True, no output is displayed, to avoid it appearing
327 If buffer_output is True, no output is displayed, to avoid it appearing
328 interleaved. In this case, the caller is responsible for displaying test
328 interleaved. In this case, the caller is responsible for displaying test
329 output on failure.
329 output on failure.
330
330
331 Returns
331 Returns
332 -------
332 -------
333 controller : TestController
333 controller : TestController
334 The same controller as passed in, as a convenience for using map() type
334 The same controller as passed in, as a convenience for using map() type
335 APIs.
335 APIs.
336 exitcode : int
336 exitcode : int
337 The exit code of the test subprocess. Non-zero indicates failure.
337 The exit code of the test subprocess. Non-zero indicates failure.
338 """
338 """
339 try:
339 try:
340 try:
340 try:
341 controller.setup()
341 controller.setup()
342 if not buffer_output:
342 if not buffer_output:
343 controller.print_extra_info()
343 controller.print_extra_info()
344 controller.launch(buffer_output=buffer_output)
344 controller.launch(buffer_output=buffer_output)
345 except Exception:
345 except Exception:
346 import traceback
346 import traceback
347 traceback.print_exc()
347 traceback.print_exc()
348 return controller, 1 # signal failure
348 return controller, 1 # signal failure
349
349
350 exitcode = controller.wait()
350 exitcode = controller.wait()
351 return controller, exitcode
351 return controller, exitcode
352
352
353 except KeyboardInterrupt:
353 except KeyboardInterrupt:
354 return controller, -signal.SIGINT
354 return controller, -signal.SIGINT
355 finally:
355 finally:
356 controller.cleanup()
356 controller.cleanup()
357
357
358 def report():
358 def report():
359 """Return a string with a summary report of test-related variables."""
359 """Return a string with a summary report of test-related variables."""
360 inf = get_sys_info()
360 inf = get_sys_info()
361 out = []
361 out = []
362 def _add(name, value):
362 def _add(name, value):
363 out.append((name, value))
363 out.append((name, value))
364
364
365 _add('IPython version', inf['ipython_version'])
365 _add('IPython version', inf['ipython_version'])
366 _add('IPython commit', "{} ({})".format(inf['commit_hash'], inf['commit_source']))
366 _add('IPython commit', "{} ({})".format(inf['commit_hash'], inf['commit_source']))
367 _add('IPython package', compress_user(inf['ipython_path']))
367 _add('IPython package', compress_user(inf['ipython_path']))
368 _add('Python version', inf['sys_version'].replace('\n',''))
368 _add('Python version', inf['sys_version'].replace('\n',''))
369 _add('sys.executable', compress_user(inf['sys_executable']))
369 _add('sys.executable', compress_user(inf['sys_executable']))
370 _add('Platform', inf['platform'])
370 _add('Platform', inf['platform'])
371
371
372 width = max(len(n) for (n,v) in out)
372 width = max(len(n) for (n,v) in out)
373 out = ["{:<{width}}: {}\n".format(n, v, width=width) for (n,v) in out]
373 out = ["{:<{width}}: {}\n".format(n, v, width=width) for (n,v) in out]
374
374
375 avail = []
375 avail = []
376 not_avail = []
376 not_avail = []
377
377
378 for k, is_avail in have.items():
378 for k, is_avail in have.items():
379 if is_avail:
379 if is_avail:
380 avail.append(k)
380 avail.append(k)
381 else:
381 else:
382 not_avail.append(k)
382 not_avail.append(k)
383
383
384 if avail:
384 if avail:
385 out.append('\nTools and libraries available at test time:\n')
385 out.append('\nTools and libraries available at test time:\n')
386 avail.sort()
386 avail.sort()
387 out.append(' ' + ' '.join(avail)+'\n')
387 out.append(' ' + ' '.join(avail)+'\n')
388
388
389 if not_avail:
389 if not_avail:
390 out.append('\nTools and libraries NOT available at test time:\n')
390 out.append('\nTools and libraries NOT available at test time:\n')
391 not_avail.sort()
391 not_avail.sort()
392 out.append(' ' + ' '.join(not_avail)+'\n')
392 out.append(' ' + ' '.join(not_avail)+'\n')
393
393
394 return ''.join(out)
394 return ''.join(out)
395
395
396 def run_iptestall(options):
396 def run_iptestall(options):
397 """Run the entire IPython test suite by calling nose and trial.
397 """Run the entire IPython test suite by calling nose and trial.
398
398
399 This function constructs :class:`IPTester` instances for all IPython
399 This function constructs :class:`IPTester` instances for all IPython
400 modules and package and then runs each of them. This causes the modules
400 modules and package and then runs each of them. This causes the modules
401 and packages of IPython to be tested each in their own subprocess using
401 and packages of IPython to be tested each in their own subprocess using
402 nose.
402 nose.
403
403
404 Parameters
404 Parameters
405 ----------
405 ----------
406
406
407 All parameters are passed as attributes of the options object.
407 All parameters are passed as attributes of the options object.
408
408
409 testgroups : list of str
409 testgroups : list of str
410 Run only these sections of the test suite. If empty, run all the available
410 Run only these sections of the test suite. If empty, run all the available
411 sections.
411 sections.
412
412
413 fast : int or None
413 fast : int or None
414 Run the test suite in parallel, using n simultaneous processes. If None
414 Run the test suite in parallel, using n simultaneous processes. If None
415 is passed, one process is used per CPU core. Default 1 (i.e. sequential)
415 is passed, one process is used per CPU core. Default 1 (i.e. sequential)
416
416
417 inc_slow : bool
417 inc_slow : bool
418 Include slow tests, like IPython.parallel. By default, these tests aren't
418 Include slow tests, like IPython.parallel. By default, these tests aren't
419 run.
419 run.
420
420
421 xunit : bool
421 xunit : bool
422 Produce Xunit XML output. This is written to multiple foo.xunit.xml files.
422 Produce Xunit XML output. This is written to multiple foo.xunit.xml files.
423
423
424 coverage : bool or str
424 coverage : bool or str
425 Measure code coverage from tests. True will store the raw coverage data,
425 Measure code coverage from tests. True will store the raw coverage data,
426 or pass 'html' or 'xml' to get reports.
426 or pass 'html' or 'xml' to get reports.
427
427
428 extra_args : list
428 extra_args : list
429 Extra arguments to pass to the test subprocesses, e.g. '-v'
429 Extra arguments to pass to the test subprocesses, e.g. '-v'
430 """
430 """
431 to_run, not_run = prepare_controllers(options)
431 to_run, not_run = prepare_controllers(options)
432
432
433 def justify(ltext, rtext, width=70, fill='-'):
433 def justify(ltext, rtext, width=70, fill='-'):
434 ltext += ' '
434 ltext += ' '
435 rtext = (' ' + rtext).rjust(width - len(ltext), fill)
435 rtext = (' ' + rtext).rjust(width - len(ltext), fill)
436 return ltext + rtext
436 return ltext + rtext
437
437
438 # Run all test runners, tracking execution time
438 # Run all test runners, tracking execution time
439 failed = []
439 failed = []
440 t_start = time.time()
440 t_start = time.time()
441
441
442 print()
442 print()
443 if options.fast == 1:
443 if options.fast == 1:
444 # This actually means sequential, i.e. with 1 job
444 # This actually means sequential, i.e. with 1 job
445 for controller in to_run:
445 for controller in to_run:
446 print('Test group:', controller.section)
446 print('Test group:', controller.section)
447 sys.stdout.flush() # Show in correct order when output is piped
447 sys.stdout.flush() # Show in correct order when output is piped
448 controller, res = do_run(controller, buffer_output=False)
448 controller, res = do_run(controller, buffer_output=False)
449 if res:
449 if res:
450 failed.append(controller)
450 failed.append(controller)
451 if res == -signal.SIGINT:
451 if res == -signal.SIGINT:
452 print("Interrupted")
452 print("Interrupted")
453 break
453 break
454 print()
454 print()
455
455
456 else:
456 else:
457 # Run tests concurrently
457 # Run tests concurrently
458 try:
458 try:
459 pool = multiprocessing.pool.ThreadPool(options.fast)
459 pool = multiprocessing.pool.ThreadPool(options.fast)
460 for (controller, res) in pool.imap_unordered(do_run, to_run):
460 for (controller, res) in pool.imap_unordered(do_run, to_run):
461 res_string = 'OK' if res == 0 else 'FAILED'
461 res_string = 'OK' if res == 0 else 'FAILED'
462 print(justify('Test group: ' + controller.section, res_string))
462 print(justify('Test group: ' + controller.section, res_string))
463 if res:
463 if res:
464 controller.print_extra_info()
464 controller.print_extra_info()
465 print(bytes_to_str(controller.stdout))
465 print(bytes_to_str(controller.stdout))
466 failed.append(controller)
466 failed.append(controller)
467 if res == -signal.SIGINT:
467 if res == -signal.SIGINT:
468 print("Interrupted")
468 print("Interrupted")
469 break
469 break
470 except KeyboardInterrupt:
470 except KeyboardInterrupt:
471 return
471 return
472
472
473 for controller in not_run:
473 for controller in not_run:
474 print(justify('Test group: ' + controller.section, 'NOT RUN'))
474 print(justify('Test group: ' + controller.section, 'NOT RUN'))
475
475
476 t_end = time.time()
476 t_end = time.time()
477 t_tests = t_end - t_start
477 t_tests = t_end - t_start
478 nrunners = len(to_run)
478 nrunners = len(to_run)
479 nfail = len(failed)
479 nfail = len(failed)
480 # summarize results
480 # summarize results
481 print('_'*70)
481 print('_'*70)
482 print('Test suite completed for system with the following information:')
482 print('Test suite completed for system with the following information:')
483 print(report())
483 print(report())
484 took = "Took %.3fs." % t_tests
484 took = "Took %.3fs." % t_tests
485 print('Status: ', end='')
485 print('Status: ', end='')
486 if not failed:
486 if not failed:
487 print('OK (%d test groups).' % nrunners, took)
487 print('OK (%d test groups).' % nrunners, took)
488 else:
488 else:
489 # If anything went wrong, point out what command to rerun manually to
489 # If anything went wrong, point out what command to rerun manually to
490 # see the actual errors and individual summary
490 # see the actual errors and individual summary
491 failed_sections = [c.section for c in failed]
491 failed_sections = [c.section for c in failed]
492 print('ERROR - {} out of {} test groups failed ({}).'.format(nfail,
492 print('ERROR - {} out of {} test groups failed ({}).'.format(nfail,
493 nrunners, ', '.join(failed_sections)), took)
493 nrunners, ', '.join(failed_sections)), took)
494 print()
494 print()
495 print('You may wish to rerun these, with:')
495 print('You may wish to rerun these, with:')
496 print(' iptest', *failed_sections)
496 print(' iptest', *failed_sections)
497 print()
497 print()
498
498
499 if options.coverage:
499 if options.coverage:
500 from coverage import coverage
500 from coverage import coverage
501 cov = coverage(data_file='.coverage')
501 cov = coverage(data_file='.coverage')
502 cov.combine()
502 cov.combine()
503 cov.save()
503 cov.save()
504
504
505 # Coverage HTML report
505 # Coverage HTML report
506 if options.coverage == 'html':
506 if options.coverage == 'html':
507 html_dir = 'ipy_htmlcov'
507 html_dir = 'ipy_htmlcov'
508 shutil.rmtree(html_dir, ignore_errors=True)
508 shutil.rmtree(html_dir, ignore_errors=True)
509 print("Writing HTML coverage report to %s/ ... " % html_dir, end="")
509 print("Writing HTML coverage report to %s/ ... " % html_dir, end="")
510 sys.stdout.flush()
510 sys.stdout.flush()
511
511
512 # Custom HTML reporter to clean up module names.
512 # Custom HTML reporter to clean up module names.
513 from coverage.html import HtmlReporter
513 from coverage.html import HtmlReporter
514 class CustomHtmlReporter(HtmlReporter):
514 class CustomHtmlReporter(HtmlReporter):
515 def find_code_units(self, morfs):
515 def find_code_units(self, morfs):
516 super(CustomHtmlReporter, self).find_code_units(morfs)
516 super(CustomHtmlReporter, self).find_code_units(morfs)
517 for cu in self.code_units:
517 for cu in self.code_units:
518 nameparts = cu.name.split(os.sep)
518 nameparts = cu.name.split(os.sep)
519 if 'IPython' not in nameparts:
519 if 'IPython' not in nameparts:
520 continue
520 continue
521 ix = nameparts.index('IPython')
521 ix = nameparts.index('IPython')
522 cu.name = '.'.join(nameparts[ix:])
522 cu.name = '.'.join(nameparts[ix:])
523
523
524 # Reimplement the html_report method with our custom reporter
524 # Reimplement the html_report method with our custom reporter
525 cov._harvest_data()
525 cov._harvest_data()
526 cov.config.from_args(omit='*{0}tests{0}*'.format(os.sep), html_dir=html_dir,
526 cov.config.from_args(omit='*{0}tests{0}*'.format(os.sep), html_dir=html_dir,
527 html_title='IPython test coverage',
527 html_title='IPython test coverage',
528 )
528 )
529 reporter = CustomHtmlReporter(cov, cov.config)
529 reporter = CustomHtmlReporter(cov, cov.config)
530 reporter.report(None)
530 reporter.report(None)
531 print('done.')
531 print('done.')
532
532
533 # Coverage XML report
533 # Coverage XML report
534 elif options.coverage == 'xml':
534 elif options.coverage == 'xml':
535 cov.xml_report(outfile='ipy_coverage.xml')
535 cov.xml_report(outfile='ipy_coverage.xml')
536
536
537 if failed:
537 if failed:
538 # Ensure that our exit code indicates failure
538 # Ensure that our exit code indicates failure
539 sys.exit(1)
539 sys.exit(1)
540
540
541 argparser = argparse.ArgumentParser(description='Run IPython test suite')
541 argparser = argparse.ArgumentParser(description='Run IPython test suite')
542 argparser.add_argument('testgroups', nargs='*',
542 argparser.add_argument('testgroups', nargs='*',
543 help='Run specified groups of tests. If omitted, run '
543 help='Run specified groups of tests. If omitted, run '
544 'all tests.')
544 'all tests.')
545 argparser.add_argument('--all', action='store_true',
545 argparser.add_argument('--all', action='store_true',
546 help='Include slow tests not run by default.')
546 help='Include slow tests not run by default.')
547 argparser.add_argument('-j', '--fast', nargs='?', const=None, default=1, type=int,
547 argparser.add_argument('-j', '--fast', nargs='?', const=None, default=1, type=int,
548 help='Run test sections in parallel. This starts as many '
548 help='Run test sections in parallel. This starts as many '
549 'processes as you have cores, or you can specify a number.')
549 'processes as you have cores, or you can specify a number.')
550 argparser.add_argument('--xunit', action='store_true',
550 argparser.add_argument('--xunit', action='store_true',
551 help='Produce Xunit XML results')
551 help='Produce Xunit XML results')
552 argparser.add_argument('--coverage', nargs='?', const=True, default=False,
552 argparser.add_argument('--coverage', nargs='?', const=True, default=False,
553 help="Measure test coverage. Specify 'html' or "
553 help="Measure test coverage. Specify 'html' or "
554 "'xml' to get reports.")
554 "'xml' to get reports.")
555 argparser.add_argument('--subproc-streams', default='capture',
555 argparser.add_argument('--subproc-streams', default='capture',
556 help="What to do with stdout/stderr from subprocesses. "
556 help="What to do with stdout/stderr from subprocesses. "
557 "'capture' (default), 'show' and 'discard' are the options.")
557 "'capture' (default), 'show' and 'discard' are the options.")
558
558
559 def default_options():
559 def default_options():
560 """Get an argparse Namespace object with the default arguments, to pass to
560 """Get an argparse Namespace object with the default arguments, to pass to
561 :func:`run_iptestall`.
561 :func:`run_iptestall`.
562 """
562 """
563 options = argparser.parse_args([])
563 options = argparser.parse_args([])
564 options.extra_args = []
564 options.extra_args = []
565 return options
565 return options
566
566
567 def main():
567 def main():
568 # iptest doesn't work correctly if the working directory is the
568 # iptest doesn't work correctly if the working directory is the
569 # root of the IPython source tree. Tell the user to avoid
569 # root of the IPython source tree. Tell the user to avoid
570 # frustration.
570 # frustration.
571 if os.path.exists(os.path.join(os.getcwd(),
571 if os.path.exists(os.path.join(os.getcwd(),
572 'IPython', 'testing', '__main__.py')):
572 'IPython', 'testing', '__main__.py')):
573 print("Don't run iptest from the IPython source directory",
573 print("Don't run iptest from the IPython source directory",
574 file=sys.stderr)
574 file=sys.stderr)
575 sys.exit(1)
575 sys.exit(1)
576 # Arguments after -- should be passed through to nose. Argparse treats
576 # Arguments after -- should be passed through to nose. Argparse treats
577 # everything after -- as regular positional arguments, so we separate them
577 # everything after -- as regular positional arguments, so we separate them
578 # first.
578 # first.
579 try:
579 try:
580 ix = sys.argv.index('--')
580 ix = sys.argv.index('--')
581 except ValueError:
581 except ValueError:
582 to_parse = sys.argv[1:]
582 to_parse = sys.argv[1:]
583 extra_args = []
583 extra_args = []
584 else:
584 else:
585 to_parse = sys.argv[1:ix]
585 to_parse = sys.argv[1:ix]
586 extra_args = sys.argv[ix+1:]
586 extra_args = sys.argv[ix+1:]
587
587
588 options = argparser.parse_args(to_parse)
588 options = argparser.parse_args(to_parse)
589 options.extra_args = extra_args
589 options.extra_args = extra_args
590
590
591 run_iptestall(options)
591 run_iptestall(options)
592
592
593
593
594 if __name__ == '__main__':
594 if __name__ == '__main__':
595 main()
595 main()
General Comments 0
You need to be logged in to leave comments. Login now