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