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= |
|
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= |
|
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= |
|
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