##// END OF EJS Templates
Merge pull request #4393 from takluyver/tests-subproc-stream-capture...
Thomas Kluyver -
r13241:1a4b088e merge
parent child Browse files
Show More
@@ -1,170 +1,173 b''
1 """utilities for testing IPython kernels"""
1 """utilities for testing IPython kernels"""
2
2
3 #-------------------------------------------------------------------------------
3 #-------------------------------------------------------------------------------
4 # Copyright (C) 2013 The IPython Development Team
4 # Copyright (C) 2013 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 atexit
14 import atexit
15
15
16 from contextlib import contextmanager
16 from contextlib import contextmanager
17 from subprocess import PIPE
17 from subprocess import PIPE, STDOUT
18 from Queue import Empty
18 from Queue import Empty
19
19
20 import nose
20 import nose.tools as nt
21 import nose.tools as nt
21
22
22 from IPython.kernel import KernelManager
23 from IPython.kernel import KernelManager
23
24
24 #-------------------------------------------------------------------------------
25 #-------------------------------------------------------------------------------
25 # Globals
26 # Globals
26 #-------------------------------------------------------------------------------
27 #-------------------------------------------------------------------------------
27
28
28 STARTUP_TIMEOUT = 60
29 STARTUP_TIMEOUT = 60
29 TIMEOUT = 15
30 TIMEOUT = 15
30
31
31 KM = None
32 KM = None
32 KC = None
33 KC = None
33
34
34 #-------------------------------------------------------------------------------
35 #-------------------------------------------------------------------------------
35 # code
36 # code
36 #-------------------------------------------------------------------------------
37 #-------------------------------------------------------------------------------
37
38
38
39
39 def start_new_kernel(argv=None):
40 def start_new_kernel(argv=None):
40 """start a new kernel, and return its Manager and Client"""
41 """start a new kernel, and return its Manager and Client"""
41 km = KernelManager()
42 km = KernelManager()
42 kwargs = dict(stdout=PIPE, stderr=PIPE)
43 kwargs = dict(stdout=PIPE, stderr=STDOUT)
43 if argv:
44 if argv:
44 kwargs['extra_arguments'] = argv
45 kwargs['extra_arguments'] = argv
45 km.start_kernel(**kwargs)
46 km.start_kernel(**kwargs)
47 nose.ipy_stream_capturer.add_stream(km.kernel.stdout.fileno())
48 nose.ipy_stream_capturer.ensure_started()
46 kc = km.client()
49 kc = km.client()
47 kc.start_channels()
50 kc.start_channels()
48
51
49 msg_id = kc.kernel_info()
52 msg_id = kc.kernel_info()
50 kc.get_shell_msg(block=True, timeout=STARTUP_TIMEOUT)
53 kc.get_shell_msg(block=True, timeout=STARTUP_TIMEOUT)
51 flush_channels(kc)
54 flush_channels(kc)
52 return km, kc
55 return km, kc
53
56
54 def flush_channels(kc=None):
57 def flush_channels(kc=None):
55 """flush any messages waiting on the queue"""
58 """flush any messages waiting on the queue"""
56 from .test_message_spec import validate_message
59 from .test_message_spec import validate_message
57
60
58 if kc is None:
61 if kc is None:
59 kc = KC
62 kc = KC
60 for channel in (kc.shell_channel, kc.iopub_channel):
63 for channel in (kc.shell_channel, kc.iopub_channel):
61 while True:
64 while True:
62 try:
65 try:
63 msg = channel.get_msg(block=True, timeout=0.1)
66 msg = channel.get_msg(block=True, timeout=0.1)
64 except Empty:
67 except Empty:
65 break
68 break
66 else:
69 else:
67 validate_message(msg)
70 validate_message(msg)
68
71
69
72
70 def execute(code='', kc=None, **kwargs):
73 def execute(code='', kc=None, **kwargs):
71 """wrapper for doing common steps for validating an execution request"""
74 """wrapper for doing common steps for validating an execution request"""
72 from .test_message_spec import validate_message
75 from .test_message_spec import validate_message
73 if kc is None:
76 if kc is None:
74 kc = KC
77 kc = KC
75 msg_id = kc.execute(code=code, **kwargs)
78 msg_id = kc.execute(code=code, **kwargs)
76 reply = kc.get_shell_msg(timeout=TIMEOUT)
79 reply = kc.get_shell_msg(timeout=TIMEOUT)
77 validate_message(reply, 'execute_reply', msg_id)
80 validate_message(reply, 'execute_reply', msg_id)
78 busy = kc.get_iopub_msg(timeout=TIMEOUT)
81 busy = kc.get_iopub_msg(timeout=TIMEOUT)
79 validate_message(busy, 'status', msg_id)
82 validate_message(busy, 'status', msg_id)
80 nt.assert_equal(busy['content']['execution_state'], 'busy')
83 nt.assert_equal(busy['content']['execution_state'], 'busy')
81
84
82 if not kwargs.get('silent'):
85 if not kwargs.get('silent'):
83 pyin = kc.get_iopub_msg(timeout=TIMEOUT)
86 pyin = kc.get_iopub_msg(timeout=TIMEOUT)
84 validate_message(pyin, 'pyin', msg_id)
87 validate_message(pyin, 'pyin', msg_id)
85 nt.assert_equal(pyin['content']['code'], code)
88 nt.assert_equal(pyin['content']['code'], code)
86
89
87 return msg_id, reply['content']
90 return msg_id, reply['content']
88
91
89 def start_global_kernel():
92 def start_global_kernel():
90 """start the global kernel (if it isn't running) and return its client"""
93 """start the global kernel (if it isn't running) and return its client"""
91 global KM, KC
94 global KM, KC
92 if KM is None:
95 if KM is None:
93 KM, KC = start_new_kernel()
96 KM, KC = start_new_kernel()
94 atexit.register(stop_global_kernel)
97 atexit.register(stop_global_kernel)
95 return KC
98 return KC
96
99
97 @contextmanager
100 @contextmanager
98 def kernel():
101 def kernel():
99 """Context manager for the global kernel instance
102 """Context manager for the global kernel instance
100
103
101 Should be used for most kernel tests
104 Should be used for most kernel tests
102
105
103 Returns
106 Returns
104 -------
107 -------
105 kernel_client: connected KernelClient instance
108 kernel_client: connected KernelClient instance
106 """
109 """
107 yield start_global_kernel()
110 yield start_global_kernel()
108
111
109 def uses_kernel(test_f):
112 def uses_kernel(test_f):
110 """Decorator for tests that use the global kernel"""
113 """Decorator for tests that use the global kernel"""
111 def wrapped_test():
114 def wrapped_test():
112 with kernel() as kc:
115 with kernel() as kc:
113 test_f(kc)
116 test_f(kc)
114 wrapped_test.__doc__ = test_f.__doc__
117 wrapped_test.__doc__ = test_f.__doc__
115 wrapped_test.__name__ = test_f.__name__
118 wrapped_test.__name__ = test_f.__name__
116 return wrapped_test
119 return wrapped_test
117
120
118 def stop_global_kernel():
121 def stop_global_kernel():
119 """Stop the global shared kernel instance, if it exists"""
122 """Stop the global shared kernel instance, if it exists"""
120 global KM, KC
123 global KM, KC
121 KC.stop_channels()
124 KC.stop_channels()
122 KC = None
125 KC = None
123 if KM is None:
126 if KM is None:
124 return
127 return
125 KM.shutdown_kernel(now=True)
128 KM.shutdown_kernel(now=True)
126 KM = None
129 KM = None
127
130
128 @contextmanager
131 @contextmanager
129 def new_kernel(argv=None):
132 def new_kernel(argv=None):
130 """Context manager for a new kernel in a subprocess
133 """Context manager for a new kernel in a subprocess
131
134
132 Should only be used for tests where the kernel must not be re-used.
135 Should only be used for tests where the kernel must not be re-used.
133
136
134 Returns
137 Returns
135 -------
138 -------
136 kernel_client: connected KernelClient instance
139 kernel_client: connected KernelClient instance
137 """
140 """
138 km, kc = start_new_kernel(argv)
141 km, kc = start_new_kernel(argv)
139 try:
142 try:
140 yield kc
143 yield kc
141 finally:
144 finally:
142 kc.stop_channels()
145 kc.stop_channels()
143 km.shutdown_kernel(now=True)
146 km.shutdown_kernel(now=True)
144
147
145
148
146 def assemble_output(iopub):
149 def assemble_output(iopub):
147 """assemble stdout/err from an execution"""
150 """assemble stdout/err from an execution"""
148 stdout = ''
151 stdout = ''
149 stderr = ''
152 stderr = ''
150 while True:
153 while True:
151 msg = iopub.get_msg(block=True, timeout=1)
154 msg = iopub.get_msg(block=True, timeout=1)
152 msg_type = msg['msg_type']
155 msg_type = msg['msg_type']
153 content = msg['content']
156 content = msg['content']
154 if msg_type == 'status' and content['execution_state'] == 'idle':
157 if msg_type == 'status' and content['execution_state'] == 'idle':
155 # idle message signals end of output
158 # idle message signals end of output
156 break
159 break
157 elif msg['msg_type'] == 'stream':
160 elif msg['msg_type'] == 'stream':
158 if content['name'] == 'stdout':
161 if content['name'] == 'stdout':
159 stdout += content['data']
162 stdout += content['data']
160 elif content['name'] == 'stderr':
163 elif content['name'] == 'stderr':
161 stderr += content['data']
164 stderr += content['data']
162 else:
165 else:
163 raise KeyError("bad stream: %r" % content['name'])
166 raise KeyError("bad stream: %r" % content['name'])
164 else:
167 else:
165 # other output, ignored
168 # other output, ignored
166 pass
169 pass
167 return stdout, stderr
170 return stdout, stderr
168
171
169
172
170
173
@@ -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,433 +1,525 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 from select import select
34 import sys
35 import sys
36 from threading import Thread, Lock, Event
35 import warnings
37 import warnings
36
38
37 # Now, proceed to import nose itself
39 # Now, proceed to import nose itself
38 import nose.plugins.builtin
40 import nose.plugins.builtin
39 from nose.plugins.xunit import Xunit
41 from nose.plugins.xunit import Xunit
40 from nose import SkipTest
42 from nose import SkipTest
41 from nose.core import TestProgram
43 from nose.core import TestProgram
42 from nose.plugins import Plugin
44 from nose.plugins import Plugin
45 from nose.util import safe_str
43
46
44 # Our own imports
47 # Our own imports
45 from IPython.utils.importstring import import_item
48 from IPython.utils.importstring import import_item
46 from IPython.testing.plugin.ipdoctest import IPythonDoctest
49 from IPython.testing.plugin.ipdoctest import IPythonDoctest
47 from IPython.external.decorators import KnownFailure, knownfailureif
50 from IPython.external.decorators import KnownFailure, knownfailureif
48
51
49 pjoin = path.join
52 pjoin = path.join
50
53
51
54
52 #-----------------------------------------------------------------------------
55 #-----------------------------------------------------------------------------
53 # Globals
56 # Globals
54 #-----------------------------------------------------------------------------
57 #-----------------------------------------------------------------------------
55
58
56
59
57 #-----------------------------------------------------------------------------
60 #-----------------------------------------------------------------------------
58 # Warnings control
61 # Warnings control
59 #-----------------------------------------------------------------------------
62 #-----------------------------------------------------------------------------
60
63
61 # 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
62 # that imports 'sets' as of today
65 # that imports 'sets' as of today
63 warnings.filterwarnings('ignore', 'the sets module is deprecated',
66 warnings.filterwarnings('ignore', 'the sets module is deprecated',
64 DeprecationWarning )
67 DeprecationWarning )
65
68
66 # This one also comes from Twisted
69 # This one also comes from Twisted
67 warnings.filterwarnings('ignore', 'the sha module is deprecated',
70 warnings.filterwarnings('ignore', 'the sha module is deprecated',
68 DeprecationWarning)
71 DeprecationWarning)
69
72
70 # Wx on Fedora11 spits these out
73 # Wx on Fedora11 spits these out
71 warnings.filterwarnings('ignore', 'wxPython/wxWidgets release number mismatch',
74 warnings.filterwarnings('ignore', 'wxPython/wxWidgets release number mismatch',
72 UserWarning)
75 UserWarning)
73
76
74 # ------------------------------------------------------------------------------
77 # ------------------------------------------------------------------------------
75 # Monkeypatch Xunit to count known failures as skipped.
78 # Monkeypatch Xunit to count known failures as skipped.
76 # ------------------------------------------------------------------------------
79 # ------------------------------------------------------------------------------
77 def monkeypatch_xunit():
80 def monkeypatch_xunit():
78 try:
81 try:
79 knownfailureif(True)(lambda: None)()
82 knownfailureif(True)(lambda: None)()
80 except Exception as e:
83 except Exception as e:
81 KnownFailureTest = type(e)
84 KnownFailureTest = type(e)
82
85
83 def addError(self, test, err, capt=None):
86 def addError(self, test, err, capt=None):
84 if issubclass(err[0], KnownFailureTest):
87 if issubclass(err[0], KnownFailureTest):
85 err = (SkipTest,) + err[1:]
88 err = (SkipTest,) + err[1:]
86 return self.orig_addError(test, err, capt)
89 return self.orig_addError(test, err, capt)
87
90
88 Xunit.orig_addError = Xunit.addError
91 Xunit.orig_addError = Xunit.addError
89 Xunit.addError = addError
92 Xunit.addError = addError
90
93
91 #-----------------------------------------------------------------------------
94 #-----------------------------------------------------------------------------
92 # Check which dependencies are installed and greater than minimum version.
95 # Check which dependencies are installed and greater than minimum version.
93 #-----------------------------------------------------------------------------
96 #-----------------------------------------------------------------------------
94 def extract_version(mod):
97 def extract_version(mod):
95 return mod.__version__
98 return mod.__version__
96
99
97 def test_for(item, min_version=None, callback=extract_version):
100 def test_for(item, min_version=None, callback=extract_version):
98 """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
99 version.
102 version.
100
103
101 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
102 `__version__` attribute of the item, but specifying `callback` allows you to
105 `__version__` attribute of the item, but specifying `callback` allows you to
103 extract the value you are interested in. e.g::
106 extract the value you are interested in. e.g::
104
107
105 In [1]: import sys
108 In [1]: import sys
106
109
107 In [2]: from IPython.testing.iptest import test_for
110 In [2]: from IPython.testing.iptest import test_for
108
111
109 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)
110 Out[3]: True
113 Out[3]: True
111
114
112 """
115 """
113 try:
116 try:
114 check = import_item(item)
117 check = import_item(item)
115 except (ImportError, RuntimeError):
118 except (ImportError, RuntimeError):
116 # 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
117 # importable.
120 # importable.
118 return False
121 return False
119 else:
122 else:
120 if min_version:
123 if min_version:
121 if callback:
124 if callback:
122 # extra processing step to get version to compare
125 # extra processing step to get version to compare
123 check = callback(check)
126 check = callback(check)
124
127
125 return check >= min_version
128 return check >= min_version
126 else:
129 else:
127 return True
130 return True
128
131
129 # 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
130 # have available at test run time
133 # have available at test run time
131 have = {}
134 have = {}
132
135
133 have['curses'] = test_for('_curses')
136 have['curses'] = test_for('_curses')
134 have['matplotlib'] = test_for('matplotlib')
137 have['matplotlib'] = test_for('matplotlib')
135 have['numpy'] = test_for('numpy')
138 have['numpy'] = test_for('numpy')
136 have['pexpect'] = test_for('IPython.external.pexpect')
139 have['pexpect'] = test_for('IPython.external.pexpect')
137 have['pymongo'] = test_for('pymongo')
140 have['pymongo'] = test_for('pymongo')
138 have['pygments'] = test_for('pygments')
141 have['pygments'] = test_for('pygments')
139 have['qt'] = test_for('IPython.external.qt')
142 have['qt'] = test_for('IPython.external.qt')
140 have['rpy2'] = test_for('rpy2')
143 have['rpy2'] = test_for('rpy2')
141 have['sqlite3'] = test_for('sqlite3')
144 have['sqlite3'] = test_for('sqlite3')
142 have['cython'] = test_for('Cython')
145 have['cython'] = test_for('Cython')
143 have['oct2py'] = test_for('oct2py')
146 have['oct2py'] = test_for('oct2py')
144 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)
145 have['jinja2'] = test_for('jinja2')
148 have['jinja2'] = test_for('jinja2')
146 have['wx'] = test_for('wx')
149 have['wx'] = test_for('wx')
147 have['wx.aui'] = test_for('wx.aui')
150 have['wx.aui'] = test_for('wx.aui')
148 have['azure'] = test_for('azure')
151 have['azure'] = test_for('azure')
149 have['requests'] = test_for('requests')
152 have['requests'] = test_for('requests')
150 have['sphinx'] = test_for('sphinx')
153 have['sphinx'] = test_for('sphinx')
151
154
152 min_zmq = (2,1,11)
155 min_zmq = (2,1,11)
153
156
154 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())
155
158
156 #-----------------------------------------------------------------------------
159 #-----------------------------------------------------------------------------
157 # Test suite definitions
160 # Test suite definitions
158 #-----------------------------------------------------------------------------
161 #-----------------------------------------------------------------------------
159
162
160 test_group_names = ['parallel', 'kernel', 'kernel.inprocess', 'config', 'core',
163 test_group_names = ['parallel', 'kernel', 'kernel.inprocess', 'config', 'core',
161 'extensions', 'lib', 'terminal', 'testing', 'utils',
164 'extensions', 'lib', 'terminal', 'testing', 'utils',
162 'nbformat', 'qt', 'html', 'nbconvert'
165 'nbformat', 'qt', 'html', 'nbconvert'
163 ]
166 ]
164
167
165 class TestSection(object):
168 class TestSection(object):
166 def __init__(self, name, includes):
169 def __init__(self, name, includes):
167 self.name = name
170 self.name = name
168 self.includes = includes
171 self.includes = includes
169 self.excludes = []
172 self.excludes = []
170 self.dependencies = []
173 self.dependencies = []
171 self.enabled = True
174 self.enabled = True
172
175
173 def exclude(self, module):
176 def exclude(self, module):
174 if not module.startswith('IPython'):
177 if not module.startswith('IPython'):
175 module = self.includes[0] + "." + module
178 module = self.includes[0] + "." + module
176 self.excludes.append(module.replace('.', os.sep))
179 self.excludes.append(module.replace('.', os.sep))
177
180
178 def requires(self, *packages):
181 def requires(self, *packages):
179 self.dependencies.extend(packages)
182 self.dependencies.extend(packages)
180
183
181 @property
184 @property
182 def will_run(self):
185 def will_run(self):
183 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)
184
187
185 # Name -> (include, exclude, dependencies_met)
188 # Name -> (include, exclude, dependencies_met)
186 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}
187
190
188 # Exclusions and dependencies
191 # Exclusions and dependencies
189 # ---------------------------
192 # ---------------------------
190
193
191 # core:
194 # core:
192 sec = test_sections['core']
195 sec = test_sections['core']
193 if not have['sqlite3']:
196 if not have['sqlite3']:
194 sec.exclude('tests.test_history')
197 sec.exclude('tests.test_history')
195 sec.exclude('history')
198 sec.exclude('history')
196 if not have['matplotlib']:
199 if not have['matplotlib']:
197 sec.exclude('pylabtools'),
200 sec.exclude('pylabtools'),
198 sec.exclude('tests.test_pylabtools')
201 sec.exclude('tests.test_pylabtools')
199
202
200 # lib:
203 # lib:
201 sec = test_sections['lib']
204 sec = test_sections['lib']
202 if not have['wx']:
205 if not have['wx']:
203 sec.exclude('inputhookwx')
206 sec.exclude('inputhookwx')
204 if not have['pexpect']:
207 if not have['pexpect']:
205 sec.exclude('irunner')
208 sec.exclude('irunner')
206 sec.exclude('tests.test_irunner')
209 sec.exclude('tests.test_irunner')
207 if not have['zmq']:
210 if not have['zmq']:
208 sec.exclude('kernel')
211 sec.exclude('kernel')
209 # 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
210 # gtk, changing the default encoding and masking some unicode bugs.
213 # gtk, changing the default encoding and masking some unicode bugs.
211 sec.exclude('inputhookgtk')
214 sec.exclude('inputhookgtk')
212 # Testing inputhook will need a lot of thought, to figure out
215 # Testing inputhook will need a lot of thought, to figure out
213 # 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
214 # loops in the picture
217 # loops in the picture
215 sec.exclude('inputhook')
218 sec.exclude('inputhook')
216
219
217 # testing:
220 # testing:
218 sec = test_sections['testing']
221 sec = test_sections['testing']
219 # This guy is probably attic material
222 # This guy is probably attic material
220 sec.exclude('mkdoctests')
223 sec.exclude('mkdoctests')
221 # 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.
222 # See ticket https://github.com/ipython/ipython/issues/87
225 # See ticket https://github.com/ipython/ipython/issues/87
223 if sys.platform == 'win32':
226 if sys.platform == 'win32':
224 sec.exclude('plugin.test_exampleip')
227 sec.exclude('plugin.test_exampleip')
225 sec.exclude('plugin.dtexample')
228 sec.exclude('plugin.dtexample')
226
229
227 # terminal:
230 # terminal:
228 if (not have['pexpect']) or (not have['zmq']):
231 if (not have['pexpect']) or (not have['zmq']):
229 test_sections['terminal'].exclude('console')
232 test_sections['terminal'].exclude('console')
230
233
231 # parallel
234 # parallel
232 sec = test_sections['parallel']
235 sec = test_sections['parallel']
233 sec.requires('zmq')
236 sec.requires('zmq')
234 if not have['pymongo']:
237 if not have['pymongo']:
235 sec.exclude('controller.mongodb')
238 sec.exclude('controller.mongodb')
236 sec.exclude('tests.test_mongodb')
239 sec.exclude('tests.test_mongodb')
237
240
238 # kernel:
241 # kernel:
239 sec = test_sections['kernel']
242 sec = test_sections['kernel']
240 sec.requires('zmq')
243 sec.requires('zmq')
241 # The in-process kernel tests are done in a separate section
244 # The in-process kernel tests are done in a separate section
242 sec.exclude('inprocess')
245 sec.exclude('inprocess')
243 # importing gtk sets the default encoding, which we want to avoid
246 # importing gtk sets the default encoding, which we want to avoid
244 sec.exclude('zmq.gui.gtkembed')
247 sec.exclude('zmq.gui.gtkembed')
245 if not have['matplotlib']:
248 if not have['matplotlib']:
246 sec.exclude('zmq.pylab')
249 sec.exclude('zmq.pylab')
247
250
248 # kernel.inprocess:
251 # kernel.inprocess:
249 test_sections['kernel.inprocess'].requires('zmq')
252 test_sections['kernel.inprocess'].requires('zmq')
250
253
251 # extensions:
254 # extensions:
252 sec = test_sections['extensions']
255 sec = test_sections['extensions']
253 if not have['cython']:
256 if not have['cython']:
254 sec.exclude('cythonmagic')
257 sec.exclude('cythonmagic')
255 sec.exclude('tests.test_cythonmagic')
258 sec.exclude('tests.test_cythonmagic')
256 if not have['oct2py']:
259 if not have['oct2py']:
257 sec.exclude('octavemagic')
260 sec.exclude('octavemagic')
258 sec.exclude('tests.test_octavemagic')
261 sec.exclude('tests.test_octavemagic')
259 if not have['rpy2'] or not have['numpy']:
262 if not have['rpy2'] or not have['numpy']:
260 sec.exclude('rmagic')
263 sec.exclude('rmagic')
261 sec.exclude('tests.test_rmagic')
264 sec.exclude('tests.test_rmagic')
262 # 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
263 sec.exclude('autoreload')
266 sec.exclude('autoreload')
264 sec.exclude('tests.test_autoreload')
267 sec.exclude('tests.test_autoreload')
265 test_sections['autoreload'] = TestSection('autoreload',
268 test_sections['autoreload'] = TestSection('autoreload',
266 ['IPython.extensions.autoreload', 'IPython.extensions.tests.test_autoreload'])
269 ['IPython.extensions.autoreload', 'IPython.extensions.tests.test_autoreload'])
267 test_group_names.append('autoreload')
270 test_group_names.append('autoreload')
268
271
269 # qt:
272 # qt:
270 test_sections['qt'].requires('zmq', 'qt', 'pygments')
273 test_sections['qt'].requires('zmq', 'qt', 'pygments')
271
274
272 # html:
275 # html:
273 sec = test_sections['html']
276 sec = test_sections['html']
274 sec.requires('zmq', 'tornado', 'requests')
277 sec.requires('zmq', 'tornado', 'requests')
275 # The notebook 'static' directory contains JS, css and other
278 # The notebook 'static' directory contains JS, css and other
276 # files for web serving. Occasionally projects may put a .py
279 # files for web serving. Occasionally projects may put a .py
277 # 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
278 # well play it safe and skip the whole thing.
281 # well play it safe and skip the whole thing.
279 sec.exclude('static')
282 sec.exclude('static')
280 sec.exclude('fabfile')
283 sec.exclude('fabfile')
281 if not have['jinja2']:
284 if not have['jinja2']:
282 sec.exclude('notebookapp')
285 sec.exclude('notebookapp')
283 if not have['azure']:
286 if not have['azure']:
284 sec.exclude('services.notebooks.azurenbmanager')
287 sec.exclude('services.notebooks.azurenbmanager')
285
288
286 # config:
289 # config:
287 # Config files aren't really importable stand-alone
290 # Config files aren't really importable stand-alone
288 test_sections['config'].exclude('profile')
291 test_sections['config'].exclude('profile')
289
292
290 # nbconvert:
293 # nbconvert:
291 sec = test_sections['nbconvert']
294 sec = test_sections['nbconvert']
292 sec.requires('pygments', 'jinja2', 'sphinx')
295 sec.requires('pygments', 'jinja2', 'sphinx')
293 # Exclude nbconvert directories containing config files used to test.
296 # Exclude nbconvert directories containing config files used to test.
294 # Executing the config files with iptest would cause an exception.
297 # Executing the config files with iptest would cause an exception.
295 sec.exclude('tests.files')
298 sec.exclude('tests.files')
296 sec.exclude('exporters.tests.files')
299 sec.exclude('exporters.tests.files')
297 if not have['tornado']:
300 if not have['tornado']:
298 sec.exclude('nbconvert.post_processors.serve')
301 sec.exclude('nbconvert.post_processors.serve')
299 sec.exclude('nbconvert.post_processors.tests.test_serve')
302 sec.exclude('nbconvert.post_processors.tests.test_serve')
300
303
301 #-----------------------------------------------------------------------------
304 #-----------------------------------------------------------------------------
302 # Functions and classes
305 # Functions and classes
303 #-----------------------------------------------------------------------------
306 #-----------------------------------------------------------------------------
304
307
305 def check_exclusions_exist():
308 def check_exclusions_exist():
306 from IPython.utils.path import get_ipython_package_dir
309 from IPython.utils.path import get_ipython_package_dir
307 from IPython.utils.warn import warn
310 from IPython.utils.warn import warn
308 parent = os.path.dirname(get_ipython_package_dir())
311 parent = os.path.dirname(get_ipython_package_dir())
309 for sec in test_sections:
312 for sec in test_sections:
310 for pattern in sec.exclusions:
313 for pattern in sec.exclusions:
311 fullpath = pjoin(parent, pattern)
314 fullpath = pjoin(parent, pattern)
312 if not os.path.exists(fullpath) and not glob.glob(fullpath + '.*'):
315 if not os.path.exists(fullpath) and not glob.glob(fullpath + '.*'):
313 warn("Excluding nonexistent file: %r" % pattern)
316 warn("Excluding nonexistent file: %r" % pattern)
314
317
315
318
316 class ExclusionPlugin(Plugin):
319 class ExclusionPlugin(Plugin):
317 """A nose plugin to effect our exclusions of files and directories.
320 """A nose plugin to effect our exclusions of files and directories.
318 """
321 """
319 name = 'exclusions'
322 name = 'exclusions'
320 score = 3000 # Should come before any other plugins
323 score = 3000 # Should come before any other plugins
321
324
322 def __init__(self, exclude_patterns=None):
325 def __init__(self, exclude_patterns=None):
323 """
326 """
324 Parameters
327 Parameters
325 ----------
328 ----------
326
329
327 exclude_patterns : sequence of strings, optional
330 exclude_patterns : sequence of strings, optional
328 Filenames containing these patterns (as raw strings, not as regular
331 Filenames containing these patterns (as raw strings, not as regular
329 expressions) are excluded from the tests.
332 expressions) are excluded from the tests.
330 """
333 """
331 self.exclude_patterns = exclude_patterns or []
334 self.exclude_patterns = exclude_patterns or []
332 super(ExclusionPlugin, self).__init__()
335 super(ExclusionPlugin, self).__init__()
333
336
334 def options(self, parser, env=os.environ):
337 def options(self, parser, env=os.environ):
335 Plugin.options(self, parser, env)
338 Plugin.options(self, parser, env)
336
339
337 def configure(self, options, config):
340 def configure(self, options, config):
338 Plugin.configure(self, options, config)
341 Plugin.configure(self, options, config)
339 # Override nose trying to disable plugin.
342 # Override nose trying to disable plugin.
340 self.enabled = True
343 self.enabled = True
341
344
342 def wantFile(self, filename):
345 def wantFile(self, filename):
343 """Return whether the given filename should be scanned for tests.
346 """Return whether the given filename should be scanned for tests.
344 """
347 """
345 if any(pat in filename for pat in self.exclude_patterns):
348 if any(pat in filename for pat in self.exclude_patterns):
346 return False
349 return False
347 return None
350 return None
348
351
349 def wantDirectory(self, directory):
352 def wantDirectory(self, directory):
350 """Return whether the given directory should be scanned for tests.
353 """Return whether the given directory should be scanned for tests.
351 """
354 """
352 if any(pat in directory for pat in self.exclude_patterns):
355 if any(pat in directory for pat in self.exclude_patterns):
353 return False
356 return False
354 return None
357 return None
355
358
356
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 captured = self.stream_capturer.get_buffer().decode('utf-8', 'replace')
431 if captured.strip():
432 ev = safe_str(ev)
433 out = [ev, '>> begin captured subprocess output <<',
434 captured,
435 '>> end captured subprocess output <<']
436 return ec, '\n'.join(out), tb
437
438 return err
439
440 formatError = formatFailure
441
442 def finalize(self, result):
443 if self.stream_capturer.started:
444 self.stream_capturer.stop.set()
445 self.stream_capturer.join()
446
447
357 def run_iptest():
448 def run_iptest():
358 """Run the IPython test suite using nose.
449 """Run the IPython test suite using nose.
359
450
360 This function is called when this script is **not** called with the form
451 This function is called when this script is **not** called with the form
361 `iptest all`. It simply calls nose with appropriate command line flags
452 `iptest all`. It simply calls nose with appropriate command line flags
362 and accepts all of the standard nose arguments.
453 and accepts all of the standard nose arguments.
363 """
454 """
364 # Apply our monkeypatch to Xunit
455 # Apply our monkeypatch to Xunit
365 if '--with-xunit' in sys.argv and not hasattr(Xunit, 'orig_addError'):
456 if '--with-xunit' in sys.argv and not hasattr(Xunit, 'orig_addError'):
366 monkeypatch_xunit()
457 monkeypatch_xunit()
367
458
368 warnings.filterwarnings('ignore',
459 warnings.filterwarnings('ignore',
369 'This will be removed soon. Use IPython.testing.util instead')
460 'This will be removed soon. Use IPython.testing.util instead')
370
461
371 arg1 = sys.argv[1]
462 arg1 = sys.argv[1]
372 if arg1 in test_sections:
463 if arg1 in test_sections:
373 section = test_sections[arg1]
464 section = test_sections[arg1]
374 sys.argv[1:2] = section.includes
465 sys.argv[1:2] = section.includes
375 elif arg1.startswith('IPython.') and arg1[8:] in test_sections:
466 elif arg1.startswith('IPython.') and arg1[8:] in test_sections:
376 section = test_sections[arg1[8:]]
467 section = test_sections[arg1[8:]]
377 sys.argv[1:2] = section.includes
468 sys.argv[1:2] = section.includes
378 else:
469 else:
379 section = TestSection(arg1, includes=[arg1])
470 section = TestSection(arg1, includes=[arg1])
380
471
381
472
382 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
473 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
383
474
384 '--with-ipdoctest',
475 '--with-ipdoctest',
385 '--ipdoctest-tests','--ipdoctest-extension=txt',
476 '--ipdoctest-tests','--ipdoctest-extension=txt',
386
477
387 # We add --exe because of setuptools' imbecility (it
478 # We add --exe because of setuptools' imbecility (it
388 # blindly does chmod +x on ALL files). Nose does the
479 # blindly does chmod +x on ALL files). Nose does the
389 # right thing and it tries to avoid executables,
480 # right thing and it tries to avoid executables,
390 # setuptools unfortunately forces our hand here. This
481 # setuptools unfortunately forces our hand here. This
391 # has been discussed on the distutils list and the
482 # has been discussed on the distutils list and the
392 # setuptools devs refuse to fix this problem!
483 # setuptools devs refuse to fix this problem!
393 '--exe',
484 '--exe',
394 ]
485 ]
395 if '-a' not in argv and '-A' not in argv:
486 if '-a' not in argv and '-A' not in argv:
396 argv = argv + ['-a', '!crash']
487 argv = argv + ['-a', '!crash']
397
488
398 if nose.__version__ >= '0.11':
489 if nose.__version__ >= '0.11':
399 # I don't fully understand why we need this one, but depending on what
490 # I don't fully understand why we need this one, but depending on what
400 # directory the test suite is run from, if we don't give it, 0 tests
491 # directory the test suite is run from, if we don't give it, 0 tests
401 # get run. Specifically, if the test suite is run from the source dir
492 # get run. Specifically, if the test suite is run from the source dir
402 # with an argument (like 'iptest.py IPython.core', 0 tests are run,
493 # with an argument (like 'iptest.py IPython.core', 0 tests are run,
403 # even if the same call done in this directory works fine). It appears
494 # even if the same call done in this directory works fine). It appears
404 # that if the requested package is in the current dir, nose bails early
495 # that if the requested package is in the current dir, nose bails early
405 # by default. Since it's otherwise harmless, leave it in by default
496 # by default. Since it's otherwise harmless, leave it in by default
406 # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it.
497 # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it.
407 argv.append('--traverse-namespace')
498 argv.append('--traverse-namespace')
408
499
409 # use our plugin for doctesting. It will remove the standard doctest plugin
500 # use our plugin for doctesting. It will remove the standard doctest plugin
410 # if it finds it enabled
501 # if it finds it enabled
411 plugins = [ExclusionPlugin(section.excludes), IPythonDoctest(), KnownFailure()]
502 plugins = [ExclusionPlugin(section.excludes), IPythonDoctest(), KnownFailure(),
503 SubprocessStreamCapturePlugin() ]
412
504
413 # Use working directory set by parent process (see iptestcontroller)
505 # Use working directory set by parent process (see iptestcontroller)
414 if 'IPTEST_WORKING_DIR' in os.environ:
506 if 'IPTEST_WORKING_DIR' in os.environ:
415 os.chdir(os.environ['IPTEST_WORKING_DIR'])
507 os.chdir(os.environ['IPTEST_WORKING_DIR'])
416
508
417 # We need a global ipython running in this process, but the special
509 # We need a global ipython running in this process, but the special
418 # in-process group spawns its own IPython kernels, so for *that* group we
510 # in-process group spawns its own IPython kernels, so for *that* group we
419 # must avoid also opening the global one (otherwise there's a conflict of
511 # must avoid also opening the global one (otherwise there's a conflict of
420 # singletons). Ultimately the solution to this problem is to refactor our
512 # singletons). Ultimately the solution to this problem is to refactor our
421 # assumptions about what needs to be a singleton and what doesn't (app
513 # assumptions about what needs to be a singleton and what doesn't (app
422 # objects should, individual shells shouldn't). But for now, this
514 # objects should, individual shells shouldn't). But for now, this
423 # workaround allows the test suite for the inprocess module to complete.
515 # workaround allows the test suite for the inprocess module to complete.
424 if 'kernel.inprocess' not in section.name:
516 if 'kernel.inprocess' not in section.name:
425 from IPython.testing import globalipapp
517 from IPython.testing import globalipapp
426 globalipapp.start_ipython()
518 globalipapp.start_ipython()
427
519
428 # Now nose can run
520 # Now nose can run
429 TestProgram(argv=argv, addplugins=plugins)
521 TestProgram(argv=argv, addplugins=plugins)
430
522
431 if __name__ == '__main__':
523 if __name__ == '__main__':
432 run_iptest()
524 run_iptest()
433
525
General Comments 0
You need to be logged in to leave comments. Login now