##// END OF EJS Templates
Capture output from subprocs during test, and display on failure...
Thomas Kluyver -
Show More
@@ -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,13 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 import re
35 from select import select
34 import sys
36 import sys
37 from threading import Thread, Lock, Event
35 import warnings
38 import warnings
36
39
37 # Now, proceed to import nose itself
40 # Now, proceed to import nose itself
@@ -40,6 +43,7 b' from nose.plugins.xunit import Xunit'
40 from nose import SkipTest
43 from nose import SkipTest
41 from nose.core import TestProgram
44 from nose.core import TestProgram
42 from nose.plugins import Plugin
45 from nose.plugins import Plugin
46 from nose.util import safe_str
43
47
44 # Our own imports
48 # Our own imports
45 from IPython.utils.importstring import import_item
49 from IPython.utils.importstring import import_item
@@ -353,6 +357,89 b' class ExclusionPlugin(Plugin):'
353 return None
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 def run_iptest():
443 def run_iptest():
357 """Run the IPython test suite using nose.
444 """Run the IPython test suite using nose.
358
445
@@ -407,7 +494,8 b' def run_iptest():'
407
494
408 # use our plugin for doctesting. It will remove the standard doctest plugin
495 # use our plugin for doctesting. It will remove the standard doctest plugin
409 # if it finds it enabled
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 # Use working directory set by parent process (see iptestcontroller)
500 # Use working directory set by parent process (see iptestcontroller)
413 if 'IPTEST_WORKING_DIR' in os.environ:
501 if 'IPTEST_WORKING_DIR' in os.environ:
General Comments 0
You need to be logged in to leave comments. Login now