"""Base class for notebook tests.""" from __future__ import print_function import sys import time import requests from contextlib import contextmanager from threading import Thread, Event from unittest import TestCase from tornado.ioloop import IOLoop from ..notebookapp import NotebookApp from IPython.utils.tempdir import TemporaryDirectory MAX_WAITTIME = 30 # seconds to wait for notebook server to start POLL_INTERVAL = 0.1 # time between attempts # TimeoutError is a builtin on Python 3. This can be removed when we stop # supporting Python 2. class TimeoutError(Exception): pass class NotebookTestBase(TestCase): """A base class for tests that need a running notebook. This creates an empty profile in a temp ipython_dir and then starts the notebook server with a separate temp notebook_dir. """ port = 12341 config = None @classmethod def wait_until_alive(cls): """Wait for the server to be alive""" url = 'http://localhost:%i/api/contents' % cls.port for _ in range(int(MAX_WAITTIME/POLL_INTERVAL)): try: requests.get(url) except Exception as e: if cls.notebook.poll() is not None: raise RuntimeError("The notebook server exited with status %s" \ % cls.notebook.poll()) time.sleep(POLL_INTERVAL) else: return raise TimeoutError("The notebook server didn't start up correctly.") @classmethod def wait_until_dead(cls): """Wait for the server process to terminate after shutdown""" cls.notebook_thread.join(timeout=MAX_WAITTIME) if cls.notebook_thread.is_alive(): raise TimeoutError("Undead notebook server") @classmethod def setup_class(cls): cls.ipython_dir = TemporaryDirectory() cls.notebook_dir = TemporaryDirectory() app = cls.notebook = NotebookApp( port=cls.port, port_retries=0, open_browser=False, ipython_dir=cls.ipython_dir.name, notebook_dir=cls.notebook_dir.name, config=cls.config, ) # clear log handlers and propagate to root for nose to capture it # needs to be redone after initialize, which reconfigures logging app.log.propagate = True app.log.handlers = [] app.initialize(argv=[]) app.log.propagate = True app.log.handlers = [] started = Event() def start_thread(): loop = IOLoop.current() loop.add_callback(started.set) try: app.start() finally: # set the event, so failure to start doesn't cause a hang started.set() cls.notebook_thread = Thread(target=start_thread) cls.notebook_thread.start() started.wait() cls.wait_until_alive() @classmethod def teardown_class(cls): cls.notebook.stop() cls.wait_until_dead() cls.ipython_dir.cleanup() cls.notebook_dir.cleanup() @classmethod def base_url(cls): return 'http://localhost:%i/' % cls.port @contextmanager def assert_http_error(status, msg=None): try: yield except requests.HTTPError as e: real_status = e.response.status_code assert real_status == status, \ "Expected status %d, got %d" % (status, real_status) if msg: assert msg in str(e), e else: assert False, "Expected HTTP error status"