From ea201ca59c034b9ae932a1c8445cd26addccf2be 2014-03-20 22:13:01 From: MinRK Date: 2014-03-20 22:13:01 Subject: [PATCH] Don't use fork to start the notebook in js tests It can encounter a weird segfault on OS X with sqlite when Qt is present (?!) The main reason to use the fork was to get the port number, but this is easy now that notebooks write a server-info file. Further advantage is that the symptom of a failed server start is no longer silence and hanging tests, but an actual failure with the server's log output. --- diff --git a/IPython/testing/iptestcontroller.py b/IPython/testing/iptestcontroller.py index 66d62f9..9742052 100644 --- a/IPython/testing/iptestcontroller.py +++ b/IPython/testing/iptestcontroller.py @@ -19,8 +19,8 @@ test suite. from __future__ import print_function import argparse +import json import multiprocessing.pool -from multiprocessing import Process, Queue import os import shutil import signal @@ -28,7 +28,7 @@ import sys import subprocess import time -from .iptest import have, test_group_names as py_test_group_names, test_sections +from .iptest import have, test_group_names as py_test_group_names, test_sections, StreamCapturer from IPython.utils.path import compress_user from IPython.utils.py3compat import bytes_to_str from IPython.utils.sysinfo import get_sys_info @@ -211,8 +211,10 @@ class JSController(TestController): os.makedirs(os.path.join(self.nbdir.name, os.path.join(u'sub ∂ir2', u'sub ∂ir 1b'))) # start the ipython notebook, so we get the port number + self.server_port = 0 self._init_server() - self.cmd.append('--port=%s' % self.server_port) + if self.server_port: + self.cmd.append("--port=%i" % self.server_port) def print_extra_info(self): print("Running tests with notebook directory %r" % self.nbdir.name) @@ -223,36 +225,60 @@ class JSController(TestController): def _init_server(self): "Start the notebook server in a separate process" - self.queue = q = Queue() - self.server = Process(target=run_webapp, args=(q, self.ipydir.name, self.nbdir.name)) - self.server.start() - self.server_port = q.get() + self.server_command = command = [sys.executable, + '-m', 'IPython.html', + '--no-browser', + '--ipython-dir', self.ipydir.name, + '--notebook-dir', self.nbdir.name, + ] + # ipc doesn't work on Windows, and darwin has crazy-long temp paths, + # which run afoul of ipc's maximum path length. + if sys.platform.startswith('linux'): + command.append('--KernelManager.transport=ipc') + self.stream_capturer = c = StreamCapturer() + c.start() + self.server = subprocess.Popen(command, stdout=c.writefd, stderr=subprocess.STDOUT) + self.server_info_file = os.path.join(self.ipydir.name, + 'profile_default', 'security', 'nbserver-%i.json' % self.server.pid + ) + self._wait_for_server() + + def _wait_for_server(self): + """Wait 30 seconds for the notebook server to start""" + for i in range(300): + if self.server.poll() is not None: + return self._failed_to_start() + if os.path.exists(self.server_info_file): + self._load_server_info() + return + time.sleep(0.1) + print("Notebook server-info file never arrived: %s" % self.server_info_file, + file=sys.stderr + ) + + def _failed_to_start(self): + """Notebook server exited prematurely""" + captured = self.stream_capturer.get_buffer().decode('utf-8', 'replace') + print("Notebook failed to start: ", file=sys.stderr) + print(self.server_command) + print(captured, file=sys.stderr) + + def _load_server_info(self): + """Notebook server started, load connection info from JSON""" + with open(self.server_info_file) as f: + info = json.load(f) + self.server_port = info['port'] def cleanup(self): - self.server.terminate() - self.server.join() + self.stream_capturer.halt() + try: + self.server.terminate() + except OSError: + # already dead + pass + self.server.wait() TestController.cleanup(self) -def run_webapp(q, ipydir, nbdir, loglevel=0): - """start the IPython Notebook, and pass port back to the queue""" - import os - import IPython.html.notebookapp as nbapp - import sys - sys.stderr = open(os.devnull, 'w') - server = nbapp.NotebookApp() - args = ['--no-browser'] - args.extend(['--ipython-dir', ipydir, - '--notebook-dir', nbdir, - '--log-level', str(loglevel), - ]) - # ipc doesn't work on Windows, and darwin has crazy-long temp paths, - # which run afoul of ipc's maximum path length. - if sys.platform.startswith('linux'): - args.append('--KernelManager.transport=ipc') - server.initialize(args) - # communicate the port number to the parent process - q.put(server.port) - server.start() def prepare_controllers(options): """Returns two lists of TestController instances, those to run, and those