##// END OF EJS Templates
Merge pull request #6237 from jasongrout/iptest-profile-dir...
Thomas Kluyver -
r17471:aaabb878 merge
parent child Browse files
Show More
@@ -1,680 +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 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 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 244 if self.xunit:
245 245 self.add_xunit()
246 246
247 247 # start the ipython notebook, so we get the port number
248 248 self.server_port = 0
249 249 self._init_server()
250 250 if self.server_port:
251 251 self.cmd.append("--port=%i" % self.server_port)
252 252 else:
253 253 # don't launch tests if the server didn't start
254 254 self.cmd = [sys.executable, '-c', 'raise SystemExit(1)']
255 255
256 256 def add_xunit(self):
257 257 xunit_file = os.path.abspath(self.section.replace('/','.') + '.xunit.xml')
258 258 self.cmd.append('--xunit=%s' % xunit_file)
259 259
260 260 def launch(self, buffer_output):
261 261 # If the engine is SlimerJS, we need to buffer the output because
262 262 # SlimerJS does not support exit codes, so CasperJS always returns 0.
263 263 if self.engine == 'slimerjs' and not buffer_output:
264 264 self.display_slimer_output = True
265 265 return super(JSController, self).launch(buffer_output=True)
266 266
267 267 else:
268 268 return super(JSController, self).launch(buffer_output=buffer_output)
269 269
270 270 def wait(self, *pargs, **kwargs):
271 271 """Wait for the JSController to finish"""
272 272 ret = super(JSController, self).wait(*pargs, **kwargs)
273 273 # If this is a SlimerJS controller, check the captured stdout for
274 274 # errors. Otherwise, just return the return code.
275 275 if self.engine == 'slimerjs':
276 276 stdout = bytes_to_str(self.stdout)
277 277 if self.display_slimer_output:
278 278 print(stdout)
279 279 if ret != 0:
280 280 # This could still happen e.g. if it's stopped by SIGINT
281 281 return ret
282 282 return bool(self.slimer_failure.search(strip_ansi(stdout)))
283 283 else:
284 284 return ret
285 285
286 286 def print_extra_info(self):
287 287 print("Running tests with notebook directory %r" % self.nbdir.name)
288 288
289 289 @property
290 290 def will_run(self):
291 291 return all(have[a] for a in self.requirements + [self.engine])
292 292
293 293 def _init_server(self):
294 294 "Start the notebook server in a separate process"
295 295 self.server_command = command = [sys.executable,
296 296 '-m', 'IPython.html',
297 297 '--no-browser',
298 298 '--ipython-dir', self.ipydir.name,
299 299 '--notebook-dir', self.nbdir.name,
300 300 ]
301 301 # ipc doesn't work on Windows, and darwin has crazy-long temp paths,
302 302 # which run afoul of ipc's maximum path length.
303 303 if sys.platform.startswith('linux'):
304 304 command.append('--KernelManager.transport=ipc')
305 305 self.stream_capturer = c = StreamCapturer()
306 306 c.start()
307 self.server = subprocess.Popen(command, stdout=c.writefd, stderr=subprocess.STDOUT)
307 self.server = subprocess.Popen(command, stdout=c.writefd, stderr=subprocess.STDOUT, cwd=self.nbdir.name)
308 308 self.server_info_file = os.path.join(self.ipydir.name,
309 309 'profile_default', 'security', 'nbserver-%i.json' % self.server.pid
310 310 )
311 311 self._wait_for_server()
312 312
313 313 def _wait_for_server(self):
314 314 """Wait 30 seconds for the notebook server to start"""
315 315 for i in range(300):
316 316 if self.server.poll() is not None:
317 317 return self._failed_to_start()
318 318 if os.path.exists(self.server_info_file):
319 319 try:
320 320 self._load_server_info()
321 321 except ValueError:
322 322 # If the server is halfway through writing the file, we may
323 323 # get invalid JSON; it should be ready next iteration.
324 324 pass
325 325 else:
326 326 return
327 327 time.sleep(0.1)
328 328 print("Notebook server-info file never arrived: %s" % self.server_info_file,
329 329 file=sys.stderr
330 330 )
331 331
332 332 def _failed_to_start(self):
333 333 """Notebook server exited prematurely"""
334 334 captured = self.stream_capturer.get_buffer().decode('utf-8', 'replace')
335 335 print("Notebook failed to start: ", file=sys.stderr)
336 336 print(self.server_command)
337 337 print(captured, file=sys.stderr)
338 338
339 339 def _load_server_info(self):
340 340 """Notebook server started, load connection info from JSON"""
341 341 with open(self.server_info_file) as f:
342 342 info = json.load(f)
343 343 self.server_port = info['port']
344 344
345 345 def cleanup(self):
346 346 try:
347 347 self.server.terminate()
348 348 except OSError:
349 349 # already dead
350 350 pass
351 351 # wait 10s for the server to shutdown
352 352 try:
353 353 popen_wait(self.server, NOTEBOOK_SHUTDOWN_TIMEOUT)
354 354 except TimeoutExpired:
355 355 # server didn't terminate, kill it
356 356 try:
357 357 print("Failed to terminate notebook server, killing it.",
358 358 file=sys.stderr
359 359 )
360 360 self.server.kill()
361 361 except OSError:
362 362 # already dead
363 363 pass
364 364 # wait another 10s
365 365 try:
366 366 popen_wait(self.server, NOTEBOOK_SHUTDOWN_TIMEOUT)
367 367 except TimeoutExpired:
368 368 print("Notebook server still running (%s)" % self.server_info_file,
369 369 file=sys.stderr
370 370 )
371 371
372 372 self.stream_capturer.halt()
373 373 TestController.cleanup(self)
374 374
375 375
376 376 def prepare_controllers(options):
377 377 """Returns two lists of TestController instances, those to run, and those
378 378 not to run."""
379 379 testgroups = options.testgroups
380 380 if testgroups:
381 381 if 'js' in testgroups:
382 382 js_testgroups = all_js_groups()
383 383 else:
384 384 js_testgroups = [g for g in testgroups if g.startswith(js_prefix)]
385 385
386 386 py_testgroups = [g for g in testgroups if g not in ['js'] + js_testgroups]
387 387 else:
388 388 py_testgroups = py_test_group_names
389 389 if not options.all:
390 390 js_testgroups = []
391 391 test_sections['parallel'].enabled = False
392 392 else:
393 393 js_testgroups = all_js_groups()
394 394
395 395 engine = 'slimerjs' if options.slimerjs else 'phantomjs'
396 396 c_js = [JSController(name, xunit=options.xunit, engine=engine) for name in js_testgroups]
397 397 c_py = [PyTestController(name, options) for name in py_testgroups]
398 398
399 399 controllers = c_py + c_js
400 400 to_run = [c for c in controllers if c.will_run]
401 401 not_run = [c for c in controllers if not c.will_run]
402 402 return to_run, not_run
403 403
404 404 def do_run(controller, buffer_output=True):
405 405 """Setup and run a test controller.
406 406
407 407 If buffer_output is True, no output is displayed, to avoid it appearing
408 408 interleaved. In this case, the caller is responsible for displaying test
409 409 output on failure.
410 410
411 411 Returns
412 412 -------
413 413 controller : TestController
414 414 The same controller as passed in, as a convenience for using map() type
415 415 APIs.
416 416 exitcode : int
417 417 The exit code of the test subprocess. Non-zero indicates failure.
418 418 """
419 419 try:
420 420 try:
421 421 controller.setup()
422 422 if not buffer_output:
423 423 controller.print_extra_info()
424 424 controller.launch(buffer_output=buffer_output)
425 425 except Exception:
426 426 import traceback
427 427 traceback.print_exc()
428 428 return controller, 1 # signal failure
429 429
430 430 exitcode = controller.wait()
431 431 return controller, exitcode
432 432
433 433 except KeyboardInterrupt:
434 434 return controller, -signal.SIGINT
435 435 finally:
436 436 controller.cleanup()
437 437
438 438 def report():
439 439 """Return a string with a summary report of test-related variables."""
440 440 inf = get_sys_info()
441 441 out = []
442 442 def _add(name, value):
443 443 out.append((name, value))
444 444
445 445 _add('IPython version', inf['ipython_version'])
446 446 _add('IPython commit', "{} ({})".format(inf['commit_hash'], inf['commit_source']))
447 447 _add('IPython package', compress_user(inf['ipython_path']))
448 448 _add('Python version', inf['sys_version'].replace('\n',''))
449 449 _add('sys.executable', compress_user(inf['sys_executable']))
450 450 _add('Platform', inf['platform'])
451 451
452 452 width = max(len(n) for (n,v) in out)
453 453 out = ["{:<{width}}: {}\n".format(n, v, width=width) for (n,v) in out]
454 454
455 455 avail = []
456 456 not_avail = []
457 457
458 458 for k, is_avail in have.items():
459 459 if is_avail:
460 460 avail.append(k)
461 461 else:
462 462 not_avail.append(k)
463 463
464 464 if avail:
465 465 out.append('\nTools and libraries available at test time:\n')
466 466 avail.sort()
467 467 out.append(' ' + ' '.join(avail)+'\n')
468 468
469 469 if not_avail:
470 470 out.append('\nTools and libraries NOT available at test time:\n')
471 471 not_avail.sort()
472 472 out.append(' ' + ' '.join(not_avail)+'\n')
473 473
474 474 return ''.join(out)
475 475
476 476 def run_iptestall(options):
477 477 """Run the entire IPython test suite by calling nose and trial.
478 478
479 479 This function constructs :class:`IPTester` instances for all IPython
480 480 modules and package and then runs each of them. This causes the modules
481 481 and packages of IPython to be tested each in their own subprocess using
482 482 nose.
483 483
484 484 Parameters
485 485 ----------
486 486
487 487 All parameters are passed as attributes of the options object.
488 488
489 489 testgroups : list of str
490 490 Run only these sections of the test suite. If empty, run all the available
491 491 sections.
492 492
493 493 fast : int or None
494 494 Run the test suite in parallel, using n simultaneous processes. If None
495 495 is passed, one process is used per CPU core. Default 1 (i.e. sequential)
496 496
497 497 inc_slow : bool
498 498 Include slow tests, like IPython.parallel. By default, these tests aren't
499 499 run.
500 500
501 501 slimerjs : bool
502 502 Use slimerjs if it's installed instead of phantomjs for casperjs tests.
503 503
504 504 xunit : bool
505 505 Produce Xunit XML output. This is written to multiple foo.xunit.xml files.
506 506
507 507 coverage : bool or str
508 508 Measure code coverage from tests. True will store the raw coverage data,
509 509 or pass 'html' or 'xml' to get reports.
510 510
511 511 extra_args : list
512 512 Extra arguments to pass to the test subprocesses, e.g. '-v'
513 513 """
514 514 to_run, not_run = prepare_controllers(options)
515 515
516 516 def justify(ltext, rtext, width=70, fill='-'):
517 517 ltext += ' '
518 518 rtext = (' ' + rtext).rjust(width - len(ltext), fill)
519 519 return ltext + rtext
520 520
521 521 # Run all test runners, tracking execution time
522 522 failed = []
523 523 t_start = time.time()
524 524
525 525 print()
526 526 if options.fast == 1:
527 527 # This actually means sequential, i.e. with 1 job
528 528 for controller in to_run:
529 529 print('Test group:', controller.section)
530 530 sys.stdout.flush() # Show in correct order when output is piped
531 531 controller, res = do_run(controller, buffer_output=False)
532 532 if res:
533 533 failed.append(controller)
534 534 if res == -signal.SIGINT:
535 535 print("Interrupted")
536 536 break
537 537 print()
538 538
539 539 else:
540 540 # Run tests concurrently
541 541 try:
542 542 pool = multiprocessing.pool.ThreadPool(options.fast)
543 543 for (controller, res) in pool.imap_unordered(do_run, to_run):
544 544 res_string = 'OK' if res == 0 else 'FAILED'
545 545 print(justify('Test group: ' + controller.section, res_string))
546 546 if res:
547 547 controller.print_extra_info()
548 548 print(bytes_to_str(controller.stdout))
549 549 failed.append(controller)
550 550 if res == -signal.SIGINT:
551 551 print("Interrupted")
552 552 break
553 553 except KeyboardInterrupt:
554 554 return
555 555
556 556 for controller in not_run:
557 557 print(justify('Test group: ' + controller.section, 'NOT RUN'))
558 558
559 559 t_end = time.time()
560 560 t_tests = t_end - t_start
561 561 nrunners = len(to_run)
562 562 nfail = len(failed)
563 563 # summarize results
564 564 print('_'*70)
565 565 print('Test suite completed for system with the following information:')
566 566 print(report())
567 567 took = "Took %.3fs." % t_tests
568 568 print('Status: ', end='')
569 569 if not failed:
570 570 print('OK (%d test groups).' % nrunners, took)
571 571 else:
572 572 # If anything went wrong, point out what command to rerun manually to
573 573 # see the actual errors and individual summary
574 574 failed_sections = [c.section for c in failed]
575 575 print('ERROR - {} out of {} test groups failed ({}).'.format(nfail,
576 576 nrunners, ', '.join(failed_sections)), took)
577 577 print()
578 578 print('You may wish to rerun these, with:')
579 579 print(' iptest', *failed_sections)
580 580 print()
581 581
582 582 if options.coverage:
583 583 from coverage import coverage
584 584 cov = coverage(data_file='.coverage')
585 585 cov.combine()
586 586 cov.save()
587 587
588 588 # Coverage HTML report
589 589 if options.coverage == 'html':
590 590 html_dir = 'ipy_htmlcov'
591 591 shutil.rmtree(html_dir, ignore_errors=True)
592 592 print("Writing HTML coverage report to %s/ ... " % html_dir, end="")
593 593 sys.stdout.flush()
594 594
595 595 # Custom HTML reporter to clean up module names.
596 596 from coverage.html import HtmlReporter
597 597 class CustomHtmlReporter(HtmlReporter):
598 598 def find_code_units(self, morfs):
599 599 super(CustomHtmlReporter, self).find_code_units(morfs)
600 600 for cu in self.code_units:
601 601 nameparts = cu.name.split(os.sep)
602 602 if 'IPython' not in nameparts:
603 603 continue
604 604 ix = nameparts.index('IPython')
605 605 cu.name = '.'.join(nameparts[ix:])
606 606
607 607 # Reimplement the html_report method with our custom reporter
608 608 cov._harvest_data()
609 609 cov.config.from_args(omit='*{0}tests{0}*'.format(os.sep), html_dir=html_dir,
610 610 html_title='IPython test coverage',
611 611 )
612 612 reporter = CustomHtmlReporter(cov, cov.config)
613 613 reporter.report(None)
614 614 print('done.')
615 615
616 616 # Coverage XML report
617 617 elif options.coverage == 'xml':
618 618 cov.xml_report(outfile='ipy_coverage.xml')
619 619
620 620 if failed:
621 621 # Ensure that our exit code indicates failure
622 622 sys.exit(1)
623 623
624 624 argparser = argparse.ArgumentParser(description='Run IPython test suite')
625 625 argparser.add_argument('testgroups', nargs='*',
626 626 help='Run specified groups of tests. If omitted, run '
627 627 'all tests.')
628 628 argparser.add_argument('--all', action='store_true',
629 629 help='Include slow tests not run by default.')
630 630 argparser.add_argument('--slimerjs', action='store_true',
631 631 help="Use slimerjs if it's installed instead of phantomjs for casperjs tests.")
632 632 argparser.add_argument('-j', '--fast', nargs='?', const=None, default=1, type=int,
633 633 help='Run test sections in parallel. This starts as many '
634 634 'processes as you have cores, or you can specify a number.')
635 635 argparser.add_argument('--xunit', action='store_true',
636 636 help='Produce Xunit XML results')
637 637 argparser.add_argument('--coverage', nargs='?', const=True, default=False,
638 638 help="Measure test coverage. Specify 'html' or "
639 639 "'xml' to get reports.")
640 640 argparser.add_argument('--subproc-streams', default='capture',
641 641 help="What to do with stdout/stderr from subprocesses. "
642 642 "'capture' (default), 'show' and 'discard' are the options.")
643 643
644 644 def default_options():
645 645 """Get an argparse Namespace object with the default arguments, to pass to
646 646 :func:`run_iptestall`.
647 647 """
648 648 options = argparser.parse_args([])
649 649 options.extra_args = []
650 650 return options
651 651
652 652 def main():
653 653 # iptest doesn't work correctly if the working directory is the
654 654 # root of the IPython source tree. Tell the user to avoid
655 655 # frustration.
656 656 if os.path.exists(os.path.join(os.getcwd(),
657 657 'IPython', 'testing', '__main__.py')):
658 658 print("Don't run iptest from the IPython source directory",
659 659 file=sys.stderr)
660 660 sys.exit(1)
661 661 # Arguments after -- should be passed through to nose. Argparse treats
662 662 # everything after -- as regular positional arguments, so we separate them
663 663 # first.
664 664 try:
665 665 ix = sys.argv.index('--')
666 666 except ValueError:
667 667 to_parse = sys.argv[1:]
668 668 extra_args = []
669 669 else:
670 670 to_parse = sys.argv[1:ix]
671 671 extra_args = sys.argv[ix+1:]
672 672
673 673 options = argparser.parse_args(to_parse)
674 674 options.extra_args = extra_args
675 675
676 676 run_iptestall(options)
677 677
678 678
679 679 if __name__ == '__main__':
680 680 main()
General Comments 0
You need to be logged in to leave comments. Login now