##// END OF EJS Templates
Revert "Enabled IPython Server output for Travis debugging"...
Jonathan Frederic -
Show More
@@ -1,510 +1,507
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 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10 # Copyright (C) 2009-2011 The IPython Development Team
10 # Copyright (C) 2009-2011 The IPython Development Team
11 #
11 #
12 # Distributed under the terms of the BSD License. The full license is in
12 # Distributed under the terms of the BSD License. The full license is in
13 # the file COPYING, distributed as part of this software.
13 # the file COPYING, distributed as part of this software.
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15
15
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 # Imports
17 # Imports
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19 from __future__ import print_function
19 from __future__ import print_function
20
20
21 import argparse
21 import argparse
22 import multiprocessing.pool
22 import multiprocessing.pool
23 from multiprocessing import Process, Queue
23 from multiprocessing import Process, Queue
24 import os
24 import os
25 import shutil
25 import shutil
26 import signal
26 import signal
27 import sys
27 import sys
28 import subprocess
28 import subprocess
29 import time
29 import time
30
30
31 from .iptest import have, test_group_names as py_test_group_names, test_sections
31 from .iptest import have, test_group_names as py_test_group_names, test_sections
32 from IPython.utils.path import compress_user
32 from IPython.utils.path import compress_user
33 from IPython.utils.py3compat import bytes_to_str
33 from IPython.utils.py3compat import bytes_to_str
34 from IPython.utils.sysinfo import get_sys_info
34 from IPython.utils.sysinfo import get_sys_info
35 from IPython.utils.tempdir import TemporaryDirectory
35 from IPython.utils.tempdir import TemporaryDirectory
36
36
37
37
38 class TestController(object):
38 class TestController(object):
39 """Run tests in a subprocess
39 """Run tests in a subprocess
40 """
40 """
41 #: str, IPython test suite to be executed.
41 #: str, IPython test suite to be executed.
42 section = None
42 section = None
43 #: list, command line arguments to be executed
43 #: list, command line arguments to be executed
44 cmd = None
44 cmd = None
45 #: dict, extra environment variables to set for the subprocess
45 #: dict, extra environment variables to set for the subprocess
46 env = None
46 env = None
47 #: list, TemporaryDirectory instances to clear up when the process finishes
47 #: list, TemporaryDirectory instances to clear up when the process finishes
48 dirs = None
48 dirs = None
49 #: subprocess.Popen instance
49 #: subprocess.Popen instance
50 process = None
50 process = None
51 #: str, process stdout+stderr
51 #: str, process stdout+stderr
52 stdout = None
52 stdout = None
53 #: bool, whether to capture process stdout & stderr
53 #: bool, whether to capture process stdout & stderr
54 buffer_output = False
54 buffer_output = False
55
55
56 def __init__(self):
56 def __init__(self):
57 self.cmd = []
57 self.cmd = []
58 self.env = {}
58 self.env = {}
59 self.dirs = []
59 self.dirs = []
60
60
61 def launch(self):
61 def launch(self):
62 # print('*** ENV:', self.env) # dbg
62 # print('*** ENV:', self.env) # dbg
63 # print('*** CMD:', self.cmd) # dbg
63 # print('*** CMD:', self.cmd) # dbg
64 env = os.environ.copy()
64 env = os.environ.copy()
65 env.update(self.env)
65 env.update(self.env)
66 output = subprocess.PIPE if self.buffer_output else None
66 output = subprocess.PIPE if self.buffer_output else None
67 stdout = subprocess.STDOUT if self.buffer_output else None
67 stdout = subprocess.STDOUT if self.buffer_output else None
68 self.process = subprocess.Popen(self.cmd, stdout=output,
68 self.process = subprocess.Popen(self.cmd, stdout=output,
69 stderr=stdout, env=env)
69 stderr=stdout, env=env)
70
70
71 def wait(self):
71 def wait(self):
72 self.stdout, _ = self.process.communicate()
72 self.stdout, _ = self.process.communicate()
73 return self.process.returncode
73 return self.process.returncode
74
74
75 def cleanup_process(self):
75 def cleanup_process(self):
76 """Cleanup on exit by killing any leftover processes."""
76 """Cleanup on exit by killing any leftover processes."""
77 subp = self.process
77 subp = self.process
78 if subp is None or (subp.poll() is not None):
78 if subp is None or (subp.poll() is not None):
79 return # Process doesn't exist, or is already dead.
79 return # Process doesn't exist, or is already dead.
80
80
81 try:
81 try:
82 print('Cleaning up stale PID: %d' % subp.pid)
82 print('Cleaning up stale PID: %d' % subp.pid)
83 subp.kill()
83 subp.kill()
84 except: # (OSError, WindowsError) ?
84 except: # (OSError, WindowsError) ?
85 # This is just a best effort, if we fail or the process was
85 # This is just a best effort, if we fail or the process was
86 # really gone, ignore it.
86 # really gone, ignore it.
87 pass
87 pass
88 else:
88 else:
89 for i in range(10):
89 for i in range(10):
90 if subp.poll() is None:
90 if subp.poll() is None:
91 time.sleep(0.1)
91 time.sleep(0.1)
92 else:
92 else:
93 break
93 break
94
94
95 if subp.poll() is None:
95 if subp.poll() is None:
96 # The process did not die...
96 # The process did not die...
97 print('... failed. Manual cleanup may be required.')
97 print('... failed. Manual cleanup may be required.')
98
98
99 def cleanup(self):
99 def cleanup(self):
100 "Kill process if it's still alive, and clean up temporary directories"
100 "Kill process if it's still alive, and clean up temporary directories"
101 self.cleanup_process()
101 self.cleanup_process()
102 for td in self.dirs:
102 for td in self.dirs:
103 td.cleanup()
103 td.cleanup()
104
104
105 __del__ = cleanup
105 __del__ = cleanup
106
106
107 class PyTestController(TestController):
107 class PyTestController(TestController):
108 """Run Python tests using IPython.testing.iptest"""
108 """Run Python tests using IPython.testing.iptest"""
109 #: str, Python command to execute in subprocess
109 #: str, Python command to execute in subprocess
110 pycmd = None
110 pycmd = None
111
111
112 def __init__(self, section):
112 def __init__(self, section):
113 """Create new test runner."""
113 """Create new test runner."""
114 TestController.__init__(self)
114 TestController.__init__(self)
115 self.section = section
115 self.section = section
116 # pycmd is put into cmd[2] in PyTestController.launch()
116 # pycmd is put into cmd[2] in PyTestController.launch()
117 self.cmd = [sys.executable, '-c', None, section]
117 self.cmd = [sys.executable, '-c', None, section]
118 self.pycmd = "from IPython.testing.iptest import run_iptest; run_iptest()"
118 self.pycmd = "from IPython.testing.iptest import run_iptest; run_iptest()"
119 ipydir = TemporaryDirectory()
119 ipydir = TemporaryDirectory()
120 self.dirs.append(ipydir)
120 self.dirs.append(ipydir)
121 self.env['IPYTHONDIR'] = ipydir.name
121 self.env['IPYTHONDIR'] = ipydir.name
122 self.workingdir = workingdir = TemporaryDirectory()
122 self.workingdir = workingdir = TemporaryDirectory()
123 self.dirs.append(workingdir)
123 self.dirs.append(workingdir)
124 self.env['IPTEST_WORKING_DIR'] = workingdir.name
124 self.env['IPTEST_WORKING_DIR'] = workingdir.name
125 # This means we won't get odd effects from our own matplotlib config
125 # This means we won't get odd effects from our own matplotlib config
126 self.env['MPLCONFIGDIR'] = workingdir.name
126 self.env['MPLCONFIGDIR'] = workingdir.name
127
127
128 @property
128 @property
129 def will_run(self):
129 def will_run(self):
130 try:
130 try:
131 return test_sections[self.section].will_run
131 return test_sections[self.section].will_run
132 except KeyError:
132 except KeyError:
133 return True
133 return True
134
134
135 def add_xunit(self):
135 def add_xunit(self):
136 xunit_file = os.path.abspath(self.section + '.xunit.xml')
136 xunit_file = os.path.abspath(self.section + '.xunit.xml')
137 self.cmd.extend(['--with-xunit', '--xunit-file', xunit_file])
137 self.cmd.extend(['--with-xunit', '--xunit-file', xunit_file])
138
138
139 def add_coverage(self):
139 def add_coverage(self):
140 try:
140 try:
141 sources = test_sections[self.section].includes
141 sources = test_sections[self.section].includes
142 except KeyError:
142 except KeyError:
143 sources = ['IPython']
143 sources = ['IPython']
144
144
145 coverage_rc = ("[run]\n"
145 coverage_rc = ("[run]\n"
146 "data_file = {data_file}\n"
146 "data_file = {data_file}\n"
147 "source =\n"
147 "source =\n"
148 " {source}\n"
148 " {source}\n"
149 ).format(data_file=os.path.abspath('.coverage.'+self.section),
149 ).format(data_file=os.path.abspath('.coverage.'+self.section),
150 source="\n ".join(sources))
150 source="\n ".join(sources))
151 config_file = os.path.join(self.workingdir.name, '.coveragerc')
151 config_file = os.path.join(self.workingdir.name, '.coveragerc')
152 with open(config_file, 'w') as f:
152 with open(config_file, 'w') as f:
153 f.write(coverage_rc)
153 f.write(coverage_rc)
154
154
155 self.env['COVERAGE_PROCESS_START'] = config_file
155 self.env['COVERAGE_PROCESS_START'] = config_file
156 self.pycmd = "import coverage; coverage.process_startup(); " + self.pycmd
156 self.pycmd = "import coverage; coverage.process_startup(); " + self.pycmd
157
157
158 def launch(self):
158 def launch(self):
159 self.cmd[2] = self.pycmd
159 self.cmd[2] = self.pycmd
160 super(PyTestController, self).launch()
160 super(PyTestController, self).launch()
161
161
162 class JSController(TestController):
162 class JSController(TestController):
163 """Run CasperJS tests """
163 """Run CasperJS tests """
164 def __init__(self, section):
164 def __init__(self, section):
165 """Create new test runner."""
165 """Create new test runner."""
166 TestController.__init__(self)
166 TestController.__init__(self)
167 self.section = section
167 self.section = section
168
168
169 self.ipydir = TemporaryDirectory()
169 self.ipydir = TemporaryDirectory()
170 self.dirs.append(self.ipydir)
170 self.dirs.append(self.ipydir)
171 self.env['IPYTHONDIR'] = self.ipydir.name
171 self.env['IPYTHONDIR'] = self.ipydir.name
172
172
173 def launch(self):
173 def launch(self):
174 # start the ipython notebook, so we get the port number
174 # start the ipython notebook, so we get the port number
175 self._init_server()
175 self._init_server()
176
176
177 import IPython.html.tests as t
177 import IPython.html.tests as t
178 test_dir = os.path.join(os.path.dirname(t.__file__), 'casperjs')
178 test_dir = os.path.join(os.path.dirname(t.__file__), 'casperjs')
179 includes = '--includes=' + os.path.join(test_dir,'util.js')
179 includes = '--includes=' + os.path.join(test_dir,'util.js')
180 test_cases = os.path.join(test_dir, 'test_cases')
180 test_cases = os.path.join(test_dir, 'test_cases')
181 port = '--port=' + str(self.server_port)
181 port = '--port=' + str(self.server_port)
182 self.cmd = ['casperjs', 'test', port, includes, test_cases]
182 self.cmd = ['casperjs', 'test', port, includes, test_cases]
183
183
184 super(JSController, self).launch()
184 super(JSController, self).launch()
185
185
186 @property
186 @property
187 def will_run(self):
187 def will_run(self):
188 return all(have[a] for a in ['zmq', 'tornado', 'jinja2', 'casperjs'])
188 return all(have[a] for a in ['zmq', 'tornado', 'jinja2', 'casperjs'])
189
189
190 def _init_server(self):
190 def _init_server(self):
191 "Start the notebook server in a separate process"
191 "Start the notebook server in a separate process"
192 self.queue = q = Queue()
192 self.queue = q = Queue()
193 self.server = Process(target=run_webapp, args=(q, self.ipydir.name))
193 self.server = Process(target=run_webapp, args=(q, self.ipydir.name))
194 self.server.start()
194 self.server.start()
195 self.server_port = q.get()
195 self.server_port = q.get()
196
196
197 def cleanup(self):
197 def cleanup(self):
198 self.server.terminate()
198 self.server.terminate()
199 self.server.join()
199 self.server.join()
200 TestController.cleanup(self)
200 TestController.cleanup(self)
201
201
202 js_test_group_names = {'js'}
202 js_test_group_names = {'js'}
203
203
204 def run_webapp(q, nbdir, loglevel=0):
204 def run_webapp(q, nbdir, loglevel=0):
205 """start the IPython Notebook, and pass port back to the queue"""
205 """start the IPython Notebook, and pass port back to the queue"""
206 import os
206 import os
207 import IPython.html.notebookapp as nbapp
207 import IPython.html.notebookapp as nbapp
208 import sys
208 import sys
209 #sys.stdout = open(str(os.getpid()) + ".out", "w")
209 sys.stderr = open(os.devnull, 'w')
210 #sys.stderr = open(str(os.getpid()) + ".err", "w")
211 print("hello")
212 # sys.stderr = open(os.devnull, 'w')
213 os.environ["IPYTHONDIR"] = nbdir
210 os.environ["IPYTHONDIR"] = nbdir
214 server = nbapp.NotebookApp()
211 server = nbapp.NotebookApp()
215 args = ['--no-browser']
212 args = ['--no-browser']
216 args.append('--notebook-dir='+nbdir)
213 args.append('--notebook-dir='+nbdir)
217 args.append('--profile-dir='+nbdir)
214 args.append('--profile-dir='+nbdir)
218 #args.append('--log-level='+str(loglevel))
215 args.append('--log-level='+str(loglevel))
219 server.initialize(args)
216 server.initialize(args)
220 # communicate the port number to the parent process
217 # communicate the port number to the parent process
221 q.put(server.port)
218 q.put(server.port)
222 server.start()
219 server.start()
223
220
224 def prepare_controllers(options):
221 def prepare_controllers(options):
225 """Returns two lists of TestController instances, those to run, and those
222 """Returns two lists of TestController instances, those to run, and those
226 not to run."""
223 not to run."""
227 testgroups = options.testgroups
224 testgroups = options.testgroups
228
225
229 if testgroups:
226 if testgroups:
230 py_testgroups = [g for g in testgroups if (g in py_test_group_names) \
227 py_testgroups = [g for g in testgroups if (g in py_test_group_names) \
231 or g.startswith('IPython')]
228 or g.startswith('IPython')]
232 js_testgroups = [g for g in testgroups if g in js_test_group_names]
229 js_testgroups = [g for g in testgroups if g in js_test_group_names]
233 else:
230 else:
234 py_testgroups = py_test_group_names
231 py_testgroups = py_test_group_names
235 js_testgroups = js_test_group_names
232 js_testgroups = js_test_group_names
236 if not options.all:
233 if not options.all:
237 test_sections['parallel'].enabled = False
234 test_sections['parallel'].enabled = False
238
235
239 c_js = [JSController(name) for name in js_testgroups]
236 c_js = [JSController(name) for name in js_testgroups]
240 c_py = [PyTestController(name) for name in py_testgroups]
237 c_py = [PyTestController(name) for name in py_testgroups]
241
238
242 configure_py_controllers(c_py, xunit=options.xunit,
239 configure_py_controllers(c_py, xunit=options.xunit,
243 coverage=options.coverage, subproc_streams=options.subproc_streams,
240 coverage=options.coverage, subproc_streams=options.subproc_streams,
244 extra_args=options.extra_args)
241 extra_args=options.extra_args)
245
242
246 controllers = c_py + c_js
243 controllers = c_py + c_js
247 to_run = [c for c in controllers if c.will_run]
244 to_run = [c for c in controllers if c.will_run]
248 not_run = [c for c in controllers if not c.will_run]
245 not_run = [c for c in controllers if not c.will_run]
249 return to_run, not_run
246 return to_run, not_run
250
247
251 def configure_py_controllers(controllers, xunit=False, coverage=False,
248 def configure_py_controllers(controllers, xunit=False, coverage=False,
252 subproc_streams='capture', extra_args=()):
249 subproc_streams='capture', extra_args=()):
253 """Apply options for a collection of TestController objects."""
250 """Apply options for a collection of TestController objects."""
254 for controller in controllers:
251 for controller in controllers:
255 if xunit:
252 if xunit:
256 controller.add_xunit()
253 controller.add_xunit()
257 if coverage:
254 if coverage:
258 controller.add_coverage()
255 controller.add_coverage()
259 controller.env['IPTEST_SUBPROC_STREAMS'] = subproc_streams
256 controller.env['IPTEST_SUBPROC_STREAMS'] = subproc_streams
260 controller.cmd.extend(extra_args)
257 controller.cmd.extend(extra_args)
261
258
262 def do_run(controller):
259 def do_run(controller):
263 try:
260 try:
264 try:
261 try:
265 controller.launch()
262 controller.launch()
266 except Exception:
263 except Exception:
267 import traceback
264 import traceback
268 traceback.print_exc()
265 traceback.print_exc()
269 return controller, 1 # signal failure
266 return controller, 1 # signal failure
270
267
271 exitcode = controller.wait()
268 exitcode = controller.wait()
272 return controller, exitcode
269 return controller, exitcode
273
270
274 except KeyboardInterrupt:
271 except KeyboardInterrupt:
275 return controller, -signal.SIGINT
272 return controller, -signal.SIGINT
276 finally:
273 finally:
277 controller.cleanup()
274 controller.cleanup()
278
275
279 def report():
276 def report():
280 """Return a string with a summary report of test-related variables."""
277 """Return a string with a summary report of test-related variables."""
281 inf = get_sys_info()
278 inf = get_sys_info()
282 out = []
279 out = []
283 def _add(name, value):
280 def _add(name, value):
284 out.append((name, value))
281 out.append((name, value))
285
282
286 _add('IPython version', inf['ipython_version'])
283 _add('IPython version', inf['ipython_version'])
287 _add('IPython commit', "{} ({})".format(inf['commit_hash'], inf['commit_source']))
284 _add('IPython commit', "{} ({})".format(inf['commit_hash'], inf['commit_source']))
288 _add('IPython package', compress_user(inf['ipython_path']))
285 _add('IPython package', compress_user(inf['ipython_path']))
289 _add('Python version', inf['sys_version'].replace('\n',''))
286 _add('Python version', inf['sys_version'].replace('\n',''))
290 _add('sys.executable', compress_user(inf['sys_executable']))
287 _add('sys.executable', compress_user(inf['sys_executable']))
291 _add('Platform', inf['platform'])
288 _add('Platform', inf['platform'])
292
289
293 width = max(len(n) for (n,v) in out)
290 width = max(len(n) for (n,v) in out)
294 out = ["{:<{width}}: {}\n".format(n, v, width=width) for (n,v) in out]
291 out = ["{:<{width}}: {}\n".format(n, v, width=width) for (n,v) in out]
295
292
296 avail = []
293 avail = []
297 not_avail = []
294 not_avail = []
298
295
299 for k, is_avail in have.items():
296 for k, is_avail in have.items():
300 if is_avail:
297 if is_avail:
301 avail.append(k)
298 avail.append(k)
302 else:
299 else:
303 not_avail.append(k)
300 not_avail.append(k)
304
301
305 if avail:
302 if avail:
306 out.append('\nTools and libraries available at test time:\n')
303 out.append('\nTools and libraries available at test time:\n')
307 avail.sort()
304 avail.sort()
308 out.append(' ' + ' '.join(avail)+'\n')
305 out.append(' ' + ' '.join(avail)+'\n')
309
306
310 if not_avail:
307 if not_avail:
311 out.append('\nTools and libraries NOT available at test time:\n')
308 out.append('\nTools and libraries NOT available at test time:\n')
312 not_avail.sort()
309 not_avail.sort()
313 out.append(' ' + ' '.join(not_avail)+'\n')
310 out.append(' ' + ' '.join(not_avail)+'\n')
314
311
315 return ''.join(out)
312 return ''.join(out)
316
313
317 def run_iptestall(options):
314 def run_iptestall(options):
318 """Run the entire IPython test suite by calling nose and trial.
315 """Run the entire IPython test suite by calling nose and trial.
319
316
320 This function constructs :class:`IPTester` instances for all IPython
317 This function constructs :class:`IPTester` instances for all IPython
321 modules and package and then runs each of them. This causes the modules
318 modules and package and then runs each of them. This causes the modules
322 and packages of IPython to be tested each in their own subprocess using
319 and packages of IPython to be tested each in their own subprocess using
323 nose.
320 nose.
324
321
325 Parameters
322 Parameters
326 ----------
323 ----------
327
324
328 All parameters are passed as attributes of the options object.
325 All parameters are passed as attributes of the options object.
329
326
330 testgroups : list of str
327 testgroups : list of str
331 Run only these sections of the test suite. If empty, run all the available
328 Run only these sections of the test suite. If empty, run all the available
332 sections.
329 sections.
333
330
334 fast : int or None
331 fast : int or None
335 Run the test suite in parallel, using n simultaneous processes. If None
332 Run the test suite in parallel, using n simultaneous processes. If None
336 is passed, one process is used per CPU core. Default 1 (i.e. sequential)
333 is passed, one process is used per CPU core. Default 1 (i.e. sequential)
337
334
338 inc_slow : bool
335 inc_slow : bool
339 Include slow tests, like IPython.parallel. By default, these tests aren't
336 Include slow tests, like IPython.parallel. By default, these tests aren't
340 run.
337 run.
341
338
342 xunit : bool
339 xunit : bool
343 Produce Xunit XML output. This is written to multiple foo.xunit.xml files.
340 Produce Xunit XML output. This is written to multiple foo.xunit.xml files.
344
341
345 coverage : bool or str
342 coverage : bool or str
346 Measure code coverage from tests. True will store the raw coverage data,
343 Measure code coverage from tests. True will store the raw coverage data,
347 or pass 'html' or 'xml' to get reports.
344 or pass 'html' or 'xml' to get reports.
348
345
349 extra_args : list
346 extra_args : list
350 Extra arguments to pass to the test subprocesses, e.g. '-v'
347 Extra arguments to pass to the test subprocesses, e.g. '-v'
351 """
348 """
352 if options.fast != 1:
349 if options.fast != 1:
353 # If running in parallel, capture output so it doesn't get interleaved
350 # If running in parallel, capture output so it doesn't get interleaved
354 TestController.buffer_output = True
351 TestController.buffer_output = True
355
352
356 to_run, not_run = prepare_controllers(options)
353 to_run, not_run = prepare_controllers(options)
357
354
358 def justify(ltext, rtext, width=70, fill='-'):
355 def justify(ltext, rtext, width=70, fill='-'):
359 ltext += ' '
356 ltext += ' '
360 rtext = (' ' + rtext).rjust(width - len(ltext), fill)
357 rtext = (' ' + rtext).rjust(width - len(ltext), fill)
361 return ltext + rtext
358 return ltext + rtext
362
359
363 # Run all test runners, tracking execution time
360 # Run all test runners, tracking execution time
364 failed = []
361 failed = []
365 t_start = time.time()
362 t_start = time.time()
366
363
367 print()
364 print()
368 if options.fast == 1:
365 if options.fast == 1:
369 # This actually means sequential, i.e. with 1 job
366 # This actually means sequential, i.e. with 1 job
370 for controller in to_run:
367 for controller in to_run:
371 print('IPython test group:', controller.section)
368 print('IPython test group:', controller.section)
372 sys.stdout.flush() # Show in correct order when output is piped
369 sys.stdout.flush() # Show in correct order when output is piped
373 controller, res = do_run(controller)
370 controller, res = do_run(controller)
374 if res:
371 if res:
375 failed.append(controller)
372 failed.append(controller)
376 if res == -signal.SIGINT:
373 if res == -signal.SIGINT:
377 print("Interrupted")
374 print("Interrupted")
378 break
375 break
379 print()
376 print()
380
377
381 else:
378 else:
382 # Run tests concurrently
379 # Run tests concurrently
383 try:
380 try:
384 pool = multiprocessing.pool.ThreadPool(options.fast)
381 pool = multiprocessing.pool.ThreadPool(options.fast)
385 for (controller, res) in pool.imap_unordered(do_run, to_run):
382 for (controller, res) in pool.imap_unordered(do_run, to_run):
386 res_string = 'OK' if res == 0 else 'FAILED'
383 res_string = 'OK' if res == 0 else 'FAILED'
387 print(justify('IPython test group: ' + controller.section, res_string))
384 print(justify('IPython test group: ' + controller.section, res_string))
388 if res:
385 if res:
389 print(bytes_to_str(controller.stdout))
386 print(bytes_to_str(controller.stdout))
390 failed.append(controller)
387 failed.append(controller)
391 if res == -signal.SIGINT:
388 if res == -signal.SIGINT:
392 print("Interrupted")
389 print("Interrupted")
393 break
390 break
394 except KeyboardInterrupt:
391 except KeyboardInterrupt:
395 return
392 return
396
393
397 for controller in not_run:
394 for controller in not_run:
398 print(justify('IPython test group: ' + controller.section, 'NOT RUN'))
395 print(justify('IPython test group: ' + controller.section, 'NOT RUN'))
399
396
400 t_end = time.time()
397 t_end = time.time()
401 t_tests = t_end - t_start
398 t_tests = t_end - t_start
402 nrunners = len(to_run)
399 nrunners = len(to_run)
403 nfail = len(failed)
400 nfail = len(failed)
404 # summarize results
401 # summarize results
405 print('_'*70)
402 print('_'*70)
406 print('Test suite completed for system with the following information:')
403 print('Test suite completed for system with the following information:')
407 print(report())
404 print(report())
408 took = "Took %.3fs." % t_tests
405 took = "Took %.3fs." % t_tests
409 print('Status: ', end='')
406 print('Status: ', end='')
410 if not failed:
407 if not failed:
411 print('OK (%d test groups).' % nrunners, took)
408 print('OK (%d test groups).' % nrunners, took)
412 else:
409 else:
413 # If anything went wrong, point out what command to rerun manually to
410 # If anything went wrong, point out what command to rerun manually to
414 # see the actual errors and individual summary
411 # see the actual errors and individual summary
415 failed_sections = [c.section for c in failed]
412 failed_sections = [c.section for c in failed]
416 print('ERROR - {} out of {} test groups failed ({}).'.format(nfail,
413 print('ERROR - {} out of {} test groups failed ({}).'.format(nfail,
417 nrunners, ', '.join(failed_sections)), took)
414 nrunners, ', '.join(failed_sections)), took)
418 print()
415 print()
419 print('You may wish to rerun these, with:')
416 print('You may wish to rerun these, with:')
420 print(' iptest', *failed_sections)
417 print(' iptest', *failed_sections)
421 print()
418 print()
422
419
423 if options.coverage:
420 if options.coverage:
424 from coverage import coverage
421 from coverage import coverage
425 cov = coverage(data_file='.coverage')
422 cov = coverage(data_file='.coverage')
426 cov.combine()
423 cov.combine()
427 cov.save()
424 cov.save()
428
425
429 # Coverage HTML report
426 # Coverage HTML report
430 if options.coverage == 'html':
427 if options.coverage == 'html':
431 html_dir = 'ipy_htmlcov'
428 html_dir = 'ipy_htmlcov'
432 shutil.rmtree(html_dir, ignore_errors=True)
429 shutil.rmtree(html_dir, ignore_errors=True)
433 print("Writing HTML coverage report to %s/ ... " % html_dir, end="")
430 print("Writing HTML coverage report to %s/ ... " % html_dir, end="")
434 sys.stdout.flush()
431 sys.stdout.flush()
435
432
436 # Custom HTML reporter to clean up module names.
433 # Custom HTML reporter to clean up module names.
437 from coverage.html import HtmlReporter
434 from coverage.html import HtmlReporter
438 class CustomHtmlReporter(HtmlReporter):
435 class CustomHtmlReporter(HtmlReporter):
439 def find_code_units(self, morfs):
436 def find_code_units(self, morfs):
440 super(CustomHtmlReporter, self).find_code_units(morfs)
437 super(CustomHtmlReporter, self).find_code_units(morfs)
441 for cu in self.code_units:
438 for cu in self.code_units:
442 nameparts = cu.name.split(os.sep)
439 nameparts = cu.name.split(os.sep)
443 if 'IPython' not in nameparts:
440 if 'IPython' not in nameparts:
444 continue
441 continue
445 ix = nameparts.index('IPython')
442 ix = nameparts.index('IPython')
446 cu.name = '.'.join(nameparts[ix:])
443 cu.name = '.'.join(nameparts[ix:])
447
444
448 # Reimplement the html_report method with our custom reporter
445 # Reimplement the html_report method with our custom reporter
449 cov._harvest_data()
446 cov._harvest_data()
450 cov.config.from_args(omit='*%stests' % os.sep, html_dir=html_dir,
447 cov.config.from_args(omit='*%stests' % os.sep, html_dir=html_dir,
451 html_title='IPython test coverage',
448 html_title='IPython test coverage',
452 )
449 )
453 reporter = CustomHtmlReporter(cov, cov.config)
450 reporter = CustomHtmlReporter(cov, cov.config)
454 reporter.report(None)
451 reporter.report(None)
455 print('done.')
452 print('done.')
456
453
457 # Coverage XML report
454 # Coverage XML report
458 elif options.coverage == 'xml':
455 elif options.coverage == 'xml':
459 cov.xml_report(outfile='ipy_coverage.xml')
456 cov.xml_report(outfile='ipy_coverage.xml')
460
457
461 if failed:
458 if failed:
462 # Ensure that our exit code indicates failure
459 # Ensure that our exit code indicates failure
463 sys.exit(1)
460 sys.exit(1)
464
461
465 argparser = argparse.ArgumentParser(description='Run IPython test suite')
462 argparser = argparse.ArgumentParser(description='Run IPython test suite')
466 argparser.add_argument('testgroups', nargs='*',
463 argparser.add_argument('testgroups', nargs='*',
467 help='Run specified groups of tests. If omitted, run '
464 help='Run specified groups of tests. If omitted, run '
468 'all tests.')
465 'all tests.')
469 argparser.add_argument('--all', action='store_true',
466 argparser.add_argument('--all', action='store_true',
470 help='Include slow tests not run by default.')
467 help='Include slow tests not run by default.')
471 argparser.add_argument('-j', '--fast', nargs='?', const=None, default=1, type=int,
468 argparser.add_argument('-j', '--fast', nargs='?', const=None, default=1, type=int,
472 help='Run test sections in parallel.')
469 help='Run test sections in parallel.')
473 argparser.add_argument('--xunit', action='store_true',
470 argparser.add_argument('--xunit', action='store_true',
474 help='Produce Xunit XML results')
471 help='Produce Xunit XML results')
475 argparser.add_argument('--coverage', nargs='?', const=True, default=False,
472 argparser.add_argument('--coverage', nargs='?', const=True, default=False,
476 help="Measure test coverage. Specify 'html' or "
473 help="Measure test coverage. Specify 'html' or "
477 "'xml' to get reports.")
474 "'xml' to get reports.")
478 argparser.add_argument('--subproc-streams', default='capture',
475 argparser.add_argument('--subproc-streams', default='capture',
479 help="What to do with stdout/stderr from subprocesses. "
476 help="What to do with stdout/stderr from subprocesses. "
480 "'capture' (default), 'show' and 'discard' are the options.")
477 "'capture' (default), 'show' and 'discard' are the options.")
481
478
482 def default_options():
479 def default_options():
483 """Get an argparse Namespace object with the default arguments, to pass to
480 """Get an argparse Namespace object with the default arguments, to pass to
484 :func:`run_iptestall`.
481 :func:`run_iptestall`.
485 """
482 """
486 options = argparser.parse_args([])
483 options = argparser.parse_args([])
487 options.extra_args = []
484 options.extra_args = []
488 return options
485 return options
489
486
490 def main():
487 def main():
491 # Arguments after -- should be passed through to nose. Argparse treats
488 # Arguments after -- should be passed through to nose. Argparse treats
492 # everything after -- as regular positional arguments, so we separate them
489 # everything after -- as regular positional arguments, so we separate them
493 # first.
490 # first.
494 try:
491 try:
495 ix = sys.argv.index('--')
492 ix = sys.argv.index('--')
496 except ValueError:
493 except ValueError:
497 to_parse = sys.argv[1:]
494 to_parse = sys.argv[1:]
498 extra_args = []
495 extra_args = []
499 else:
496 else:
500 to_parse = sys.argv[1:ix]
497 to_parse = sys.argv[1:ix]
501 extra_args = sys.argv[ix+1:]
498 extra_args = sys.argv[ix+1:]
502
499
503 options = argparser.parse_args(to_parse)
500 options = argparser.parse_args(to_parse)
504 options.extra_args = extra_args
501 options.extra_args = extra_args
505
502
506 run_iptestall(options)
503 run_iptestall(options)
507
504
508
505
509 if __name__ == '__main__':
506 if __name__ == '__main__':
510 main()
507 main()
General Comments 0
You need to be logged in to leave comments. Login now