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