##// END OF EJS Templates
Merge pull request #4393 from takluyver/tests-subproc-stream-capture...
Thomas Kluyver -
r13241:1a4b088e merge
parent child Browse files
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=PIPE)
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=blackhole, stderr=blackhole,
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=50', '--ping=250', '--dictdb']
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