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