Unify docker usage in tests

This commit is contained in:
Ayaz Salikhov
2022-01-22 00:46:20 +02:00
parent 2fee7b754c
commit 12b618c5da
17 changed files with 104 additions and 146 deletions

View File

@@ -30,14 +30,11 @@ def test_nbconvert(container: TrackedContainer, test_file: str) -> None:
+ f"--output-dir {output_dir} " + f"--output-dir {output_dir} "
+ f"--execute {cont_data_dir}/{test_file}.ipynb" + f"--execute {cont_data_dir}/{test_file}.ipynb"
) )
c = container.run( logs = container.run_and_wait(
timeout=timeout_ms / 10 + 10,
volumes={str(host_data_dir): {"bind": cont_data_dir, "mode": "ro"}}, volumes={str(host_data_dir): {"bind": cont_data_dir, "mode": "ro"}},
tty=True, tty=True,
command=["start.sh", "bash", "-c", command], command=["start.sh", "bash", "-c", command],
) )
rv = c.wait(timeout=timeout_ms / 10 + 10)
logs = c.logs(stdout=True).decode("utf-8")
LOGGER.debug(logs)
assert rv == 0 or rv["StatusCode"] == 0, f"Command {command} failed"
expected_file = f"{output_dir}/{test_file}.md" expected_file = f"{output_dir}/{test_file}.md"
assert expected_file in logs, f"Expected file {expected_file} not generated" assert expected_file in logs, f"Expected file {expected_file} not generated"

View File

