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