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