##// END OF EJS Templates
Capture output from subprocs during test, and display on failure...
Thomas Kluyver -
Show More
@@ -1,125 +1,132 b''
1 """toplevel setup/teardown for parallel tests."""
1 """toplevel setup/teardown for parallel tests."""
2
2
3 #-------------------------------------------------------------------------------
3 #-------------------------------------------------------------------------------
4 # Copyright (C) 2011 The IPython Development Team
4 # Copyright (C) 2011 The IPython Development Team
5 #
5 #
6 # Distributed under the terms of the BSD License. The full license is in
6 # Distributed under the terms of the BSD License. The full license is in
7 # the file COPYING, distributed as part of this software.
7 # the file COPYING, distributed as part of this software.
8 #-------------------------------------------------------------------------------
8 #-------------------------------------------------------------------------------
9
9
10 #-------------------------------------------------------------------------------
10 #-------------------------------------------------------------------------------
11 # Imports
11 # Imports
12 #-------------------------------------------------------------------------------
12 #-------------------------------------------------------------------------------
13
13
14 import os
14 import os
15 import tempfile
15 import tempfile
16 import time
16 import time
17 from subprocess import Popen
17 from subprocess import Popen, PIPE, STDOUT
18
19 import nose
18
20
19 from IPython.utils.path import get_ipython_dir
21 from IPython.utils.path import get_ipython_dir
20 from IPython.parallel import Client
22 from IPython.parallel import Client
21 from IPython.parallel.apps.launcher import (LocalProcessLauncher,
23 from IPython.parallel.apps.launcher import (LocalProcessLauncher,
22 ipengine_cmd_argv,
24 ipengine_cmd_argv,
23 ipcontroller_cmd_argv,
25 ipcontroller_cmd_argv,
24 SIGKILL,
26 SIGKILL,
25 ProcessStateError,
27 ProcessStateError,
26 )
28 )
27
29
28 # globals
30 # globals
29 launchers = []
31 launchers = []
30 blackhole = open(os.devnull, 'w')
32 blackhole = open(os.devnull, 'w')
31
33
32 # Launcher class
34 # Launcher class
33 class TestProcessLauncher(LocalProcessLauncher):
35 class TestProcessLauncher(LocalProcessLauncher):
34 """subclass LocalProcessLauncher, to prevent extra sockets and threads being created on Windows"""
36 """subclass LocalProcessLauncher, to prevent extra sockets and threads being created on Windows"""
35 def start(self):
37 def start(self):
36 if self.state == 'before':
38 if self.state == 'before':
37 self.process = Popen(self.args,
39 self.process = Popen(self.args,
38 stdout=blackhole, stderr=blackhole,
40 stdout=PIPE, stderr=STDOUT,
39 env=os.environ,
41 env=os.environ,
40 cwd=self.work_dir
42 cwd=self.work_dir
41 )
43 )
42 self.notify_start(self.process.pid)
44 self.notify_start(self.process.pid)
43 self.poll = self.process.poll
45 self.poll = self.process.poll
46 # Store stdout & stderr to show with failing tests.
47 # This is defined in IPython.testing.iptest
48 nose.ipy_stream_capturer.add_stream(self.process.stdout.fileno())
49 nose.ipy_stream_capturer.ensure_started()
44 else:
50 else:
45 s = 'The process was already started and has state: %r' % self.state
51 s = 'The process was already started and has state: %r' % self.state
46 raise ProcessStateError(s)
52 raise ProcessStateError(s)
47
53
48 # nose setup/teardown
54 # nose setup/teardown
49
55
50 def setup():
56 def setup():
51 cluster_dir = os.path.join(get_ipython_dir(), 'profile_iptest')
57 cluster_dir = os.path.join(get_ipython_dir(), 'profile_iptest')
52 engine_json = os.path.join(cluster_dir, 'security', 'ipcontroller-engine.json')
58 engine_json = os.path.join(cluster_dir, 'security', 'ipcontroller-engine.json')
53 client_json = os.path.join(cluster_dir, 'security', 'ipcontroller-client.json')
59 client_json = os.path.join(cluster_dir, 'security', 'ipcontroller-client.json')
54 for json in (engine_json, client_json):
60 for json in (engine_json, client_json):
55 if os.path.exists(json):
61 if os.path.exists(json):
56 os.remove(json)
62 os.remove(json)
57
63
58 cp = TestProcessLauncher()
64 cp = TestProcessLauncher()
59 cp.cmd_and_args = ipcontroller_cmd_argv + \
65 cp.cmd_and_args = ipcontroller_cmd_argv + \
60 ['--profile=iptest', '--log-level=50', '--ping=250', '--dictdb']
66 ['--profile=iptest', '--log-level=20', '--ping=250', '--dictdb']
61 cp.start()
67 cp.start()
62 launchers.append(cp)
68 launchers.append(cp)
63 tic = time.time()
69 tic = time.time()
64 while not os.path.exists(engine_json) or not os.path.exists(client_json):
70 while not os.path.exists(engine_json) or not os.path.exists(client_json):
65 if cp.poll() is not None:
71 if cp.poll() is not None:
66 raise RuntimeError("The test controller exited with status %s" % cp.poll())
72 raise RuntimeError("The test controller exited with status %s" % cp.poll())
67 elif time.time()-tic > 15:
73 elif time.time()-tic > 15:
68 raise RuntimeError("Timeout waiting for the test controller to start.")
74 raise RuntimeError("Timeout waiting for the test controller to start.")
69 time.sleep(0.1)
75 time.sleep(0.1)
70 add_engines(1)
76 add_engines(1)
71
77
72 def add_engines(n=1, profile='iptest', total=False):
78 def add_engines(n=1, profile='iptest', total=False):
73 """add a number of engines to a given profile.
79 """add a number of engines to a given profile.
74
80
75 If total is True, then already running engines are counted, and only
81 If total is True, then already running engines are counted, and only
76 the additional engines necessary (if any) are started.
82 the additional engines necessary (if any) are started.
77 """
83 """
78 rc = Client(profile=profile)
84 rc = Client(profile=profile)
79 base = len(rc)
85 base = len(rc)
80
86
81 if total:
87 if total:
82 n = max(n - base, 0)
88 n = max(n - base, 0)
83
89
84 eps = []
90 eps = []
85 for i in range(n):
91 for i in range(n):
86 ep = TestProcessLauncher()
92 ep = TestProcessLauncher()
87 ep.cmd_and_args = ipengine_cmd_argv + [
93 ep.cmd_and_args = ipengine_cmd_argv + [
88 '--profile=%s' % profile,
94 '--profile=%s' % profile,
89 '--log-level=50',
95 '--log-level=50',
90 '--InteractiveShell.colors=nocolor'
96 '--InteractiveShell.colors=nocolor'
91 ]
97 ]
92 ep.start()
98 ep.start()
93 launchers.append(ep)
99 launchers.append(ep)
94 eps.append(ep)
100 eps.append(ep)
95 tic = time.time()
101 tic = time.time()
96 while len(rc) < base+n:
102 while len(rc) < base+n:
97 if any([ ep.poll() is not None for ep in eps ]):
103 if any([ ep.poll() is not None for ep in eps ]):
98 raise RuntimeError("A test engine failed to start.")
104 raise RuntimeError("A test engine failed to start.")
99 elif time.time()-tic > 15:
105 elif time.time()-tic > 15:
100 raise RuntimeError("Timeout waiting for engines to connect.")
106 raise RuntimeError("Timeout waiting for engines to connect.")
101 time.sleep(.1)
107 time.sleep(.1)
102 rc.spin()
108 rc.spin()
103 rc.close()
109 rc.close()
104 return eps
110 return eps
105
111
106 def teardown():
112 def teardown():
107 time.sleep(1)
113 time.sleep(1)
108 while launchers:
114 while launchers:
109 p = launchers.pop()
115 p = launchers.pop()
116 nose.ipy_stream_capturer.remove_stream(p.process.stdout.fileno())
110 if p.poll() is None:
117 if p.poll() is None:
111 try:
118 try:
112 p.stop()
119 p.stop()
113 except Exception as e:
120 except Exception as e:
114 print e
121 print e
115 pass
122 pass
116 if p.poll() is None:
123 if p.poll() is None:
117 time.sleep(.25)
124 time.sleep(.25)
118 if p.poll() is None:
125 if p.poll() is None:
119 try:
126 try:
120 print 'cleaning up test process...'
127 print 'cleaning up test process...'
121 p.signal(SIGKILL)
128 p.signal(SIGKILL)
122 except:
129 except:
123 print "couldn't shutdown process: ", p
130 print "couldn't shutdown process: ", p
124 blackhole.close()
131 blackhole.close()
125
132
@@ -1,432 +1,520 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 import os
32 import os
32 import os.path as path
33 import os.path as path
33 import re
34 import re
35 from select import select
34 import sys
36 import sys
37 from threading import Thread, Lock, Event
35 import warnings
38 import warnings
36
39
37 # Now, proceed to import nose itself
40 # Now, proceed to import nose itself
38 import nose.plugins.builtin
41 import nose.plugins.builtin
39 from nose.plugins.xunit import Xunit
42 from nose.plugins.xunit import Xunit
40 from nose import SkipTest
43 from nose import SkipTest
41 from nose.core import TestProgram
44 from nose.core import TestProgram
42 from nose.plugins import Plugin
45 from nose.plugins import Plugin
46 from nose.util import safe_str
43
47
44 # Our own imports
48 # Our own imports
45 from IPython.utils.importstring import import_item
49 from IPython.utils.importstring import import_item
46 from IPython.testing.plugin.ipdoctest import IPythonDoctest
50 from IPython.testing.plugin.ipdoctest import IPythonDoctest
47 from IPython.external.decorators import KnownFailure, knownfailureif
51 from IPython.external.decorators import KnownFailure, knownfailureif
48
52
49 pjoin = path.join
53 pjoin = path.join
50
54
51
55
52 #-----------------------------------------------------------------------------
56 #-----------------------------------------------------------------------------
53 # Globals
57 # Globals
54 #-----------------------------------------------------------------------------
58 #-----------------------------------------------------------------------------
55
59
56
60
57 #-----------------------------------------------------------------------------
61 #-----------------------------------------------------------------------------
58 # Warnings control
62 # Warnings control
59 #-----------------------------------------------------------------------------
63 #-----------------------------------------------------------------------------
60
64
61 # Twisted generates annoying warnings with Python 2.6, as will do other code
65 # Twisted generates annoying warnings with Python 2.6, as will do other code
62 # that imports 'sets' as of today
66 # that imports 'sets' as of today
63 warnings.filterwarnings('ignore', 'the sets module is deprecated',
67 warnings.filterwarnings('ignore', 'the sets module is deprecated',
64 DeprecationWarning )
68 DeprecationWarning )
65
69
66 # This one also comes from Twisted
70 # This one also comes from Twisted
67 warnings.filterwarnings('ignore', 'the sha module is deprecated',
71 warnings.filterwarnings('ignore', 'the sha module is deprecated',
68 DeprecationWarning)
72 DeprecationWarning)
69
73
70 # Wx on Fedora11 spits these out
74 # Wx on Fedora11 spits these out
71 warnings.filterwarnings('ignore', 'wxPython/wxWidgets release number mismatch',
75 warnings.filterwarnings('ignore', 'wxPython/wxWidgets release number mismatch',
72 UserWarning)
76 UserWarning)
73
77
74 # ------------------------------------------------------------------------------
78 # ------------------------------------------------------------------------------
75 # Monkeypatch Xunit to count known failures as skipped.
79 # Monkeypatch Xunit to count known failures as skipped.
76 # ------------------------------------------------------------------------------
80 # ------------------------------------------------------------------------------
77 def monkeypatch_xunit():
81 def monkeypatch_xunit():
78 try:
82 try:
79 knownfailureif(True)(lambda: None)()
83 knownfailureif(True)(lambda: None)()
80 except Exception as e:
84 except Exception as e:
81 KnownFailureTest = type(e)
85 KnownFailureTest = type(e)
82
86
83 def addError(self, test, err, capt=None):
87 def addError(self, test, err, capt=None):
84 if issubclass(err[0], KnownFailureTest):
88 if issubclass(err[0], KnownFailureTest):
85 err = (SkipTest,) + err[1:]
89 err = (SkipTest,) + err[1:]
86 return self.orig_addError(test, err, capt)
90 return self.orig_addError(test, err, capt)
87
91
88 Xunit.orig_addError = Xunit.addError
92 Xunit.orig_addError = Xunit.addError
89 Xunit.addError = addError
93 Xunit.addError = addError
90
94
91 #-----------------------------------------------------------------------------
95 #-----------------------------------------------------------------------------
92 # Check which dependencies are installed and greater than minimum version.
96 # Check which dependencies are installed and greater than minimum version.
93 #-----------------------------------------------------------------------------
97 #-----------------------------------------------------------------------------
94 def extract_version(mod):
98 def extract_version(mod):
95 return mod.__version__
99 return mod.__version__
96
100
97 def test_for(item, min_version=None, callback=extract_version):
101 def test_for(item, min_version=None, callback=extract_version):
98 """Test to see if item is importable, and optionally check against a minimum
102 """Test to see if item is importable, and optionally check against a minimum
99 version.
103 version.
100
104
101 If min_version is given, the default behavior is to check against the
105 If min_version is given, the default behavior is to check against the
102 `__version__` attribute of the item, but specifying `callback` allows you to
106 `__version__` attribute of the item, but specifying `callback` allows you to
103 extract the value you are interested in. e.g::
107 extract the value you are interested in. e.g::
104
108
105 In [1]: import sys
109 In [1]: import sys
106
110
107 In [2]: from IPython.testing.iptest import test_for
111 In [2]: from IPython.testing.iptest import test_for
108
112
109 In [3]: test_for('sys', (2,6), callback=lambda sys: sys.version_info)
113 In [3]: test_for('sys', (2,6), callback=lambda sys: sys.version_info)
110 Out[3]: True
114 Out[3]: True
111
115
112 """
116 """
113 try:
117 try:
114 check = import_item(item)
118 check = import_item(item)
115 except (ImportError, RuntimeError):
119 except (ImportError, RuntimeError):
116 # GTK reports Runtime error if it can't be initialized even if it's
120 # GTK reports Runtime error if it can't be initialized even if it's
117 # importable.
121 # importable.
118 return False
122 return False
119 else:
123 else:
120 if min_version:
124 if min_version:
121 if callback:
125 if callback:
122 # extra processing step to get version to compare
126 # extra processing step to get version to compare
123 check = callback(check)
127 check = callback(check)
124
128
125 return check >= min_version
129 return check >= min_version
126 else:
130 else:
127 return True
131 return True
128
132
129 # Global dict where we can store information on what we have and what we don't
133 # Global dict where we can store information on what we have and what we don't
130 # have available at test run time
134 # have available at test run time
131 have = {}
135 have = {}
132
136
133 have['curses'] = test_for('_curses')
137 have['curses'] = test_for('_curses')
134 have['matplotlib'] = test_for('matplotlib')
138 have['matplotlib'] = test_for('matplotlib')
135 have['numpy'] = test_for('numpy')
139 have['numpy'] = test_for('numpy')
136 have['pexpect'] = test_for('IPython.external.pexpect')
140 have['pexpect'] = test_for('IPython.external.pexpect')
137 have['pymongo'] = test_for('pymongo')
141 have['pymongo'] = test_for('pymongo')
138 have['pygments'] = test_for('pygments')
142 have['pygments'] = test_for('pygments')
139 have['qt'] = test_for('IPython.external.qt')
143 have['qt'] = test_for('IPython.external.qt')
140 have['rpy2'] = test_for('rpy2')
144 have['rpy2'] = test_for('rpy2')
141 have['sqlite3'] = test_for('sqlite3')
145 have['sqlite3'] = test_for('sqlite3')
142 have['cython'] = test_for('Cython')
146 have['cython'] = test_for('Cython')
143 have['oct2py'] = test_for('oct2py')
147 have['oct2py'] = test_for('oct2py')
144 have['tornado'] = test_for('tornado.version_info', (2,1,0), callback=None)
148 have['tornado'] = test_for('tornado.version_info', (2,1,0), callback=None)
145 have['jinja2'] = test_for('jinja2')
149 have['jinja2'] = test_for('jinja2')
146 have['wx'] = test_for('wx')
150 have['wx'] = test_for('wx')
147 have['wx.aui'] = test_for('wx.aui')
151 have['wx.aui'] = test_for('wx.aui')
148 have['azure'] = test_for('azure')
152 have['azure'] = test_for('azure')
149 have['sphinx'] = test_for('sphinx')
153 have['sphinx'] = test_for('sphinx')
150
154
151 min_zmq = (2,1,11)
155 min_zmq = (2,1,11)
152
156
153 have['zmq'] = test_for('zmq.pyzmq_version_info', min_zmq, callback=lambda x: x())
157 have['zmq'] = test_for('zmq.pyzmq_version_info', min_zmq, callback=lambda x: x())
154
158
155 #-----------------------------------------------------------------------------
159 #-----------------------------------------------------------------------------
156 # Test suite definitions
160 # Test suite definitions
157 #-----------------------------------------------------------------------------
161 #-----------------------------------------------------------------------------
158
162
159 test_group_names = ['parallel', 'kernel', 'kernel.inprocess', 'config', 'core',
163 test_group_names = ['parallel', 'kernel', 'kernel.inprocess', 'config', 'core',
160 'extensions', 'lib', 'terminal', 'testing', 'utils',
164 'extensions', 'lib', 'terminal', 'testing', 'utils',
161 'nbformat', 'qt', 'html', 'nbconvert'
165 'nbformat', 'qt', 'html', 'nbconvert'
162 ]
166 ]
163
167
164 class TestSection(object):
168 class TestSection(object):
165 def __init__(self, name, includes):
169 def __init__(self, name, includes):
166 self.name = name
170 self.name = name
167 self.includes = includes
171 self.includes = includes
168 self.excludes = []
172 self.excludes = []
169 self.dependencies = []
173 self.dependencies = []
170 self.enabled = True
174 self.enabled = True
171
175
172 def exclude(self, module):
176 def exclude(self, module):
173 if not module.startswith('IPython'):
177 if not module.startswith('IPython'):
174 module = self.includes[0] + "." + module
178 module = self.includes[0] + "." + module
175 self.excludes.append(module.replace('.', os.sep))
179 self.excludes.append(module.replace('.', os.sep))
176
180
177 def requires(self, *packages):
181 def requires(self, *packages):
178 self.dependencies.extend(packages)
182 self.dependencies.extend(packages)
179
183
180 @property
184 @property
181 def will_run(self):
185 def will_run(self):
182 return self.enabled and all(have[p] for p in self.dependencies)
186 return self.enabled and all(have[p] for p in self.dependencies)
183
187
184 # Name -> (include, exclude, dependencies_met)
188 # Name -> (include, exclude, dependencies_met)
185 test_sections = {n:TestSection(n, ['IPython.%s' % n]) for n in test_group_names}
189 test_sections = {n:TestSection(n, ['IPython.%s' % n]) for n in test_group_names}
186
190
187 # Exclusions and dependencies
191 # Exclusions and dependencies
188 # ---------------------------
192 # ---------------------------
189
193
190 # core:
194 # core:
191 sec = test_sections['core']
195 sec = test_sections['core']
192 if not have['sqlite3']:
196 if not have['sqlite3']:
193 sec.exclude('tests.test_history')
197 sec.exclude('tests.test_history')
194 sec.exclude('history')
198 sec.exclude('history')
195 if not have['matplotlib']:
199 if not have['matplotlib']:
196 sec.exclude('pylabtools'),
200 sec.exclude('pylabtools'),
197 sec.exclude('tests.test_pylabtools')
201 sec.exclude('tests.test_pylabtools')
198
202
199 # lib:
203 # lib:
200 sec = test_sections['lib']
204 sec = test_sections['lib']
201 if not have['wx']:
205 if not have['wx']:
202 sec.exclude('inputhookwx')
206 sec.exclude('inputhookwx')
203 if not have['pexpect']:
207 if not have['pexpect']:
204 sec.exclude('irunner')
208 sec.exclude('irunner')
205 sec.exclude('tests.test_irunner')
209 sec.exclude('tests.test_irunner')
206 if not have['zmq']:
210 if not have['zmq']:
207 sec.exclude('kernel')
211 sec.exclude('kernel')
208 # We do this unconditionally, so that the test suite doesn't import
212 # We do this unconditionally, so that the test suite doesn't import
209 # gtk, changing the default encoding and masking some unicode bugs.
213 # gtk, changing the default encoding and masking some unicode bugs.
210 sec.exclude('inputhookgtk')
214 sec.exclude('inputhookgtk')
211 # Testing inputhook will need a lot of thought, to figure out
215 # 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
216 # how to have tests that don't lock up with the gui event
213 # loops in the picture
217 # loops in the picture
214 sec.exclude('inputhook')
218 sec.exclude('inputhook')
215
219
216 # testing:
220 # testing:
217 sec = test_sections['testing']
221 sec = test_sections['testing']
218 # This guy is probably attic material
222 # This guy is probably attic material
219 sec.exclude('mkdoctests')
223 sec.exclude('mkdoctests')
220 # These have to be skipped on win32 because they use echo, rm, cd, etc.
224 # These have to be skipped on win32 because they use echo, rm, cd, etc.
221 # See ticket https://github.com/ipython/ipython/issues/87
225 # See ticket https://github.com/ipython/ipython/issues/87
222 if sys.platform == 'win32':
226 if sys.platform == 'win32':
223 sec.exclude('plugin.test_exampleip')
227 sec.exclude('plugin.test_exampleip')
224 sec.exclude('plugin.dtexample')
228 sec.exclude('plugin.dtexample')
225
229
226 # terminal:
230 # terminal:
227 if (not have['pexpect']) or (not have['zmq']):
231 if (not have['pexpect']) or (not have['zmq']):
228 test_sections['terminal'].exclude('console')
232 test_sections['terminal'].exclude('console')
229
233
230 # parallel
234 # parallel
231 sec = test_sections['parallel']
235 sec = test_sections['parallel']
232 sec.requires('zmq')
236 sec.requires('zmq')
233 if not have['pymongo']:
237 if not have['pymongo']:
234 sec.exclude('controller.mongodb')
238 sec.exclude('controller.mongodb')
235 sec.exclude('tests.test_mongodb')
239 sec.exclude('tests.test_mongodb')
236
240
237 # kernel:
241 # kernel:
238 sec = test_sections['kernel']
242 sec = test_sections['kernel']
239 sec.requires('zmq')
243 sec.requires('zmq')
240 # The in-process kernel tests are done in a separate section
244 # The in-process kernel tests are done in a separate section
241 sec.exclude('inprocess')
245 sec.exclude('inprocess')
242 # importing gtk sets the default encoding, which we want to avoid
246 # importing gtk sets the default encoding, which we want to avoid
243 sec.exclude('zmq.gui.gtkembed')
247 sec.exclude('zmq.gui.gtkembed')
244 if not have['matplotlib']:
248 if not have['matplotlib']:
245 sec.exclude('zmq.pylab')
249 sec.exclude('zmq.pylab')
246
250
247 # kernel.inprocess:
251 # kernel.inprocess:
248 test_sections['kernel.inprocess'].requires('zmq')
252 test_sections['kernel.inprocess'].requires('zmq')
249
253
250 # extensions:
254 # extensions:
251 sec = test_sections['extensions']
255 sec = test_sections['extensions']
252 if not have['cython']:
256 if not have['cython']:
253 sec.exclude('cythonmagic')
257 sec.exclude('cythonmagic')
254 sec.exclude('tests.test_cythonmagic')
258 sec.exclude('tests.test_cythonmagic')
255 if not have['oct2py']:
259 if not have['oct2py']:
256 sec.exclude('octavemagic')
260 sec.exclude('octavemagic')
257 sec.exclude('tests.test_octavemagic')
261 sec.exclude('tests.test_octavemagic')
258 if not have['rpy2'] or not have['numpy']:
262 if not have['rpy2'] or not have['numpy']:
259 sec.exclude('rmagic')
263 sec.exclude('rmagic')
260 sec.exclude('tests.test_rmagic')
264 sec.exclude('tests.test_rmagic')
261 # autoreload does some strange stuff, so move it to its own test section
265 # autoreload does some strange stuff, so move it to its own test section
262 sec.exclude('autoreload')
266 sec.exclude('autoreload')
263 sec.exclude('tests.test_autoreload')
267 sec.exclude('tests.test_autoreload')
264 test_sections['autoreload'] = TestSection('autoreload',
268 test_sections['autoreload'] = TestSection('autoreload',
265 ['IPython.extensions.autoreload', 'IPython.extensions.tests.test_autoreload'])
269 ['IPython.extensions.autoreload', 'IPython.extensions.tests.test_autoreload'])
266 test_group_names.append('autoreload')
270 test_group_names.append('autoreload')
267
271
268 # qt:
272 # qt:
269 test_sections['qt'].requires('zmq', 'qt', 'pygments')
273 test_sections['qt'].requires('zmq', 'qt', 'pygments')
270
274
271 # html:
275 # html:
272 sec = test_sections['html']
276 sec = test_sections['html']
273 sec.requires('zmq', 'tornado')
277 sec.requires('zmq', 'tornado')
274 # The notebook 'static' directory contains JS, css and other
278 # The notebook 'static' directory contains JS, css and other
275 # files for web serving. Occasionally projects may put a .py
279 # files for web serving. Occasionally projects may put a .py
276 # file in there (MathJax ships a conf.py), so we might as
280 # file in there (MathJax ships a conf.py), so we might as
277 # well play it safe and skip the whole thing.
281 # well play it safe and skip the whole thing.
278 sec.exclude('static')
282 sec.exclude('static')
279 sec.exclude('fabfile')
283 sec.exclude('fabfile')
280 if not have['jinja2']:
284 if not have['jinja2']:
281 sec.exclude('notebookapp')
285 sec.exclude('notebookapp')
282 if not have['azure']:
286 if not have['azure']:
283 sec.exclude('services.notebooks.azurenbmanager')
287 sec.exclude('services.notebooks.azurenbmanager')
284
288
285 # config:
289 # config:
286 # Config files aren't really importable stand-alone
290 # Config files aren't really importable stand-alone
287 test_sections['config'].exclude('profile')
291 test_sections['config'].exclude('profile')
288
292
289 # nbconvert:
293 # nbconvert:
290 sec = test_sections['nbconvert']
294 sec = test_sections['nbconvert']
291 sec.requires('pygments', 'jinja2', 'sphinx')
295 sec.requires('pygments', 'jinja2', 'sphinx')
292 # Exclude nbconvert directories containing config files used to test.
296 # Exclude nbconvert directories containing config files used to test.
293 # Executing the config files with iptest would cause an exception.
297 # Executing the config files with iptest would cause an exception.
294 sec.exclude('tests.files')
298 sec.exclude('tests.files')
295 sec.exclude('exporters.tests.files')
299 sec.exclude('exporters.tests.files')
296 if not have['tornado']:
300 if not have['tornado']:
297 sec.exclude('nbconvert.post_processors.serve')
301 sec.exclude('nbconvert.post_processors.serve')
298 sec.exclude('nbconvert.post_processors.tests.test_serve')
302 sec.exclude('nbconvert.post_processors.tests.test_serve')
299
303
300 #-----------------------------------------------------------------------------
304 #-----------------------------------------------------------------------------
301 # Functions and classes
305 # Functions and classes
302 #-----------------------------------------------------------------------------
306 #-----------------------------------------------------------------------------
303
307
304 def check_exclusions_exist():
308 def check_exclusions_exist():
305 from IPython.utils.path import get_ipython_package_dir
309 from IPython.utils.path import get_ipython_package_dir
306 from IPython.utils.warn import warn
310 from IPython.utils.warn import warn
307 parent = os.path.dirname(get_ipython_package_dir())
311 parent = os.path.dirname(get_ipython_package_dir())
308 for sec in test_sections:
312 for sec in test_sections:
309 for pattern in sec.exclusions:
313 for pattern in sec.exclusions:
310 fullpath = pjoin(parent, pattern)
314 fullpath = pjoin(parent, pattern)
311 if not os.path.exists(fullpath) and not glob.glob(fullpath + '.*'):
315 if not os.path.exists(fullpath) and not glob.glob(fullpath + '.*'):
312 warn("Excluding nonexistent file: %r" % pattern)
316 warn("Excluding nonexistent file: %r" % pattern)
313
317
314
318
315 class ExclusionPlugin(Plugin):
319 class ExclusionPlugin(Plugin):
316 """A nose plugin to effect our exclusions of files and directories.
320 """A nose plugin to effect our exclusions of files and directories.
317 """
321 """
318 name = 'exclusions'
322 name = 'exclusions'
319 score = 3000 # Should come before any other plugins
323 score = 3000 # Should come before any other plugins
320
324
321 def __init__(self, exclude_patterns=None):
325 def __init__(self, exclude_patterns=None):
322 """
326 """
323 Parameters
327 Parameters
324 ----------
328 ----------
325
329
326 exclude_patterns : sequence of strings, optional
330 exclude_patterns : sequence of strings, optional
327 Filenames containing these patterns (as raw strings, not as regular
331 Filenames containing these patterns (as raw strings, not as regular
328 expressions) are excluded from the tests.
332 expressions) are excluded from the tests.
329 """
333 """
330 self.exclude_patterns = exclude_patterns or []
334 self.exclude_patterns = exclude_patterns or []
331 super(ExclusionPlugin, self).__init__()
335 super(ExclusionPlugin, self).__init__()
332
336
333 def options(self, parser, env=os.environ):
337 def options(self, parser, env=os.environ):
334 Plugin.options(self, parser, env)
338 Plugin.options(self, parser, env)
335
339
336 def configure(self, options, config):
340 def configure(self, options, config):
337 Plugin.configure(self, options, config)
341 Plugin.configure(self, options, config)
338 # Override nose trying to disable plugin.
342 # Override nose trying to disable plugin.
339 self.enabled = True
343 self.enabled = True
340
344
341 def wantFile(self, filename):
345 def wantFile(self, filename):
342 """Return whether the given filename should be scanned for tests.
346 """Return whether the given filename should be scanned for tests.
343 """
347 """
344 if any(pat in filename for pat in self.exclude_patterns):
348 if any(pat in filename for pat in self.exclude_patterns):
345 return False
349 return False
346 return None
350 return None
347
351
348 def wantDirectory(self, directory):
352 def wantDirectory(self, directory):
349 """Return whether the given directory should be scanned for tests.
353 """Return whether the given directory should be scanned for tests.
350 """
354 """
351 if any(pat in directory for pat in self.exclude_patterns):
355 if any(pat in directory for pat in self.exclude_patterns):
352 return False
356 return False
353 return None
357 return None
354
358
355
359
360 class StreamCapturer(Thread):
361 started = False
362 def __init__(self):
363 super(StreamCapturer, self).__init__()
364 self.streams = []
365 self.buffer = BytesIO()
366 self.streams_lock = Lock()
367 self.buffer_lock = Lock()
368 self.stream_added = Event()
369 self.stop = Event()
370
371 def run(self):
372 self.started = True
373 while not self.stop.is_set():
374 with self.streams_lock:
375 streams = self.streams
376
377 if not streams:
378 self.stream_added.wait(timeout=1)
379 self.stream_added.clear()
380 continue
381
382 ready = select(streams, [], [], 0.5)[0]
383 with self.buffer_lock:
384 for fd in ready:
385 self.buffer.write(os.read(fd, 1024))
386
387 def add_stream(self, fd):
388 with self.streams_lock:
389 self.streams.append(fd)
390 self.stream_added.set()
391
392 def remove_stream(self, fd):
393 with self.streams_lock:
394 self.streams.remove(fd)
395
396 def reset_buffer(self):
397 with self.buffer_lock:
398 self.buffer.truncate(0)
399 self.buffer.seek(0)
400
401 def get_buffer(self):
402 with self.buffer_lock:
403 return self.buffer.getvalue()
404
405 def ensure_started(self):
406 if not self.started:
407 self.start()
408
409 class SubprocessStreamCapturePlugin(Plugin):
410 name='subprocstreams'
411 def __init__(self):
412 Plugin.__init__(self)
413 self.stream_capturer = StreamCapturer()
414 # This is ugly, but distant parts of the test machinery need to be able
415 # to add streams, so we make the object globally accessible.
416 nose.ipy_stream_capturer = self.stream_capturer
417
418 def configure(self, options, config):
419 Plugin.configure(self, options, config)
420 # Override nose trying to disable plugin.
421 self.enabled = True
422
423 def startTest(self, test):
424 # Reset log capture
425 self.stream_capturer.reset_buffer()
426
427 def formatFailure(self, test, err):
428 # Show output
429 ec, ev, tb = err
430 ev = safe_str(ev)
431 out = [ev, '>> begin captured subprocess output <<',
432 self.stream_capturer.get_buffer().decode('utf-8', 'replace'),
433 '>> end captured subprocess output <<']
434 return ec, '\n'.join(out), tb
435
436 formatError = formatFailure
437
438 def finalize(self, result):
439 self.stream_capturer.stop.set()
440 self.stream_capturer.join()
441
442
356 def run_iptest():
443 def run_iptest():
357 """Run the IPython test suite using nose.
444 """Run the IPython test suite using nose.
358
445
359 This function is called when this script is **not** called with the form
446 This function is called when this script is **not** called with the form
360 `iptest all`. It simply calls nose with appropriate command line flags
447 `iptest all`. It simply calls nose with appropriate command line flags
361 and accepts all of the standard nose arguments.
448 and accepts all of the standard nose arguments.
362 """
449 """
363 # Apply our monkeypatch to Xunit
450 # Apply our monkeypatch to Xunit
364 if '--with-xunit' in sys.argv and not hasattr(Xunit, 'orig_addError'):
451 if '--with-xunit' in sys.argv and not hasattr(Xunit, 'orig_addError'):
365 monkeypatch_xunit()
452 monkeypatch_xunit()
366
453
367 warnings.filterwarnings('ignore',
454 warnings.filterwarnings('ignore',
368 'This will be removed soon. Use IPython.testing.util instead')
455 'This will be removed soon. Use IPython.testing.util instead')
369
456
370 arg1 = sys.argv[1]
457 arg1 = sys.argv[1]
371 if arg1 in test_sections:
458 if arg1 in test_sections:
372 section = test_sections[arg1]
459 section = test_sections[arg1]
373 sys.argv[1:2] = section.includes
460 sys.argv[1:2] = section.includes
374 elif arg1.startswith('IPython.') and arg1[8:] in test_sections:
461 elif arg1.startswith('IPython.') and arg1[8:] in test_sections:
375 section = test_sections[arg1[8:]]
462 section = test_sections[arg1[8:]]
376 sys.argv[1:2] = section.includes
463 sys.argv[1:2] = section.includes
377 else:
464 else:
378 section = TestSection(arg1, includes=[arg1])
465 section = TestSection(arg1, includes=[arg1])
379
466
380
467
381 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
468 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
382
469
383 '--with-ipdoctest',
470 '--with-ipdoctest',
384 '--ipdoctest-tests','--ipdoctest-extension=txt',
471 '--ipdoctest-tests','--ipdoctest-extension=txt',
385
472
386 # We add --exe because of setuptools' imbecility (it
473 # We add --exe because of setuptools' imbecility (it
387 # blindly does chmod +x on ALL files). Nose does the
474 # blindly does chmod +x on ALL files). Nose does the
388 # right thing and it tries to avoid executables,
475 # right thing and it tries to avoid executables,
389 # setuptools unfortunately forces our hand here. This
476 # setuptools unfortunately forces our hand here. This
390 # has been discussed on the distutils list and the
477 # has been discussed on the distutils list and the
391 # setuptools devs refuse to fix this problem!
478 # setuptools devs refuse to fix this problem!
392 '--exe',
479 '--exe',
393 ]
480 ]
394 if '-a' not in argv and '-A' not in argv:
481 if '-a' not in argv and '-A' not in argv:
395 argv = argv + ['-a', '!crash']
482 argv = argv + ['-a', '!crash']
396
483
397 if nose.__version__ >= '0.11':
484 if nose.__version__ >= '0.11':
398 # I don't fully understand why we need this one, but depending on what
485 # I don't fully understand why we need this one, but depending on what
399 # directory the test suite is run from, if we don't give it, 0 tests
486 # directory the test suite is run from, if we don't give it, 0 tests
400 # get run. Specifically, if the test suite is run from the source dir
487 # get run. Specifically, if the test suite is run from the source dir
401 # with an argument (like 'iptest.py IPython.core', 0 tests are run,
488 # with an argument (like 'iptest.py IPython.core', 0 tests are run,
402 # even if the same call done in this directory works fine). It appears
489 # even if the same call done in this directory works fine). It appears
403 # that if the requested package is in the current dir, nose bails early
490 # that if the requested package is in the current dir, nose bails early
404 # by default. Since it's otherwise harmless, leave it in by default
491 # by default. Since it's otherwise harmless, leave it in by default
405 # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it.
492 # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it.
406 argv.append('--traverse-namespace')
493 argv.append('--traverse-namespace')
407
494
408 # use our plugin for doctesting. It will remove the standard doctest plugin
495 # use our plugin for doctesting. It will remove the standard doctest plugin
409 # if it finds it enabled
496 # if it finds it enabled
410 plugins = [ExclusionPlugin(section.excludes), IPythonDoctest(), KnownFailure()]
497 plugins = [ExclusionPlugin(section.excludes), IPythonDoctest(), KnownFailure(),
498 SubprocessStreamCapturePlugin() ]
411
499
412 # Use working directory set by parent process (see iptestcontroller)
500 # Use working directory set by parent process (see iptestcontroller)
413 if 'IPTEST_WORKING_DIR' in os.environ:
501 if 'IPTEST_WORKING_DIR' in os.environ:
414 os.chdir(os.environ['IPTEST_WORKING_DIR'])
502 os.chdir(os.environ['IPTEST_WORKING_DIR'])
415
503
416 # We need a global ipython running in this process, but the special
504 # We need a global ipython running in this process, but the special
417 # in-process group spawns its own IPython kernels, so for *that* group we
505 # in-process group spawns its own IPython kernels, so for *that* group we
418 # must avoid also opening the global one (otherwise there's a conflict of
506 # must avoid also opening the global one (otherwise there's a conflict of
419 # singletons). Ultimately the solution to this problem is to refactor our
507 # singletons). Ultimately the solution to this problem is to refactor our
420 # assumptions about what needs to be a singleton and what doesn't (app
508 # assumptions about what needs to be a singleton and what doesn't (app
421 # objects should, individual shells shouldn't). But for now, this
509 # objects should, individual shells shouldn't). But for now, this
422 # workaround allows the test suite for the inprocess module to complete.
510 # workaround allows the test suite for the inprocess module to complete.
423 if 'kernel.inprocess' not in section.name:
511 if 'kernel.inprocess' not in section.name:
424 from IPython.testing import globalipapp
512 from IPython.testing import globalipapp
425 globalipapp.start_ipython()
513 globalipapp.start_ipython()
426
514
427 # Now nose can run
515 # Now nose can run
428 TestProgram(argv=argv, addplugins=plugins)
516 TestProgram(argv=argv, addplugins=plugins)
429
517
430 if __name__ == '__main__':
518 if __name__ == '__main__':
431 run_iptest()
519 run_iptest()
432
520
General Comments 0
You need to be logged in to leave comments. Login now