##// END OF EJS Templates
enable test coverage on coveralls
Matthias Bussonnier -
Show More
@@ -1,29 +1,35 b''
1 # http://travis-ci.org/#!/ipython/ipython
1 # http://travis-ci.org/#!/ipython/ipython
2 language: python
2 language: python
3 python:
3 python:
4 - 3.4
4 - 3.4
5 - 2.7
5 - 2.7
6 - 3.3
6 - 3.3
7 env:
7 env:
8 - GROUP=js
8 - GROUP=js
9 - GROUP=
9 - GROUP=
10 before_install:
10 before_install:
11 # workaround for https://github.com/travis-ci/travis-cookbooks/issues/155
11 # workaround for https://github.com/travis-ci/travis-cookbooks/issues/155
12 - sudo rm -rf /dev/shm && sudo ln -s /run/shm /dev/shm
12 - sudo rm -rf /dev/shm && sudo ln -s /run/shm /dev/shm
13 # Pierre Carrier's PPA for PhantomJS and CasperJS
13 # Pierre Carrier's PPA for PhantomJS and CasperJS
14 - sudo add-apt-repository -y ppa:pcarrier/ppa
14 - sudo add-apt-repository -y ppa:pcarrier/ppa
15 # Needed to get recent version of pandoc in ubntu 12.04
15 # Needed to get recent version of pandoc in ubntu 12.04
16 - sudo add-apt-repository -y ppa:marutter/c2d4u
16 - sudo add-apt-repository -y ppa:marutter/c2d4u
17 - sudo apt-get update
17 - sudo apt-get update
18 - sudo apt-get install pandoc casperjs libzmq3-dev
18 - sudo apt-get install pandoc casperjs libzmq3-dev
19 - git clone --quiet --depth 1 https://github.com/minrk/travis-wheels travis-wheels
19 - git clone --quiet --depth 1 https://github.com/minrk/travis-wheels travis-wheels
20 - 'if [[ $GROUP == js* ]]; then python -m IPython.external.mathjax; fi'
20 - 'if [[ $GROUP == js* ]]; then python -m IPython.external.mathjax; fi'
21 install:
21 install:
22 - pip install coveralls
22 - pip install -f travis-wheels/wheelhouse file://$PWD#egg=ipython[all]
23 - pip install -f travis-wheels/wheelhouse file://$PWD#egg=ipython[all]
23 script:
24 script:
24 - cd /tmp && iptest $GROUP
25 - cd /tmp && iptest $GROUP --coverage xml && cd -
25
26
26 matrix:
27 matrix:
27 exclude:
28 exclude:
28 - python: 3.3
29 - python: 3.3
29 env: GROUP=js
30 env: GROUP=js
31
32 after_success:
33 - cp /tmp/ipy_coverage.xml ./
34 - cp /tmp/.coverage ./
35 - coveralls
@@ -1,737 +1,740 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """IPython Test Process Controller
2 """IPython Test Process Controller
3
3
4 This module runs one or more subprocesses which will actually run the IPython
4 This module runs one or more subprocesses which will actually run the IPython
5 test suite.
5 test suite.
6
6
7 """
7 """
8
8
9 # Copyright (c) IPython Development Team.
9 # Copyright (c) IPython Development Team.
10 # Distributed under the terms of the Modified BSD License.
10 # Distributed under the terms of the Modified BSD License.
11
11
12 from __future__ import print_function
12 from __future__ import print_function
13
13
14 import argparse
14 import argparse
15 import json
15 import json
16 import multiprocessing.pool
16 import multiprocessing.pool
17 import os
17 import os
18 import stat
18 import stat
19 import re
19 import re
20 import requests
20 import requests
21 import shutil
21 import shutil
22 import signal
22 import signal
23 import sys
23 import sys
24 import subprocess
24 import subprocess
25 import time
25 import time
26
26
27 from .iptest import (
27 from .iptest import (
28 have, test_group_names as py_test_group_names, test_sections, StreamCapturer,
28 have, test_group_names as py_test_group_names, test_sections, StreamCapturer,
29 test_for,
29 test_for,
30 )
30 )
31 from IPython.utils.path import compress_user
31 from IPython.utils.path import compress_user
32 from IPython.utils.py3compat import bytes_to_str
32 from IPython.utils.py3compat import bytes_to_str
33 from IPython.utils.sysinfo import get_sys_info
33 from IPython.utils.sysinfo import get_sys_info
34 from IPython.utils.tempdir import TemporaryDirectory
34 from IPython.utils.tempdir import TemporaryDirectory
35 from IPython.utils.text import strip_ansi
35 from IPython.utils.text import strip_ansi
36
36
37 try:
37 try:
38 # Python >= 3.3
38 # Python >= 3.3
39 from subprocess import TimeoutExpired
39 from subprocess import TimeoutExpired
40 def popen_wait(p, timeout):
40 def popen_wait(p, timeout):
41 return p.wait(timeout)
41 return p.wait(timeout)
42 except ImportError:
42 except ImportError:
43 class TimeoutExpired(Exception):
43 class TimeoutExpired(Exception):
44 pass
44 pass
45 def popen_wait(p, timeout):
45 def popen_wait(p, timeout):
46 """backport of Popen.wait from Python 3"""
46 """backport of Popen.wait from Python 3"""
47 for i in range(int(10 * timeout)):
47 for i in range(int(10 * timeout)):
48 if p.poll() is not None:
48 if p.poll() is not None:
49 return
49 return
50 time.sleep(0.1)
50 time.sleep(0.1)
51 if p.poll() is None:
51 if p.poll() is None:
52 raise TimeoutExpired
52 raise TimeoutExpired
53
53
54 NOTEBOOK_SHUTDOWN_TIMEOUT = 10
54 NOTEBOOK_SHUTDOWN_TIMEOUT = 10
55
55
56 class TestController(object):
56 class TestController(object):
57 """Run tests in a subprocess
57 """Run tests in a subprocess
58 """
58 """
59 #: str, IPython test suite to be executed.
59 #: str, IPython test suite to be executed.
60 section = None
60 section = None
61 #: list, command line arguments to be executed
61 #: list, command line arguments to be executed
62 cmd = None
62 cmd = None
63 #: dict, extra environment variables to set for the subprocess
63 #: dict, extra environment variables to set for the subprocess
64 env = None
64 env = None
65 #: list, TemporaryDirectory instances to clear up when the process finishes
65 #: list, TemporaryDirectory instances to clear up when the process finishes
66 dirs = None
66 dirs = None
67 #: subprocess.Popen instance
67 #: subprocess.Popen instance
68 process = None
68 process = None
69 #: str, process stdout+stderr
69 #: str, process stdout+stderr
70 stdout = None
70 stdout = None
71
71
72 def __init__(self):
72 def __init__(self):
73 self.cmd = []
73 self.cmd = []
74 self.env = {}
74 self.env = {}
75 self.dirs = []
75 self.dirs = []
76
76
77 def setup(self):
77 def setup(self):
78 """Create temporary directories etc.
78 """Create temporary directories etc.
79
79
80 This is only called when we know the test group will be run. Things
80 This is only called when we know the test group will be run. Things
81 created here may be cleaned up by self.cleanup().
81 created here may be cleaned up by self.cleanup().
82 """
82 """
83 pass
83 pass
84
84
85 def launch(self, buffer_output=False, capture_output=False):
85 def launch(self, buffer_output=False, capture_output=False):
86 # print('*** ENV:', self.env) # dbg
86 # print('*** ENV:', self.env) # dbg
87 # print('*** CMD:', self.cmd) # dbg
87 # print('*** CMD:', self.cmd) # dbg
88 env = os.environ.copy()
88 env = os.environ.copy()
89 env.update(self.env)
89 env.update(self.env)
90 if buffer_output:
90 if buffer_output:
91 capture_output = True
91 capture_output = True
92 self.stdout_capturer = c = StreamCapturer(echo=not buffer_output)
92 self.stdout_capturer = c = StreamCapturer(echo=not buffer_output)
93 c.start()
93 c.start()
94 stdout = c.writefd if capture_output else None
94 stdout = c.writefd if capture_output else None
95 stderr = subprocess.STDOUT if capture_output else None
95 stderr = subprocess.STDOUT if capture_output else None
96 self.process = subprocess.Popen(self.cmd, stdout=stdout,
96 self.process = subprocess.Popen(self.cmd, stdout=stdout,
97 stderr=stderr, env=env)
97 stderr=stderr, env=env)
98
98
99 def wait(self):
99 def wait(self):
100 self.process.wait()
100 self.process.wait()
101 self.stdout_capturer.halt()
101 self.stdout_capturer.halt()
102 self.stdout = self.stdout_capturer.get_buffer()
102 self.stdout = self.stdout_capturer.get_buffer()
103 return self.process.returncode
103 return self.process.returncode
104
104
105 def print_extra_info(self):
105 def print_extra_info(self):
106 """Print extra information about this test run.
106 """Print extra information about this test run.
107
107
108 If we're running in parallel and showing the concise view, this is only
108 If we're running in parallel and showing the concise view, this is only
109 called if the test group fails. Otherwise, it's called before the test
109 called if the test group fails. Otherwise, it's called before the test
110 group is started.
110 group is started.
111
111
112 The base implementation does nothing, but it can be overridden by
112 The base implementation does nothing, but it can be overridden by
113 subclasses.
113 subclasses.
114 """
114 """
115 return
115 return
116
116
117 def cleanup_process(self):
117 def cleanup_process(self):
118 """Cleanup on exit by killing any leftover processes."""
118 """Cleanup on exit by killing any leftover processes."""
119 subp = self.process
119 subp = self.process
120 if subp is None or (subp.poll() is not None):
120 if subp is None or (subp.poll() is not None):
121 return # Process doesn't exist, or is already dead.
121 return # Process doesn't exist, or is already dead.
122
122
123 try:
123 try:
124 print('Cleaning up stale PID: %d' % subp.pid)
124 print('Cleaning up stale PID: %d' % subp.pid)
125 subp.kill()
125 subp.kill()
126 except: # (OSError, WindowsError) ?
126 except: # (OSError, WindowsError) ?
127 # This is just a best effort, if we fail or the process was
127 # This is just a best effort, if we fail or the process was
128 # really gone, ignore it.
128 # really gone, ignore it.
129 pass
129 pass
130 else:
130 else:
131 for i in range(10):
131 for i in range(10):
132 if subp.poll() is None:
132 if subp.poll() is None:
133 time.sleep(0.1)
133 time.sleep(0.1)
134 else:
134 else:
135 break
135 break
136
136
137 if subp.poll() is None:
137 if subp.poll() is None:
138 # The process did not die...
138 # The process did not die...
139 print('... failed. Manual cleanup may be required.')
139 print('... failed. Manual cleanup may be required.')
140
140
141 def cleanup(self):
141 def cleanup(self):
142 "Kill process if it's still alive, and clean up temporary directories"
142 "Kill process if it's still alive, and clean up temporary directories"
143 self.cleanup_process()
143 self.cleanup_process()
144 for td in self.dirs:
144 for td in self.dirs:
145 td.cleanup()
145 td.cleanup()
146
146
147 __del__ = cleanup
147 __del__ = cleanup
148
148
149
149
150 class PyTestController(TestController):
150 class PyTestController(TestController):
151 """Run Python tests using IPython.testing.iptest"""
151 """Run Python tests using IPython.testing.iptest"""
152 #: str, Python command to execute in subprocess
152 #: str, Python command to execute in subprocess
153 pycmd = None
153 pycmd = None
154
154
155 def __init__(self, section, options):
155 def __init__(self, section, options):
156 """Create new test runner."""
156 """Create new test runner."""
157 TestController.__init__(self)
157 TestController.__init__(self)
158 self.section = section
158 self.section = section
159 # pycmd is put into cmd[2] in PyTestController.launch()
159 # pycmd is put into cmd[2] in PyTestController.launch()
160 self.cmd = [sys.executable, '-c', None, section]
160 self.cmd = [sys.executable, '-c', None, section]
161 self.pycmd = "from IPython.testing.iptest import run_iptest; run_iptest()"
161 self.pycmd = "from IPython.testing.iptest import run_iptest; run_iptest()"
162 self.options = options
162 self.options = options
163
163
164 def setup(self):
164 def setup(self):
165 ipydir = TemporaryDirectory()
165 ipydir = TemporaryDirectory()
166 self.dirs.append(ipydir)
166 self.dirs.append(ipydir)
167 self.env['IPYTHONDIR'] = ipydir.name
167 self.env['IPYTHONDIR'] = ipydir.name
168 self.workingdir = workingdir = TemporaryDirectory()
168 self.workingdir = workingdir = TemporaryDirectory()
169 self.dirs.append(workingdir)
169 self.dirs.append(workingdir)
170 self.env['IPTEST_WORKING_DIR'] = workingdir.name
170 self.env['IPTEST_WORKING_DIR'] = workingdir.name
171 # This means we won't get odd effects from our own matplotlib config
171 # This means we won't get odd effects from our own matplotlib config
172 self.env['MPLCONFIGDIR'] = workingdir.name
172 self.env['MPLCONFIGDIR'] = workingdir.name
173 # For security reasons (http://bugs.python.org/issue16202), use
173 # For security reasons (http://bugs.python.org/issue16202), use
174 # a temporary directory to which other users have no access.
174 # a temporary directory to which other users have no access.
175 self.env['TMPDIR'] = workingdir.name
175 self.env['TMPDIR'] = workingdir.name
176
176
177 # Add a non-accessible directory to PATH (see gh-7053)
177 # Add a non-accessible directory to PATH (see gh-7053)
178 noaccess = os.path.join(self.workingdir.name, "_no_access_")
178 noaccess = os.path.join(self.workingdir.name, "_no_access_")
179 self.noaccess = noaccess
179 self.noaccess = noaccess
180 os.mkdir(noaccess, 0)
180 os.mkdir(noaccess, 0)
181
181
182 PATH = os.environ.get('PATH', '')
182 PATH = os.environ.get('PATH', '')
183 if PATH:
183 if PATH:
184 PATH = noaccess + os.pathsep + PATH
184 PATH = noaccess + os.pathsep + PATH
185 else:
185 else:
186 PATH = noaccess
186 PATH = noaccess
187 self.env['PATH'] = PATH
187 self.env['PATH'] = PATH
188
188
189 # From options:
189 # From options:
190 if self.options.xunit:
190 if self.options.xunit:
191 self.add_xunit()
191 self.add_xunit()
192 if self.options.coverage:
192 if self.options.coverage:
193 self.add_coverage()
193 self.add_coverage()
194 self.env['IPTEST_SUBPROC_STREAMS'] = self.options.subproc_streams
194 self.env['IPTEST_SUBPROC_STREAMS'] = self.options.subproc_streams
195 self.cmd.extend(self.options.extra_args)
195 self.cmd.extend(self.options.extra_args)
196
196
197 def cleanup(self):
197 def cleanup(self):
198 """
198 """
199 Make the non-accessible directory created in setup() accessible
199 Make the non-accessible directory created in setup() accessible
200 again, otherwise deleting the workingdir will fail.
200 again, otherwise deleting the workingdir will fail.
201 """
201 """
202 os.chmod(self.noaccess, stat.S_IRWXU)
202 os.chmod(self.noaccess, stat.S_IRWXU)
203 TestController.cleanup(self)
203 TestController.cleanup(self)
204
204
205 @property
205 @property
206 def will_run(self):
206 def will_run(self):
207 try:
207 try:
208 return test_sections[self.section].will_run
208 return test_sections[self.section].will_run
209 except KeyError:
209 except KeyError:
210 return True
210 return True
211
211
212 def add_xunit(self):
212 def add_xunit(self):
213 xunit_file = os.path.abspath(self.section + '.xunit.xml')
213 xunit_file = os.path.abspath(self.section + '.xunit.xml')
214 self.cmd.extend(['--with-xunit', '--xunit-file', xunit_file])
214 self.cmd.extend(['--with-xunit', '--xunit-file', xunit_file])
215
215
216 def add_coverage(self):
216 def add_coverage(self):
217 try:
217 try:
218 sources = test_sections[self.section].includes
218 sources = test_sections[self.section].includes
219 except KeyError:
219 except KeyError:
220 sources = ['IPython']
220 sources = ['IPython']
221
221
222 coverage_rc = ("[run]\n"
222 coverage_rc = ("[run]\n"
223 "data_file = {data_file}\n"
223 "data_file = {data_file}\n"
224 "source =\n"
224 "source =\n"
225 " {source}\n"
225 " {source}\n"
226 ).format(data_file=os.path.abspath('.coverage.'+self.section),
226 ).format(data_file=os.path.abspath('.coverage.'+self.section),
227 source="\n ".join(sources))
227 source="\n ".join(sources))
228 config_file = os.path.join(self.workingdir.name, '.coveragerc')
228 config_file = os.path.join(self.workingdir.name, '.coveragerc')
229 with open(config_file, 'w') as f:
229 with open(config_file, 'w') as f:
230 f.write(coverage_rc)
230 f.write(coverage_rc)
231
231
232 self.env['COVERAGE_PROCESS_START'] = config_file
232 self.env['COVERAGE_PROCESS_START'] = config_file
233 self.pycmd = "import coverage; coverage.process_startup(); " + self.pycmd
233 self.pycmd = "import coverage; coverage.process_startup(); " + self.pycmd
234
234
235 def launch(self, buffer_output=False):
235 def launch(self, buffer_output=False):
236 self.cmd[2] = self.pycmd
236 self.cmd[2] = self.pycmd
237 super(PyTestController, self).launch(buffer_output=buffer_output)
237 super(PyTestController, self).launch(buffer_output=buffer_output)
238
238
239
239
240 js_prefix = 'js/'
240 js_prefix = 'js/'
241
241
242 def get_js_test_dir():
242 def get_js_test_dir():
243 import IPython.html.tests as t
243 import IPython.html.tests as t
244 return os.path.join(os.path.dirname(t.__file__), '')
244 return os.path.join(os.path.dirname(t.__file__), '')
245
245
246 def all_js_groups():
246 def all_js_groups():
247 import glob
247 import glob
248 test_dir = get_js_test_dir()
248 test_dir = get_js_test_dir()
249 all_subdirs = glob.glob(test_dir + '[!_]*/')
249 all_subdirs = glob.glob(test_dir + '[!_]*/')
250 return [js_prefix+os.path.relpath(x, test_dir) for x in all_subdirs]
250 return [js_prefix+os.path.relpath(x, test_dir) for x in all_subdirs]
251
251
252 class JSController(TestController):
252 class JSController(TestController):
253 """Run CasperJS tests """
253 """Run CasperJS tests """
254
254
255 requirements = ['zmq', 'tornado', 'jinja2', 'casperjs', 'sqlite3',
255 requirements = ['zmq', 'tornado', 'jinja2', 'casperjs', 'sqlite3',
256 'jsonschema']
256 'jsonschema']
257
257
258 def __init__(self, section, xunit=True, engine='phantomjs', url=None):
258 def __init__(self, section, xunit=True, engine='phantomjs', url=None):
259 """Create new test runner."""
259 """Create new test runner."""
260 TestController.__init__(self)
260 TestController.__init__(self)
261 self.engine = engine
261 self.engine = engine
262 self.section = section
262 self.section = section
263 self.xunit = xunit
263 self.xunit = xunit
264 self.url = url
264 self.url = url
265 self.slimer_failure = re.compile('^FAIL.*', flags=re.MULTILINE)
265 self.slimer_failure = re.compile('^FAIL.*', flags=re.MULTILINE)
266 js_test_dir = get_js_test_dir()
266 js_test_dir = get_js_test_dir()
267 includes = '--includes=' + os.path.join(js_test_dir,'util.js')
267 includes = '--includes=' + os.path.join(js_test_dir,'util.js')
268 test_cases = os.path.join(js_test_dir, self.section[len(js_prefix):])
268 test_cases = os.path.join(js_test_dir, self.section[len(js_prefix):])
269 self.cmd = ['casperjs', 'test', includes, test_cases, '--engine=%s' % self.engine]
269 self.cmd = ['casperjs', 'test', includes, test_cases, '--engine=%s' % self.engine]
270
270
271 def setup(self):
271 def setup(self):
272 self.ipydir = TemporaryDirectory()
272 self.ipydir = TemporaryDirectory()
273 self.nbdir = TemporaryDirectory()
273 self.nbdir = TemporaryDirectory()
274 self.dirs.append(self.ipydir)
274 self.dirs.append(self.ipydir)
275 self.dirs.append(self.nbdir)
275 self.dirs.append(self.nbdir)
276 os.makedirs(os.path.join(self.nbdir.name, os.path.join(u'sub βˆ‚ir1', u'sub βˆ‚ir 1a')))
276 os.makedirs(os.path.join(self.nbdir.name, os.path.join(u'sub βˆ‚ir1', u'sub βˆ‚ir 1a')))
277 os.makedirs(os.path.join(self.nbdir.name, os.path.join(u'sub βˆ‚ir2', u'sub βˆ‚ir 1b')))
277 os.makedirs(os.path.join(self.nbdir.name, os.path.join(u'sub βˆ‚ir2', u'sub βˆ‚ir 1b')))
278
278
279 if self.xunit:
279 if self.xunit:
280 self.add_xunit()
280 self.add_xunit()
281
281
282 # If a url was specified, use that for the testing.
282 # If a url was specified, use that for the testing.
283 if self.url:
283 if self.url:
284 try:
284 try:
285 alive = requests.get(self.url).status_code == 200
285 alive = requests.get(self.url).status_code == 200
286 except:
286 except:
287 alive = False
287 alive = False
288
288
289 if alive:
289 if alive:
290 self.cmd.append("--url=%s" % self.url)
290 self.cmd.append("--url=%s" % self.url)
291 else:
291 else:
292 raise Exception('Could not reach "%s".' % self.url)
292 raise Exception('Could not reach "%s".' % self.url)
293 else:
293 else:
294 # start the ipython notebook, so we get the port number
294 # start the ipython notebook, so we get the port number
295 self.server_port = 0
295 self.server_port = 0
296 self._init_server()
296 self._init_server()
297 if self.server_port:
297 if self.server_port:
298 self.cmd.append("--port=%i" % self.server_port)
298 self.cmd.append("--port=%i" % self.server_port)
299 else:
299 else:
300 # don't launch tests if the server didn't start
300 # don't launch tests if the server didn't start
301 self.cmd = [sys.executable, '-c', 'raise SystemExit(1)']
301 self.cmd = [sys.executable, '-c', 'raise SystemExit(1)']
302
302
303 def add_xunit(self):
303 def add_xunit(self):
304 xunit_file = os.path.abspath(self.section.replace('/','.') + '.xunit.xml')
304 xunit_file = os.path.abspath(self.section.replace('/','.') + '.xunit.xml')
305 self.cmd.append('--xunit=%s' % xunit_file)
305 self.cmd.append('--xunit=%s' % xunit_file)
306
306
307 def launch(self, buffer_output):
307 def launch(self, buffer_output):
308 # If the engine is SlimerJS, we need to buffer the output because
308 # If the engine is SlimerJS, we need to buffer the output because
309 # SlimerJS does not support exit codes, so CasperJS always returns 0.
309 # SlimerJS does not support exit codes, so CasperJS always returns 0.
310 if self.engine == 'slimerjs' and not buffer_output:
310 if self.engine == 'slimerjs' and not buffer_output:
311 return super(JSController, self).launch(capture_output=True)
311 return super(JSController, self).launch(capture_output=True)
312
312
313 else:
313 else:
314 return super(JSController, self).launch(buffer_output=buffer_output)
314 return super(JSController, self).launch(buffer_output=buffer_output)
315
315
316 def wait(self, *pargs, **kwargs):
316 def wait(self, *pargs, **kwargs):
317 """Wait for the JSController to finish"""
317 """Wait for the JSController to finish"""
318 ret = super(JSController, self).wait(*pargs, **kwargs)
318 ret = super(JSController, self).wait(*pargs, **kwargs)
319 # If this is a SlimerJS controller, check the captured stdout for
319 # If this is a SlimerJS controller, check the captured stdout for
320 # errors. Otherwise, just return the return code.
320 # errors. Otherwise, just return the return code.
321 if self.engine == 'slimerjs':
321 if self.engine == 'slimerjs':
322 stdout = bytes_to_str(self.stdout)
322 stdout = bytes_to_str(self.stdout)
323 if ret != 0:
323 if ret != 0:
324 # This could still happen e.g. if it's stopped by SIGINT
324 # This could still happen e.g. if it's stopped by SIGINT
325 return ret
325 return ret
326 return bool(self.slimer_failure.search(strip_ansi(stdout)))
326 return bool(self.slimer_failure.search(strip_ansi(stdout)))
327 else:
327 else:
328 return ret
328 return ret
329
329
330 def print_extra_info(self):
330 def print_extra_info(self):
331 print("Running tests with notebook directory %r" % self.nbdir.name)
331 print("Running tests with notebook directory %r" % self.nbdir.name)
332
332
333 @property
333 @property
334 def will_run(self):
334 def will_run(self):
335 should_run = all(have[a] for a in self.requirements + [self.engine])
335 should_run = all(have[a] for a in self.requirements + [self.engine])
336 return should_run
336 return should_run
337
337
338 def _init_server(self):
338 def _init_server(self):
339 "Start the notebook server in a separate process"
339 "Start the notebook server in a separate process"
340 self.server_command = command = [sys.executable,
340 self.server_command = command = [sys.executable,
341 '-m', 'IPython.html',
341 '-m', 'IPython.html',
342 '--no-browser',
342 '--no-browser',
343 '--ipython-dir', self.ipydir.name,
343 '--ipython-dir', self.ipydir.name,
344 '--notebook-dir', self.nbdir.name,
344 '--notebook-dir', self.nbdir.name,
345 ]
345 ]
346 # ipc doesn't work on Windows, and darwin has crazy-long temp paths,
346 # ipc doesn't work on Windows, and darwin has crazy-long temp paths,
347 # which run afoul of ipc's maximum path length.
347 # which run afoul of ipc's maximum path length.
348 if sys.platform.startswith('linux'):
348 if sys.platform.startswith('linux'):
349 command.append('--KernelManager.transport=ipc')
349 command.append('--KernelManager.transport=ipc')
350 self.stream_capturer = c = StreamCapturer()
350 self.stream_capturer = c = StreamCapturer()
351 c.start()
351 c.start()
352 env = os.environ.copy()
352 env = os.environ.copy()
353 if self.engine == 'phantomjs':
353 if self.engine == 'phantomjs':
354 env['IPYTHON_ALLOW_DRAFT_WEBSOCKETS_FOR_PHANTOMJS'] = '1'
354 env['IPYTHON_ALLOW_DRAFT_WEBSOCKETS_FOR_PHANTOMJS'] = '1'
355 self.server = subprocess.Popen(command,
355 self.server = subprocess.Popen(command,
356 stdout=c.writefd,
356 stdout=c.writefd,
357 stderr=subprocess.STDOUT,
357 stderr=subprocess.STDOUT,
358 cwd=self.nbdir.name,
358 cwd=self.nbdir.name,
359 env=env,
359 env=env,
360 )
360 )
361 self.server_info_file = os.path.join(self.ipydir.name,
361 self.server_info_file = os.path.join(self.ipydir.name,
362 'profile_default', 'security', 'nbserver-%i.json' % self.server.pid
362 'profile_default', 'security', 'nbserver-%i.json' % self.server.pid
363 )
363 )
364 self._wait_for_server()
364 self._wait_for_server()
365
365
366 def _wait_for_server(self):
366 def _wait_for_server(self):
367 """Wait 30 seconds for the notebook server to start"""
367 """Wait 30 seconds for the notebook server to start"""
368 for i in range(300):
368 for i in range(300):
369 if self.server.poll() is not None:
369 if self.server.poll() is not None:
370 return self._failed_to_start()
370 return self._failed_to_start()
371 if os.path.exists(self.server_info_file):
371 if os.path.exists(self.server_info_file):
372 try:
372 try:
373 self._load_server_info()
373 self._load_server_info()
374 except ValueError:
374 except ValueError:
375 # If the server is halfway through writing the file, we may
375 # If the server is halfway through writing the file, we may
376 # get invalid JSON; it should be ready next iteration.
376 # get invalid JSON; it should be ready next iteration.
377 pass
377 pass
378 else:
378 else:
379 return
379 return
380 time.sleep(0.1)
380 time.sleep(0.1)
381 print("Notebook server-info file never arrived: %s" % self.server_info_file,
381 print("Notebook server-info file never arrived: %s" % self.server_info_file,
382 file=sys.stderr
382 file=sys.stderr
383 )
383 )
384
384
385 def _failed_to_start(self):
385 def _failed_to_start(self):
386 """Notebook server exited prematurely"""
386 """Notebook server exited prematurely"""
387 captured = self.stream_capturer.get_buffer().decode('utf-8', 'replace')
387 captured = self.stream_capturer.get_buffer().decode('utf-8', 'replace')
388 print("Notebook failed to start: ", file=sys.stderr)
388 print("Notebook failed to start: ", file=sys.stderr)
389 print(self.server_command)
389 print(self.server_command)
390 print(captured, file=sys.stderr)
390 print(captured, file=sys.stderr)
391
391
392 def _load_server_info(self):
392 def _load_server_info(self):
393 """Notebook server started, load connection info from JSON"""
393 """Notebook server started, load connection info from JSON"""
394 with open(self.server_info_file) as f:
394 with open(self.server_info_file) as f:
395 info = json.load(f)
395 info = json.load(f)
396 self.server_port = info['port']
396 self.server_port = info['port']
397
397
398 def cleanup(self):
398 def cleanup(self):
399 try:
399 try:
400 self.server.terminate()
400 self.server.terminate()
401 except OSError:
401 except OSError:
402 # already dead
402 # already dead
403 pass
403 pass
404 # wait 10s for the server to shutdown
404 # wait 10s for the server to shutdown
405 try:
405 try:
406 popen_wait(self.server, NOTEBOOK_SHUTDOWN_TIMEOUT)
406 popen_wait(self.server, NOTEBOOK_SHUTDOWN_TIMEOUT)
407 except TimeoutExpired:
407 except TimeoutExpired:
408 # server didn't terminate, kill it
408 # server didn't terminate, kill it
409 try:
409 try:
410 print("Failed to terminate notebook server, killing it.",
410 print("Failed to terminate notebook server, killing it.",
411 file=sys.stderr
411 file=sys.stderr
412 )
412 )
413 self.server.kill()
413 self.server.kill()
414 except OSError:
414 except OSError:
415 # already dead
415 # already dead
416 pass
416 pass
417 # wait another 10s
417 # wait another 10s
418 try:
418 try:
419 popen_wait(self.server, NOTEBOOK_SHUTDOWN_TIMEOUT)
419 popen_wait(self.server, NOTEBOOK_SHUTDOWN_TIMEOUT)
420 except TimeoutExpired:
420 except TimeoutExpired:
421 print("Notebook server still running (%s)" % self.server_info_file,
421 print("Notebook server still running (%s)" % self.server_info_file,
422 file=sys.stderr
422 file=sys.stderr
423 )
423 )
424
424
425 self.stream_capturer.halt()
425 self.stream_capturer.halt()
426 TestController.cleanup(self)
426 TestController.cleanup(self)
427
427
428
428
429 def prepare_controllers(options):
429 def prepare_controllers(options):
430 """Returns two lists of TestController instances, those to run, and those
430 """Returns two lists of TestController instances, those to run, and those
431 not to run."""
431 not to run."""
432 testgroups = options.testgroups
432 testgroups = options.testgroups
433 if testgroups:
433 if testgroups:
434 if 'js' in testgroups:
434 if 'js' in testgroups:
435 js_testgroups = all_js_groups()
435 js_testgroups = all_js_groups()
436 else:
436 else:
437 js_testgroups = [g for g in testgroups if g.startswith(js_prefix)]
437 js_testgroups = [g for g in testgroups if g.startswith(js_prefix)]
438
438
439 py_testgroups = [g for g in testgroups if g not in ['js'] + js_testgroups]
439 py_testgroups = [g for g in testgroups if g not in ['js'] + js_testgroups]
440 else:
440 else:
441 py_testgroups = py_test_group_names
441 py_testgroups = py_test_group_names
442 if not options.all:
442 if not options.all:
443 js_testgroups = []
443 js_testgroups = []
444 test_sections['parallel'].enabled = False
444 test_sections['parallel'].enabled = False
445 else:
445 else:
446 js_testgroups = all_js_groups()
446 js_testgroups = all_js_groups()
447
447
448 engine = 'slimerjs' if options.slimerjs else 'phantomjs'
448 engine = 'slimerjs' if options.slimerjs else 'phantomjs'
449 c_js = [JSController(name, xunit=options.xunit, engine=engine, url=options.url) for name in js_testgroups]
449 c_js = [JSController(name, xunit=options.xunit, engine=engine, url=options.url) for name in js_testgroups]
450 c_py = [PyTestController(name, options) for name in py_testgroups]
450 c_py = [PyTestController(name, options) for name in py_testgroups]
451
451
452 controllers = c_py + c_js
452 controllers = c_py + c_js
453 to_run = [c for c in controllers if c.will_run]
453 to_run = [c for c in controllers if c.will_run]
454 not_run = [c for c in controllers if not c.will_run]
454 not_run = [c for c in controllers if not c.will_run]
455 return to_run, not_run
455 return to_run, not_run
456
456
457 def do_run(controller, buffer_output=True):
457 def do_run(controller, buffer_output=True):
458 """Setup and run a test controller.
458 """Setup and run a test controller.
459
459
460 If buffer_output is True, no output is displayed, to avoid it appearing
460 If buffer_output is True, no output is displayed, to avoid it appearing
461 interleaved. In this case, the caller is responsible for displaying test
461 interleaved. In this case, the caller is responsible for displaying test
462 output on failure.
462 output on failure.
463
463
464 Returns
464 Returns
465 -------
465 -------
466 controller : TestController
466 controller : TestController
467 The same controller as passed in, as a convenience for using map() type
467 The same controller as passed in, as a convenience for using map() type
468 APIs.
468 APIs.
469 exitcode : int
469 exitcode : int
470 The exit code of the test subprocess. Non-zero indicates failure.
470 The exit code of the test subprocess. Non-zero indicates failure.
471 """
471 """
472 try:
472 try:
473 try:
473 try:
474 controller.setup()
474 controller.setup()
475 if not buffer_output:
475 if not buffer_output:
476 controller.print_extra_info()
476 controller.print_extra_info()
477 controller.launch(buffer_output=buffer_output)
477 controller.launch(buffer_output=buffer_output)
478 except Exception:
478 except Exception:
479 import traceback
479 import traceback
480 traceback.print_exc()
480 traceback.print_exc()
481 return controller, 1 # signal failure
481 return controller, 1 # signal failure
482
482
483 exitcode = controller.wait()
483 exitcode = controller.wait()
484 return controller, exitcode
484 return controller, exitcode
485
485
486 except KeyboardInterrupt:
486 except KeyboardInterrupt:
487 return controller, -signal.SIGINT
487 return controller, -signal.SIGINT
488 finally:
488 finally:
489 controller.cleanup()
489 controller.cleanup()
490
490
491 def report():
491 def report():
492 """Return a string with a summary report of test-related variables."""
492 """Return a string with a summary report of test-related variables."""
493 inf = get_sys_info()
493 inf = get_sys_info()
494 out = []
494 out = []
495 def _add(name, value):
495 def _add(name, value):
496 out.append((name, value))
496 out.append((name, value))
497
497
498 _add('IPython version', inf['ipython_version'])
498 _add('IPython version', inf['ipython_version'])
499 _add('IPython commit', "{} ({})".format(inf['commit_hash'], inf['commit_source']))
499 _add('IPython commit', "{} ({})".format(inf['commit_hash'], inf['commit_source']))
500 _add('IPython package', compress_user(inf['ipython_path']))
500 _add('IPython package', compress_user(inf['ipython_path']))
501 _add('Python version', inf['sys_version'].replace('\n',''))
501 _add('Python version', inf['sys_version'].replace('\n',''))
502 _add('sys.executable', compress_user(inf['sys_executable']))
502 _add('sys.executable', compress_user(inf['sys_executable']))
503 _add('Platform', inf['platform'])
503 _add('Platform', inf['platform'])
504
504
505 width = max(len(n) for (n,v) in out)
505 width = max(len(n) for (n,v) in out)
506 out = ["{:<{width}}: {}\n".format(n, v, width=width) for (n,v) in out]
506 out = ["{:<{width}}: {}\n".format(n, v, width=width) for (n,v) in out]
507
507
508 avail = []
508 avail = []
509 not_avail = []
509 not_avail = []
510
510
511 for k, is_avail in have.items():
511 for k, is_avail in have.items():
512 if is_avail:
512 if is_avail:
513 avail.append(k)
513 avail.append(k)
514 else:
514 else:
515 not_avail.append(k)
515 not_avail.append(k)
516
516
517 if avail:
517 if avail:
518 out.append('\nTools and libraries available at test time:\n')
518 out.append('\nTools and libraries available at test time:\n')
519 avail.sort()
519 avail.sort()
520 out.append(' ' + ' '.join(avail)+'\n')
520 out.append(' ' + ' '.join(avail)+'\n')
521
521
522 if not_avail:
522 if not_avail:
523 out.append('\nTools and libraries NOT available at test time:\n')
523 out.append('\nTools and libraries NOT available at test time:\n')
524 not_avail.sort()
524 not_avail.sort()
525 out.append(' ' + ' '.join(not_avail)+'\n')
525 out.append(' ' + ' '.join(not_avail)+'\n')
526
526
527 return ''.join(out)
527 return ''.join(out)
528
528
529 def run_iptestall(options):
529 def run_iptestall(options):
530 """Run the entire IPython test suite by calling nose and trial.
530 """Run the entire IPython test suite by calling nose and trial.
531
531
532 This function constructs :class:`IPTester` instances for all IPython
532 This function constructs :class:`IPTester` instances for all IPython
533 modules and package and then runs each of them. This causes the modules
533 modules and package and then runs each of them. This causes the modules
534 and packages of IPython to be tested each in their own subprocess using
534 and packages of IPython to be tested each in their own subprocess using
535 nose.
535 nose.
536
536
537 Parameters
537 Parameters
538 ----------
538 ----------
539
539
540 All parameters are passed as attributes of the options object.
540 All parameters are passed as attributes of the options object.
541
541
542 testgroups : list of str
542 testgroups : list of str
543 Run only these sections of the test suite. If empty, run all the available
543 Run only these sections of the test suite. If empty, run all the available
544 sections.
544 sections.
545
545
546 fast : int or None
546 fast : int or None
547 Run the test suite in parallel, using n simultaneous processes. If None
547 Run the test suite in parallel, using n simultaneous processes. If None
548 is passed, one process is used per CPU core. Default 1 (i.e. sequential)
548 is passed, one process is used per CPU core. Default 1 (i.e. sequential)
549
549
550 inc_slow : bool
550 inc_slow : bool
551 Include slow tests, like IPython.parallel. By default, these tests aren't
551 Include slow tests, like IPython.parallel. By default, these tests aren't
552 run.
552 run.
553
553
554 slimerjs : bool
554 slimerjs : bool
555 Use slimerjs if it's installed instead of phantomjs for casperjs tests.
555 Use slimerjs if it's installed instead of phantomjs for casperjs tests.
556
556
557 url : unicode
557 url : unicode
558 Address:port to use when running the JS tests.
558 Address:port to use when running the JS tests.
559
559
560 xunit : bool
560 xunit : bool
561 Produce Xunit XML output. This is written to multiple foo.xunit.xml files.
561 Produce Xunit XML output. This is written to multiple foo.xunit.xml files.
562
562
563 coverage : bool or str
563 coverage : bool or str
564 Measure code coverage from tests. True will store the raw coverage data,
564 Measure code coverage from tests. True will store the raw coverage data,
565 or pass 'html' or 'xml' to get reports.
565 or pass 'html' or 'xml' to get reports.
566
566
567 extra_args : list
567 extra_args : list
568 Extra arguments to pass to the test subprocesses, e.g. '-v'
568 Extra arguments to pass to the test subprocesses, e.g. '-v'
569 """
569 """
570 to_run, not_run = prepare_controllers(options)
570 to_run, not_run = prepare_controllers(options)
571
571
572 def justify(ltext, rtext, width=70, fill='-'):
572 def justify(ltext, rtext, width=70, fill='-'):
573 ltext += ' '
573 ltext += ' '
574 rtext = (' ' + rtext).rjust(width - len(ltext), fill)
574 rtext = (' ' + rtext).rjust(width - len(ltext), fill)
575 return ltext + rtext
575 return ltext + rtext
576
576
577 # Run all test runners, tracking execution time
577 # Run all test runners, tracking execution time
578 failed = []
578 failed = []
579 t_start = time.time()
579 t_start = time.time()
580
580
581 print()
581 print()
582 if options.fast == 1:
582 if options.fast == 1:
583 # This actually means sequential, i.e. with 1 job
583 # This actually means sequential, i.e. with 1 job
584 for controller in to_run:
584 for controller in to_run:
585 print('Test group:', controller.section)
585 print('Test group:', controller.section)
586 sys.stdout.flush() # Show in correct order when output is piped
586 sys.stdout.flush() # Show in correct order when output is piped
587 controller, res = do_run(controller, buffer_output=False)
587 controller, res = do_run(controller, buffer_output=False)
588 if res:
588 if res:
589 failed.append(controller)
589 failed.append(controller)
590 if res == -signal.SIGINT:
590 if res == -signal.SIGINT:
591 print("Interrupted")
591 print("Interrupted")
592 break
592 break
593 print()
593 print()
594
594
595 else:
595 else:
596 # Run tests concurrently
596 # Run tests concurrently
597 try:
597 try:
598 pool = multiprocessing.pool.ThreadPool(options.fast)
598 pool = multiprocessing.pool.ThreadPool(options.fast)
599 for (controller, res) in pool.imap_unordered(do_run, to_run):
599 for (controller, res) in pool.imap_unordered(do_run, to_run):
600 res_string = 'OK' if res == 0 else 'FAILED'
600 res_string = 'OK' if res == 0 else 'FAILED'
601 print(justify('Test group: ' + controller.section, res_string))
601 print(justify('Test group: ' + controller.section, res_string))
602 if res:
602 if res:
603 controller.print_extra_info()
603 controller.print_extra_info()
604 print(bytes_to_str(controller.stdout))
604 print(bytes_to_str(controller.stdout))
605 failed.append(controller)
605 failed.append(controller)
606 if res == -signal.SIGINT:
606 if res == -signal.SIGINT:
607 print("Interrupted")
607 print("Interrupted")
608 break
608 break
609 except KeyboardInterrupt:
609 except KeyboardInterrupt:
610 return
610 return
611
611
612 for controller in not_run:
612 for controller in not_run:
613 print(justify('Test group: ' + controller.section, 'NOT RUN'))
613 print(justify('Test group: ' + controller.section, 'NOT RUN'))
614
614
615 t_end = time.time()
615 t_end = time.time()
616 t_tests = t_end - t_start
616 t_tests = t_end - t_start
617 nrunners = len(to_run)
617 nrunners = len(to_run)
618 nfail = len(failed)
618 nfail = len(failed)
619 # summarize results
619 # summarize results
620 print('_'*70)
620 print('_'*70)
621 print('Test suite completed for system with the following information:')
621 print('Test suite completed for system with the following information:')
622 print(report())
622 print(report())
623 took = "Took %.3fs." % t_tests
623 took = "Took %.3fs." % t_tests
624 print('Status: ', end='')
624 print('Status: ', end='')
625 if not failed:
625 if not failed:
626 print('OK (%d test groups).' % nrunners, took)
626 print('OK (%d test groups).' % nrunners, took)
627 else:
627 else:
628 # If anything went wrong, point out what command to rerun manually to
628 # If anything went wrong, point out what command to rerun manually to
629 # see the actual errors and individual summary
629 # see the actual errors and individual summary
630 failed_sections = [c.section for c in failed]
630 failed_sections = [c.section for c in failed]
631 print('ERROR - {} out of {} test groups failed ({}).'.format(nfail,
631 print('ERROR - {} out of {} test groups failed ({}).'.format(nfail,
632 nrunners, ', '.join(failed_sections)), took)
632 nrunners, ', '.join(failed_sections)), took)
633 print()
633 print()
634 print('You may wish to rerun these, with:')
634 print('You may wish to rerun these, with:')
635 print(' iptest', *failed_sections)
635 print(' iptest', *failed_sections)
636 print()
636 print()
637
637
638 if options.coverage:
638 if options.coverage:
639 from coverage import coverage
639 from coverage import coverage, CoverageException
640 cov = coverage(data_file='.coverage')
640 cov = coverage(data_file='.coverage')
641 cov.combine()
641 cov.combine()
642 cov.save()
642 cov.save()
643
643
644 # Coverage HTML report
644 # Coverage HTML report
645 if options.coverage == 'html':
645 if options.coverage == 'html':
646 html_dir = 'ipy_htmlcov'
646 html_dir = 'ipy_htmlcov'
647 shutil.rmtree(html_dir, ignore_errors=True)
647 shutil.rmtree(html_dir, ignore_errors=True)
648 print("Writing HTML coverage report to %s/ ... " % html_dir, end="")
648 print("Writing HTML coverage report to %s/ ... " % html_dir, end="")
649 sys.stdout.flush()
649 sys.stdout.flush()
650
650
651 # Custom HTML reporter to clean up module names.
651 # Custom HTML reporter to clean up module names.
652 from coverage.html import HtmlReporter
652 from coverage.html import HtmlReporter
653 class CustomHtmlReporter(HtmlReporter):
653 class CustomHtmlReporter(HtmlReporter):
654 def find_code_units(self, morfs):
654 def find_code_units(self, morfs):
655 super(CustomHtmlReporter, self).find_code_units(morfs)
655 super(CustomHtmlReporter, self).find_code_units(morfs)
656 for cu in self.code_units:
656 for cu in self.code_units:
657 nameparts = cu.name.split(os.sep)
657 nameparts = cu.name.split(os.sep)
658 if 'IPython' not in nameparts:
658 if 'IPython' not in nameparts:
659 continue
659 continue
660 ix = nameparts.index('IPython')
660 ix = nameparts.index('IPython')
661 cu.name = '.'.join(nameparts[ix:])
661 cu.name = '.'.join(nameparts[ix:])
662
662
663 # Reimplement the html_report method with our custom reporter
663 # Reimplement the html_report method with our custom reporter
664 cov._harvest_data()
664 cov._harvest_data()
665 cov.config.from_args(omit='*{0}tests{0}*'.format(os.sep), html_dir=html_dir,
665 cov.config.from_args(omit='*{0}tests{0}*'.format(os.sep), html_dir=html_dir,
666 html_title='IPython test coverage',
666 html_title='IPython test coverage',
667 )
667 )
668 reporter = CustomHtmlReporter(cov, cov.config)
668 reporter = CustomHtmlReporter(cov, cov.config)
669 reporter.report(None)
669 reporter.report(None)
670 print('done.')
670 print('done.')
671
671
672 # Coverage XML report
672 # Coverage XML report
673 elif options.coverage == 'xml':
673 elif options.coverage == 'xml':
674 cov.xml_report(outfile='ipy_coverage.xml')
674 try:
675 cov.xml_report(outfile='ipy_coverage.xml')
676 except CoverageException:
677 pass
675
678
676 if failed:
679 if failed:
677 # Ensure that our exit code indicates failure
680 # Ensure that our exit code indicates failure
678 sys.exit(1)
681 sys.exit(1)
679
682
680 argparser = argparse.ArgumentParser(description='Run IPython test suite')
683 argparser = argparse.ArgumentParser(description='Run IPython test suite')
681 argparser.add_argument('testgroups', nargs='*',
684 argparser.add_argument('testgroups', nargs='*',
682 help='Run specified groups of tests. If omitted, run '
685 help='Run specified groups of tests. If omitted, run '
683 'all tests.')
686 'all tests.')
684 argparser.add_argument('--all', action='store_true',
687 argparser.add_argument('--all', action='store_true',
685 help='Include slow tests not run by default.')
688 help='Include slow tests not run by default.')
686 argparser.add_argument('--slimerjs', action='store_true',
689 argparser.add_argument('--slimerjs', action='store_true',
687 help="Use slimerjs if it's installed instead of phantomjs for casperjs tests.")
690 help="Use slimerjs if it's installed instead of phantomjs for casperjs tests.")
688 argparser.add_argument('--url', help="URL to use for the JS tests.")
691 argparser.add_argument('--url', help="URL to use for the JS tests.")
689 argparser.add_argument('-j', '--fast', nargs='?', const=None, default=1, type=int,
692 argparser.add_argument('-j', '--fast', nargs='?', const=None, default=1, type=int,
690 help='Run test sections in parallel. This starts as many '
693 help='Run test sections in parallel. This starts as many '
691 'processes as you have cores, or you can specify a number.')
694 'processes as you have cores, or you can specify a number.')
692 argparser.add_argument('--xunit', action='store_true',
695 argparser.add_argument('--xunit', action='store_true',
693 help='Produce Xunit XML results')
696 help='Produce Xunit XML results')
694 argparser.add_argument('--coverage', nargs='?', const=True, default=False,
697 argparser.add_argument('--coverage', nargs='?', const=True, default=False,
695 help="Measure test coverage. Specify 'html' or "
698 help="Measure test coverage. Specify 'html' or "
696 "'xml' to get reports.")
699 "'xml' to get reports.")
697 argparser.add_argument('--subproc-streams', default='capture',
700 argparser.add_argument('--subproc-streams', default='capture',
698 help="What to do with stdout/stderr from subprocesses. "
701 help="What to do with stdout/stderr from subprocesses. "
699 "'capture' (default), 'show' and 'discard' are the options.")
702 "'capture' (default), 'show' and 'discard' are the options.")
700
703
701 def default_options():
704 def default_options():
702 """Get an argparse Namespace object with the default arguments, to pass to
705 """Get an argparse Namespace object with the default arguments, to pass to
703 :func:`run_iptestall`.
706 :func:`run_iptestall`.
704 """
707 """
705 options = argparser.parse_args([])
708 options = argparser.parse_args([])
706 options.extra_args = []
709 options.extra_args = []
707 return options
710 return options
708
711
709 def main():
712 def main():
710 # iptest doesn't work correctly if the working directory is the
713 # iptest doesn't work correctly if the working directory is the
711 # root of the IPython source tree. Tell the user to avoid
714 # root of the IPython source tree. Tell the user to avoid
712 # frustration.
715 # frustration.
713 if os.path.exists(os.path.join(os.getcwd(),
716 if os.path.exists(os.path.join(os.getcwd(),
714 'IPython', 'testing', '__main__.py')):
717 'IPython', 'testing', '__main__.py')):
715 print("Don't run iptest from the IPython source directory",
718 print("Don't run iptest from the IPython source directory",
716 file=sys.stderr)
719 file=sys.stderr)
717 sys.exit(1)
720 sys.exit(1)
718 # Arguments after -- should be passed through to nose. Argparse treats
721 # Arguments after -- should be passed through to nose. Argparse treats
719 # everything after -- as regular positional arguments, so we separate them
722 # everything after -- as regular positional arguments, so we separate them
720 # first.
723 # first.
721 try:
724 try:
722 ix = sys.argv.index('--')
725 ix = sys.argv.index('--')
723 except ValueError:
726 except ValueError:
724 to_parse = sys.argv[1:]
727 to_parse = sys.argv[1:]
725 extra_args = []
728 extra_args = []
726 else:
729 else:
727 to_parse = sys.argv[1:ix]
730 to_parse = sys.argv[1:ix]
728 extra_args = sys.argv[ix+1:]
731 extra_args = sys.argv[ix+1:]
729
732
730 options = argparser.parse_args(to_parse)
733 options = argparser.parse_args(to_parse)
731 options.extra_args = extra_args
734 options.extra_args = extra_args
732
735
733 run_iptestall(options)
736 run_iptestall(options)
734
737
735
738
736 if __name__ == '__main__':
739 if __name__ == '__main__':
737 main()
740 main()
General Comments 0
You need to be logged in to leave comments. Login now