##// END OF EJS Templates
Capture output from subprocs during test, and display on failure...
Thomas Kluyver -
Show More
@@ -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,13 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 34 import re
35 from select import select
34 36 import sys
37 from threading import Thread, Lock, Event
35 38 import warnings
36 39
37 40 # Now, proceed to import nose itself
@@ -40,6 +43,7 b' from nose.plugins.xunit import Xunit'
40 43 from nose import SkipTest
41 44 from nose.core import TestProgram
42 45 from nose.plugins import Plugin
46 from nose.util import safe_str
43 47
44 48 # Our own imports
45 49 from IPython.utils.importstring import import_item
@@ -353,6 +357,89 b' class ExclusionPlugin(Plugin):'
353 357 return None
354 358
355 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 ev = safe_str(ev)
431 out = [ev, '>> begin captured subprocess output <<',
432 self.stream_capturer.get_buffer().decode('utf-8', 'replace'),
433 '>> end captured subprocess output <<']
434 return ec, '\n'.join(out), tb
435
436 formatError = formatFailure
437
438 def finalize(self, result):
439 self.stream_capturer.stop.set()
440 self.stream_capturer.join()
441
442
356 443 def run_iptest():
357 444 """Run the IPython test suite using nose.
358 445
@@ -407,7 +494,8 b' def run_iptest():'
407 494
408 495 # use our plugin for doctesting. It will remove the standard doctest plugin
409 496 # if it finds it enabled
410 plugins = [ExclusionPlugin(section.excludes), IPythonDoctest(), KnownFailure()]
497 plugins = [ExclusionPlugin(section.excludes), IPythonDoctest(), KnownFailure(),
498 SubprocessStreamCapturePlugin() ]
411 499
412 500 # Use working directory set by parent process (see iptestcontroller)
413 501 if 'IPTEST_WORKING_DIR' in os.environ:
General Comments 0
You need to be logged in to leave comments. Login now