##// END OF EJS Templates
More concise test summary info
Thomas Kluyver -
Show More
@@ -1,423 +1,422 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 import os
23 import os
24 import shutil
24 import shutil
25 import signal
25 import signal
26 import sys
26 import sys
27 import subprocess
27 import subprocess
28 import time
28 import time
29
29
30 from .iptest import have, test_group_names, test_sections
30 from .iptest import have, test_group_names, test_sections
31 from IPython.utils.py3compat import bytes_to_str
31 from IPython.utils.py3compat import bytes_to_str
32 from IPython.utils.sysinfo import sys_info
32 from IPython.utils.sysinfo import brief_sys_info
33 from IPython.utils.tempdir import TemporaryDirectory
33 from IPython.utils.tempdir import TemporaryDirectory
34
34
35
35
36 class TestController(object):
36 class TestController(object):
37 """Run tests in a subprocess
37 """Run tests in a subprocess
38 """
38 """
39 #: str, IPython test suite to be executed.
39 #: str, IPython test suite to be executed.
40 section = None
40 section = None
41 #: list, command line arguments to be executed
41 #: list, command line arguments to be executed
42 cmd = None
42 cmd = None
43 #: dict, extra environment variables to set for the subprocess
43 #: dict, extra environment variables to set for the subprocess
44 env = None
44 env = None
45 #: list, TemporaryDirectory instances to clear up when the process finishes
45 #: list, TemporaryDirectory instances to clear up when the process finishes
46 dirs = None
46 dirs = None
47 #: subprocess.Popen instance
47 #: subprocess.Popen instance
48 process = None
48 process = None
49 #: str, process stdout+stderr
49 #: str, process stdout+stderr
50 stdout = None
50 stdout = None
51 #: bool, whether to capture process stdout & stderr
51 #: bool, whether to capture process stdout & stderr
52 buffer_output = False
52 buffer_output = False
53
53
54 def __init__(self):
54 def __init__(self):
55 self.cmd = []
55 self.cmd = []
56 self.env = {}
56 self.env = {}
57 self.dirs = []
57 self.dirs = []
58
58
59 @property
59 @property
60 def will_run(self):
60 def will_run(self):
61 """Override in subclasses to check for dependencies."""
61 """Override in subclasses to check for dependencies."""
62 return False
62 return False
63
63
64 def launch(self):
64 def launch(self):
65 # print('*** ENV:', self.env) # dbg
65 # print('*** ENV:', self.env) # dbg
66 # print('*** CMD:', self.cmd) # dbg
66 # print('*** CMD:', self.cmd) # dbg
67 env = os.environ.copy()
67 env = os.environ.copy()
68 env.update(self.env)
68 env.update(self.env)
69 output = subprocess.PIPE if self.buffer_output else None
69 output = subprocess.PIPE if self.buffer_output else None
70 stdout = subprocess.STDOUT if self.buffer_output else None
70 stdout = subprocess.STDOUT if self.buffer_output else None
71 self.process = subprocess.Popen(self.cmd, stdout=output,
71 self.process = subprocess.Popen(self.cmd, stdout=output,
72 stderr=stdout, env=env)
72 stderr=stdout, env=env)
73
73
74 def wait(self):
74 def wait(self):
75 self.stdout, _ = self.process.communicate()
75 self.stdout, _ = self.process.communicate()
76 return self.process.returncode
76 return self.process.returncode
77
77
78 def cleanup_process(self):
78 def cleanup_process(self):
79 """Cleanup on exit by killing any leftover processes."""
79 """Cleanup on exit by killing any leftover processes."""
80 subp = self.process
80 subp = self.process
81 if subp is None or (subp.poll() is not None):
81 if subp is None or (subp.poll() is not None):
82 return # Process doesn't exist, or is already dead.
82 return # Process doesn't exist, or is already dead.
83
83
84 try:
84 try:
85 print('Cleaning up stale PID: %d' % subp.pid)
85 print('Cleaning up stale PID: %d' % subp.pid)
86 subp.kill()
86 subp.kill()
87 except: # (OSError, WindowsError) ?
87 except: # (OSError, WindowsError) ?
88 # This is just a best effort, if we fail or the process was
88 # This is just a best effort, if we fail or the process was
89 # really gone, ignore it.
89 # really gone, ignore it.
90 pass
90 pass
91 else:
91 else:
92 for i in range(10):
92 for i in range(10):
93 if subp.poll() is None:
93 if subp.poll() is None:
94 time.sleep(0.1)
94 time.sleep(0.1)
95 else:
95 else:
96 break
96 break
97
97
98 if subp.poll() is None:
98 if subp.poll() is None:
99 # The process did not die...
99 # The process did not die...
100 print('... failed. Manual cleanup may be required.')
100 print('... failed. Manual cleanup may be required.')
101
101
102 def cleanup(self):
102 def cleanup(self):
103 "Kill process if it's still alive, and clean up temporary directories"
103 "Kill process if it's still alive, and clean up temporary directories"
104 self.cleanup_process()
104 self.cleanup_process()
105 for td in self.dirs:
105 for td in self.dirs:
106 td.cleanup()
106 td.cleanup()
107
107
108 __del__ = cleanup
108 __del__ = cleanup
109
109
110 class PyTestController(TestController):
110 class PyTestController(TestController):
111 """Run Python tests using IPython.testing.iptest"""
111 """Run Python tests using IPython.testing.iptest"""
112 #: str, Python command to execute in subprocess
112 #: str, Python command to execute in subprocess
113 pycmd = None
113 pycmd = None
114
114
115 def __init__(self, section):
115 def __init__(self, section):
116 """Create new test runner."""
116 """Create new test runner."""
117 TestController.__init__(self)
117 TestController.__init__(self)
118 self.section = section
118 self.section = section
119 # pycmd is put into cmd[2] in PyTestController.launch()
119 # pycmd is put into cmd[2] in PyTestController.launch()
120 self.cmd = [sys.executable, '-c', None, section]
120 self.cmd = [sys.executable, '-c', None, section]
121 self.pycmd = "from IPython.testing.iptest import run_iptest; run_iptest()"
121 self.pycmd = "from IPython.testing.iptest import run_iptest; run_iptest()"
122 ipydir = TemporaryDirectory()
122 ipydir = TemporaryDirectory()
123 self.dirs.append(ipydir)
123 self.dirs.append(ipydir)
124 self.env['IPYTHONDIR'] = ipydir.name
124 self.env['IPYTHONDIR'] = ipydir.name
125 self.workingdir = workingdir = TemporaryDirectory()
125 self.workingdir = workingdir = TemporaryDirectory()
126 self.dirs.append(workingdir)
126 self.dirs.append(workingdir)
127 self.env['IPTEST_WORKING_DIR'] = workingdir.name
127 self.env['IPTEST_WORKING_DIR'] = workingdir.name
128 # This means we won't get odd effects from our own matplotlib config
128 # This means we won't get odd effects from our own matplotlib config
129 self.env['MPLCONFIGDIR'] = workingdir.name
129 self.env['MPLCONFIGDIR'] = workingdir.name
130
130
131 @property
131 @property
132 def will_run(self):
132 def will_run(self):
133 try:
133 try:
134 return test_sections[self.section].will_run
134 return test_sections[self.section].will_run
135 except KeyError:
135 except KeyError:
136 return True
136 return True
137
137
138 def add_xunit(self):
138 def add_xunit(self):
139 xunit_file = os.path.abspath(self.section + '.xunit.xml')
139 xunit_file = os.path.abspath(self.section + '.xunit.xml')
140 self.cmd.extend(['--with-xunit', '--xunit-file', xunit_file])
140 self.cmd.extend(['--with-xunit', '--xunit-file', xunit_file])
141
141
142 def add_coverage(self):
142 def add_coverage(self):
143 try:
143 try:
144 sources = test_sections[self.section].includes
144 sources = test_sections[self.section].includes
145 except KeyError:
145 except KeyError:
146 sources = ['IPython']
146 sources = ['IPython']
147
147
148 coverage_rc = ("[run]\n"
148 coverage_rc = ("[run]\n"
149 "data_file = {data_file}\n"
149 "data_file = {data_file}\n"
150 "source =\n"
150 "source =\n"
151 " {source}\n"
151 " {source}\n"
152 ).format(data_file=os.path.abspath('.coverage.'+self.section),
152 ).format(data_file=os.path.abspath('.coverage.'+self.section),
153 source="\n ".join(sources))
153 source="\n ".join(sources))
154 config_file = os.path.join(self.workingdir.name, '.coveragerc')
154 config_file = os.path.join(self.workingdir.name, '.coveragerc')
155 with open(config_file, 'w') as f:
155 with open(config_file, 'w') as f:
156 f.write(coverage_rc)
156 f.write(coverage_rc)
157
157
158 self.env['COVERAGE_PROCESS_START'] = config_file
158 self.env['COVERAGE_PROCESS_START'] = config_file
159 self.pycmd = "import coverage; coverage.process_startup(); " + self.pycmd
159 self.pycmd = "import coverage; coverage.process_startup(); " + self.pycmd
160
160
161 def launch(self):
161 def launch(self):
162 self.cmd[2] = self.pycmd
162 self.cmd[2] = self.pycmd
163 super(PyTestController, self).launch()
163 super(PyTestController, self).launch()
164
164
165
165
166 def prepare_py_test_controllers(inc_slow=False):
166 def prepare_py_test_controllers(inc_slow=False):
167 """Returns an ordered list of PyTestController instances to be run."""
167 """Returns an ordered list of PyTestController instances to be run."""
168 to_run, not_run = [], []
168 to_run, not_run = [], []
169 if not inc_slow:
169 if not inc_slow:
170 test_sections['parallel'].enabled = False
170 test_sections['parallel'].enabled = False
171
171
172 for name in test_group_names:
172 for name in test_group_names:
173 controller = PyTestController(name)
173 controller = PyTestController(name)
174 if controller.will_run:
174 if controller.will_run:
175 to_run.append(controller)
175 to_run.append(controller)
176 else:
176 else:
177 not_run.append(controller)
177 not_run.append(controller)
178 return to_run, not_run
178 return to_run, not_run
179
179
180 def configure_controllers(controllers, xunit=False, coverage=False, extra_args=()):
180 def configure_controllers(controllers, xunit=False, coverage=False, extra_args=()):
181 """Apply options for a collection of TestController objects."""
181 """Apply options for a collection of TestController objects."""
182 for controller in controllers:
182 for controller in controllers:
183 if xunit:
183 if xunit:
184 controller.add_xunit()
184 controller.add_xunit()
185 if coverage:
185 if coverage:
186 controller.add_coverage()
186 controller.add_coverage()
187 controller.cmd.extend(extra_args)
187 controller.cmd.extend(extra_args)
188
188
189 def do_run(controller):
189 def do_run(controller):
190 try:
190 try:
191 try:
191 try:
192 controller.launch()
192 controller.launch()
193 except Exception:
193 except Exception:
194 import traceback
194 import traceback
195 traceback.print_exc()
195 traceback.print_exc()
196 return controller, 1 # signal failure
196 return controller, 1 # signal failure
197
197
198 exitcode = controller.wait()
198 exitcode = controller.wait()
199 return controller, exitcode
199 return controller, exitcode
200
200
201 except KeyboardInterrupt:
201 except KeyboardInterrupt:
202 return controller, -signal.SIGINT
202 return controller, -signal.SIGINT
203 finally:
203 finally:
204 controller.cleanup()
204 controller.cleanup()
205
205
206 def report():
206 def report():
207 """Return a string with a summary report of test-related variables."""
207 """Return a string with a summary report of test-related variables."""
208
208
209 out = [ sys_info(), '\n']
209 out = [ brief_sys_info(), '\n']
210
210
211 avail = []
211 avail = []
212 not_avail = []
212 not_avail = []
213
213
214 for k, is_avail in have.items():
214 for k, is_avail in have.items():
215 if is_avail:
215 if is_avail:
216 avail.append(k)
216 avail.append(k)
217 else:
217 else:
218 not_avail.append(k)
218 not_avail.append(k)
219
219
220 if avail:
220 if avail:
221 out.append('\nTools and libraries available at test time:\n')
221 out.append('\nTools and libraries available at test time:\n')
222 avail.sort()
222 avail.sort()
223 out.append(' ' + ' '.join(avail)+'\n')
223 out.append(' ' + ' '.join(avail)+'\n')
224
224
225 if not_avail:
225 if not_avail:
226 out.append('\nTools and libraries NOT available at test time:\n')
226 out.append('\nTools and libraries NOT available at test time:\n')
227 not_avail.sort()
227 not_avail.sort()
228 out.append(' ' + ' '.join(not_avail)+'\n')
228 out.append(' ' + ' '.join(not_avail)+'\n')
229
229
230 return ''.join(out)
230 return ''.join(out)
231
231
232 def run_iptestall(options):
232 def run_iptestall(options):
233 """Run the entire IPython test suite by calling nose and trial.
233 """Run the entire IPython test suite by calling nose and trial.
234
234
235 This function constructs :class:`IPTester` instances for all IPython
235 This function constructs :class:`IPTester` instances for all IPython
236 modules and package and then runs each of them. This causes the modules
236 modules and package and then runs each of them. This causes the modules
237 and packages of IPython to be tested each in their own subprocess using
237 and packages of IPython to be tested each in their own subprocess using
238 nose.
238 nose.
239
239
240 Parameters
240 Parameters
241 ----------
241 ----------
242
242
243 All parameters are passed as attributes of the options object.
243 All parameters are passed as attributes of the options object.
244
244
245 testgroups : list of str
245 testgroups : list of str
246 Run only these sections of the test suite. If empty, run all the available
246 Run only these sections of the test suite. If empty, run all the available
247 sections.
247 sections.
248
248
249 fast : int or None
249 fast : int or None
250 Run the test suite in parallel, using n simultaneous processes. If None
250 Run the test suite in parallel, using n simultaneous processes. If None
251 is passed, one process is used per CPU core. Default 1 (i.e. sequential)
251 is passed, one process is used per CPU core. Default 1 (i.e. sequential)
252
252
253 inc_slow : bool
253 inc_slow : bool
254 Include slow tests, like IPython.parallel. By default, these tests aren't
254 Include slow tests, like IPython.parallel. By default, these tests aren't
255 run.
255 run.
256
256
257 xunit : bool
257 xunit : bool
258 Produce Xunit XML output. This is written to multiple foo.xunit.xml files.
258 Produce Xunit XML output. This is written to multiple foo.xunit.xml files.
259
259
260 coverage : bool or str
260 coverage : bool or str
261 Measure code coverage from tests. True will store the raw coverage data,
261 Measure code coverage from tests. True will store the raw coverage data,
262 or pass 'html' or 'xml' to get reports.
262 or pass 'html' or 'xml' to get reports.
263
263
264 extra_args : list
264 extra_args : list
265 Extra arguments to pass to the test subprocesses, e.g. '-v'
265 Extra arguments to pass to the test subprocesses, e.g. '-v'
266 """
266 """
267 if options.fast != 1:
267 if options.fast != 1:
268 # If running in parallel, capture output so it doesn't get interleaved
268 # If running in parallel, capture output so it doesn't get interleaved
269 TestController.buffer_output = True
269 TestController.buffer_output = True
270
270
271 if options.testgroups:
271 if options.testgroups:
272 to_run = [PyTestController(name) for name in options.testgroups]
272 to_run = [PyTestController(name) for name in options.testgroups]
273 not_run = []
273 not_run = []
274 else:
274 else:
275 to_run, not_run = prepare_py_test_controllers(inc_slow=options.all)
275 to_run, not_run = prepare_py_test_controllers(inc_slow=options.all)
276
276
277 configure_controllers(to_run, xunit=options.xunit, coverage=options.coverage,
277 configure_controllers(to_run, xunit=options.xunit, coverage=options.coverage,
278 extra_args=options.extra_args)
278 extra_args=options.extra_args)
279
279
280 def justify(ltext, rtext, width=70, fill='-'):
280 def justify(ltext, rtext, width=70, fill='-'):
281 ltext += ' '
281 ltext += ' '
282 rtext = (' ' + rtext).rjust(width - len(ltext), fill)
282 rtext = (' ' + rtext).rjust(width - len(ltext), fill)
283 return ltext + rtext
283 return ltext + rtext
284
284
285 # Run all test runners, tracking execution time
285 # Run all test runners, tracking execution time
286 failed = []
286 failed = []
287 t_start = time.time()
287 t_start = time.time()
288
288
289 print()
289 print()
290 if options.fast == 1:
290 if options.fast == 1:
291 # This actually means sequential, i.e. with 1 job
291 # This actually means sequential, i.e. with 1 job
292 for controller in to_run:
292 for controller in to_run:
293 print('IPython test group:', controller.section)
293 print('IPython test group:', controller.section)
294 sys.stdout.flush() # Show in correct order when output is piped
294 sys.stdout.flush() # Show in correct order when output is piped
295 controller, res = do_run(controller)
295 controller, res = do_run(controller)
296 if res:
296 if res:
297 failed.append(controller)
297 failed.append(controller)
298 if res == -signal.SIGINT:
298 if res == -signal.SIGINT:
299 print("Interrupted")
299 print("Interrupted")
300 break
300 break
301 print()
301 print()
302
302
303 else:
303 else:
304 # Run tests concurrently
304 # Run tests concurrently
305 try:
305 try:
306 pool = multiprocessing.pool.ThreadPool(options.fast)
306 pool = multiprocessing.pool.ThreadPool(options.fast)
307 for (controller, res) in pool.imap_unordered(do_run, to_run):
307 for (controller, res) in pool.imap_unordered(do_run, to_run):
308 res_string = 'OK' if res == 0 else 'FAILED'
308 res_string = 'OK' if res == 0 else 'FAILED'
309 print(justify('IPython test group: ' + controller.section, res_string))
309 print(justify('IPython test group: ' + controller.section, res_string))
310 if res:
310 if res:
311 print(bytes_to_str(controller.stdout))
311 print(bytes_to_str(controller.stdout))
312 failed.append(controller)
312 failed.append(controller)
313 if res == -signal.SIGINT:
313 if res == -signal.SIGINT:
314 print("Interrupted")
314 print("Interrupted")
315 break
315 break
316 except KeyboardInterrupt:
316 except KeyboardInterrupt:
317 return
317 return
318
318
319 for controller in not_run:
319 for controller in not_run:
320 print(justify('IPython test group: ' + controller.section, 'NOT RUN'))
320 print(justify('IPython test group: ' + controller.section, 'NOT RUN'))
321
321
322 t_end = time.time()
322 t_end = time.time()
323 t_tests = t_end - t_start
323 t_tests = t_end - t_start
324 nrunners = len(to_run)
324 nrunners = len(to_run)
325 nfail = len(failed)
325 nfail = len(failed)
326 # summarize results
326 # summarize results
327 print('_'*70)
327 print('_'*70)
328 print('Test suite completed for system with the following information:')
328 print('Test suite completed for system with the following information:')
329 print(report())
329 print(report())
330 print('Ran %s test groups in %.3fs' % (nrunners, t_tests))
330 took = "Took %.3fs." % t_tests
331 print()
332 print('Status: ', end='')
331 print('Status: ', end='')
333 if not failed:
332 if not failed:
334 print('OK')
333 print('OK.', took)
335 else:
334 else:
336 # If anything went wrong, point out what command to rerun manually to
335 # If anything went wrong, point out what command to rerun manually to
337 # see the actual errors and individual summary
336 # see the actual errors and individual summary
338 failed_sections = [c.section for c in failed]
337 failed_sections = [c.section for c in failed]
339 print('ERROR - {} out of {} test groups failed ({}).'.format(nfail,
338 print('ERROR - {} out of {} test groups failed ({}).'.format(nfail,
340 nrunners, ', '.join(failed_sections)))
339 nrunners, ', '.join(failed_sections)), took)
341 print()
340 print()
342 print('You may wish to rerun these, with:')
341 print('You may wish to rerun these, with:')
343 print(' iptest', *failed_sections)
342 print(' iptest', *failed_sections)
344 print()
343 print()
345
344
346 if options.coverage:
345 if options.coverage:
347 from coverage import coverage
346 from coverage import coverage
348 cov = coverage(data_file='.coverage')
347 cov = coverage(data_file='.coverage')
349 cov.combine()
348 cov.combine()
350 cov.save()
349 cov.save()
351
350
352 # Coverage HTML report
351 # Coverage HTML report
353 if options.coverage == 'html':
352 if options.coverage == 'html':
354 html_dir = 'ipy_htmlcov'
353 html_dir = 'ipy_htmlcov'
355 shutil.rmtree(html_dir, ignore_errors=True)
354 shutil.rmtree(html_dir, ignore_errors=True)
356 print("Writing HTML coverage report to %s/ ... " % html_dir, end="")
355 print("Writing HTML coverage report to %s/ ... " % html_dir, end="")
357 sys.stdout.flush()
356 sys.stdout.flush()
358
357
359 # Custom HTML reporter to clean up module names.
358 # Custom HTML reporter to clean up module names.
360 from coverage.html import HtmlReporter
359 from coverage.html import HtmlReporter
361 class CustomHtmlReporter(HtmlReporter):
360 class CustomHtmlReporter(HtmlReporter):
362 def find_code_units(self, morfs):
361 def find_code_units(self, morfs):
363 super(CustomHtmlReporter, self).find_code_units(morfs)
362 super(CustomHtmlReporter, self).find_code_units(morfs)
364 for cu in self.code_units:
363 for cu in self.code_units:
365 nameparts = cu.name.split(os.sep)
364 nameparts = cu.name.split(os.sep)
366 if 'IPython' not in nameparts:
365 if 'IPython' not in nameparts:
367 continue
366 continue
368 ix = nameparts.index('IPython')
367 ix = nameparts.index('IPython')
369 cu.name = '.'.join(nameparts[ix:])
368 cu.name = '.'.join(nameparts[ix:])
370
369
371 # Reimplement the html_report method with our custom reporter
370 # Reimplement the html_report method with our custom reporter
372 cov._harvest_data()
371 cov._harvest_data()
373 cov.config.from_args(omit='*%stests' % os.sep, html_dir=html_dir,
372 cov.config.from_args(omit='*%stests' % os.sep, html_dir=html_dir,
374 html_title='IPython test coverage',
373 html_title='IPython test coverage',
375 )
374 )
376 reporter = CustomHtmlReporter(cov, cov.config)
375 reporter = CustomHtmlReporter(cov, cov.config)
377 reporter.report(None)
376 reporter.report(None)
378 print('done.')
377 print('done.')
379
378
380 # Coverage XML report
379 # Coverage XML report
381 elif options.coverage == 'xml':
380 elif options.coverage == 'xml':
382 cov.xml_report(outfile='ipy_coverage.xml')
381 cov.xml_report(outfile='ipy_coverage.xml')
383
382
384 if failed:
383 if failed:
385 # Ensure that our exit code indicates failure
384 # Ensure that our exit code indicates failure
386 sys.exit(1)
385 sys.exit(1)
387
386
388
387
389 def main():
388 def main():
390 # Arguments after -- should be passed through to nose. Argparse treats
389 # Arguments after -- should be passed through to nose. Argparse treats
391 # everything after -- as regular positional arguments, so we separate them
390 # everything after -- as regular positional arguments, so we separate them
392 # first.
391 # first.
393 try:
392 try:
394 ix = sys.argv.index('--')
393 ix = sys.argv.index('--')
395 except ValueError:
394 except ValueError:
396 to_parse = sys.argv[1:]
395 to_parse = sys.argv[1:]
397 extra_args = []
396 extra_args = []
398 else:
397 else:
399 to_parse = sys.argv[1:ix]
398 to_parse = sys.argv[1:ix]
400 extra_args = sys.argv[ix+1:]
399 extra_args = sys.argv[ix+1:]
401
400
402 parser = argparse.ArgumentParser(description='Run IPython test suite')
401 parser = argparse.ArgumentParser(description='Run IPython test suite')
403 parser.add_argument('testgroups', nargs='*',
402 parser.add_argument('testgroups', nargs='*',
404 help='Run specified groups of tests. If omitted, run '
403 help='Run specified groups of tests. If omitted, run '
405 'all tests.')
404 'all tests.')
406 parser.add_argument('--all', action='store_true',
405 parser.add_argument('--all', action='store_true',
407 help='Include slow tests not run by default.')
406 help='Include slow tests not run by default.')
408 parser.add_argument('-j', '--fast', nargs='?', const=None, default=1, type=int,
407 parser.add_argument('-j', '--fast', nargs='?', const=None, default=1, type=int,
409 help='Run test sections in parallel.')
408 help='Run test sections in parallel.')
410 parser.add_argument('--xunit', action='store_true',
409 parser.add_argument('--xunit', action='store_true',
411 help='Produce Xunit XML results')
410 help='Produce Xunit XML results')
412 parser.add_argument('--coverage', nargs='?', const=True, default=False,
411 parser.add_argument('--coverage', nargs='?', const=True, default=False,
413 help="Measure test coverage. Specify 'html' or "
412 help="Measure test coverage. Specify 'html' or "
414 "'xml' to get reports.")
413 "'xml' to get reports.")
415
414
416 options = parser.parse_args(to_parse)
415 options = parser.parse_args(to_parse)
417 options.extra_args = extra_args
416 options.extra_args = extra_args
418
417
419 run_iptestall(options)
418 run_iptestall(options)
420
419
421
420
422 if __name__ == '__main__':
421 if __name__ == '__main__':
423 main()
422 main()
@@ -1,169 +1,197 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 Utilities for getting information about IPython and the system it's running in.
3 Utilities for getting information about IPython and the system it's running in.
4 """
4 """
5
5
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7 # Copyright (C) 2008-2011 The IPython Development Team
7 # Copyright (C) 2008-2011 The IPython Development Team
8 #
8 #
9 # Distributed under the terms of the BSD License. The full license is in
9 # Distributed under the terms of the BSD License. The full license is in
10 # the file COPYING, distributed as part of this software.
10 # the file COPYING, distributed as part of this software.
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16
16
17 import os
17 import os
18 import platform
18 import platform
19 import pprint
19 import pprint
20 import sys
20 import sys
21 import subprocess
21 import subprocess
22
22
23 from IPython.core import release
23 from IPython.core import release
24 from IPython.utils import py3compat, _sysinfo, encoding
24 from IPython.utils import py3compat, _sysinfo, encoding
25
25
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27 # Code
27 # Code
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29
29
30 def pkg_commit_hash(pkg_path):
30 def pkg_commit_hash(pkg_path):
31 """Get short form of commit hash given directory `pkg_path`
31 """Get short form of commit hash given directory `pkg_path`
32
32
33 We get the commit hash from (in order of preference):
33 We get the commit hash from (in order of preference):
34
34
35 * IPython.utils._sysinfo.commit
35 * IPython.utils._sysinfo.commit
36 * git output, if we are in a git repository
36 * git output, if we are in a git repository
37
37
38 If these fail, we return a not-found placeholder tuple
38 If these fail, we return a not-found placeholder tuple
39
39
40 Parameters
40 Parameters
41 ----------
41 ----------
42 pkg_path : str
42 pkg_path : str
43 directory containing package
43 directory containing package
44 only used for getting commit from active repo
44 only used for getting commit from active repo
45
45
46 Returns
46 Returns
47 -------
47 -------
48 hash_from : str
48 hash_from : str
49 Where we got the hash from - description
49 Where we got the hash from - description
50 hash_str : str
50 hash_str : str
51 short form of hash
51 short form of hash
52 """
52 """
53 # Try and get commit from written commit text file
53 # Try and get commit from written commit text file
54 if _sysinfo.commit:
54 if _sysinfo.commit:
55 return "installation", _sysinfo.commit
55 return "installation", _sysinfo.commit
56
56
57 # maybe we are in a repository
57 # maybe we are in a repository
58 proc = subprocess.Popen('git rev-parse --short HEAD',
58 proc = subprocess.Popen('git rev-parse --short HEAD',
59 stdout=subprocess.PIPE,
59 stdout=subprocess.PIPE,
60 stderr=subprocess.PIPE,
60 stderr=subprocess.PIPE,
61 cwd=pkg_path, shell=True)
61 cwd=pkg_path, shell=True)
62 repo_commit, _ = proc.communicate()
62 repo_commit, _ = proc.communicate()
63 if repo_commit:
63 if repo_commit:
64 return 'repository', repo_commit.strip()
64 return 'repository', repo_commit.strip()
65 return '(none found)', '<not found>'
65 return '(none found)', '<not found>'
66
66
67
67
68 def pkg_info(pkg_path):
68 def pkg_info(pkg_path):
69 """Return dict describing the context of this package
69 """Return dict describing the context of this package
70
70
71 Parameters
71 Parameters
72 ----------
72 ----------
73 pkg_path : str
73 pkg_path : str
74 path containing __init__.py for package
74 path containing __init__.py for package
75
75
76 Returns
76 Returns
77 -------
77 -------
78 context : dict
78 context : dict
79 with named parameters of interest
79 with named parameters of interest
80 """
80 """
81 src, hsh = pkg_commit_hash(pkg_path)
81 src, hsh = pkg_commit_hash(pkg_path)
82 return dict(
82 return dict(
83 ipython_version=release.version,
83 ipython_version=release.version,
84 ipython_path=pkg_path,
84 ipython_path=pkg_path,
85 codename=release.codename,
85 codename=release.codename,
86 commit_source=src,
86 commit_source=src,
87 commit_hash=hsh,
87 commit_hash=hsh,
88 sys_version=sys.version,
88 sys_version=sys.version,
89 sys_executable=sys.executable,
89 sys_executable=sys.executable,
90 sys_platform=sys.platform,
90 sys_platform=sys.platform,
91 platform=platform.platform(),
91 platform=platform.platform(),
92 os_name=os.name,
92 os_name=os.name,
93 default_encoding=encoding.DEFAULT_ENCODING,
93 default_encoding=encoding.DEFAULT_ENCODING,
94 )
94 )
95
95
96
96
97 @py3compat.doctest_refactor_print
97 @py3compat.doctest_refactor_print
98 def sys_info():
98 def sys_info():
99 """Return useful information about IPython and the system, as a string.
99 """Return useful information about IPython and the system, as a string.
100
100
101 Examples
101 Examples
102 --------
102 --------
103 ::
103 ::
104
104
105 In [2]: print sys_info()
105 In [2]: print sys_info()
106 {'commit_hash': '144fdae', # random
106 {'commit_hash': '144fdae', # random
107 'commit_source': 'repository',
107 'commit_source': 'repository',
108 'ipython_path': '/home/fperez/usr/lib/python2.6/site-packages/IPython',
108 'ipython_path': '/home/fperez/usr/lib/python2.6/site-packages/IPython',
109 'ipython_version': '0.11.dev',
109 'ipython_version': '0.11.dev',
110 'os_name': 'posix',
110 'os_name': 'posix',
111 'platform': 'Linux-2.6.35-22-generic-i686-with-Ubuntu-10.10-maverick',
111 'platform': 'Linux-2.6.35-22-generic-i686-with-Ubuntu-10.10-maverick',
112 'sys_executable': '/usr/bin/python',
112 'sys_executable': '/usr/bin/python',
113 'sys_platform': 'linux2',
113 'sys_platform': 'linux2',
114 'sys_version': '2.6.6 (r266:84292, Sep 15 2010, 15:52:39) \\n[GCC 4.4.5]'}
114 'sys_version': '2.6.6 (r266:84292, Sep 15 2010, 15:52:39) \\n[GCC 4.4.5]'}
115 """
115 """
116 p = os.path
116 p = os.path
117 path = p.dirname(p.abspath(p.join(__file__, '..')))
117 path = p.dirname(p.abspath(p.join(__file__, '..')))
118 return pprint.pformat(pkg_info(path))
118 return pprint.pformat(pkg_info(path))
119
119
120 def _compress_user(path):
121 """Reverse of :func:`os.path.expanduser`
122 """
123 home = os.path.expanduser('~')
124 if path.startswith(home):
125 path = "~" + path[len(home):]
126 return path
127
128 def brief_sys_info():
129 """Return summary information about IPython and the system, as a string.
130 """
131 p = os.path
132 path = p.dirname(p.abspath(p.join(__file__, '..')))
133 inf = pkg_info(path)
134 out = []
135 def _add(name, value):
136 out.append((name, value))
137
138 _add('IPython version', inf['ipython_version'])
139 _add('IPython commit', "{} ({})".format(inf['commit_hash'], inf['commit_source']))
140 _add('IPython package', _compress_user(inf['ipython_path']))
141 _add('Python version', inf['sys_version'].replace('\n',''))
142 _add('sys.executable', _compress_user(inf['sys_executable']))
143 _add('Platform', inf['platform'])
144
145 width = max(len(n) for (n,v) in out)
146 return '\n'.join("{:<{width}}: {}".format(n, v, width=width) for (n,v) in out)
147
120
148
121 def _num_cpus_unix():
149 def _num_cpus_unix():
122 """Return the number of active CPUs on a Unix system."""
150 """Return the number of active CPUs on a Unix system."""
123 return os.sysconf("SC_NPROCESSORS_ONLN")
151 return os.sysconf("SC_NPROCESSORS_ONLN")
124
152
125
153
126 def _num_cpus_darwin():
154 def _num_cpus_darwin():
127 """Return the number of active CPUs on a Darwin system."""
155 """Return the number of active CPUs on a Darwin system."""
128 p = subprocess.Popen(['sysctl','-n','hw.ncpu'],stdout=subprocess.PIPE)
156 p = subprocess.Popen(['sysctl','-n','hw.ncpu'],stdout=subprocess.PIPE)
129 return p.stdout.read()
157 return p.stdout.read()
130
158
131
159
132 def _num_cpus_windows():
160 def _num_cpus_windows():
133 """Return the number of active CPUs on a Windows system."""
161 """Return the number of active CPUs on a Windows system."""
134 return os.environ.get("NUMBER_OF_PROCESSORS")
162 return os.environ.get("NUMBER_OF_PROCESSORS")
135
163
136
164
137 def num_cpus():
165 def num_cpus():
138 """Return the effective number of CPUs in the system as an integer.
166 """Return the effective number of CPUs in the system as an integer.
139
167
140 This cross-platform function makes an attempt at finding the total number of
168 This cross-platform function makes an attempt at finding the total number of
141 available CPUs in the system, as returned by various underlying system and
169 available CPUs in the system, as returned by various underlying system and
142 python calls.
170 python calls.
143
171
144 If it can't find a sensible answer, it returns 1 (though an error *may* make
172 If it can't find a sensible answer, it returns 1 (though an error *may* make
145 it return a large positive number that's actually incorrect).
173 it return a large positive number that's actually incorrect).
146 """
174 """
147
175
148 # Many thanks to the Parallel Python project (http://www.parallelpython.com)
176 # Many thanks to the Parallel Python project (http://www.parallelpython.com)
149 # for the names of the keys we needed to look up for this function. This
177 # for the names of the keys we needed to look up for this function. This
150 # code was inspired by their equivalent function.
178 # code was inspired by their equivalent function.
151
179
152 ncpufuncs = {'Linux':_num_cpus_unix,
180 ncpufuncs = {'Linux':_num_cpus_unix,
153 'Darwin':_num_cpus_darwin,
181 'Darwin':_num_cpus_darwin,
154 'Windows':_num_cpus_windows,
182 'Windows':_num_cpus_windows,
155 # On Vista, python < 2.5.2 has a bug and returns 'Microsoft'
183 # On Vista, python < 2.5.2 has a bug and returns 'Microsoft'
156 # See http://bugs.python.org/issue1082 for details.
184 # See http://bugs.python.org/issue1082 for details.
157 'Microsoft':_num_cpus_windows,
185 'Microsoft':_num_cpus_windows,
158 }
186 }
159
187
160 ncpufunc = ncpufuncs.get(platform.system(),
188 ncpufunc = ncpufuncs.get(platform.system(),
161 # default to unix version (Solaris, AIX, etc)
189 # default to unix version (Solaris, AIX, etc)
162 _num_cpus_unix)
190 _num_cpus_unix)
163
191
164 try:
192 try:
165 ncpus = max(1,int(ncpufunc()))
193 ncpus = max(1,int(ncpufunc()))
166 except:
194 except:
167 ncpus = 1
195 ncpus = 1
168 return ncpus
196 return ncpus
169
197
General Comments 0
You need to be logged in to leave comments. Login now