Skip to content

Commit

Permalink
Refactor to share fixtures with option tests
Browse files Browse the repository at this point in the history
  • Loading branch information
parente committed Nov 29, 2017
1 parent 79ad94e commit 8a59d74
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 56 deletions.
36 changes: 29 additions & 7 deletions base-notebook/test/test_container_options.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,38 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
import pytest
import time

import pytest

@pytest.mark.skip('placeholder')
def test_cli_args():
pass

def test_cli_args(container, http_client):
"""Container should respect notebook server command line args
(e.g., disabling token security)"""
container.run(
command=['start-notebook.sh', '--NotebookApp.token=""']
)
resp = http_client.get('http://localhost:8888')
resp.raise_for_status()
assert 'login_submit' not in resp.text


@pytest.mark.filterwarnings('ignore:Unverified HTTPS request')
def test_unsigned_ssl(container, http_client):
"""Container should generate a self-signed SSL certificate
and notebook server should use it to enable HTTPS.
"""
c = container.run(
environment=['GEN_CERT=yes']
)
# NOTE: The requests.Session backing the http_client fixture does not retry
# properly while the server is booting up. An SSL handshake error seems to
# abort the retry logic. Forcing a long sleep for the moment until I have
# time to dig more.
time.sleep(5)
resp = http_client.get('https://localhost:8888', verify=False)
resp.raise_for_status()
assert 'login_submit' in resp.text

@pytest.mark.skip('placeholder')
def test_unsigned_ssl():
pass

@pytest.mark.skip('placeholder')
def test_uid_change():
Expand Down
101 changes: 101 additions & 0 deletions conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
import os

import docker
import pytest
import requests

from requests.packages.urllib3.util.retry import Retry
from requests.adapters import HTTPAdapter


@pytest.fixture(scope='session')
def http_client():
"""Requests session with retries and backoff."""
s = requests.Session()
retries = Retry(total=5, backoff_factor=1)
s.mount('http://', HTTPAdapter(max_retries=retries))
s.mount('https://', HTTPAdapter(max_retries=retries))
return s


@pytest.fixture(scope='session')
def docker_client():
"""Docker client configured based on the host environment"""
return docker.from_env()


@pytest.fixture(scope='session')
def image_name():
"""Image name to test"""
return os.getenv('TEST_IMAGE', 'jupyter/base-notebook')


class TrackedContainer(object):
"""Wrapper that collects docker container configuration and delays
container creation/execution.
Parameters
----------
docker_client: docker.DockerClient
Docker client instance
image_name: str
Name of the docker image to launch
**kwargs: dict, optional
Default keyword arguments to pass to docker.DockerClient.containers.run
"""
def __init__(self, docker_client, image_name, **kwargs):
self.container = None
self.docker_client = docker_client
self.image_name = image_name
self.kwargs = kwargs

def run(self, **kwargs):
"""Runs a docker container using the preconfigured image name
and a mix of the preconfigured container options and those passed
to this method.
Keeps track of the docker.Container instance spawned to kill it
later.
Parameters
----------
**kwargs: dict, optional
Keyword arguments to pass to docker.DockerClient.containers.run
extending and/or overriding key/value pairs passed to the constructor
Returns
-------
docker.Container
"""
all_kwargs = {}
all_kwargs.update(self.kwargs)
all_kwargs.update(kwargs)
self.container = self.docker_client.containers.run(self.image_name, **all_kwargs)
return self.container

def kill(self):
"""Kills the tracked docker container."""
if self.container:
self.container.kill()


@pytest.fixture(scope='function')
def container(docker_client, image_name):
"""Notebook container with initial configuration appropriate for testing
(e.g., HTTP port exposed to the host for HTTP calls).
Yields the container instance and kills it when the caller is done with it.
"""
container = TrackedContainer(
docker_client,
image_name,
detach=True,
auto_remove=False,
ports={
'8888/tcp': 8888
}
)
yield container
container.kill()
55 changes: 6 additions & 49 deletions test/test_notebook.py
Original file line number Diff line number Diff line change
@@ -1,52 +1,9 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
import os
import time

import docker
import pytest
import requests


@pytest.fixture(scope='session')
def docker_client():
"""Docker client to use"""
return docker.from_env()


@pytest.fixture(scope='session')
def image_name():
"""Image name to test"""
return os.getenv('TEST_IMAGE', 'jupyter/base-notebook')


@pytest.fixture(scope='function')
def nb_container(docker_client, image_name):
"""Notebook container to test"""
container = docker_client.containers.run(
image_name,
detach=True,
auto_remove=True,
ports={
'8888/tcp': 8888
}
)
yield container
container.kill()


def test_server_liveliness(nb_container):
"""Notebook server should eventually respond with HTTP 200 OK."""
for i in range(10):
try:
resp = requests.get('http://localhost:8888')
except requests.exceptions.ConnectionError:
# Wait a bit and try again. Just because the docker container
# is running doesn't mean the notebook server is ready to accept
# connections inside it.
time.sleep(i)
else:
assert resp.status_code == 200
break
else:
assert False, 'could not connect to server'
def test_secured_server(container, http_client):
"""Notebook server should eventually request user login."""
container.run()
resp = http_client.get('http://localhost:8888')
resp.raise_for_status()
assert 'login_submit' in resp.text

0 comments on commit 8a59d74

Please sign in to comment.