##// END OF EJS Templates
test IPython.kernel
MinRK -
Show More
@@ -0,0 +1,253 b''
1 """Utilities for launching kernels
2
3 Authors:
4
5 * Min Ragan-Kelley
6
7 """
8
9 #-----------------------------------------------------------------------------
10 # Copyright (C) 2013 The IPython Development Team
11 #
12 # Distributed under the terms of the BSD License. The full license is in
13 # the file COPYING, distributed as part of this software.
14 #-----------------------------------------------------------------------------
15
16 #-----------------------------------------------------------------------------
17 # Imports
18 #-----------------------------------------------------------------------------
19
20 import os
21 import sys
22 from subprocess import Popen, PIPE
23
24
25 #-----------------------------------------------------------------------------
26 # Launching Kernels
27 #-----------------------------------------------------------------------------
28
29 def swallow_argv(argv, aliases=None, flags=None):
30 """strip frontend-specific aliases and flags from an argument list
31
32 For use primarily in frontend apps that want to pass a subset of command-line
33 arguments through to a subprocess, where frontend-specific flags and aliases
34 should be removed from the list.
35
36 Parameters
37 ----------
38
39 argv : list(str)
40 The starting argv, to be filtered
41 aliases : container of aliases (dict, list, set, etc.)
42 The frontend-specific aliases to be removed
43 flags : container of flags (dict, list, set, etc.)
44 The frontend-specific flags to be removed
45
46 Returns
47 -------
48
49 argv : list(str)
50 The argv list, excluding flags and aliases that have been stripped
51 """
52
53 if aliases is None:
54 aliases = set()
55 if flags is None:
56 flags = set()
57
58 stripped = list(argv) # copy
59
60 swallow_next = False
61 was_flag = False
62 for a in argv:
63 if swallow_next:
64 swallow_next = False
65 # last arg was an alias, remove the next one
66 # *unless* the last alias has a no-arg flag version, in which
67 # case, don't swallow the next arg if it's also a flag:
68 if not (was_flag and a.startswith('-')):
69 stripped.remove(a)
70 continue
71 if a.startswith('-'):
72 split = a.lstrip('-').split('=')
73 alias = split[0]
74 if alias in aliases:
75 stripped.remove(a)
76 if len(split) == 1:
77 # alias passed with arg via space
78 swallow_next = True
79 # could have been a flag that matches an alias, e.g. `existing`
80 # in which case, we might not swallow the next arg
81 was_flag = alias in flags
82 elif alias in flags and len(split) == 1:
83 # strip flag, but don't swallow next, as flags don't take args
84 stripped.remove(a)
85
86 # return shortened list
87 return stripped
88
89
90 def make_ipkernel_cmd(code, executable=None, extra_arguments=[], **kw):
91 """Build Popen command list for launching an IPython kernel.
92
93 Parameters
94 ----------
95 code : str,
96 A string of Python code that imports and executes a kernel entry point.
97
98 executable : str, optional (default sys.executable)
99 The Python executable to use for the kernel process.
100
101 extra_arguments : list, optional
102 A list of extra arguments to pass when executing the launch code.
103
104 Returns
105 -------
106
107 A Popen command list
108 """
109
110 # Build the kernel launch command.
111 if executable is None:
112 executable = sys.executable
113 arguments = [ executable, '-c', code, '-f', '{connection_file}' ]
114 arguments.extend(extra_arguments)
115
116 # Spawn a kernel.
117 if sys.platform == 'win32':
118
119 # If the kernel is running on pythonw and stdout/stderr are not been
120 # re-directed, it will crash when more than 4KB of data is written to
121 # stdout or stderr. This is a bug that has been with Python for a very
122 # long time; see http://bugs.python.org/issue706263.
123 # A cleaner solution to this problem would be to pass os.devnull to
124 # Popen directly. Unfortunately, that does not work.
125 if executable.endswith('pythonw.exe'):
126 arguments.append('--no-stdout')
127 arguments.append('--no-stderr')
128
129 return arguments
130
131
132 def launch_kernel(cmd, stdin=None, stdout=None, stderr=None,
133 independent=False,
134 cwd=None, ipython_kernel=True,
135 **kw
136 ):
137 """ Launches a localhost kernel, binding to the specified ports.
138
139 Parameters
140 ----------
141 cmd : Popen list,
142 A string of Python code that imports and executes a kernel entry point.
143
144 stdin, stdout, stderr : optional (default None)
145 Standards streams, as defined in subprocess.Popen.
146
147 independent : bool, optional (default False)
148 If set, the kernel process is guaranteed to survive if this process
149 dies. If not set, an effort is made to ensure that the kernel is killed
150 when this process dies. Note that in this case it is still good practice
151 to kill kernels manually before exiting.
152
153 cwd : path, optional
154 The working dir of the kernel process (default: cwd of this process).
155
156 ipython_kernel : bool, optional
157 Whether the kernel is an official IPython one,
158 and should get a bit of special treatment.
159
160 Returns
161 -------
162
163 Popen instance for the kernel subprocess
164 """
165
166 # Popen will fail (sometimes with a deadlock) if stdin, stdout, and stderr
167 # are invalid. Unfortunately, there is in general no way to detect whether
168 # they are valid. The following two blocks redirect them to (temporary)
169 # pipes in certain important cases.
170
171 # If this process has been backgrounded, our stdin is invalid. Since there
172 # is no compelling reason for the kernel to inherit our stdin anyway, we'll
173 # place this one safe and always redirect.
174 redirect_in = True
175 _stdin = PIPE if stdin is None else stdin
176
177 # If this process in running on pythonw, we know that stdin, stdout, and
178 # stderr are all invalid.
179 redirect_out = sys.executable.endswith('pythonw.exe')
180 if redirect_out:
181 _stdout = PIPE if stdout is None else stdout
182 _stderr = PIPE if stderr is None else stderr
183 else:
184 _stdout, _stderr = stdout, stderr
185
186 # Spawn a kernel.
187 if sys.platform == 'win32':
188 from IPython.zmq.parentpoller import ParentPollerWindows
189 # Create a Win32 event for interrupting the kernel.
190 interrupt_event = ParentPollerWindows.create_interrupt_event()
191 if ipython_kernel:
192 cmd += [ '--interrupt=%i' % interrupt_event ]
193
194 # If the kernel is running on pythonw and stdout/stderr are not been
195 # re-directed, it will crash when more than 4KB of data is written to
196 # stdout or stderr. This is a bug that has been with Python for a very
197 # long time; see http://bugs.python.org/issue706263.
198 # A cleaner solution to this problem would be to pass os.devnull to
199 # Popen directly. Unfortunately, that does not work.
200 if cmd[0].endswith('pythonw.exe'):
201 if stdout is None:
202 cmd.append('--no-stdout')
203 if stderr is None:
204 cmd.append('--no-stderr')
205
206 # Launch the kernel process.
207 if independent:
208 proc = Popen(cmd,
209 creationflags=512, # CREATE_NEW_PROCESS_GROUP
210 stdin=_stdin, stdout=_stdout, stderr=_stderr)
211 else:
212 if ipython_kernel:
213 try:
214 from _winapi import DuplicateHandle, GetCurrentProcess, \
215 DUPLICATE_SAME_ACCESS
216 except:
217 from _subprocess import DuplicateHandle, GetCurrentProcess, \
218 DUPLICATE_SAME_ACCESS
219 pid = GetCurrentProcess()
220 handle = DuplicateHandle(pid, pid, pid, 0,
221 True, # Inheritable by new processes.
222 DUPLICATE_SAME_ACCESS)
223 cmd +=[ '--parent=%i' % handle ]
224
225
226 proc = Popen(cmd,
227 stdin=_stdin, stdout=_stdout, stderr=_stderr, cwd=cwd)
228
229 # Attach the interrupt event to the Popen objet so it can be used later.
230 proc.win32_interrupt_event = interrupt_event
231
232 else:
233 if independent:
234 proc = Popen(cmd, preexec_fn=lambda: os.setsid(),
235 stdin=_stdin, stdout=_stdout, stderr=_stderr, cwd=cwd)
236 else:
237 if ipython_kernel:
238 cmd += ['--parent=1']
239 proc = Popen(cmd,
240 stdin=_stdin, stdout=_stdout, stderr=_stderr, cwd=cwd)
241
242 # Clean up pipes created to work around Popen bug.
243 if redirect_in:
244 if stdin is None:
245 proc.stdin.close()
246 if redirect_out:
247 if stdout is None:
248 proc.stdout.close()
249 if stderr is None:
250 proc.stderr.close()
251
252 return proc
253
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
@@ -1,63 +1,63 b''
1 """Tests for kernel utility functions
1 """Tests for kernel utility functions
2
2
3 Authors
3 Authors
4 -------
4 -------
5 * MinRK
5 * MinRK
6 """
6 """
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8 # Copyright (c) 2011, the IPython Development Team.
8 # Copyright (c) 2011, the IPython Development Team.
9 #
9 #
10 # Distributed under the terms of the Modified BSD License.
10 # Distributed under the terms of the Modified BSD License.
11 #
11 #
12 # The full license is in the file COPYING.txt, distributed with this software.
12 # The full license is in the file COPYING.txt, distributed with this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 # Stdlib imports
19 # Stdlib imports
20 from unittest import TestCase
20 from unittest import TestCase
21
21
22 # Third-party imports
22 # Third-party imports
23 import nose.tools as nt
23 import nose.tools as nt
24
24
25 # Our own imports
25 # Our own imports
26 from IPython.testing import decorators as dec
26 from IPython.testing import decorators as dec
27 from IPython.utils import kernel
27 from IPython.kernel.launcher import swallow_argv
28
28
29 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
30 # Classes and functions
30 # Classes and functions
31 #-----------------------------------------------------------------------------
31 #-----------------------------------------------------------------------------
32
32
33 @dec.parametric
33 @dec.parametric
34 def test_swallow_argv():
34 def test_swallow_argv():
35 tests = [
35 tests = [
36 # expected , argv , aliases, flags
36 # expected , argv , aliases, flags
37 (['-a', '5'], ['-a', '5'], None, None),
37 (['-a', '5'], ['-a', '5'], None, None),
38 (['5'], ['-a', '5'], None, ['a']),
38 (['5'], ['-a', '5'], None, ['a']),
39 ([], ['-a', '5'], ['a'], None),
39 ([], ['-a', '5'], ['a'], None),
40 ([], ['-a', '5'], ['a'], ['a']),
40 ([], ['-a', '5'], ['a'], ['a']),
41 ([], ['--foo'], None, ['foo']),
41 ([], ['--foo'], None, ['foo']),
42 (['--foo'], ['--foo'], ['foobar'], []),
42 (['--foo'], ['--foo'], ['foobar'], []),
43 ([], ['--foo', '5'], ['foo'], []),
43 ([], ['--foo', '5'], ['foo'], []),
44 ([], ['--foo=5'], ['foo'], []),
44 ([], ['--foo=5'], ['foo'], []),
45 (['--foo=5'], ['--foo=5'], [], ['foo']),
45 (['--foo=5'], ['--foo=5'], [], ['foo']),
46 (['5'], ['--foo', '5'], [], ['foo']),
46 (['5'], ['--foo', '5'], [], ['foo']),
47 (['bar'], ['--foo', '5', 'bar'], ['foo'], ['foo']),
47 (['bar'], ['--foo', '5', 'bar'], ['foo'], ['foo']),
48 (['bar'], ['--foo=5', 'bar'], ['foo'], ['foo']),
48 (['bar'], ['--foo=5', 'bar'], ['foo'], ['foo']),
49 (['5','bar'], ['--foo', '5', 'bar'], None, ['foo']),
49 (['5','bar'], ['--foo', '5', 'bar'], None, ['foo']),
50 (['bar'], ['--foo', '5', 'bar'], ['foo'], None),
50 (['bar'], ['--foo', '5', 'bar'], ['foo'], None),
51 (['bar'], ['--foo=5', 'bar'], ['foo'], None),
51 (['bar'], ['--foo=5', 'bar'], ['foo'], None),
52 ]
52 ]
53 for expected, argv, aliases, flags in tests:
53 for expected, argv, aliases, flags in tests:
54 stripped = kernel.swallow_argv(argv, aliases=aliases, flags=flags)
54 stripped = swallow_argv(argv, aliases=aliases, flags=flags)
55 message = '\n'.join(['',
55 message = '\n'.join(['',
56 "argv: %r" % argv,
56 "argv: %r" % argv,
57 "aliases: %r" % aliases,
57 "aliases: %r" % aliases,
58 "flags : %r" % flags,
58 "flags : %r" % flags,
59 "expected : %r" % expected,
59 "expected : %r" % expected,
60 "returned : %r" % stripped,
60 "returned : %r" % stripped,
61 ])
61 ])
62 yield nt.assert_equal(expected, stripped, message)
62 yield nt.assert_equal(expected, stripped, message)
63
63
@@ -1,598 +1,598 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 import os
31 import os
32 import os.path as path
32 import os.path as path
33 import signal
33 import signal
34 import sys
34 import sys
35 import subprocess
35 import subprocess
36 import tempfile
36 import tempfile
37 import time
37 import time
38 import warnings
38 import warnings
39
39
40 # Note: monkeypatch!
40 # Note: monkeypatch!
41 # We need to monkeypatch a small problem in nose itself first, before importing
41 # We need to monkeypatch a small problem in nose itself first, before importing
42 # it for actual use. This should get into nose upstream, but its release cycle
42 # it for actual use. This should get into nose upstream, but its release cycle
43 # is slow and we need it for our parametric tests to work correctly.
43 # is slow and we need it for our parametric tests to work correctly.
44 from IPython.testing import nosepatch
44 from IPython.testing import nosepatch
45
45
46 # Monkeypatch extra assert methods into nose.tools if they're not already there.
46 # Monkeypatch extra assert methods into nose.tools if they're not already there.
47 # This can be dropped once we no longer test on Python 2.6
47 # This can be dropped once we no longer test on Python 2.6
48 from IPython.testing import nose_assert_methods
48 from IPython.testing import nose_assert_methods
49
49
50 # Now, proceed to import nose itself
50 # Now, proceed to import nose itself
51 import nose.plugins.builtin
51 import nose.plugins.builtin
52 from nose.plugins.xunit import Xunit
52 from nose.plugins.xunit import Xunit
53 from nose import SkipTest
53 from nose import SkipTest
54 from nose.core import TestProgram
54 from nose.core import TestProgram
55
55
56 # Our own imports
56 # Our own imports
57 from IPython.utils import py3compat
57 from IPython.utils import py3compat
58 from IPython.utils.importstring import import_item
58 from IPython.utils.importstring import import_item
59 from IPython.utils.path import get_ipython_module_path, get_ipython_package_dir
59 from IPython.utils.path import get_ipython_module_path, get_ipython_package_dir
60 from IPython.utils.process import find_cmd, pycmd2argv
60 from IPython.utils.process import find_cmd, pycmd2argv
61 from IPython.utils.sysinfo import sys_info
61 from IPython.utils.sysinfo import sys_info
62 from IPython.utils.tempdir import TemporaryDirectory
62 from IPython.utils.tempdir import TemporaryDirectory
63 from IPython.utils.warn import warn
63 from IPython.utils.warn import warn
64
64
65 from IPython.testing import globalipapp
65 from IPython.testing import globalipapp
66 from IPython.testing.plugin.ipdoctest import IPythonDoctest
66 from IPython.testing.plugin.ipdoctest import IPythonDoctest
67 from IPython.external.decorators import KnownFailure, knownfailureif
67 from IPython.external.decorators import KnownFailure, knownfailureif
68
68
69 pjoin = path.join
69 pjoin = path.join
70
70
71
71
72 #-----------------------------------------------------------------------------
72 #-----------------------------------------------------------------------------
73 # Globals
73 # Globals
74 #-----------------------------------------------------------------------------
74 #-----------------------------------------------------------------------------
75
75
76
76
77 #-----------------------------------------------------------------------------
77 #-----------------------------------------------------------------------------
78 # Warnings control
78 # Warnings control
79 #-----------------------------------------------------------------------------
79 #-----------------------------------------------------------------------------
80
80
81 # Twisted generates annoying warnings with Python 2.6, as will do other code
81 # Twisted generates annoying warnings with Python 2.6, as will do other code
82 # that imports 'sets' as of today
82 # that imports 'sets' as of today
83 warnings.filterwarnings('ignore', 'the sets module is deprecated',
83 warnings.filterwarnings('ignore', 'the sets module is deprecated',
84 DeprecationWarning )
84 DeprecationWarning )
85
85
86 # This one also comes from Twisted
86 # This one also comes from Twisted
87 warnings.filterwarnings('ignore', 'the sha module is deprecated',
87 warnings.filterwarnings('ignore', 'the sha module is deprecated',
88 DeprecationWarning)
88 DeprecationWarning)
89
89
90 # Wx on Fedora11 spits these out
90 # Wx on Fedora11 spits these out
91 warnings.filterwarnings('ignore', 'wxPython/wxWidgets release number mismatch',
91 warnings.filterwarnings('ignore', 'wxPython/wxWidgets release number mismatch',
92 UserWarning)
92 UserWarning)
93
93
94 # ------------------------------------------------------------------------------
94 # ------------------------------------------------------------------------------
95 # Monkeypatch Xunit to count known failures as skipped.
95 # Monkeypatch Xunit to count known failures as skipped.
96 # ------------------------------------------------------------------------------
96 # ------------------------------------------------------------------------------
97 def monkeypatch_xunit():
97 def monkeypatch_xunit():
98 try:
98 try:
99 knownfailureif(True)(lambda: None)()
99 knownfailureif(True)(lambda: None)()
100 except Exception as e:
100 except Exception as e:
101 KnownFailureTest = type(e)
101 KnownFailureTest = type(e)
102
102
103 def addError(self, test, err, capt=None):
103 def addError(self, test, err, capt=None):
104 if issubclass(err[0], KnownFailureTest):
104 if issubclass(err[0], KnownFailureTest):
105 err = (SkipTest,) + err[1:]
105 err = (SkipTest,) + err[1:]
106 return self.orig_addError(test, err, capt)
106 return self.orig_addError(test, err, capt)
107
107
108 Xunit.orig_addError = Xunit.addError
108 Xunit.orig_addError = Xunit.addError
109 Xunit.addError = addError
109 Xunit.addError = addError
110
110
111 #-----------------------------------------------------------------------------
111 #-----------------------------------------------------------------------------
112 # Logic for skipping doctests
112 # Logic for skipping doctests
113 #-----------------------------------------------------------------------------
113 #-----------------------------------------------------------------------------
114 def extract_version(mod):
114 def extract_version(mod):
115 return mod.__version__
115 return mod.__version__
116
116
117 def test_for(item, min_version=None, callback=extract_version):
117 def test_for(item, min_version=None, callback=extract_version):
118 """Test to see if item is importable, and optionally check against a minimum
118 """Test to see if item is importable, and optionally check against a minimum
119 version.
119 version.
120
120
121 If min_version is given, the default behavior is to check against the
121 If min_version is given, the default behavior is to check against the
122 `__version__` attribute of the item, but specifying `callback` allows you to
122 `__version__` attribute of the item, but specifying `callback` allows you to
123 extract the value you are interested in. e.g::
123 extract the value you are interested in. e.g::
124
124
125 In [1]: import sys
125 In [1]: import sys
126
126
127 In [2]: from IPython.testing.iptest import test_for
127 In [2]: from IPython.testing.iptest import test_for
128
128
129 In [3]: test_for('sys', (2,6), callback=lambda sys: sys.version_info)
129 In [3]: test_for('sys', (2,6), callback=lambda sys: sys.version_info)
130 Out[3]: True
130 Out[3]: True
131
131
132 """
132 """
133 try:
133 try:
134 check = import_item(item)
134 check = import_item(item)
135 except (ImportError, RuntimeError):
135 except (ImportError, RuntimeError):
136 # GTK reports Runtime error if it can't be initialized even if it's
136 # GTK reports Runtime error if it can't be initialized even if it's
137 # importable.
137 # importable.
138 return False
138 return False
139 else:
139 else:
140 if min_version:
140 if min_version:
141 if callback:
141 if callback:
142 # extra processing step to get version to compare
142 # extra processing step to get version to compare
143 check = callback(check)
143 check = callback(check)
144
144
145 return check >= min_version
145 return check >= min_version
146 else:
146 else:
147 return True
147 return True
148
148
149 # Global dict where we can store information on what we have and what we don't
149 # Global dict where we can store information on what we have and what we don't
150 # have available at test run time
150 # have available at test run time
151 have = {}
151 have = {}
152
152
153 have['curses'] = test_for('_curses')
153 have['curses'] = test_for('_curses')
154 have['matplotlib'] = test_for('matplotlib')
154 have['matplotlib'] = test_for('matplotlib')
155 have['numpy'] = test_for('numpy')
155 have['numpy'] = test_for('numpy')
156 have['pexpect'] = test_for('IPython.external.pexpect')
156 have['pexpect'] = test_for('IPython.external.pexpect')
157 have['pymongo'] = test_for('pymongo')
157 have['pymongo'] = test_for('pymongo')
158 have['pygments'] = test_for('pygments')
158 have['pygments'] = test_for('pygments')
159 have['qt'] = test_for('IPython.external.qt')
159 have['qt'] = test_for('IPython.external.qt')
160 have['rpy2'] = test_for('rpy2')
160 have['rpy2'] = test_for('rpy2')
161 have['sqlite3'] = test_for('sqlite3')
161 have['sqlite3'] = test_for('sqlite3')
162 have['cython'] = test_for('Cython')
162 have['cython'] = test_for('Cython')
163 have['oct2py'] = test_for('oct2py')
163 have['oct2py'] = test_for('oct2py')
164 have['tornado'] = test_for('tornado.version_info', (2,1,0), callback=None)
164 have['tornado'] = test_for('tornado.version_info', (2,1,0), callback=None)
165 have['jinja2'] = test_for('jinja2')
165 have['jinja2'] = test_for('jinja2')
166 have['wx'] = test_for('wx')
166 have['wx'] = test_for('wx')
167 have['wx.aui'] = test_for('wx.aui')
167 have['wx.aui'] = test_for('wx.aui')
168 have['azure'] = test_for('azure')
168 have['azure'] = test_for('azure')
169
169
170 min_zmq = (2,1,11)
170 min_zmq = (2,1,11)
171
171
172 have['zmq'] = test_for('zmq.pyzmq_version_info', min_zmq, callback=lambda x: x())
172 have['zmq'] = test_for('zmq.pyzmq_version_info', min_zmq, callback=lambda x: x())
173
173
174 #-----------------------------------------------------------------------------
174 #-----------------------------------------------------------------------------
175 # Functions and classes
175 # Functions and classes
176 #-----------------------------------------------------------------------------
176 #-----------------------------------------------------------------------------
177
177
178 def report():
178 def report():
179 """Return a string with a summary report of test-related variables."""
179 """Return a string with a summary report of test-related variables."""
180
180
181 out = [ sys_info(), '\n']
181 out = [ sys_info(), '\n']
182
182
183 avail = []
183 avail = []
184 not_avail = []
184 not_avail = []
185
185
186 for k, is_avail in have.items():
186 for k, is_avail in have.items():
187 if is_avail:
187 if is_avail:
188 avail.append(k)
188 avail.append(k)
189 else:
189 else:
190 not_avail.append(k)
190 not_avail.append(k)
191
191
192 if avail:
192 if avail:
193 out.append('\nTools and libraries available at test time:\n')
193 out.append('\nTools and libraries available at test time:\n')
194 avail.sort()
194 avail.sort()
195 out.append(' ' + ' '.join(avail)+'\n')
195 out.append(' ' + ' '.join(avail)+'\n')
196
196
197 if not_avail:
197 if not_avail:
198 out.append('\nTools and libraries NOT available at test time:\n')
198 out.append('\nTools and libraries NOT available at test time:\n')
199 not_avail.sort()
199 not_avail.sort()
200 out.append(' ' + ' '.join(not_avail)+'\n')
200 out.append(' ' + ' '.join(not_avail)+'\n')
201
201
202 return ''.join(out)
202 return ''.join(out)
203
203
204
204
205 def make_exclude():
205 def make_exclude():
206 """Make patterns of modules and packages to exclude from testing.
206 """Make patterns of modules and packages to exclude from testing.
207
207
208 For the IPythonDoctest plugin, we need to exclude certain patterns that
208 For the IPythonDoctest plugin, we need to exclude certain patterns that
209 cause testing problems. We should strive to minimize the number of
209 cause testing problems. We should strive to minimize the number of
210 skipped modules, since this means untested code.
210 skipped modules, since this means untested code.
211
211
212 These modules and packages will NOT get scanned by nose at all for tests.
212 These modules and packages will NOT get scanned by nose at all for tests.
213 """
213 """
214 # Simple utility to make IPython paths more readably, we need a lot of
214 # Simple utility to make IPython paths more readably, we need a lot of
215 # these below
215 # these below
216 ipjoin = lambda *paths: pjoin('IPython', *paths)
216 ipjoin = lambda *paths: pjoin('IPython', *paths)
217
217
218 exclusions = [ipjoin('external'),
218 exclusions = [ipjoin('external'),
219 ipjoin('quarantine'),
219 ipjoin('quarantine'),
220 ipjoin('deathrow'),
220 ipjoin('deathrow'),
221 # This guy is probably attic material
221 # This guy is probably attic material
222 ipjoin('testing', 'mkdoctests'),
222 ipjoin('testing', 'mkdoctests'),
223 # Testing inputhook will need a lot of thought, to figure out
223 # Testing inputhook will need a lot of thought, to figure out
224 # how to have tests that don't lock up with the gui event
224 # how to have tests that don't lock up with the gui event
225 # loops in the picture
225 # loops in the picture
226 ipjoin('lib', 'inputhook'),
226 ipjoin('lib', 'inputhook'),
227 # Config files aren't really importable stand-alone
227 # Config files aren't really importable stand-alone
228 ipjoin('config', 'profile'),
228 ipjoin('config', 'profile'),
229 # The notebook 'static' directory contains JS, css and other
229 # The notebook 'static' directory contains JS, css and other
230 # files for web serving. Occasionally projects may put a .py
230 # files for web serving. Occasionally projects may put a .py
231 # file in there (MathJax ships a conf.py), so we might as
231 # file in there (MathJax ships a conf.py), so we might as
232 # well play it safe and skip the whole thing.
232 # well play it safe and skip the whole thing.
233 ipjoin('frontend', 'html', 'notebook', 'static')
233 ipjoin('frontend', 'html', 'notebook', 'static')
234 ]
234 ]
235 if not have['sqlite3']:
235 if not have['sqlite3']:
236 exclusions.append(ipjoin('core', 'tests', 'test_history'))
236 exclusions.append(ipjoin('core', 'tests', 'test_history'))
237 exclusions.append(ipjoin('core', 'history'))
237 exclusions.append(ipjoin('core', 'history'))
238 if not have['wx']:
238 if not have['wx']:
239 exclusions.append(ipjoin('lib', 'inputhookwx'))
239 exclusions.append(ipjoin('lib', 'inputhookwx'))
240
240
241 # FIXME: temporarily disable autoreload tests, as they can produce
241 # FIXME: temporarily disable autoreload tests, as they can produce
242 # spurious failures in subsequent tests (cythonmagic).
242 # spurious failures in subsequent tests (cythonmagic).
243 exclusions.append(ipjoin('extensions', 'autoreload'))
243 exclusions.append(ipjoin('extensions', 'autoreload'))
244 exclusions.append(ipjoin('extensions', 'tests', 'test_autoreload'))
244 exclusions.append(ipjoin('extensions', 'tests', 'test_autoreload'))
245
245
246 # We do this unconditionally, so that the test suite doesn't import
246 # We do this unconditionally, so that the test suite doesn't import
247 # gtk, changing the default encoding and masking some unicode bugs.
247 # gtk, changing the default encoding and masking some unicode bugs.
248 exclusions.append(ipjoin('lib', 'inputhookgtk'))
248 exclusions.append(ipjoin('lib', 'inputhookgtk'))
249 exclusions.append(ipjoin('zmq', 'gui', 'gtkembed'))
249 exclusions.append(ipjoin('zmq', 'gui', 'gtkembed'))
250
250
251 # These have to be skipped on win32 because the use echo, rm, cd, etc.
251 # These have to be skipped on win32 because the use echo, rm, cd, etc.
252 # See ticket https://github.com/ipython/ipython/issues/87
252 # See ticket https://github.com/ipython/ipython/issues/87
253 if sys.platform == 'win32':
253 if sys.platform == 'win32':
254 exclusions.append(ipjoin('testing', 'plugin', 'test_exampleip'))
254 exclusions.append(ipjoin('testing', 'plugin', 'test_exampleip'))
255 exclusions.append(ipjoin('testing', 'plugin', 'dtexample'))
255 exclusions.append(ipjoin('testing', 'plugin', 'dtexample'))
256
256
257 if not have['pexpect']:
257 if not have['pexpect']:
258 exclusions.extend([ipjoin('lib', 'irunner'),
258 exclusions.extend([ipjoin('lib', 'irunner'),
259 ipjoin('lib', 'tests', 'test_irunner'),
259 ipjoin('lib', 'tests', 'test_irunner'),
260 ipjoin('frontend', 'terminal', 'console'),
260 ipjoin('frontend', 'terminal', 'console'),
261 ])
261 ])
262
262
263 if not have['zmq']:
263 if not have['zmq']:
264 exclusions.append(ipjoin('zmq'))
264 exclusions.append(ipjoin('zmq'))
265 exclusions.append(ipjoin('frontend', 'qt'))
265 exclusions.append(ipjoin('frontend', 'qt'))
266 exclusions.append(ipjoin('frontend', 'html'))
266 exclusions.append(ipjoin('frontend', 'html'))
267 exclusions.append(ipjoin('frontend', 'consoleapp.py'))
267 exclusions.append(ipjoin('frontend', 'consoleapp.py'))
268 exclusions.append(ipjoin('frontend', 'terminal', 'console'))
268 exclusions.append(ipjoin('frontend', 'terminal', 'console'))
269 exclusions.append(ipjoin('parallel'))
269 exclusions.append(ipjoin('parallel'))
270 elif not have['qt'] or not have['pygments']:
270 elif not have['qt'] or not have['pygments']:
271 exclusions.append(ipjoin('frontend', 'qt'))
271 exclusions.append(ipjoin('frontend', 'qt'))
272
272
273 if not have['pymongo']:
273 if not have['pymongo']:
274 exclusions.append(ipjoin('parallel', 'controller', 'mongodb'))
274 exclusions.append(ipjoin('parallel', 'controller', 'mongodb'))
275 exclusions.append(ipjoin('parallel', 'tests', 'test_mongodb'))
275 exclusions.append(ipjoin('parallel', 'tests', 'test_mongodb'))
276
276
277 if not have['matplotlib']:
277 if not have['matplotlib']:
278 exclusions.extend([ipjoin('core', 'pylabtools'),
278 exclusions.extend([ipjoin('core', 'pylabtools'),
279 ipjoin('core', 'tests', 'test_pylabtools'),
279 ipjoin('core', 'tests', 'test_pylabtools'),
280 ipjoin('zmq', 'pylab'),
280 ipjoin('zmq', 'pylab'),
281 ])
281 ])
282
282
283 if not have['cython']:
283 if not have['cython']:
284 exclusions.extend([ipjoin('extensions', 'cythonmagic')])
284 exclusions.extend([ipjoin('extensions', 'cythonmagic')])
285 exclusions.extend([ipjoin('extensions', 'tests', 'test_cythonmagic')])
285 exclusions.extend([ipjoin('extensions', 'tests', 'test_cythonmagic')])
286
286
287 if not have['oct2py']:
287 if not have['oct2py']:
288 exclusions.extend([ipjoin('extensions', 'octavemagic')])
288 exclusions.extend([ipjoin('extensions', 'octavemagic')])
289 exclusions.extend([ipjoin('extensions', 'tests', 'test_octavemagic')])
289 exclusions.extend([ipjoin('extensions', 'tests', 'test_octavemagic')])
290
290
291 if not have['tornado']:
291 if not have['tornado']:
292 exclusions.append(ipjoin('frontend', 'html'))
292 exclusions.append(ipjoin('frontend', 'html'))
293
293
294 if not have['jinja2']:
294 if not have['jinja2']:
295 exclusions.append(ipjoin('frontend', 'html', 'notebook', 'notebookapp'))
295 exclusions.append(ipjoin('frontend', 'html', 'notebook', 'notebookapp'))
296
296
297 if not have['rpy2'] or not have['numpy']:
297 if not have['rpy2'] or not have['numpy']:
298 exclusions.append(ipjoin('extensions', 'rmagic'))
298 exclusions.append(ipjoin('extensions', 'rmagic'))
299 exclusions.append(ipjoin('extensions', 'tests', 'test_rmagic'))
299 exclusions.append(ipjoin('extensions', 'tests', 'test_rmagic'))
300
300
301 if not have['azure']:
301 if not have['azure']:
302 exclusions.append(ipjoin('frontend', 'html', 'notebook', 'azurenbmanager'))
302 exclusions.append(ipjoin('frontend', 'html', 'notebook', 'azurenbmanager'))
303
303
304 # This is needed for the reg-exp to match on win32 in the ipdoctest plugin.
304 # This is needed for the reg-exp to match on win32 in the ipdoctest plugin.
305 if sys.platform == 'win32':
305 if sys.platform == 'win32':
306 exclusions = [s.replace('\\','\\\\') for s in exclusions]
306 exclusions = [s.replace('\\','\\\\') for s in exclusions]
307
307
308 # check for any exclusions that don't seem to exist:
308 # check for any exclusions that don't seem to exist:
309 parent, _ = os.path.split(get_ipython_package_dir())
309 parent, _ = os.path.split(get_ipython_package_dir())
310 for exclusion in exclusions:
310 for exclusion in exclusions:
311 if exclusion.endswith(('deathrow', 'quarantine')):
311 if exclusion.endswith(('deathrow', 'quarantine')):
312 # ignore deathrow/quarantine, which exist in dev, but not install
312 # ignore deathrow/quarantine, which exist in dev, but not install
313 continue
313 continue
314 fullpath = pjoin(parent, exclusion)
314 fullpath = pjoin(parent, exclusion)
315 if not os.path.exists(fullpath) and not glob.glob(fullpath + '.*'):
315 if not os.path.exists(fullpath) and not glob.glob(fullpath + '.*'):
316 warn("Excluding nonexistent file: %r" % exclusion)
316 warn("Excluding nonexistent file: %r" % exclusion)
317
317
318 return exclusions
318 return exclusions
319
319
320
320
321 class IPTester(object):
321 class IPTester(object):
322 """Call that calls iptest or trial in a subprocess.
322 """Call that calls iptest or trial in a subprocess.
323 """
323 """
324 #: string, name of test runner that will be called
324 #: string, name of test runner that will be called
325 runner = None
325 runner = None
326 #: list, parameters for test runner
326 #: list, parameters for test runner
327 params = None
327 params = None
328 #: list, arguments of system call to be made to call test runner
328 #: list, arguments of system call to be made to call test runner
329 call_args = None
329 call_args = None
330 #: list, subprocesses we start (for cleanup)
330 #: list, subprocesses we start (for cleanup)
331 processes = None
331 processes = None
332 #: str, coverage xml output file
332 #: str, coverage xml output file
333 coverage_xml = None
333 coverage_xml = None
334
334
335 def __init__(self, runner='iptest', params=None):
335 def __init__(self, runner='iptest', params=None):
336 """Create new test runner."""
336 """Create new test runner."""
337 p = os.path
337 p = os.path
338 if runner == 'iptest':
338 if runner == 'iptest':
339 iptest_app = get_ipython_module_path('IPython.testing.iptest')
339 iptest_app = get_ipython_module_path('IPython.testing.iptest')
340 self.runner = pycmd2argv(iptest_app) + sys.argv[1:]
340 self.runner = pycmd2argv(iptest_app) + sys.argv[1:]
341 else:
341 else:
342 raise Exception('Not a valid test runner: %s' % repr(runner))
342 raise Exception('Not a valid test runner: %s' % repr(runner))
343 if params is None:
343 if params is None:
344 params = []
344 params = []
345 if isinstance(params, str):
345 if isinstance(params, str):
346 params = [params]
346 params = [params]
347 self.params = params
347 self.params = params
348
348
349 # Assemble call
349 # Assemble call
350 self.call_args = self.runner+self.params
350 self.call_args = self.runner+self.params
351
351
352 # Find the section we're testing (IPython.foo)
352 # Find the section we're testing (IPython.foo)
353 for sect in self.params:
353 for sect in self.params:
354 if sect.startswith('IPython'): break
354 if sect.startswith('IPython'): break
355 else:
355 else:
356 raise ValueError("Section not found", self.params)
356 raise ValueError("Section not found", self.params)
357
357
358 if '--with-xunit' in self.call_args:
358 if '--with-xunit' in self.call_args:
359
359
360 self.call_args.append('--xunit-file')
360 self.call_args.append('--xunit-file')
361 # FIXME: when Windows uses subprocess.call, these extra quotes are unnecessary:
361 # FIXME: when Windows uses subprocess.call, these extra quotes are unnecessary:
362 xunit_file = path.abspath(sect+'.xunit.xml')
362 xunit_file = path.abspath(sect+'.xunit.xml')
363 if sys.platform == 'win32':
363 if sys.platform == 'win32':
364 xunit_file = '"%s"' % xunit_file
364 xunit_file = '"%s"' % xunit_file
365 self.call_args.append(xunit_file)
365 self.call_args.append(xunit_file)
366
366
367 if '--with-xml-coverage' in self.call_args:
367 if '--with-xml-coverage' in self.call_args:
368 self.coverage_xml = path.abspath(sect+".coverage.xml")
368 self.coverage_xml = path.abspath(sect+".coverage.xml")
369 self.call_args.remove('--with-xml-coverage')
369 self.call_args.remove('--with-xml-coverage')
370 self.call_args = ["coverage", "run", "--source="+sect] + self.call_args[1:]
370 self.call_args = ["coverage", "run", "--source="+sect] + self.call_args[1:]
371
371
372 # Store anything we start to clean up on deletion
372 # Store anything we start to clean up on deletion
373 self.processes = []
373 self.processes = []
374
374
375 def _run_cmd(self):
375 def _run_cmd(self):
376 with TemporaryDirectory() as IPYTHONDIR:
376 with TemporaryDirectory() as IPYTHONDIR:
377 env = os.environ.copy()
377 env = os.environ.copy()
378 env['IPYTHONDIR'] = IPYTHONDIR
378 env['IPYTHONDIR'] = IPYTHONDIR
379 # print >> sys.stderr, '*** CMD:', ' '.join(self.call_args) # dbg
379 # print >> sys.stderr, '*** CMD:', ' '.join(self.call_args) # dbg
380 subp = subprocess.Popen(self.call_args, env=env)
380 subp = subprocess.Popen(self.call_args, env=env)
381 self.processes.append(subp)
381 self.processes.append(subp)
382 # If this fails, the process will be left in self.processes and
382 # If this fails, the process will be left in self.processes and
383 # cleaned up later, but if the wait call succeeds, then we can
383 # cleaned up later, but if the wait call succeeds, then we can
384 # clear the stored process.
384 # clear the stored process.
385 retcode = subp.wait()
385 retcode = subp.wait()
386 self.processes.pop()
386 self.processes.pop()
387 return retcode
387 return retcode
388
388
389 def run(self):
389 def run(self):
390 """Run the stored commands"""
390 """Run the stored commands"""
391 try:
391 try:
392 retcode = self._run_cmd()
392 retcode = self._run_cmd()
393 except KeyboardInterrupt:
393 except KeyboardInterrupt:
394 return -signal.SIGINT
394 return -signal.SIGINT
395 except:
395 except:
396 import traceback
396 import traceback
397 traceback.print_exc()
397 traceback.print_exc()
398 return 1 # signal failure
398 return 1 # signal failure
399
399
400 if self.coverage_xml:
400 if self.coverage_xml:
401 subprocess.call(["coverage", "xml", "-o", self.coverage_xml])
401 subprocess.call(["coverage", "xml", "-o", self.coverage_xml])
402 return retcode
402 return retcode
403
403
404 def __del__(self):
404 def __del__(self):
405 """Cleanup on exit by killing any leftover processes."""
405 """Cleanup on exit by killing any leftover processes."""
406 for subp in self.processes:
406 for subp in self.processes:
407 if subp.poll() is not None:
407 if subp.poll() is not None:
408 continue # process is already dead
408 continue # process is already dead
409
409
410 try:
410 try:
411 print('Cleaning up stale PID: %d' % subp.pid)
411 print('Cleaning up stale PID: %d' % subp.pid)
412 subp.kill()
412 subp.kill()
413 except: # (OSError, WindowsError) ?
413 except: # (OSError, WindowsError) ?
414 # This is just a best effort, if we fail or the process was
414 # This is just a best effort, if we fail or the process was
415 # really gone, ignore it.
415 # really gone, ignore it.
416 pass
416 pass
417 else:
417 else:
418 for i in range(10):
418 for i in range(10):
419 if subp.poll() is None:
419 if subp.poll() is None:
420 time.sleep(0.1)
420 time.sleep(0.1)
421 else:
421 else:
422 break
422 break
423
423
424 if subp.poll() is None:
424 if subp.poll() is None:
425 # The process did not die...
425 # The process did not die...
426 print('... failed. Manual cleanup may be required.')
426 print('... failed. Manual cleanup may be required.')
427
427
428 def make_runners(inc_slow=False):
428 def make_runners(inc_slow=False):
429 """Define the top-level packages that need to be tested.
429 """Define the top-level packages that need to be tested.
430 """
430 """
431
431
432 # Packages to be tested via nose, that only depend on the stdlib
432 # Packages to be tested via nose, that only depend on the stdlib
433 nose_pkg_names = ['config', 'core', 'extensions', 'frontend', 'lib',
433 nose_pkg_names = ['config', 'core', 'extensions', 'frontend', 'kernel', 'lib',
434 'testing', 'utils', 'nbformat', 'inprocess' ]
434 'testing', 'utils', 'nbformat', 'inprocess' ]
435
435
436 if have['zmq']:
436 if have['zmq']:
437 nose_pkg_names.append('zmq')
437 nose_pkg_names.append('zmq')
438 if inc_slow:
438 if inc_slow:
439 nose_pkg_names.append('parallel')
439 nose_pkg_names.append('parallel')
440
440
441 # For debugging this code, only load quick stuff
441 # For debugging this code, only load quick stuff
442 #nose_pkg_names = ['core', 'extensions'] # dbg
442 #nose_pkg_names = ['core', 'extensions'] # dbg
443
443
444 # Make fully qualified package names prepending 'IPython.' to our name lists
444 # Make fully qualified package names prepending 'IPython.' to our name lists
445 nose_packages = ['IPython.%s' % m for m in nose_pkg_names ]
445 nose_packages = ['IPython.%s' % m for m in nose_pkg_names ]
446
446
447 # Make runners
447 # Make runners
448 runners = [ (v, IPTester('iptest', params=v)) for v in nose_packages ]
448 runners = [ (v, IPTester('iptest', params=v)) for v in nose_packages ]
449
449
450 return runners
450 return runners
451
451
452
452
453 def run_iptest():
453 def run_iptest():
454 """Run the IPython test suite using nose.
454 """Run the IPython test suite using nose.
455
455
456 This function is called when this script is **not** called with the form
456 This function is called when this script is **not** called with the form
457 `iptest all`. It simply calls nose with appropriate command line flags
457 `iptest all`. It simply calls nose with appropriate command line flags
458 and accepts all of the standard nose arguments.
458 and accepts all of the standard nose arguments.
459 """
459 """
460 # Apply our monkeypatch to Xunit
460 # Apply our monkeypatch to Xunit
461 if '--with-xunit' in sys.argv and not hasattr(Xunit, 'orig_addError'):
461 if '--with-xunit' in sys.argv and not hasattr(Xunit, 'orig_addError'):
462 monkeypatch_xunit()
462 monkeypatch_xunit()
463
463
464 warnings.filterwarnings('ignore',
464 warnings.filterwarnings('ignore',
465 'This will be removed soon. Use IPython.testing.util instead')
465 'This will be removed soon. Use IPython.testing.util instead')
466
466
467 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
467 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
468
468
469 '--with-ipdoctest',
469 '--with-ipdoctest',
470 '--ipdoctest-tests','--ipdoctest-extension=txt',
470 '--ipdoctest-tests','--ipdoctest-extension=txt',
471
471
472 # We add --exe because of setuptools' imbecility (it
472 # We add --exe because of setuptools' imbecility (it
473 # blindly does chmod +x on ALL files). Nose does the
473 # blindly does chmod +x on ALL files). Nose does the
474 # right thing and it tries to avoid executables,
474 # right thing and it tries to avoid executables,
475 # setuptools unfortunately forces our hand here. This
475 # setuptools unfortunately forces our hand here. This
476 # has been discussed on the distutils list and the
476 # has been discussed on the distutils list and the
477 # setuptools devs refuse to fix this problem!
477 # setuptools devs refuse to fix this problem!
478 '--exe',
478 '--exe',
479 ]
479 ]
480 if '-a' not in argv and '-A' not in argv:
480 if '-a' not in argv and '-A' not in argv:
481 argv = argv + ['-a', '!crash']
481 argv = argv + ['-a', '!crash']
482
482
483 if nose.__version__ >= '0.11':
483 if nose.__version__ >= '0.11':
484 # I don't fully understand why we need this one, but depending on what
484 # I don't fully understand why we need this one, but depending on what
485 # directory the test suite is run from, if we don't give it, 0 tests
485 # directory the test suite is run from, if we don't give it, 0 tests
486 # get run. Specifically, if the test suite is run from the source dir
486 # get run. Specifically, if the test suite is run from the source dir
487 # with an argument (like 'iptest.py IPython.core', 0 tests are run,
487 # with an argument (like 'iptest.py IPython.core', 0 tests are run,
488 # even if the same call done in this directory works fine). It appears
488 # even if the same call done in this directory works fine). It appears
489 # that if the requested package is in the current dir, nose bails early
489 # that if the requested package is in the current dir, nose bails early
490 # by default. Since it's otherwise harmless, leave it in by default
490 # by default. Since it's otherwise harmless, leave it in by default
491 # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it.
491 # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it.
492 argv.append('--traverse-namespace')
492 argv.append('--traverse-namespace')
493
493
494 # use our plugin for doctesting. It will remove the standard doctest plugin
494 # use our plugin for doctesting. It will remove the standard doctest plugin
495 # if it finds it enabled
495 # if it finds it enabled
496 plugins = [IPythonDoctest(make_exclude()), KnownFailure()]
496 plugins = [IPythonDoctest(make_exclude()), KnownFailure()]
497
497
498 # 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
499 # 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
500 # 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
501 # singletons). Ultimately the solution to this problem is to refactor our
501 # singletons). Ultimately the solution to this problem is to refactor our
502 # 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
503 # objects should, individual shells shouldn't). But for now, this
503 # objects should, individual shells shouldn't). But for now, this
504 # workaround allows the test suite for the inprocess module to complete.
504 # workaround allows the test suite for the inprocess module to complete.
505 if not 'IPython.inprocess' in sys.argv:
505 if not 'IPython.inprocess' in sys.argv:
506 globalipapp.start_ipython()
506 globalipapp.start_ipython()
507
507
508 # Now nose can run
508 # Now nose can run
509 TestProgram(argv=argv, addplugins=plugins)
509 TestProgram(argv=argv, addplugins=plugins)
510
510
511
511
512 def run_iptestall(inc_slow=False):
512 def run_iptestall(inc_slow=False):
513 """Run the entire IPython test suite by calling nose and trial.
513 """Run the entire IPython test suite by calling nose and trial.
514
514
515 This function constructs :class:`IPTester` instances for all IPython
515 This function constructs :class:`IPTester` instances for all IPython
516 modules and package and then runs each of them. This causes the modules
516 modules and package and then runs each of them. This causes the modules
517 and packages of IPython to be tested each in their own subprocess using
517 and packages of IPython to be tested each in their own subprocess using
518 nose.
518 nose.
519
519
520 Parameters
520 Parameters
521 ----------
521 ----------
522
522
523 inc_slow : bool, optional
523 inc_slow : bool, optional
524 Include slow tests, like IPython.parallel. By default, these tests aren't
524 Include slow tests, like IPython.parallel. By default, these tests aren't
525 run.
525 run.
526 """
526 """
527
527
528 runners = make_runners(inc_slow=inc_slow)
528 runners = make_runners(inc_slow=inc_slow)
529
529
530 # Run the test runners in a temporary dir so we can nuke it when finished
530 # Run the test runners in a temporary dir so we can nuke it when finished
531 # to clean up any junk files left over by accident. This also makes it
531 # to clean up any junk files left over by accident. This also makes it
532 # robust against being run in non-writeable directories by mistake, as the
532 # robust against being run in non-writeable directories by mistake, as the
533 # temp dir will always be user-writeable.
533 # temp dir will always be user-writeable.
534 curdir = os.getcwdu()
534 curdir = os.getcwdu()
535 testdir = tempfile.gettempdir()
535 testdir = tempfile.gettempdir()
536 os.chdir(testdir)
536 os.chdir(testdir)
537
537
538 # Run all test runners, tracking execution time
538 # Run all test runners, tracking execution time
539 failed = []
539 failed = []
540 t_start = time.time()
540 t_start = time.time()
541 try:
541 try:
542 for (name, runner) in runners:
542 for (name, runner) in runners:
543 print('*'*70)
543 print('*'*70)
544 print('IPython test group:',name)
544 print('IPython test group:',name)
545 res = runner.run()
545 res = runner.run()
546 if res:
546 if res:
547 failed.append( (name, runner) )
547 failed.append( (name, runner) )
548 if res == -signal.SIGINT:
548 if res == -signal.SIGINT:
549 print("Interrupted")
549 print("Interrupted")
550 break
550 break
551 finally:
551 finally:
552 os.chdir(curdir)
552 os.chdir(curdir)
553 t_end = time.time()
553 t_end = time.time()
554 t_tests = t_end - t_start
554 t_tests = t_end - t_start
555 nrunners = len(runners)
555 nrunners = len(runners)
556 nfail = len(failed)
556 nfail = len(failed)
557 # summarize results
557 # summarize results
558 print()
558 print()
559 print('*'*70)
559 print('*'*70)
560 print('Test suite completed for system with the following information:')
560 print('Test suite completed for system with the following information:')
561 print(report())
561 print(report())
562 print('Ran %s test groups in %.3fs' % (nrunners, t_tests))
562 print('Ran %s test groups in %.3fs' % (nrunners, t_tests))
563 print()
563 print()
564 print('Status:')
564 print('Status:')
565 if not failed:
565 if not failed:
566 print('OK')
566 print('OK')
567 else:
567 else:
568 # If anything went wrong, point out what command to rerun manually to
568 # If anything went wrong, point out what command to rerun manually to
569 # see the actual errors and individual summary
569 # see the actual errors and individual summary
570 print('ERROR - %s out of %s test groups failed.' % (nfail, nrunners))
570 print('ERROR - %s out of %s test groups failed.' % (nfail, nrunners))
571 for name, failed_runner in failed:
571 for name, failed_runner in failed:
572 print('-'*40)
572 print('-'*40)
573 print('Runner failed:',name)
573 print('Runner failed:',name)
574 print('You may wish to rerun this one individually, with:')
574 print('You may wish to rerun this one individually, with:')
575 failed_call_args = [py3compat.cast_unicode(x) for x in failed_runner.call_args]
575 failed_call_args = [py3compat.cast_unicode(x) for x in failed_runner.call_args]
576 print(u' '.join(failed_call_args))
576 print(u' '.join(failed_call_args))
577 print()
577 print()
578 # Ensure that our exit code indicates failure
578 # Ensure that our exit code indicates failure
579 sys.exit(1)
579 sys.exit(1)
580
580
581
581
582 def main():
582 def main():
583 for arg in sys.argv[1:]:
583 for arg in sys.argv[1:]:
584 if arg.startswith('IPython'):
584 if arg.startswith('IPython'):
585 # This is in-process
585 # This is in-process
586 run_iptest()
586 run_iptest()
587 else:
587 else:
588 if "--all" in sys.argv:
588 if "--all" in sys.argv:
589 sys.argv.remove("--all")
589 sys.argv.remove("--all")
590 inc_slow = True
590 inc_slow = True
591 else:
591 else:
592 inc_slow = False
592 inc_slow = False
593 # This starts subprocesses
593 # This starts subprocesses
594 run_iptestall(inc_slow=inc_slow)
594 run_iptestall(inc_slow=inc_slow)
595
595
596
596
597 if __name__ == '__main__':
597 if __name__ == '__main__':
598 main()
598 main()
General Comments 0
You need to be logged in to leave comments. Login now