@@ -1,5 +1,6 @@
# Copyright (c) Jupyter Development Team. # Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License. # Distributed under the terms of the Modified BSD License.
import pathlib
import time import time
import logging import logging
@@ -14,10 +15,12 @@ LOGGER = logging.getLogger(__name__)
def test_cli_args(container: TrackedContainer, http_client: requests.Session) -> None: def test_cli_args(container: TrackedContainer, http_client: requests.Session) -> None:
"""Container should respect notebook server command line args """Container should respect notebook server command line args
(e.g., disabling token security)""" (e.g., disabling token security)"""
c = container.run(command=["start-notebook.sh", "--NotebookApp.token=''"]) running_container = container.run_detached(
command=["start-notebook.sh", "--NotebookApp.token=''"]
)
resp = http_client.get("http://localhost:8888") resp = http_client.get("http://localhost:8888")
resp.raise_for_status() resp.raise_for_status()
logs = c.logs(stdout=True).decode("utf-8") logs = running_container.logs().decode("utf-8")
LOGGER.debug(logs) LOGGER.debug(logs)
assert "ERROR" not in logs assert "ERROR" not in logs
warnings = [ warnings = [
@@ -34,7 +37,7 @@ def test_unsigned_ssl(
"""Container should generate a self-signed SSL certificate """Container should generate a self-signed SSL certificate
and notebook server should use it to enable HTTPS. and notebook server should use it to enable HTTPS.
""" """
c = container.run(environment=["GEN_CERT=yes"]) running_container = container.run_detached(environment=["GEN_CERT=yes"])
# NOTE: The requests.Session backing the http_client fixture does not retry # 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 # 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 # abort the retry logic. Forcing a long sleep for the moment until I have
@@ -43,7 +46,7 @@ def test_unsigned_ssl(
resp = http_client.get("https://localhost:8888", verify=False) resp = http_client.get("https://localhost:8888", verify=False)
resp.raise_for_status() resp.raise_for_status()
assert "login_submit" in resp.text assert "login_submit" in resp.text
logs = c.logs(stdout=True).decode("utf-8") logs = running_container.logs().decode("utf-8")
assert "ERROR" not in logs assert "ERROR" not in logs
warnings = [ warnings = [
warning for warning in logs.split("\n") if warning.startswith("WARNING") warning for warning in logs.split("\n") if warning.startswith("WARNING")
@@ -53,34 +56,25 @@ def test_unsigned_ssl(
def test_uid_change(container: TrackedContainer) -> None: def test_uid_change(container: TrackedContainer) -> None:
"""Container should change the UID of the default user.""" """Container should change the UID of the default user."""
c = container.run( logs = container.run_and_wait(
timeout=120, # usermod is slow so give it some time
tty=True, tty=True,
user="root", user="root",
environment=["NB_UID=1010"], environment=["NB_UID=1010"],
command=["start.sh", "bash", "-c", "id && touch /opt/conda/test-file"], command=["start.sh", "bash", "-c", "id && touch /opt/conda/test-file"],
) )
# usermod is slow so give it some time assert "uid=1010(jovyan)" in logs
rv = c.wait(timeout=120)
logs = c.logs(stdout=True).decode("utf-8")
assert "ERROR" not in logs
assert "WARNING" not in logs
assert rv == 0 or rv["StatusCode"] == 0
assert "uid=1010(jovyan)" in c.logs(stdout=True).decode("utf-8")
def test_gid_change(container: TrackedContainer) -> None: def test_gid_change(container: TrackedContainer) -> None:
"""Container should change the GID of the default user.""" """Container should change the GID of the default user."""
c = container.run( logs = container.run_and_wait(
timeout=10,
tty=True, tty=True,
user="root", user="root",
environment=["NB_GID=110"], environment=["NB_GID=110"],
command=["start.sh", "id"], command=["start.sh", "id"],
) )
rv = c.wait(timeout=10)
assert rv == 0 or rv["StatusCode"] == 0
logs = c.logs(stdout=True).decode("utf-8")
assert "ERROR" not in logs
assert "WARNING" not in logs
assert "gid=110(jovyan)" in logs assert "gid=110(jovyan)" in logs
assert "groups=110(jovyan),100(users)" in logs assert "groups=110(jovyan),100(users)" in logs
@@ -88,7 +82,7 @@ def test_gid_change(container: TrackedContainer) -> None:
def test_nb_user_change(container: TrackedContainer) -> None: def test_nb_user_change(container: TrackedContainer) -> None:
"""Container should change the user name (`NB_USER`) of the default user.""" """Container should change the user name (`NB_USER`) of the default user."""
nb_user = "nayvoj" nb_user = "nayvoj"
running_container = container.run( running_container = container.run_detached(
tty=True, tty=True,
user="root", user="root",
environment=[f"NB_USER={nb_user}", "CHOWN_HOME=yes"], environment=[f"NB_USER={nb_user}", "CHOWN_HOME=yes"],
@@ -99,7 +93,7 @@ def test_nb_user_change(container: TrackedContainer) -> None:
# container sleeps forever. # container sleeps forever.
time.sleep(10) time.sleep(10)
LOGGER.info(f"Checking if the user is changed to {nb_user} by the start script ...") LOGGER.info(f"Checking if the user is changed to {nb_user} by the start script ...")
output = running_container.logs(stdout=True).decode("utf-8") output = running_container.logs().decode("utf-8")
assert "ERROR" not in output assert "ERROR" not in output
assert "WARNING" not in output assert "WARNING" not in output
assert ( assert (
@@ -137,7 +131,8 @@ def test_nb_user_change(container: TrackedContainer) -> None:
def test_chown_extra(container: TrackedContainer) -> None: def test_chown_extra(container: TrackedContainer) -> None:
"""Container should change the UID/GID of a comma separated """Container should change the UID/GID of a comma separated
CHOWN_EXTRA list of folders.""" CHOWN_EXTRA list of folders."""
c = container.run( logs = container.run_and_wait(
timeout=120, # chown is slow so give it some time
tty=True, tty=True,
user="root", user="root",
environment=[ environment=[
@@ -153,12 +148,6 @@ def test_chown_extra(container: TrackedContainer) -> None:
"stat -c '%n:%u:%g' /home/jovyan/.bashrc /opt/conda/bin/jupyter", "stat -c '%n:%u:%g' /home/jovyan/.bashrc /opt/conda/bin/jupyter",
], ],
) )
# chown is slow so give it some time
rv = c.wait(timeout=120)
assert rv == 0 or rv["StatusCode"] == 0
logs = c.logs(stdout=True).decode("utf-8")
assert "ERROR" not in logs
assert "WARNING" not in logs
assert "/home/jovyan/.bashrc:1010:101" in logs assert "/home/jovyan/.bashrc:1010:101" in logs
assert "/opt/conda/bin/jupyter:1010:101" in logs assert "/opt/conda/bin/jupyter:1010:101" in logs
@@ -166,7 +155,8 @@ def test_chown_extra(container: TrackedContainer) -> None:
def test_chown_home(container: TrackedContainer) -> None: def test_chown_home(container: TrackedContainer) -> None:
"""Container should change the NB_USER home directory owner and """Container should change the NB_USER home directory owner and
group to the current value of NB_UID and NB_GID.""" group to the current value of NB_UID and NB_GID."""
c = container.run( logs = container.run_and_wait(
timeout=120, # chown is slow so give it some time
tty=True, tty=True,
user="root", user="root",
environment=[ environment=[
@@ -178,58 +168,41 @@ def test_chown_home(container: TrackedContainer) -> None:
], ],
command=["start.sh", "bash", "-c", "stat -c '%n:%u:%g' /home/kitten/.bashrc"], command=["start.sh", "bash", "-c", "stat -c '%n:%u:%g' /home/kitten/.bashrc"],
) )
rv = c.wait(timeout=120)
assert rv == 0 or rv["StatusCode"] == 0
logs = c.logs(stdout=True).decode("utf-8")
assert "ERROR" not in logs
assert "WARNING" not in logs
assert "/home/kitten/.bashrc:1010:101" in logs assert "/home/kitten/.bashrc:1010:101" in logs
def test_sudo(container: TrackedContainer) -> None: def test_sudo(container: TrackedContainer) -> None:
"""Container should grant passwordless sudo to the default user.""" """Container should grant passwordless sudo to the default user."""
c = container.run( logs = container.run_and_wait(
timeout=10,
tty=True, tty=True,
user="root", user="root",
environment=["GRANT_SUDO=yes"], environment=["GRANT_SUDO=yes"],
command=["start.sh", "sudo", "id"], command=["start.sh", "sudo", "id"],
) )
rv = c.wait(timeout=10)
assert rv == 0 or rv["StatusCode"] == 0
logs = c.logs(stdout=True).decode("utf-8")
assert "ERROR" not in logs
assert "WARNING" not in logs
assert "uid=0(root)" in logs assert "uid=0(root)" in logs
def test_sudo_path(container: TrackedContainer) -> None: def test_sudo_path(container: TrackedContainer) -> None:
"""Container should include /opt/conda/bin in the sudo secure_path.""" """Container should include /opt/conda/bin in the sudo secure_path."""
c = container.run( logs = container.run_and_wait(
timeout=10,
tty=True, tty=True,
user="root", user="root",
environment=["GRANT_SUDO=yes"], environment=["GRANT_SUDO=yes"],
command=["start.sh", "sudo", "which", "jupyter"], command=["start.sh", "sudo", "which", "jupyter"],
) )
rv = c.wait(timeout=10)
assert rv == 0 or rv["StatusCode"] == 0
logs = c.logs(stdout=True).decode("utf-8")
assert "ERROR" not in logs
assert "WARNING" not in logs
assert logs.rstrip().endswith("/opt/conda/bin/jupyter") assert logs.rstrip().endswith("/opt/conda/bin/jupyter")
def test_sudo_path_without_grant(container: TrackedContainer) -> None: def test_sudo_path_without_grant(container: TrackedContainer) -> None:
"""Container should include /opt/conda/bin in the sudo secure_path.""" """Container should include /opt/conda/bin in the sudo secure_path."""
c = container.run( logs = container.run_and_wait(
timeout=10,
tty=True, tty=True,
user="root", user="root",
command=["start.sh", "which", "jupyter"], command=["start.sh", "which", "jupyter"],
) )
rv = c.wait(timeout=10)
assert rv == 0 or rv["StatusCode"] == 0
logs = c.logs(stdout=True).decode("utf-8")
assert "ERROR" not in logs
assert "WARNING" not in logs
assert logs.rstrip().endswith("/opt/conda/bin/jupyter") assert logs.rstrip().endswith("/opt/conda/bin/jupyter")
@@ -238,15 +211,13 @@ def test_group_add(container: TrackedContainer) -> None:
group. It won't be possible to modify /etc/passwd since gid is nonzero, so group. It won't be possible to modify /etc/passwd since gid is nonzero, so
additionally verify that setting gid=0 is suggested in a warning. additionally verify that setting gid=0 is suggested in a warning.
""" """
c = container.run( logs = container.run_and_wait(
timeout=5,
no_warnings=False,
user="1010:1010", user="1010:1010",
group_add=["users"], # Ensures write access to /home/jovyan group_add=["users"], # Ensures write access to /home/jovyan
command=["start.sh", "id"], command=["start.sh", "id"],
) )
rv = c.wait(timeout=5)
assert rv == 0 or rv["StatusCode"] == 0
logs = c.logs(stdout=True).decode("utf-8")
assert "ERROR" not in logs
warnings = [ warnings = [
warning for warning in logs.split("\n") if warning.startswith("WARNING") warning for warning in logs.split("\n") if warning.startswith("WARNING")
] ]
@@ -261,14 +232,12 @@ def test_set_uid(container: TrackedContainer) -> None:
Additionally verify that "--group-add=users" is suggested in a warning to restore Additionally verify that "--group-add=users" is suggested in a warning to restore
write access. write access.
""" """
c = container.run( logs = container.run_and_wait(
timeout=5,
no_warnings=False,
user="1010", user="1010",
command=["start.sh", "id"], command=["start.sh", "id"],
) )
rv = c.wait(timeout=5)
assert rv == 0 or rv["StatusCode"] == 0
logs = c.logs(stdout=True).decode("utf-8")
assert "ERROR" not in logs
assert "uid=1010(jovyan) gid=0(root)" in logs assert "uid=1010(jovyan) gid=0(root)" in logs
warnings = [ warnings = [
warning for warning in logs.split("\n") if warning.startswith("WARNING") warning for warning in logs.split("\n") if warning.startswith("WARNING")
@@ -279,16 +248,14 @@ def test_set_uid(container: TrackedContainer) -> None:
def test_set_uid_and_nb_user(container: TrackedContainer) -> None: def test_set_uid_and_nb_user(container: TrackedContainer) -> None:
"""Container should run with the specified uid and NB_USER.""" """Container should run with the specified uid and NB_USER."""
c = container.run( logs = container.run_and_wait(
timeout=5,
no_warnings=False,
user="1010", user="1010",
environment=["NB_USER=kitten"], environment=["NB_USER=kitten"],
group_add=["users"], # Ensures write access to /home/jovyan group_add=["users"], # Ensures write access to /home/jovyan
command=["start.sh", "id"], command=["start.sh", "id"],
) )
rv = c.wait(timeout=5)
assert rv == 0 or rv["StatusCode"] == 0
logs = c.logs(stdout=True).decode("utf-8")
assert "ERROR" not in logs
assert "uid=1010(kitten) gid=0(root)" in logs assert "uid=1010(kitten) gid=0(root)" in logs
warnings = [ warnings = [
warning for warning in logs.split("\n") if warning.startswith("WARNING") warning for warning in logs.split("\n") if warning.startswith("WARNING")
@@ -297,7 +264,9 @@ def test_set_uid_and_nb_user(container: TrackedContainer) -> None:
assert "user is kitten but home is /home/jovyan" in warnings[0] assert "user is kitten but home is /home/jovyan" in warnings[0]
def test_container_not_delete_bind_mount(container: TrackedContainer, tmp_path) -> None: def test_container_not_delete_bind_mount(
container: TrackedContainer, tmp_path: pathlib.Path
) -> None:
"""Container should not delete host system files when using the (docker) """Container should not delete host system files when using the (docker)
-v bind mount flag and mapping to /home/jovyan. -v bind mount flag and mapping to /home/jovyan.
""" """
@@ -306,7 +275,8 @@ def test_container_not_delete_bind_mount(container: TrackedContainer, tmp_path)
p = d / "foo.txt" p = d / "foo.txt"
p.write_text("some-content") p.write_text("some-content")
c = container.run( container.run_and_wait(
timeout=5,
tty=True, tty=True,
user="root", user="root",
working_dir="/home/", working_dir="/home/",
@@ -317,11 +287,6 @@ def test_container_not_delete_bind_mount(container: TrackedContainer, tmp_path)
volumes={d: {"bind": "/home/jovyan/data", "mode": "rw"}}, volumes={d: {"bind": "/home/jovyan/data", "mode": "rw"}},
command=["start.sh", "ls"], command=["start.sh", "ls"],
) )
rv = c.wait(timeout=5)
logs = c.logs(stdout=True).decode("utf-8")
assert "ERROR" not in logs
assert "WARNING" not in logs
assert rv == 0 or rv["StatusCode"] == 0
assert p.read_text() == "some-content" assert p.read_text() == "some-content"
assert len(list(tmp_path.iterdir())) == 1 assert len(list(tmp_path.iterdir())) == 1
@@ -333,7 +298,8 @@ def test_jupyter_env_vars_to_unset_as_root(
"""Environment variables names listed in JUPYTER_ENV_VARS_TO_UNSET """Environment variables names listed in JUPYTER_ENV_VARS_TO_UNSET
should be unset in the final environment.""" should be unset in the final environment."""
root_args = {"user": "root"} if enable_root else {} root_args = {"user": "root"} if enable_root else {}
c = container.run( logs = container.run_and_wait(
timeout=10,
tty=True, tty=True,
environment=[ environment=[
"JUPYTER_ENV_VARS_TO_UNSET=SECRET_ANIMAL,UNUSED_ENV,SECRET_FRUIT", "JUPYTER_ENV_VARS_TO_UNSET=SECRET_ANIMAL,UNUSED_ENV,SECRET_FRUIT",
@@ -349,9 +315,4 @@ def test_jupyter_env_vars_to_unset_as_root(
], ],
**root_args, **root_args,
) )
rv = c.wait(timeout=10)
assert rv == 0 or rv["StatusCode"] == 0
logs = c.logs(stdout=True).decode("utf-8")
assert "ERROR" not in logs
assert "WARNING" not in logs
assert "I like bananas and stuff, and love to keep secrets!" in logs assert "I like bananas and stuff, and love to keep secrets!" in logs

View File

@@ -26,15 +26,8 @@ def test_package_manager(
LOGGER.info( LOGGER.info(
f"Test that the package manager {package_manager} is working properly ..." f"Test that the package manager {package_manager} is working properly ..."
) )
c = container.run( container.run_and_wait(
timeout=5,
tty=True, tty=True,
command=["start.sh", "bash", "-c", f"{package_manager} {version_arg}"], command=["start.sh", "bash", "-c", f"{package_manager} {version_arg}"],
) )
rv = c.wait(timeout=5)
logs = c.logs(stdout=True).decode("utf-8")
LOGGER.debug(logs)
assert "ERROR" not in logs
assert "WARNING" not in logs
assert (
rv == 0 or rv["StatusCode"] == 0
), f"Package manager {package_manager} not working"

View File

@@ -10,13 +10,9 @@ LOGGER = logging.getLogger(__name__)
def test_pandoc(container: TrackedContainer) -> None: def test_pandoc(container: TrackedContainer) -> None:
"""Pandoc shall be able to convert MD to HTML.""" """Pandoc shall be able to convert MD to HTML."""
c = container.run( logs = container.run_and_wait(
timeout=10,
tty=True, tty=True,
command=["start.sh", "bash", "-c", 'echo "**BOLD**" | pandoc'], command=["start.sh", "bash", "-c", 'echo "**BOLD**" | pandoc'],
) )
c.wait(timeout=10)
logs = c.logs(stdout=True).decode("utf-8")
assert "ERROR" not in logs
assert "WARNING" not in logs
LOGGER.debug(logs)
assert "<p><strong>BOLD</strong></p>" in logs assert "<p><strong>BOLD</strong></p>" in logs

View File

@@ -14,15 +14,12 @@ def test_python_version(
) -> None: ) -> None:
"""Check that python version is lower than the next version""" """Check that python version is lower than the next version"""
LOGGER.info(f"Checking that python version is lower than {python_next_version}") LOGGER.info(f"Checking that python version is lower than {python_next_version}")
c = container.run( logs = container.run_and_wait(
timeout=5,
tty=True, tty=True,
command=["start.sh"], command=["start.sh", "python", "--version"],
) )
cmd = c.exec_run("python --version") actual_python_version = version.parse(logs.split()[1])
output = cmd.output.decode("utf-8")
assert "ERROR" not in output
assert "WARNING" not in output
actual_python_version = version.parse(output.split()[1])
assert actual_python_version < version.parse( assert actual_python_version < version.parse(
python_next_version python_next_version
), f"Python version shall be lower than {python_next_version}" ), f"Python version shall be lower than {python_next_version}"

View File

@@ -48,14 +48,14 @@ def test_start_notebook(
LOGGER.info( LOGGER.info(
f"Test that the start-notebook launches the {expected_command} server from the env {env} ..." f"Test that the start-notebook launches the {expected_command} server from the env {env} ..."
) )
c = container.run( running_container = container.run_detached(
tty=True, tty=True,
environment=env, environment=env,
command=["start-notebook.sh"], command=["start-notebook.sh"],
) )
# sleeping some time to let the server start # sleeping some time to let the server start
time.sleep(3) time.sleep(3)
logs = c.logs(stdout=True).decode("utf-8") logs = running_container.logs().decode("utf-8")
LOGGER.debug(logs) LOGGER.debug(logs)
# checking that the expected command is launched # checking that the expected command is launched
assert ( assert (
@@ -84,12 +84,12 @@ def test_tini_entrypoint(
https://superuser.com/questions/632979/if-i-know-the-pid-number-of-a-process-how-can-i-get-its-name https://superuser.com/questions/632979/if-i-know-the-pid-number-of-a-process-how-can-i-get-its-name
""" """
LOGGER.info(f"Test that {command} is launched as PID {pid} ...") LOGGER.info(f"Test that {command} is launched as PID {pid} ...")
c = container.run( running_container = container.run_detached(
tty=True, tty=True,
command=["start.sh"], command=["start.sh"],
) )
# Select the PID 1 and get the corresponding command # Select the PID 1 and get the corresponding command
cmd = c.exec_run(f"ps -p {pid} -o comm=") cmd = running_container.exec_run(f"ps -p {pid} -o comm=")
output = cmd.output.decode("utf-8").strip("\n") output = cmd.output.decode("utf-8").strip("\n")
assert "ERROR" not in output assert "ERROR" not in output
assert "WARNING" not in output assert "WARNING" not in output

View File

@@ -5,6 +5,7 @@ import logging
import typing import typing
import docker import docker
from docker.models.containers import Container
import pytest import pytest
import requests import requests
@@ -59,7 +60,7 @@ class TrackedContainer:
self.image_name = image_name self.image_name = image_name
self.kwargs = kwargs self.kwargs = kwargs
def run(self, **kwargs: typing.Any): def run_detached(self, **kwargs: typing.Any) -> Container:
"""Runs a docker container using the preconfigured image name """Runs a docker container using the preconfigured image name
and a mix of the preconfigured container options and those passed and a mix of the preconfigured container options and those passed
to this method. to this method.
@@ -85,6 +86,24 @@ class TrackedContainer:
) )
return self.container return self.container
def run_and_wait(
self,
timeout: int,
no_warnings: bool = True,
no_errors: bool = True,
**kwargs: typing.Any,
) -> str:
running_container = self.run_and_wait(**kwargs)
rv = running_container.wait(timeout=timeout)
logs = running_container.logs().decode("utf-8")
LOGGER.debug(logs)
if no_warnings:
assert "WARNING" not in logs
if no_errors:
assert "ERROR" not in logs
assert rv == 0 or rv["StatusCode"] == 0
return logs
def remove(self): def remove(self):
"""Kills and removes the tracked docker container.""" """Kills and removes the tracked docker container."""
if self.container: if self.container:

View File

@@ -10,7 +10,7 @@ LOGGER = logging.getLogger(__name__)
def test_julia(container: TrackedContainer) -> None: def test_julia(container: TrackedContainer) -> None:
"""Basic julia test""" """Basic julia test"""
LOGGER.info("Test that julia is correctly installed ...") LOGGER.info("Test that julia is correctly installed ...")
running_container = container.run( running_container = container.run_detached(
tty=True, tty=True,
command=["start.sh", "bash", "-c", "sleep infinity"], command=["start.sh", "bash", "-c", "sleep infinity"],
) )

View File

@@ -11,11 +11,9 @@ LOGGER = logging.getLogger(__name__)
def test_inkscape(container: TrackedContainer) -> None: def test_inkscape(container: TrackedContainer) -> None:
"""Inkscape shall be installed to be able to convert SVG files.""" """Inkscape shall be installed to be able to convert SVG files."""
LOGGER.info("Test that inkscape is working by printing its version ...") LOGGER.info("Test that inkscape is working by printing its version ...")
c = container.run( logs = container.run_and_wait(
timeout=10,
tty=True, tty=True,
command=["start.sh", "bash", "-c", "inkscape --version"], command=["start.sh", "bash", "-c", "inkscape --version"],
) )
c.wait(timeout=10)
logs = c.logs(stdout=True).decode("utf-8")
LOGGER.debug(logs)
assert "Inkscape" in logs, "Inkscape not installed or not working" assert "Inkscape" in logs, "Inkscape not installed or not working"

View File

@@ -6,6 +6,8 @@ import logging
import pytest import pytest
from pathlib import Path from pathlib import Path
from conftest import TrackedContainer
LOGGER = logging.getLogger(__name__) LOGGER = logging.getLogger(__name__)
THIS_DIR = Path(__file__).parent.resolve() THIS_DIR = Path(__file__).parent.resolve()
@@ -19,7 +21,9 @@ THIS_DIR = Path(__file__).parent.resolve()
("notebook_svg", "html"), ("notebook_svg", "html"),
], ],
) )
def test_nbconvert(container, test_file: str, output_format: str) -> None: def test_nbconvert(
container: TrackedContainer, test_file: str, output_format: str
) -> None:
"""Check if nbconvert is able to convert a notebook file""" """Check if nbconvert is able to convert a notebook file"""
host_data_dir = THIS_DIR / "data" host_data_dir = THIS_DIR / "data"
cont_data_dir = "/home/jovyan/data" cont_data_dir = "/home/jovyan/data"
@@ -28,14 +32,11 @@ def test_nbconvert(container, test_file: str, output_format: str) -> None:
f"Test that the example notebook {test_file} can be converted to {output_format} ..." f"Test that the example notebook {test_file} can be converted to {output_format} ..."
) )
command = f"jupyter nbconvert {cont_data_dir}/{test_file}.ipynb --output-dir {output_dir} --to {output_format}" command = f"jupyter nbconvert {cont_data_dir}/{test_file}.ipynb --output-dir {output_dir} --to {output_format}"
c = container.run( logs = container.run_and_wait(
timeout=30,
volumes={str(host_data_dir): {"bind": cont_data_dir, "mode": "ro"}}, volumes={str(host_data_dir): {"bind": cont_data_dir, "mode": "ro"}},
tty=True, tty=True,
command=["start.sh", "bash", "-c", command], command=["start.sh", "bash", "-c", command],
) )
rv = c.wait(timeout=30)
logs = c.logs(stdout=True).decode("utf-8")
LOGGER.debug(logs)
assert rv == 0 or rv["StatusCode"] == 0, f"Command {command} failed"
expected_file = f"{output_dir}/{test_file}.{output_format}" expected_file = f"{output_dir}/{test_file}.{output_format}"
assert expected_file in logs, f"Expected file {expected_file} not generated" assert expected_file in logs, f"Expected file {expected_file} not generated"

View File

@@ -10,11 +10,9 @@ LOGGER = logging.getLogger(__name__)
def test_spark_shell(container: TrackedContainer) -> None: def test_spark_shell(container: TrackedContainer) -> None:
"""Checking if Spark (spark-shell) is running properly""" """Checking if Spark (spark-shell) is running properly"""
c = container.run( logs = container.run_and_wait(
timeout=60,
tty=True, tty=True,
command=["start.sh", "bash", "-c", 'spark-shell <<< "1+1"'], command=["start.sh", "bash", "-c", 'spark-shell <<< "1+1"'],
) )
c.wait(timeout=60)
logs = c.logs(stdout=True).decode("utf-8")
LOGGER.debug(logs)
assert "res0: Int = 2" in logs, "spark-shell does not work" assert "res0: Int = 2" in logs, "spark-shell does not work"

View File

@@ -27,11 +27,8 @@ def test_check_extension(container: TrackedContainer, extension: str) -> None:
""" """
LOGGER.info(f"Checking the extension: {extension} ...") LOGGER.info(f"Checking the extension: {extension} ...")
c = container.run( container.run_and_wait(
timeout=10,
tty=True, tty=True,
command=["start.sh", "jupyter", "labextension", "check", extension], command=["start.sh", "jupyter", "labextension", "check", extension],
) )
rv = c.wait(timeout=10)
logs = c.logs(stdout=True).decode("utf-8")
LOGGER.debug(logs)
assert rv == 0 or rv["StatusCode"] == 0, f"Extension {extension} check failed"

View File

@@ -40,7 +40,7 @@ def test_matplotlib(
output_dir = "/tmp" output_dir = "/tmp"
LOGGER.info(description) LOGGER.info(description)
command = "sleep infinity" command = "sleep infinity"
running_container = container.run( running_container = container.run_detached(
volumes={str(host_data_dir): {"bind": cont_data_dir, "mode": "ro"}}, volumes={str(host_data_dir): {"bind": cont_data_dir, "mode": "ro"}},
tty=True, tty=True,
command=["start.sh", "bash", "-c", command], command=["start.sh", "bash", "-c", command],

View File

@@ -1,6 +1,8 @@
# Copyright (c) Jupyter Development Team. # Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License. # Distributed under the terms of the Modified BSD License.
from typing import Optional
import docker import docker
from docker.models.containers import Container
import logging import logging
@@ -14,12 +16,12 @@ class DockerRunner:
docker_client=docker.from_env(), docker_client=docker.from_env(),
command: str = "sleep infinity", command: str = "sleep infinity",
): ):
self.container = None self.container: Optional[Container] = None
self.image_name = image_name self.image_name: str = image_name
self.command = command self.command: str = command
self.docker_client = docker_client self.docker_client: docker.DockerClient = docker_client
def __enter__(self): def __enter__(self) -> Container:
LOGGER.info(f"Creating container for image {self.image_name} ...") LOGGER.info(f"Creating container for image {self.image_name} ...")
self.container = self.docker_client.containers.run( self.container = self.docker_client.containers.run(
image=self.image_name, image=self.image_name,
@@ -29,14 +31,16 @@ class DockerRunner:
LOGGER.info(f"Container {self.container.name} created") LOGGER.info(f"Container {self.container.name} created")
return self.container return self.container
def __exit__(self, exc_type, exc_value, traceback): def __exit__(self, exc_type, exc_value, traceback) -> None:
LOGGER.info(f"Removing container {self.container.name} ...") LOGGER.info(f"Removing container {self.container.name} ...")
if self.container: if self.container:
self.container.remove(force=True) self.container.remove(force=True)
LOGGER.info(f"Container {self.container.name} removed") LOGGER.info(f"Container {self.container.name} removed")
@staticmethod @staticmethod
def run_simple_command(container, cmd: str, print_result: bool = True): def run_simple_command(
container: Container, cmd: str, print_result: bool = True
) -> str:
LOGGER.info(f"Running cmd: '{cmd}' on container: {container}") LOGGER.info(f"Running cmd: '{cmd}' on container: {container}")
out = container.exec_run(cmd) out = container.exec_run(cmd)
result = out.output.decode("utf-8").rstrip() result = out.output.decode("utf-8").rstrip()

View File

@@ -50,7 +50,7 @@ class CondaPackageHelper:
def start_container(container: TrackedContainer): def start_container(container: TrackedContainer):
"""Start the TrackedContainer and return an instance of a running container""" """Start the TrackedContainer and return an instance of a running container"""
LOGGER.info(f"Starting container {container.image_name} ...") LOGGER.info(f"Starting container {container.image_name} ...")
return container.run( return container.run_detached(
tty=True, tty=True,
command=["start.sh", "bash", "-c", "sleep infinity"], command=["start.sh", "bash", "-c", "sleep infinity"],
) )

View File

@@ -10,7 +10,7 @@ def test_secured_server(
container: TrackedContainer, http_client: requests.Session container: TrackedContainer, http_client: requests.Session
) -> None: ) -> None:
"""Notebook server should eventually request user login.""" """Notebook server should eventually request user login."""
container.run() container.run_detached()
resp = http_client.get("http://localhost:8888") resp = http_client.get("http://localhost:8888")
resp.raise_for_status() resp.raise_for_status()
assert "login_submit" in resp.text, "User login not requested" assert "login_submit" in resp.text, "User login not requested"

View File

@@ -27,12 +27,9 @@ def test_units(container: TrackedContainer) -> None:
test_file_name = test_file.name test_file_name = test_file.name
LOGGER.info(f"Running unit test: {test_file_name}") LOGGER.info(f"Running unit test: {test_file_name}")
c = container.run( container.run_and_wait(
timeout=30,
volumes={str(host_data_dir): {"bind": cont_data_dir, "mode": "ro"}}, volumes={str(host_data_dir): {"bind": cont_data_dir, "mode": "ro"}},
tty=True, tty=True,
command=["start.sh", "python", f"{cont_data_dir}/{test_file_name}"], command=["start.sh", "python", f"{cont_data_dir}/{test_file_name}"],
) )
rv = c.wait(timeout=30)
logs = c.logs(stdout=True).decode("utf-8")
LOGGER.debug(logs)
assert rv == 0 or rv["StatusCode"] == 0