##// 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 import atexit
14 import atexit
15
15
16 from contextlib import contextmanager
16 from contextlib import contextmanager
17 from subprocess import PIPE
17 from subprocess import PIPE, STDOUT
18 from Queue import Empty
18 from Queue import Empty
19
19
20 import nose
20 import nose.tools as nt
21 import nose.tools as nt
21
22
22 from IPython.kernel import KernelManager
23 from IPython.kernel import KernelManager
@@ -39,10 +40,12 b' KC = None'
39 def start_new_kernel(argv=None):
40 def start_new_kernel(argv=None):
40 """start a new kernel, and return its Manager and Client"""
41 """start a new kernel, and return its Manager and Client"""
41 km = KernelManager()
42 km = KernelManager()
42 kwargs = dict(stdout=PIPE, stderr=PIPE)
43 kwargs = dict(stdout=PIPE, stderr=STDOUT)
43 if argv:
44 if argv:
44 kwargs['extra_arguments'] = argv
45 kwargs['extra_arguments'] = argv
45 km.start_kernel(**kwargs)
46 km.start_kernel(**kwargs)
47 nose.ipy_stream_capturer.add_stream(km.kernel.stdout.fileno())
48 nose.ipy_stream_capturer.ensure_started()
46 kc = km.client()
49 kc = km.client()
47 kc.start_channels()
50 kc.start_channels()
48
51
@@ -14,7 +14,9 b''
14 import os
14 import os
15 import tempfile
15 import tempfile
16 import time
16 import time
17 from subprocess import Popen
17 from subprocess import Popen, PIPE, STDOUT
18
19 import nose
18
20
19 from IPython.utils.path import get_ipython_dir
21 from IPython.utils.path import get_ipython_dir
20 from IPython.parallel import Client
22 from IPython.parallel import Client
@@ -35,12 +37,16 b' class TestProcessLauncher(LocalProcessLauncher):'
35 def start(self):
37 def start(self):
36 if self.state == 'before':
38 if self.state == 'before':
37 self.process = Popen(self.args,
39 self.process = Popen(self.args,
38 stdout=blackhole, stderr=blackhole,
40 stdout=PIPE, stderr=STDOUT,
39 env=os.environ,
41 env=os.environ,
40 cwd=self.work_dir
42 cwd=self.work_dir
41 )
43 )
42 self.notify_start(self.process.pid)
44 self.notify_start(self.process.pid)
43 self.poll = self.process.poll
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 else:
50 else:
45 s = 'The process was already started and has state: %r' % self.state
51 s = 'The process was already started and has state: %r' % self.state
46 raise ProcessStateError(s)
52 raise ProcessStateError(s)
@@ -57,7 +63,7 b' def setup():'
57
63
58 cp = TestProcessLauncher()
64 cp = TestProcessLauncher()
59 cp.cmd_and_args = ipcontroller_cmd_argv + \
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 cp.start()
67 cp.start()
62 launchers.append(cp)
68 launchers.append(cp)
63 tic = time.time()
69 tic = time.time()
@@ -107,6 +113,7 b' def teardown():'
107 time.sleep(1)
113 time.sleep(1)
108 while launchers:
114 while launchers:
109 p = launchers.pop()
115 p = launchers.pop()
116 nose.ipy_stream_capturer.remove_stream(p.process.stdout.fileno())
110 if p.poll() is None:
117 if p.poll() is None:
111 try:
118 try:
112 p.stop()
119 p.stop()
@@ -28,10 +28,12 b' from __future__ import print_function'
28
28
29 # Stdlib
29 # Stdlib
30 import glob
30 import glob
31 from io import BytesIO
31 import os
32 import os
32 import os.path as path
33 import os.path as path
33 import re
34 from select import select
34 import sys
35 import sys
36 from threading import Thread, Lock, Event
35 import warnings
37 import warnings
36
38
37 # Now, proceed to import nose itself
39 # Now, proceed to import nose itself
@@ -40,6 +42,7 b' from nose.plugins.xunit import Xunit'
40 from nose import SkipTest
42 from nose import SkipTest
41 from nose.core import TestProgram
43 from nose.core import TestProgram
42 from nose.plugins import Plugin
44 from nose.plugins import Plugin
45 from nose.util import safe_str
43
46
44 # Our own imports
47 # Our own imports
45 from IPython.utils.importstring import import_item
48 from IPython.utils.importstring import import_item
@@ -354,6 +357,94 b' class ExclusionPlugin(Plugin):'
354 return None
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 def run_iptest():
448 def run_iptest():
358 """Run the IPython test suite using nose.
449 """Run the IPython test suite using nose.
359
450
@@ -408,7 +499,8 b' def run_iptest():'
408
499
409 # use our plugin for doctesting. It will remove the standard doctest plugin
500 # use our plugin for doctesting. It will remove the standard doctest plugin
410 # if it finds it enabled
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 # Use working directory set by parent process (see iptestcontroller)
505 # Use working directory set by parent process (see iptestcontroller)
414 if 'IPTEST_WORKING_DIR' in os.environ:
506 if 'IPTEST_WORKING_DIR' in os.environ:
General Comments 0
You need to be logged in to leave comments. Login now