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