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.
This commit is contained in:
MinRK 2014-03-12 15:52:05 -07:00
parent fa44edcb47
commit 386b08634c

View File

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