Show More
@@ -14,9 +14,10 b'' | |||
|
14 | 14 | import atexit |
|
15 | 15 | |
|
16 | 16 | from contextlib import contextmanager |
|
17 | from subprocess import PIPE | |
|
17 | from subprocess import PIPE, STDOUT | |
|
18 | 18 | from Queue import Empty |
|
19 | 19 | |
|
20 | import nose | |
|
20 | 21 | import nose.tools as nt |
|
21 | 22 | |
|
22 | 23 | from IPython.kernel import KernelManager |
@@ -39,10 +40,12 b' KC = None' | |||
|
39 | 40 | def start_new_kernel(argv=None): |
|
40 | 41 | """start a new kernel, and return its Manager and Client""" |
|
41 | 42 | km = KernelManager() |
|
42 |
kwargs = dict(stdout=PIPE, stderr= |
|
|
43 | kwargs = dict(stdout=PIPE, stderr=STDOUT) | |
|
43 | 44 | if argv: |
|
44 | 45 | kwargs['extra_arguments'] = argv |
|
45 | 46 | km.start_kernel(**kwargs) |
|
47 | nose.ipy_stream_capturer.add_stream(km.kernel.stdout.fileno()) | |
|
48 | nose.ipy_stream_capturer.ensure_started() | |
|
46 | 49 | kc = km.client() |
|
47 | 50 | kc.start_channels() |
|
48 | 51 |
@@ -14,7 +14,9 b'' | |||
|
14 | 14 | import os |
|
15 | 15 | import tempfile |
|
16 | 16 | import time |
|
17 | from subprocess import Popen | |
|
17 | from subprocess import Popen, PIPE, STDOUT | |
|
18 | ||
|
19 | import nose | |
|
18 | 20 | |
|
19 | 21 | from IPython.utils.path import get_ipython_dir |
|
20 | 22 | from IPython.parallel import Client |
@@ -35,12 +37,16 b' class TestProcessLauncher(LocalProcessLauncher):' | |||
|
35 | 37 | def start(self): |
|
36 | 38 | if self.state == 'before': |
|
37 | 39 | self.process = Popen(self.args, |
|
38 |
stdout= |
|
|
40 | stdout=PIPE, stderr=STDOUT, | |
|
39 | 41 | env=os.environ, |
|
40 | 42 | cwd=self.work_dir |
|
41 | 43 | ) |
|
42 | 44 | self.notify_start(self.process.pid) |
|
43 | 45 | self.poll = self.process.poll |
|
46 | # Store stdout & stderr to show with failing tests. | |
|
47 | # This is defined in IPython.testing.iptest | |
|
48 | nose.ipy_stream_capturer.add_stream(self.process.stdout.fileno()) | |
|
49 | nose.ipy_stream_capturer.ensure_started() | |
|
44 | 50 | else: |
|
45 | 51 | s = 'The process was already started and has state: %r' % self.state |
|
46 | 52 | raise ProcessStateError(s) |
@@ -57,7 +63,7 b' def setup():' | |||
|
57 | 63 | |
|
58 | 64 | cp = TestProcessLauncher() |
|
59 | 65 | cp.cmd_and_args = ipcontroller_cmd_argv + \ |
|
60 |
['--profile=iptest', '--log-level= |
|
|
66 | ['--profile=iptest', '--log-level=20', '--ping=250', '--dictdb'] | |
|
61 | 67 | cp.start() |
|
62 | 68 | launchers.append(cp) |
|
63 | 69 | tic = time.time() |
@@ -107,6 +113,7 b' def teardown():' | |||
|
107 | 113 | time.sleep(1) |
|
108 | 114 | while launchers: |
|
109 | 115 | p = launchers.pop() |
|
116 | nose.ipy_stream_capturer.remove_stream(p.process.stdout.fileno()) | |
|
110 | 117 | if p.poll() is None: |
|
111 | 118 | try: |
|
112 | 119 | p.stop() |
@@ -28,10 +28,12 b' from __future__ import print_function' | |||
|
28 | 28 | |
|
29 | 29 | # Stdlib |
|
30 | 30 | import glob |
|
31 | from io import BytesIO | |
|
31 | 32 | import os |
|
32 | 33 | import os.path as path |
|
33 | import re | |
|
34 | from select import select | |
|
34 | 35 | import sys |
|
36 | from threading import Thread, Lock, Event | |
|
35 | 37 | import warnings |
|
36 | 38 | |
|
37 | 39 | # Now, proceed to import nose itself |
@@ -40,6 +42,7 b' from nose.plugins.xunit import Xunit' | |||
|
40 | 42 | from nose import SkipTest |
|
41 | 43 | from nose.core import TestProgram |
|
42 | 44 | from nose.plugins import Plugin |
|
45 | from nose.util import safe_str | |
|
43 | 46 | |
|
44 | 47 | # Our own imports |
|
45 | 48 | from IPython.utils.importstring import import_item |
@@ -354,6 +357,94 b' class ExclusionPlugin(Plugin):' | |||
|
354 | 357 | return None |
|
355 | 358 | |
|
356 | 359 | |
|
360 | class StreamCapturer(Thread): | |
|
361 | started = False | |
|
362 | def __init__(self): | |
|
363 | super(StreamCapturer, self).__init__() | |
|
364 | self.streams = [] | |
|
365 | self.buffer = BytesIO() | |
|
366 | self.streams_lock = Lock() | |
|
367 | self.buffer_lock = Lock() | |
|
368 | self.stream_added = Event() | |
|
369 | self.stop = Event() | |
|
370 | ||
|
371 | def run(self): | |
|
372 | self.started = True | |
|
373 | while not self.stop.is_set(): | |
|
374 | with self.streams_lock: | |
|
375 | streams = self.streams | |
|
376 | ||
|
377 | if not streams: | |
|
378 | self.stream_added.wait(timeout=1) | |
|
379 | self.stream_added.clear() | |
|
380 | continue | |
|
381 | ||
|
382 | ready = select(streams, [], [], 0.5)[0] | |
|
383 | with self.buffer_lock: | |
|
384 | for fd in ready: | |
|
385 | self.buffer.write(os.read(fd, 1024)) | |
|
386 | ||
|
387 | def add_stream(self, fd): | |
|
388 | with self.streams_lock: | |
|
389 | self.streams.append(fd) | |
|
390 | self.stream_added.set() | |
|
391 | ||
|
392 | def remove_stream(self, fd): | |
|
393 | with self.streams_lock: | |
|
394 | self.streams.remove(fd) | |
|
395 | ||
|
396 | def reset_buffer(self): | |
|
397 | with self.buffer_lock: | |
|
398 | self.buffer.truncate(0) | |
|
399 | self.buffer.seek(0) | |
|
400 | ||
|
401 | def get_buffer(self): | |
|
402 | with self.buffer_lock: | |
|
403 | return self.buffer.getvalue() | |
|
404 | ||
|
405 | def ensure_started(self): | |
|
406 | if not self.started: | |
|
407 | self.start() | |
|
408 | ||
|
409 | class SubprocessStreamCapturePlugin(Plugin): | |
|
410 | name='subprocstreams' | |
|
411 | def __init__(self): | |
|
412 | Plugin.__init__(self) | |
|
413 | self.stream_capturer = StreamCapturer() | |
|
414 | # This is ugly, but distant parts of the test machinery need to be able | |
|
415 | # to add streams, so we make the object globally accessible. | |
|
416 | nose.ipy_stream_capturer = self.stream_capturer | |
|
417 | ||
|
418 | def configure(self, options, config): | |
|
419 | Plugin.configure(self, options, config) | |
|
420 | # Override nose trying to disable plugin. | |
|
421 | self.enabled = True | |
|
422 | ||
|
423 | def startTest(self, test): | |
|
424 | # Reset log capture | |
|
425 | self.stream_capturer.reset_buffer() | |
|
426 | ||
|
427 | def formatFailure(self, test, err): | |
|
428 | # Show output | |
|
429 | ec, ev, tb = err | |
|
430 | captured = self.stream_capturer.get_buffer().decode('utf-8', 'replace') | |
|
431 | if captured.strip(): | |
|
432 | ev = safe_str(ev) | |
|
433 | out = [ev, '>> begin captured subprocess output <<', | |
|
434 | captured, | |
|
435 | '>> end captured subprocess output <<'] | |
|
436 | return ec, '\n'.join(out), tb | |
|
437 | ||
|
438 | return err | |
|
439 | ||
|
440 | formatError = formatFailure | |
|
441 | ||
|
442 | def finalize(self, result): | |
|
443 | if self.stream_capturer.started: | |
|
444 | self.stream_capturer.stop.set() | |
|
445 | self.stream_capturer.join() | |
|
446 | ||
|
447 | ||
|
357 | 448 | def run_iptest(): |
|
358 | 449 | """Run the IPython test suite using nose. |
|
359 | 450 | |
@@ -408,7 +499,8 b' def run_iptest():' | |||
|
408 | 499 | |
|
409 | 500 | # use our plugin for doctesting. It will remove the standard doctest plugin |
|
410 | 501 | # if it finds it enabled |
|
411 |
plugins = [ExclusionPlugin(section.excludes), IPythonDoctest(), KnownFailure() |
|
|
502 | plugins = [ExclusionPlugin(section.excludes), IPythonDoctest(), KnownFailure(), | |
|
503 | SubprocessStreamCapturePlugin() ] | |
|
412 | 504 | |
|
413 | 505 | # Use working directory set by parent process (see iptestcontroller) |
|
414 | 506 | if 'IPTEST_WORKING_DIR' in os.environ: |
General Comments 0
You need to be logged in to leave comments.
Login now