Better tests directory structure (#2231)

This commit is contained in:
Ayaz Salikhov
2025-02-21 17:46:43 +00:00
committed by GitHub
parent 80f4426b8e
commit 165bbc1e71
67 changed files with 174 additions and 148 deletions

View File

@@ -0,0 +1,61 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This is a test for the issue [#1168](https://github.com/jupyter/docker-stacks/issues/1168)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from pyspark.sql import SparkSession\n",
"\n",
"# Spark session & context\n",
"spark = SparkSession.builder.master(\"local\").getOrCreate()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"df = spark.createDataFrame([(1, 21), (2, 30)], (\"id\", \"age\"))\n",
"\n",
"\n",
"def filter_func(iterator):\n",
" for pdf in iterator:\n",
" yield pdf[pdf.id == 1]\n",
"\n",
"\n",
"df.mapInPandas(filter_func, df.schema).show()"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.10"
}
},
"nbformat": 4,
"nbformat_minor": 4
}

View File

@@ -0,0 +1,43 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from pyspark.sql import SparkSession\n",
"\n",
"# Spark session & context\n",
"spark = SparkSession.builder.master(\"local\").getOrCreate()\n",
"sc = spark.sparkContext\n",
"\n",
"# Sum of the first 100 whole numbers\n",
"rdd = sc.parallelize(range(100 + 1))\n",
"rdd.sum()\n",
"# 5050"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.10"
}
},
"nbformat": 4,
"nbformat_minor": 4
}

View File

@@ -0,0 +1,41 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"library(SparkR)\n",
"\n",
"# Spark session & context\n",
"sc <- sparkR.session(\"local\")\n",
"\n",
"# Sum of the first 100 whole numbers\n",
"sdf <- createDataFrame(list(1:100))\n",
"dapplyCollect(sdf,\n",
" function(x) \n",
" { x <- sum(x)}\n",
" )\n",
"# 5050"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "R",
"language": "R",
"name": "ir"
},
"language_info": {
"codemirror_mode": "r",
"file_extension": ".r",
"mimetype": "text/x-r-source",
"name": "R",
"pygments_lexer": "r",
"version": "3.6.3"
}
},
"nbformat": 4,
"nbformat_minor": 4
}

View File

@@ -0,0 +1,43 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"library(sparklyr)\n",
"\n",
"# get the default config\n",
"conf <- spark_config()\n",
"# Set the catalog implementation in-memory\n",
"conf$spark.sql.catalogImplementation <- \"in-memory\"\n",
"\n",
"# Spark session & context\n",
"sc <- spark_connect(master = \"local\", config = conf)\n",
"\n",
"# Sum of the first 100 whole numbers\n",
"sdf_len(sc, 100, repartition = 1) %>% \n",
" spark_apply(function(e) sum(e))\n",
"# 5050"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "R",
"language": "R",
"name": "ir"
},
"language_info": {
"codemirror_mode": "r",
"file_extension": ".r",
"mimetype": "text/x-r-source",
"name": "R",
"pygments_lexer": "r",
"version": "3.6.3"
}
},
"nbformat": 4,
"nbformat_minor": 4
}

View File

@@ -0,0 +1,44 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
import logging
from pathlib import Path
import pytest # type: ignore
from tests.utils.tracked_container import TrackedContainer
LOGGER = logging.getLogger(__name__)
THIS_DIR = Path(__file__).parent.resolve()
@pytest.mark.flaky(retries=3, delay=1)
@pytest.mark.parametrize(
"test_file",
["issue_1168", "local_pyspark", "local_sparkR"],
)
def test_nbconvert(container: TrackedContainer, test_file: str) -> None:
"""Check if Spark notebooks can be executed"""
host_data_dir = THIS_DIR / "data"
cont_data_dir = "/home/jovyan/data"
output_dir = "/tmp"
conversion_timeout_ms = 5000
LOGGER.info(f"Test that {test_file} notebook can be executed ...")
command = (
"jupyter nbconvert --to markdown "
+ f"--ExecutePreprocessor.timeout={conversion_timeout_ms} "
+ f"--output-dir {output_dir} "
+ f"--execute {cont_data_dir}/{test_file}.ipynb"
)
logs = container.run_and_wait(
timeout=60,
no_warnings=False,
volumes={str(host_data_dir): {"bind": cont_data_dir, "mode": "ro"}},
tty=True,
command=["bash", "-c", command],
)
warnings = TrackedContainer.get_warnings(logs)
assert len(warnings) == 1
assert "Using incubator modules: jdk.incubator.vector" in warnings[0]
expected_file = f"{output_dir}/{test_file}.md"
assert expected_file in logs, f"Expected file {expected_file} not generated"

View File

@@ -0,0 +1,116 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
import logging
import time
import pytest # type: ignore
import requests
from tests.utils.find_free_port import find_free_port
from tests.utils.tracked_container import TrackedContainer
LOGGER = logging.getLogger(__name__)
def test_cli_args(container: TrackedContainer, http_client: requests.Session) -> None:
"""Image should respect command line args (e.g., disabling token security)"""
host_port = find_free_port()
running_container = container.run_detached(
command=["start-notebook.py", "--IdentityProvider.token=''"],
ports={"8888/tcp": host_port},
)
resp = http_client.get(f"http://localhost:{host_port}")
resp.raise_for_status()
logs = running_container.logs().decode("utf-8")
LOGGER.debug(logs)
assert "ERROR" not in logs
warnings = TrackedContainer.get_warnings(logs)
assert not warnings
assert "login_submit" not in resp.text
def test_nb_user_change(container: TrackedContainer) -> None:
"""Container should change the username (`NB_USER`) of the default user."""
nb_user = "nayvoj"
running_container = container.run_detached(
tty=True,
user="root",
environment=[f"NB_USER={nb_user}", "CHOWN_HOME=yes"],
command=["bash", "-c", "sleep infinity"],
)
# Give the chown time to complete.
# Use sleep, not wait, because the container sleeps forever.
time.sleep(1)
LOGGER.info(
f"Checking if a home folder of {nb_user} contains the hidden '.jupyter' folder with appropriate permissions ..."
)
command = f'stat -c "%F %U %G" /home/{nb_user}/.jupyter'
expected_output = f"directory {nb_user} users"
cmd = running_container.exec_run(command, workdir=f"/home/{nb_user}")
output = cmd.output.decode("utf-8").strip("\n")
assert (
output == expected_output
), f"Hidden folder .jupyter was not copied properly to {nb_user} home folder. stat: {output}, expected {expected_output}"
@pytest.mark.filterwarnings("ignore:Unverified HTTPS request")
def test_unsigned_ssl(
container: TrackedContainer, http_client: requests.Session
) -> None:
"""Container should generate a self-signed SSL certificate
and Jupyter Server should use it to enable HTTPS.
"""
host_port = find_free_port()
running_container = container.run_detached(
environment=["GEN_CERT=yes"],
ports={"8888/tcp": host_port},
)
# 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(1)
resp = http_client.get(f"https://localhost:{host_port}", verify=False)
resp.raise_for_status()
assert "login_submit" in resp.text
logs = running_container.logs().decode("utf-8")
assert "ERROR" not in logs
warnings = TrackedContainer.get_warnings(logs)
assert not warnings
@pytest.mark.parametrize(
"env",
[
{},
{"JUPYTER_PORT": 1234, "DOCKER_STACKS_JUPYTER_CMD": "lab"},
{"JUPYTER_PORT": 2345, "DOCKER_STACKS_JUPYTER_CMD": "notebook"},
{"JUPYTER_PORT": 3456, "DOCKER_STACKS_JUPYTER_CMD": "server"},
{"JUPYTER_PORT": 4567, "DOCKER_STACKS_JUPYTER_CMD": "nbclassic"},
{"JUPYTER_PORT": 5678, "RESTARTABLE": "yes"},
{"JUPYTER_PORT": 6789},
{"JUPYTER_PORT": 7890, "DOCKER_STACKS_JUPYTER_CMD": "notebook"},
],
)
def test_custom_internal_port(
container: TrackedContainer,
http_client: requests.Session,
env: dict[str, str],
) -> None:
"""Container should be accessible from the host
when using custom internal port"""
host_port = find_free_port()
internal_port = env.get("JUPYTER_PORT", 8888)
running_container = container.run_detached(
command=["start-notebook.py", "--IdentityProvider.token=''"],
environment=env,
ports={internal_port: host_port},
)
resp = http_client.get(f"http://localhost:{host_port}")
resp.raise_for_status()
logs = running_container.logs().decode("utf-8")
LOGGER.debug(logs)
assert "ERROR" not in logs
warnings = TrackedContainer.get_warnings(logs)
assert not warnings

View File

@@ -0,0 +1,162 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
import logging
import time
import pytest # type: ignore
from tests.utils.get_container_health import get_health
from tests.utils.tracked_container import TrackedContainer
LOGGER = logging.getLogger(__name__)
@pytest.mark.parametrize(
"env,cmd,user",
[
(None, None, None),
(["DOCKER_STACKS_JUPYTER_CMD=lab"], None, None),
(["DOCKER_STACKS_JUPYTER_CMD=notebook"], None, None),
(["DOCKER_STACKS_JUPYTER_CMD=server"], None, None),
(["DOCKER_STACKS_JUPYTER_CMD=nbclassic"], None, None),
(["RESTARTABLE=yes"], None, None),
(["JUPYTER_PORT=8171"], None, None),
(["JUPYTER_PORT=8117", "DOCKER_STACKS_JUPYTER_CMD=notebook"], None, None),
(None, ["start-notebook.sh"], None),
(None, ["start-notebook.py", "--ServerApp.base_url=/test"], None),
(None, ["start-notebook.py", "--ServerApp.base_url=/test/"], None),
(["GEN_CERT=1"], ["start-notebook.py", "--ServerApp.base_url=/test"], None),
(
["GEN_CERT=1", "JUPYTER_PORT=7891"],
["start-notebook.py", "--ServerApp.base_url=/test"],
None,
),
(["NB_USER=testuser", "CHOWN_HOME=1"], None, "root"),
(
["NB_USER=testuser", "CHOWN_HOME=1"],
["start-notebook.py", "--ServerApp.base_url=/test"],
"root",
),
(
["NB_USER=testuser", "CHOWN_HOME=1", "JUPYTER_PORT=8123"],
["start-notebook.py", "--ServerApp.base_url=/test"],
"root",
),
(["JUPYTER_RUNTIME_DIR=/tmp/jupyter-runtime"], ["start-notebook.sh"], None),
(
[
"NB_USER=testuser",
"CHOWN_HOME=1",
"JUPYTER_RUNTIME_DIR=/tmp/jupyter-runtime",
],
["start-notebook.sh"],
"root",
),
],
)
def test_healthy(
container: TrackedContainer,
env: list[str] | None,
cmd: list[str] | None,
user: str | None,
) -> None:
running_container = container.run_detached(
tty=True,
environment=env,
command=cmd,
user=user,
)
# giving some time to let the server start
finish_time = time.time() + 10
sleep_time = 0.1
while time.time() < finish_time:
time.sleep(sleep_time)
if get_health(running_container) == "healthy":
return
assert get_health(running_container) == "healthy"
@pytest.mark.parametrize(
"env,cmd,user",
[
(
[
"HTTPS_PROXY=https://host.docker.internal",
"HTTP_PROXY=http://host.docker.internal",
],
None,
None,
),
(
[
"NB_USER=testuser",
"CHOWN_HOME=1",
"JUPYTER_PORT=8123",
"HTTPS_PROXY=https://host.docker.internal",
"HTTP_PROXY=http://host.docker.internal",
],
["start-notebook.py", "--ServerApp.base_url=/test"],
"root",
),
],
)
def test_healthy_with_proxy(
container: TrackedContainer,
env: list[str] | None,
cmd: list[str] | None,
user: str | None,
) -> None:
running_container = container.run_detached(
tty=True,
environment=env,
command=cmd,
user=user,
)
# giving some time to let the server start
finish_time = time.time() + 10
sleep_time = 0.1
while time.time() < finish_time:
time.sleep(sleep_time)
if get_health(running_container) == "healthy":
return
assert get_health(running_container) == "healthy"
@pytest.mark.parametrize(
"env,cmd",
[
(["NB_USER=testuser", "CHOWN_HOME=1"], None),
(
["NB_USER=testuser", "CHOWN_HOME=1"],
["start-notebook.py", "--ServerApp.base_url=/test"],
),
(
["NB_USER=testuser", "CHOWN_HOME=1", "JUPYTER_PORT=8123"],
["start-notebook.py", "--ServerApp.base_url=/test"],
),
],
)
def test_not_healthy(
container: TrackedContainer,
env: list[str] | None,
cmd: list[str] | None,
) -> None:
running_container = container.run_detached(
tty=True,
environment=env,
command=cmd,
)
# giving some time to let the server start
finish_time = time.time() + 5
sleep_time = 0.1
while time.time() < finish_time:
time.sleep(sleep_time)
if get_health(running_container) == "healthy":
raise RuntimeError("Container should not be healthy for this testcase")
assert get_health(running_container) != "healthy"

View File

@@ -0,0 +1,17 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
import requests
from tests.utils.find_free_port import find_free_port
from tests.utils.tracked_container import TrackedContainer
def test_secured_server(
container: TrackedContainer, http_client: requests.Session
) -> None:
"""Jupyter Server should eventually request user login."""
host_port = find_free_port()
container.run_detached(ports={"8888/tcp": host_port})
resp = http_client.get(f"http://localhost:{host_port}")
resp.raise_for_status()
assert "login_submit" in resp.text, "User login not requested"

View File

@@ -0,0 +1,17 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
import logging
from tests.utils.tracked_container import TrackedContainer
LOGGER = logging.getLogger(__name__)
def test_pandoc(container: TrackedContainer) -> None:
"""Pandoc shall be able to convert MD to HTML."""
logs = container.run_and_wait(
timeout=10,
tty=True,
command=["bash", "-c", 'echo "**BOLD**" | pandoc'],
)
assert "<p><strong>BOLD</strong></p>" in logs

View File

@@ -0,0 +1,85 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
import logging
import time
import pytest # type: ignore
import requests
from tests.utils.find_free_port import find_free_port
from tests.utils.tracked_container import TrackedContainer
LOGGER = logging.getLogger(__name__)
@pytest.mark.parametrize(
"env,expected_command,expected_start,expected_warnings",
[
(None, "jupyter lab", True, []),
(["DOCKER_STACKS_JUPYTER_CMD=lab"], "jupyter lab", True, []),
(["RESTARTABLE=yes"], "run-one-constantly jupyter lab", True, []),
(["DOCKER_STACKS_JUPYTER_CMD=notebook"], "jupyter notebook", True, []),
(["DOCKER_STACKS_JUPYTER_CMD=server"], "jupyter server", True, []),
(["DOCKER_STACKS_JUPYTER_CMD=nbclassic"], "jupyter nbclassic", True, []),
(
["JUPYTERHUB_API_TOKEN=my_token"],
"jupyterhub-singleuser",
False,
["WARNING: using start-singleuser.py"],
),
],
)
def test_start_notebook(
container: TrackedContainer,
http_client: requests.Session,
env: list[str] | None,
expected_command: str,
expected_start: bool,
expected_warnings: list[str],
) -> None:
"""Test the notebook start-notebook.py script"""
LOGGER.info(
f"Test that the start-notebook.py launches the {expected_command} server from the env {env} ..."
)
host_port = find_free_port()
running_container = container.run_detached(
tty=True,
environment=env,
ports={"8888/tcp": host_port},
)
# sleeping some time to let the server start
time.sleep(2)
logs = running_container.logs().decode("utf-8")
LOGGER.debug(logs)
# checking that the expected command is launched
assert (
f"Executing: {expected_command}" in logs
), f"Not the expected command ({expected_command}) was launched"
# checking errors and warnings in logs
assert "ERROR" not in logs, "ERROR(s) found in logs"
for exp_warning in expected_warnings:
assert exp_warning in logs, f"Expected warning {exp_warning} not found in logs"
warnings = TrackedContainer.get_warnings(logs)
assert len(expected_warnings) == len(warnings)
# checking if the server is listening
if expected_start:
resp = http_client.get(f"http://localhost:{host_port}")
assert resp.status_code == 200, "Server is not listening"
def test_tini_entrypoint(
container: TrackedContainer, pid: int = 1, command: str = "tini"
) -> None:
"""Check that tini is launched as PID 1
Credits to the following answer for the ps options used in the test:
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} ...")
running_container = container.run_detached(tty=True)
# Select the PID 1 and get the corresponding command
cmd = running_container.exec_run(f"ps -p {pid} -o comm=")
output = cmd.output.decode("utf-8").strip("\n")
assert "ERROR" not in output
assert "WARNING" not in output
assert output == command, f"{command} shall be launched as pid {pid}, got {output}"

View File

@@ -0,0 +1,8 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
from tests.utils.run_command import run_command
from tests.utils.tracked_container import TrackedContainer
def test_julia(container: TrackedContainer) -> None:
run_command(container, "julia --version")

View File

@@ -0,0 +1,9 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
from tests.shared_checks.R_mimetype_check import check_r_mimetypes
from tests.utils.tracked_container import TrackedContainer
def test_mimetypes(container: TrackedContainer) -> None:
"""Check if Rscript command for mimetypes can be executed"""
check_r_mimetypes(container)

View File

@@ -0,0 +1,13 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
import requests
from tests.shared_checks.pluto_check import check_pluto_proxy
from tests.utils.tracked_container import TrackedContainer
def test_pluto_proxy(
container: TrackedContainer, http_client: requests.Session
) -> None:
"""Pluto proxy starts Pluto correctly"""
check_pluto_proxy(container, http_client)

View File

@@ -0,0 +1,6 @@
#!/bin/bash
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
export MY_VAR=123
echo "Inside a.sh MY_VAR variable has ${MY_VAR} value"

View File

@@ -0,0 +1,8 @@
#!/bin/bash
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
echo "Inside b.sh MY_VAR variable has ${MY_VAR} value"
echo "Changing value of MY_VAR"
export MY_VAR=456
echo "After change inside b.sh MY_VAR variable has ${MY_VAR} value"

View File

@@ -0,0 +1,5 @@
#!/bin/bash
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
echo "Inside c.sh MY_VAR variable has ${MY_VAR} value"

View File

@@ -0,0 +1,5 @@
#!/usr/bin/env python3
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
print("Executable python file was successfully run")

View File

@@ -0,0 +1,5 @@
#!/usr/bin/env python3
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
assert False

View File

@@ -0,0 +1,5 @@
#!/bin/bash
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
export SOME_VAR=123

View File

@@ -0,0 +1,11 @@
#!/bin/bash
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
echo "Started: a.sh"
export OTHER_VAR=456
run-unknown-command
echo "Finished: a.sh"

View File

@@ -0,0 +1,12 @@
#!/usr/bin/env python3
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
import os
import sys
print("Started: b.py")
print(f"OTHER_VAR={os.environ['OTHER_VAR']}")
sys.exit(1)
print("Finished: b.py")

View File

@@ -0,0 +1,7 @@
#!/bin/bash
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
echo "Started: c.sh"
run-unknown-command

View File

@@ -0,0 +1,11 @@
#!/bin/bash
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
set -e
echo "Started: d.sh"
run-unknown-command
echo "Finished: d.sh"

View File

@@ -0,0 +1,6 @@
#!/bin/bash
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
export MY_VAR=123
echo "Inside a.sh MY_VAR variable has ${MY_VAR} value"

View File

@@ -0,0 +1,7 @@
#!/bin/bash
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
echo "Inside b.sh MY_VAR variable has ${MY_VAR} value"
echo "Unsetting MY_VAR"
unset MY_VAR

View File

@@ -0,0 +1,5 @@
#!/bin/bash
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
echo "Inside c.sh MY_VAR variable has ${MY_VAR} value"

View File

@@ -0,0 +1,22 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
import logging
import pytest # type: ignore
from tests.utils.conda_package_helper import CondaPackageHelper
from tests.utils.tracked_container import TrackedContainer
LOGGER = logging.getLogger(__name__)
@pytest.mark.info
def test_outdated_packages(
container: TrackedContainer, requested_only: bool = True
) -> None:
"""Getting the list of updatable packages"""
LOGGER.info(f"Checking outdated packages in {container.image_name} ...")
pkg_helper = CondaPackageHelper(container)
pkg_helper.check_updatable_packages(requested_only)
LOGGER.info(pkg_helper.get_outdated_summary(requested_only))
LOGGER.info(f"\n{pkg_helper.get_outdated_table()}\n")

View File

@@ -0,0 +1,22 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
import pytest # type: ignore
from tests.utils.run_command import run_command
from tests.utils.tracked_container import TrackedContainer
@pytest.mark.parametrize(
"package_manager_command",
[
"apt --version",
"conda --version",
"mamba --version",
"pip --version",
],
)
def test_package_manager(
container: TrackedContainer, package_manager_command: str
) -> None:
"""Test that package managers are installed and run."""
run_command(container, package_manager_command)

View File

@@ -0,0 +1,188 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
"""
test_packages
~~~~~~~~~~~~~~~
This test module tests if the R and Python packages installed can be imported.
It's a basic test aiming to prove that the package is working properly.
The goal is to detect import errors that can be caused by incompatibilities between packages, for example:
- #1012: issue importing `sympy`
- #966: issue importing `pyarrow`
This module checks dynamically, through the `CondaPackageHelper`,
only the requested packages i.e. packages requested by `mamba install` in the `Dockerfile`s.
This means that it does not check dependencies.
This choice is a tradeoff to cover the main requirements while achieving a reasonable test duration.
However, it could be easily changed (or completed) to cover dependencies as well.
Use `package_helper.installed_packages()` instead of `package_helper.requested_packages()`.
Example:
$ make test/docker-stacks-foundation
# [...]
# tests/docker-stacks-foundation/test_packages.py::test_python_packages
# -------------------------------- live log setup --------------------------------
# 2024-01-21 17:46:43 [ INFO] Starting container quay.io/jupyter/docker-stacks-foundation ... (package_helper.py:55)
# 2024-01-21 17:46:43 [ INFO] Running quay.io/jupyter/docker-stacks-foundation with args {'detach': True, 'tty': True, 'command': ['bash', '-c', 'sleep infinity']} ... (conftest.py:99)
# 2024-01-21 17:46:44 [ INFO] Grabbing the list of manually requested packages ... (package_helper.py:83)
# -------------------------------- live log call ---------------------------------
# 2024-01-21 17:46:44 [ INFO] Testing the import of packages ... (test_packages.py:151)
# 2024-01-21 17:46:44 [ INFO] Trying to import mamba (test_packages.py:153)
# 2024-01-21 17:46:44 [ INFO] Trying to import jupyter_core (test_packages.py:153)
PASSED [ 17%]
# ------------------------------ live log teardown -------------------------------
# [...]
"""
import logging
from collections.abc import Callable, Iterable
import pytest # type: ignore
from tests.utils.conda_package_helper import CondaPackageHelper
from tests.utils.tracked_container import TrackedContainer
LOGGER = logging.getLogger(__name__)
# Mapping between package and module name
PACKAGE_MAPPING = {
# Python
"beautifulsoup4": "bs4",
"jupyter-pluto-proxy": "jupyter_pluto_proxy",
"matplotlib-base": "matplotlib",
"pytables": "tables",
"scikit-image": "skimage",
"scikit-learn": "sklearn",
# R
"randomforest": "randomForest",
"rcurl": "RCurl",
"rodbc": "RODBC",
"rsqlite": "DBI",
}
# List of packages that cannot be tested in a standard way
EXCLUDED_PACKAGES = [
"bzip2",
"ca-certificates",
"conda-forge::blas=*",
"grpcio-status",
"grpcio",
"hdf5",
"jupyterhub-singleuser",
"jupyterlab-git",
"mamba",
"notebook>",
"openssl",
"protobuf",
"python",
"r-irkernel",
"unixodbc",
]
@pytest.fixture(scope="function")
def package_helper(container: TrackedContainer) -> CondaPackageHelper:
"""Return a package helper object that can be used to perform tests on installed packages"""
return CondaPackageHelper(container)
@pytest.fixture(scope="function")
def requested_packages(package_helper: CondaPackageHelper) -> dict[str, set[str]]:
"""Return the list of requested packages (i.e. packages explicitly installed excluding dependencies)"""
return package_helper.requested_packages()
def is_r_package(package: str) -> bool:
"""Check if a package is an R package"""
return package.startswith("r-")
def get_package_import_name(package: str) -> str:
"""Perform a mapping between the package name and the name used for the import"""
if is_r_package(package):
package = package[2:]
return PACKAGE_MAPPING.get(package, package)
def _check_import_package(
package_helper: CondaPackageHelper, command: list[str]
) -> None:
"""Generic function executing a command"""
LOGGER.debug(f"Trying to import a package with [{command}] ...")
exec_result = package_helper.running_container.exec_run(command)
assert exec_result.exit_code == 0, exec_result.output.decode("utf-8")
def check_import_python_package(
package_helper: CondaPackageHelper, package: str
) -> None:
"""Try to import a Python package from the command line"""
_check_import_package(package_helper, ["python", "-c", f"import {package}"])
def check_import_r_package(package_helper: CondaPackageHelper, package: str) -> None:
"""Try to import an R package from the command line"""
_check_import_package(package_helper, ["R", "--slave", "-e", f"library({package})"])
def _check_import_packages(
package_helper: CondaPackageHelper,
packages_to_check: Iterable[str],
check_function: Callable[[CondaPackageHelper, str], None],
) -> None:
"""Test if packages can be imported
Note: using a list of packages instead of a fixture for the list of packages
since pytest prevents the use of multiple yields
"""
failed_imports = []
LOGGER.info("Testing the import of packages ...")
for package in packages_to_check:
LOGGER.info(f"Trying to import {package}")
try:
check_function(package_helper, package)
except AssertionError as err:
failed_imports.append(package)
LOGGER.error(f"Failed to import package: {package}, output:\n {err}")
if failed_imports:
pytest.fail(f"following packages are not import-able: {failed_imports}")
@pytest.fixture(scope="function")
def r_packages(requested_packages: dict[str, set[str]]) -> Iterable[str]:
"""Return an iterable of R packages"""
return (
get_package_import_name(pkg)
for pkg in requested_packages
if is_r_package(pkg) and pkg not in EXCLUDED_PACKAGES
)
def test_r_packages(
package_helper: CondaPackageHelper, r_packages: Iterable[str]
) -> None:
"""Test the import of specified R packages"""
_check_import_packages(package_helper, r_packages, check_import_r_package)
@pytest.fixture(scope="function")
def python_packages(requested_packages: dict[str, set[str]]) -> Iterable[str]:
"""Return an iterable of Python packages"""
return (
get_package_import_name(pkg)
for pkg in requested_packages
if not is_r_package(pkg) and pkg not in EXCLUDED_PACKAGES
)
def test_python_packages(
package_helper: CondaPackageHelper,
python_packages: Iterable[str],
) -> None:
"""Test the import of specified python packages"""
_check_import_packages(package_helper, python_packages, check_import_python_package)

View File

@@ -0,0 +1,34 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
import logging
from tests.utils.tracked_container import TrackedContainer
LOGGER = logging.getLogger(__name__)
EXPECTED_PYTHON_VERSION = "3.12"
def test_python_version(container: TrackedContainer) -> None:
LOGGER.info(
f"Checking that python major.minor version is {EXPECTED_PYTHON_VERSION}"
)
logs = container.run_and_wait(
timeout=5,
tty=True,
command=["python", "--version"],
)
python = next(line for line in logs.splitlines() if line.startswith("Python "))
full_version = python.split()[1]
major_minor_version = full_version[: full_version.rfind(".")]
assert major_minor_version == EXPECTED_PYTHON_VERSION
def test_python_pinned_version(container: TrackedContainer) -> None:
LOGGER.info(f"Checking that pinned python version is {EXPECTED_PYTHON_VERSION}.*")
logs = container.run_and_wait(
timeout=5,
tty=True,
command=["cat", "/opt/conda/conda-meta/pinned"],
)
assert f"python {EXPECTED_PYTHON_VERSION}.*" in logs

View File

@@ -0,0 +1,147 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
import logging
from pathlib import Path
from tests.utils.tracked_container import TrackedContainer
LOGGER = logging.getLogger(__name__)
THIS_DIR = Path(__file__).parent.resolve()
def test_run_hooks_zero_args(container: TrackedContainer) -> None:
logs = container.run_and_wait(
timeout=5,
tty=True,
no_failure=False,
command=["bash", "-c", "source /usr/local/bin/run-hooks.sh"],
)
assert "Should pass exactly one directory" in logs
def test_run_hooks_two_args(container: TrackedContainer) -> None:
logs = container.run_and_wait(
timeout=5,
tty=True,
no_failure=False,
command=[
"bash",
"-c",
"source /usr/local/bin/run-hooks.sh first-arg second-arg",
],
)
assert "Should pass exactly one directory" in logs
def test_run_hooks_missing_dir(container: TrackedContainer) -> None:
logs = container.run_and_wait(
timeout=5,
tty=True,
no_failure=False,
command=[
"bash",
"-c",
"source /usr/local/bin/run-hooks.sh /tmp/missing-dir/",
],
)
assert "Directory /tmp/missing-dir/ doesn't exist or is not a directory" in logs
def test_run_hooks_dir_is_file(container: TrackedContainer) -> None:
logs = container.run_and_wait(
timeout=5,
tty=True,
no_failure=False,
command=[
"bash",
"-c",
"touch /tmp/some-file && source /usr/local/bin/run-hooks.sh /tmp/some-file",
],
)
assert "Directory /tmp/some-file doesn't exist or is not a directory" in logs
def test_run_hooks_empty_dir(container: TrackedContainer) -> None:
container.run_and_wait(
timeout=5,
tty=True,
command=[
"bash",
"-c",
"mkdir /tmp/empty-dir && source /usr/local/bin/run-hooks.sh /tmp/empty-dir/",
],
)
def run_source_in_dir(
container: TrackedContainer,
subdir: str,
command_suffix: str = "",
no_failure: bool = True,
) -> str:
host_data_dir = THIS_DIR / subdir
cont_data_dir = "/home/jovyan/data"
# https://forums.docker.com/t/all-files-appear-as-executable-in-file-paths-using-bind-mount/99921
# Unfortunately, Docker treats all files in mounter dir as executable files
# So we make a copy of the mounted dir inside a container
command = (
"cp -r /home/jovyan/data/ /home/jovyan/data-copy/ &&"
"source /usr/local/bin/run-hooks.sh /home/jovyan/data-copy/" + command_suffix
)
return container.run_and_wait(
timeout=5,
volumes={str(host_data_dir): {"bind": cont_data_dir, "mode": "ro"}},
tty=True,
no_failure=no_failure,
command=["bash", "-c", command],
)
def test_run_hooks_executables(container: TrackedContainer) -> None:
logs = run_source_in_dir(
container,
subdir="run-hooks-executables",
command_suffix="&& echo SOME_VAR is ${SOME_VAR}",
)
assert "Executable python file was successfully run" in logs
assert "Ignoring non-executable: /home/jovyan/data-copy//non_executable.py" in logs
assert "SOME_VAR is 123" in logs
def test_run_hooks_with_failures(container: TrackedContainer) -> None:
logs = run_source_in_dir(container, subdir="run-hooks-failures", no_failure=False)
for file in ["a.sh", "b.py", "c.sh", "d.sh"]:
assert f"Started: {file}" in logs
for file in ["a.sh"]:
assert f"Finished: {file}" in logs
for file in ["b.py", "c.sh", "d.sh"]:
assert f"Finished: {file}" not in logs
for file in ["b.py", "c.sh"]:
assert (
f"/home/jovyan/data-copy//{file} has failed, continuing execution" in logs
)
assert "OTHER_VAR=456" in logs
def test_run_hooks_unset(container: TrackedContainer) -> None:
logs = run_source_in_dir(container, subdir="run-hooks-unset")
assert "Inside a.sh MY_VAR variable has 123 value" in logs
assert "Inside b.sh MY_VAR variable has 123 value" in logs
assert "Unsetting MY_VAR" in logs
assert "Inside c.sh MY_VAR variable has value" in logs
def test_run_hooks_change(container: TrackedContainer) -> None:
logs = run_source_in_dir(container, subdir="run-hooks-change")
assert "Inside a.sh MY_VAR variable has 123 value" in logs
assert "Inside b.sh MY_VAR variable has 123 value" in logs
assert "Changing value of MY_VAR" in logs
assert "After change inside b.sh MY_VAR variable has 456 value" in logs
assert "Inside c.sh MY_VAR variable has 456 value" in logs

View File

@@ -0,0 +1,38 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
import logging
from tests.hierarchy.images_hierarchy import get_test_dirs
from tests.utils.tracked_container import TrackedContainer
LOGGER = logging.getLogger(__name__)
def test_units(container: TrackedContainer) -> None:
"""Various units tests
Add a py file in the `tests/<somestack>/units` dir, and it will be automatically tested
"""
short_image_name = container.image_name[container.image_name.rfind("/") + 1 :]
LOGGER.info(f"Running unit tests for: {short_image_name}")
test_dirs = get_test_dirs(short_image_name)
for test_dir in test_dirs:
host_data_dir = test_dir / "units"
LOGGER.info(f"Searching for units tests in {host_data_dir}")
cont_data_dir = "/home/jovyan/data"
if not host_data_dir.exists():
LOGGER.info(f"Not found unit tests for image: {container.image_name}")
continue
for test_file in host_data_dir.iterdir():
test_file_name = test_file.name
LOGGER.info(f"Running unit test: {test_file_name}")
container.run_and_wait(
timeout=30,
volumes={str(host_data_dir): {"bind": cont_data_dir, "mode": "ro"}},
tty=True,
command=["python", f"{cont_data_dir}/{test_file_name}"],
)

View File

@@ -0,0 +1,346 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
import logging
import pathlib
import time
import pytest # type: ignore
from tests.utils.tracked_container import TrackedContainer
LOGGER = logging.getLogger(__name__)
def test_uid_change(container: TrackedContainer) -> None:
"""Container should change the UID of the default user."""
logs = container.run_and_wait(
timeout=120, # usermod is slow so give it some time
tty=True,
user="root",
environment=["NB_UID=1010"],
command=["bash", "-c", "id && touch /opt/conda/test-file"],
)
assert "uid=1010(jovyan)" in logs
def test_gid_change(container: TrackedContainer) -> None:
"""Container should change the GID of the default user."""
logs = container.run_and_wait(
timeout=10,
tty=True,
user="root",
environment=["NB_GID=110"],
command=["id"],
)
assert "gid=110(jovyan)" in logs
assert "groups=110(jovyan),100(users)" in logs
def test_nb_user_change(container: TrackedContainer) -> None:
"""Container should change the username (`NB_USER`) of the default user."""
nb_user = "nayvoj"
running_container = container.run_detached(
tty=True,
user="root",
environment=[f"NB_USER={nb_user}", "CHOWN_HOME=yes"],
command=["bash", "-c", "sleep infinity"],
)
# Give the chown time to complete.
# Use sleep, not wait, because the container sleeps forever.
time.sleep(1)
LOGGER.info(f"Checking if the user is changed to {nb_user} by the start script ...")
output = running_container.logs().decode("utf-8")
assert "ERROR" not in output
assert "WARNING" not in output
assert (
f"username: jovyan -> {nb_user}" in output
), f"User is not changed to {nb_user}"
LOGGER.info(f"Checking {nb_user} id ...")
command = "id"
expected_output = f"uid=1000({nb_user}) gid=100(users) groups=100(users)"
cmd = running_container.exec_run(command, user=nb_user, workdir=f"/home/{nb_user}")
output = cmd.output.decode("utf-8").strip("\n")
assert output == expected_output, f"Bad user {output}, expected {expected_output}"
LOGGER.info(f"Checking if {nb_user} owns his home folder ...")
command = f'stat -c "%U %G" /home/{nb_user}/'
expected_output = f"{nb_user} users"
cmd = running_container.exec_run(command, workdir=f"/home/{nb_user}")
output = cmd.output.decode("utf-8").strip("\n")
assert (
output == expected_output
), f"Bad owner for the {nb_user} home folder {output}, expected {expected_output}"
LOGGER.info(
f"Checking if a home folder of {nb_user} contains the 'work' folder with appropriate permissions ..."
)
command = f'stat -c "%F %U %G" /home/{nb_user}/work'
expected_output = f"directory {nb_user} users"
cmd = running_container.exec_run(command, workdir=f"/home/{nb_user}")
output = cmd.output.decode("utf-8").strip("\n")
assert (
output == expected_output
), f"Folder work was not copied properly to {nb_user} home folder. stat: {output}, expected {expected_output}"
def test_chown_extra(container: TrackedContainer) -> None:
"""Container should change the UID/GID of a comma-separated
CHOWN_EXTRA list of folders."""
logs = container.run_and_wait(
timeout=120, # chown is slow so give it some time
tty=True,
user="root",
environment=[
"NB_UID=1010",
"NB_GID=101",
"CHOWN_EXTRA=/home/jovyan,/opt/conda/bin",
"CHOWN_EXTRA_OPTS=-R",
],
command=[
"bash",
"-c",
"stat -c '%n:%u:%g' /home/jovyan/.bashrc /opt/conda/bin/jupyter",
],
)
assert "/home/jovyan/.bashrc:1010:101" in logs
assert "/opt/conda/bin/jupyter:1010:101" in logs
def test_chown_home(container: TrackedContainer) -> None:
"""Container should change the NB_USER home directory owner and
group to the current value of NB_UID and NB_GID."""
logs = container.run_and_wait(
timeout=120, # chown is slow so give it some time
tty=True,
user="root",
environment=[
"CHOWN_HOME=yes",
"CHOWN_HOME_OPTS=-R",
"NB_USER=kitten",
"NB_UID=1010",
"NB_GID=101",
],
command=["bash", "-c", "stat -c '%n:%u:%g' /home/kitten/.bashrc"],
)
assert "/home/kitten/.bashrc:1010:101" in logs
def test_sudo(container: TrackedContainer) -> None:
"""Container should grant passwordless sudo to the default user."""
logs = container.run_and_wait(
timeout=10,
tty=True,
user="root",
environment=["GRANT_SUDO=yes"],
command=["sudo", "id"],
)
assert "uid=0(root)" in logs
def test_sudo_path(container: TrackedContainer) -> None:
"""Container should include /opt/conda/bin in the sudo secure_path."""
logs = container.run_and_wait(
timeout=10,
tty=True,
user="root",
environment=["GRANT_SUDO=yes"],
command=["sudo", "which", "jupyter"],
)
assert logs.rstrip().endswith("/opt/conda/bin/jupyter")
def test_sudo_path_without_grant(container: TrackedContainer) -> None:
"""Container should include /opt/conda/bin in the sudo secure_path."""
logs = container.run_and_wait(
timeout=10,
tty=True,
user="root",
command=["which", "jupyter"],
)
assert logs.rstrip().endswith("/opt/conda/bin/jupyter")
def test_group_add(container: TrackedContainer) -> None:
"""Container should run with the specified uid, gid, and secondary
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.
"""
logs = container.run_and_wait(
timeout=5,
no_warnings=False,
user="1010:1010",
group_add=["users"], # Ensures write access to /home/jovyan
command=["id"],
)
warnings = TrackedContainer.get_warnings(logs)
assert len(warnings) == 1
assert "Try setting gid=0" in warnings[0]
assert "uid=1010 gid=1010 groups=1010,100(users)" in logs
def test_set_uid(container: TrackedContainer) -> None:
"""Container should run with the specified uid and NB_USER.
The /home/jovyan directory will not be writable since it's owned by 1000:users.
Additionally, verify that "--group-add=users" is suggested in a warning to restore
write access.
"""
logs = container.run_and_wait(
timeout=5,
no_warnings=False,
user="1010",
command=["id"],
)
assert "uid=1010(jovyan) gid=0(root)" in logs
warnings = TrackedContainer.get_warnings(logs)
assert len(warnings) == 1
assert "--group-add=users" in warnings[0]
def test_set_uid_and_nb_user(container: TrackedContainer) -> None:
"""Container should run with the specified uid and NB_USER."""
logs = container.run_and_wait(
timeout=5,
no_warnings=False,
user="1010",
environment=["NB_USER=kitten"],
group_add=["users"], # Ensures write access to /home/jovyan
command=["id"],
)
assert "uid=1010(kitten) gid=0(root)" in logs
warnings = TrackedContainer.get_warnings(logs)
assert len(warnings) == 1
assert "user is kitten but home is /home/jovyan" in warnings[0]
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)
-v bind mount flag and mapping to /home/jovyan.
"""
d = tmp_path / "data"
d.mkdir()
p = d / "foo.txt"
p.write_text("some-content")
container.run_and_wait(
timeout=5,
tty=True,
user="root",
working_dir="/home/",
environment=[
"NB_USER=user",
"CHOWN_HOME=yes",
],
volumes={d: {"bind": "/home/jovyan/data", "mode": "rw"}},
command=["ls"],
)
assert p.read_text() == "some-content"
assert len(list(tmp_path.iterdir())) == 1
@pytest.mark.parametrize("enable_root", [False, True])
def test_jupyter_env_vars_to_unset(
container: TrackedContainer, enable_root: bool
) -> None:
"""Environment variables names listed in JUPYTER_ENV_VARS_TO_UNSET
should be unset in the final environment."""
root_args = {"user": "root"} if enable_root else {}
logs = container.run_and_wait(
timeout=10,
tty=True,
environment=[
"JUPYTER_ENV_VARS_TO_UNSET=SECRET_ANIMAL,UNUSED_ENV,SECRET_FRUIT",
"FRUIT=bananas",
"SECRET_ANIMAL=cats",
"SECRET_FRUIT=mango",
],
command=[
"bash",
"-c",
"echo I like ${FRUIT} and ${SECRET_FRUIT:-stuff}, and love ${SECRET_ANIMAL:-to keep secrets}!",
],
**root_args, # type: ignore
)
assert "I like bananas and stuff, and love to keep secrets!" in logs
def test_secure_path(container: TrackedContainer, tmp_path: pathlib.Path) -> None:
"""Make sure that the sudo command has conda's python (not system's) on PATH.
See <https://github.com/jupyter/docker-stacks/issues/1053>.
"""
d = tmp_path / "data"
d.mkdir()
p = d / "wrong_python.sh"
p.write_text('#!/bin/bash\necho "Wrong python executable invoked!"')
p.chmod(0o755)
logs = container.run_and_wait(
timeout=5,
tty=True,
user="root",
volumes={p: {"bind": "/usr/bin/python", "mode": "ro"}},
command=["python", "--version"],
)
assert "Wrong python" not in logs
assert "Python" in logs
def test_startsh_multiple_exec(container: TrackedContainer) -> None:
"""If start.sh is executed multiple times check that configuration only occurs once."""
logs = container.run_and_wait(
timeout=10,
no_warnings=False,
tty=True,
user="root",
environment=["GRANT_SUDO=yes"],
command=["start.sh", "sudo", "id"],
)
assert "uid=0(root)" in logs
warnings = TrackedContainer.get_warnings(logs)
assert len(warnings) == 1
assert (
"WARNING: start.sh is the default ENTRYPOINT, do not include it in CMD"
in warnings[0]
)
def test_rootless_triplet_change(container: TrackedContainer) -> None:
"""Container should change the username (`NB_USER`), the UID and the GID of the default user."""
logs = container.run_and_wait(
timeout=10,
tty=True,
user="root",
environment=["NB_USER=root", "NB_UID=0", "NB_GID=0"],
command=["id"],
)
assert "uid=0(root)" in logs
assert "gid=0(root)" in logs
assert "groups=0(root)" in logs
def test_rootless_triplet_home(container: TrackedContainer) -> None:
"""Container should change the home directory for triplet NB_USER=root, NB_UID=0, NB_GID=0."""
logs = container.run_and_wait(
timeout=10,
tty=True,
user="root",
environment=["NB_USER=root", "NB_UID=0", "NB_GID=0"],
command=["bash", "-c", "echo HOME=${HOME} && getent passwd root"],
)
assert "HOME=/home/root" in logs
assert "root:x:0:0:root:/home/root:/bin/bash" in logs
def test_rootless_triplet_sudo(container: TrackedContainer) -> None:
"""Container should not be started with sudo for triplet NB_USER=root, NB_UID=0, NB_GID=0."""
logs = container.run_and_wait(
timeout=10,
tty=True,
user="root",
environment=["NB_USER=root", "NB_UID=0", "NB_GID=0"],
command=["env"],
)
assert "SUDO" not in logs

View File

@@ -0,0 +1,8 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
from tests.utils.run_command import run_command
from tests.utils.tracked_container import TrackedContainer
def test_julia(container: TrackedContainer) -> None:
run_command(container, "julia --version")

View File

@@ -0,0 +1,13 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
import requests
from tests.shared_checks.pluto_check import check_pluto_proxy
from tests.utils.tracked_container import TrackedContainer
def test_pluto_proxy(
container: TrackedContainer, http_client: requests.Session
) -> None:
"""Pluto proxy starts Pluto correctly"""
check_pluto_proxy(container, http_client)

View File

@@ -0,0 +1,90 @@
<svg width="44" height="51" viewBox="0 0 44 51" version="2.0" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:figma="http://www.figma.com/figma/ns">
<title>Group.svg</title>
<desc>Created using Figma 0.90</desc>
<g id="Canvas" transform="translate(-1640 -2453)" figma:type="canvas">
<g id="Group" style="mix-blend-mode:normal;" figma:type="group">
<g id="Group" style="mix-blend-mode:normal;" figma:type="group">
<g id="Group" style="mix-blend-mode:normal;" figma:type="group">
<g id="g" style="mix-blend-mode:normal;" figma:type="group">
<g id="path" style="mix-blend-mode:normal;" figma:type="group">
<g id="path9 fill" style="mix-blend-mode:normal;" figma:type="vector">
<use xlink:href="#path0_fill" transform="translate(1640.54 2474.36)" fill="#4E4E4E" style="mix-blend-mode:normal;"/>
</g>
</g>
<g id="path" style="mix-blend-mode:normal;" figma:type="group">
<g id="path10 fill" style="mix-blend-mode:normal;" figma:type="vector">
<use xlink:href="#path1_fill" transform="translate(1645.68 2474.37)" fill="#4E4E4E" style="mix-blend-mode:normal;"/>
</g>
</g>
<g id="path" style="mix-blend-mode:normal;" figma:type="group">
<g id="path11 fill" style="mix-blend-mode:normal;" figma:type="vector">
<use xlink:href="#path2_fill" transform="translate(1653.39 2474.26)" fill="#4E4E4E" style="mix-blend-mode:normal;"/>
</g>
</g>
<g id="path" style="mix-blend-mode:normal;" figma:type="group">
<g id="path12 fill" style="mix-blend-mode:normal;" figma:type="vector">
<use xlink:href="#path3_fill" transform="translate(1660.43 2474.39)" fill="#4E4E4E" style="mix-blend-mode:normal;"/>
</g>
</g>
<g id="path" style="mix-blend-mode:normal;" figma:type="group">
<g id="path13 fill" style="mix-blend-mode:normal;" figma:type="vector">
<use xlink:href="#path4_fill" transform="translate(1667.55 2472.54)" fill="#4E4E4E" style="mix-blend-mode:normal;"/>
</g>
</g>
<g id="path" style="mix-blend-mode:normal;" figma:type="group">
<g id="path14 fill" style="mix-blend-mode:normal;" figma:type="vector">
<use xlink:href="#path5_fill" transform="translate(1672.47 2474.29)" fill="#4E4E4E" style="mix-blend-mode:normal;"/>
</g>
</g>
<g id="path" style="mix-blend-mode:normal;" figma:type="group">
<g id="path15 fill" style="mix-blend-mode:normal;" figma:type="vector">
<use xlink:href="#path6_fill" transform="translate(1679.98 2474.24)" fill="#4E4E4E" style="mix-blend-mode:normal;"/>
</g>
</g>
</g>
</g>
<g id="g" style="mix-blend-mode:normal;" figma:type="group">
<g id="path" style="mix-blend-mode:normal;" figma:type="group">
<g id="path16 fill" style="mix-blend-mode:normal;" figma:type="vector">
<use xlink:href="#path7_fill" transform="translate(1673.48 2453.69)" fill="#767677" style="mix-blend-mode:normal;"/>
</g>
</g>
<g id="path" style="mix-blend-mode:normal;" figma:type="group">
<g id="path17 fill" style="mix-blend-mode:normal;" figma:type="vector">
<use xlink:href="#path8_fill" transform="translate(1643.21 2484.27)" fill="#F37726" style="mix-blend-mode:normal;"/>
</g>
</g>
<g id="path" style="mix-blend-mode:normal;" figma:type="group">
<g id="path18 fill" style="mix-blend-mode:normal;" figma:type="vector">
<use xlink:href="#path9_fill" transform="translate(1643.21 2457.88)" fill="#F37726" style="mix-blend-mode:normal;"/>
</g>
</g>
<g id="path" style="mix-blend-mode:normal;" figma:type="group">
<g id="path19 fill" style="mix-blend-mode:normal;" figma:type="vector">
<use xlink:href="#path10_fill" transform="translate(1643.28 2496.09)" fill="#9E9E9E" style="mix-blend-mode:normal;"/>
</g>
</g>
<g id="path" style="mix-blend-mode:normal;" figma:type="group">
<g id="path20 fill" style="mix-blend-mode:normal;" figma:type="vector">
<use xlink:href="#path11_fill" transform="translate(1641.87 2458.43)" fill="#616262" style="mix-blend-mode:normal;"/>
</g>
</g>
</g>
</g>
</g>
</g>
<defs>
<path id="path0_fill" d="M 1.74498 5.47533C 1.74498 7.03335 1.62034 7.54082 1.29983 7.91474C 0.943119 8.23595 0.480024 8.41358 0 8.41331L 0.124642 9.3036C 0.86884 9.31366 1.59095 9.05078 2.15452 8.56466C 2.45775 8.19487 2.6834 7.76781 2.818 7.30893C 2.95261 6.85005 2.99341 6.36876 2.93798 5.89377L 2.93798 0L 1.74498 0L 1.74498 5.43972L 1.74498 5.47533Z"/>
<path id="path1_fill" d="M 5.50204 4.76309C 5.50204 5.43081 5.50204 6.02731 5.55545 6.54368L 4.496 6.54368L 4.42478 5.48423C 4.20318 5.85909 3.88627 6.16858 3.50628 6.38125C 3.12628 6.59392 2.69675 6.70219 2.26135 6.69503C 1.22861 6.69503 0 6.13415 0 3.84608L 0 0.0445149L 1.193 0.0445149L 1.193 3.6057C 1.193 4.84322 1.57583 5.67119 2.65309 5.67119C 2.87472 5.67358 3.09459 5.63168 3.29982 5.54796C 3.50505 5.46424 3.69149 5.34039 3.84822 5.18366C 4.00494 5.02694 4.1288 4.84049 4.21252 4.63527C 4.29623 4.43004 4.33813 4.21016 4.33575 3.98853L 4.33575 0L 5.52874 0L 5.52874 4.72748L 5.50204 4.76309Z"/>
<path id="path2_fill" d="M 0.0534178 2.27264C 0.0534178 1.44466 0.0534178 0.768036 0 0.153731L 1.06836 0.153731L 1.12177 1.2666C 1.3598 0.864535 1.70247 0.534594 2.11325 0.311954C 2.52404 0.0893145 2.98754 -0.0176786 3.45435 0.00238095C 5.03908 0.00238095 6.23208 1.32892 6.23208 3.30538C 6.23208 5.63796 4.7987 6.79535 3.24958 6.79535C 2.85309 6.81304 2.45874 6.7281 2.10469 6.54874C 1.75064 6.36937 1.44888 6.10166 1.22861 5.77151L 1.22861 5.77151L 1.22861 9.33269L 0.0534178 9.33269L 0.0534178 2.29935L 0.0534178 2.27264ZM 1.22861 4.00872C 1.23184 4.17026 1.24972 4.33117 1.28203 4.48948C 1.38304 4.88479 1.61299 5.23513 1.93548 5.48506C 2.25798 5.735 2.65461 5.87026 3.06262 5.86944C 4.31794 5.86944 5.05689 4.8456 5.05689 3.3588C 5.05689 2.05897 4.36246 0.946096 3.10714 0.946096C 2.61036 0.986777 2.14548 1.20726 1.79965 1.5662C 1.45382 1.92514 1.25079 2.3979 1.22861 2.89585L 1.22861 4.00872Z"/>
<path id="path3_fill" d="M 1.31764 0.0178059L 2.75102 3.85499C 2.90237 4.28233 3.06262 4.7987 3.16946 5.18153C 3.2941 4.7898 3.42764 4.29123 3.5879 3.82828L 4.88773 0.0178059L 6.14305 0.0178059L 4.36246 4.64735C 3.47216 6.87309 2.92908 8.02158 2.11 8.71601C 1.69745 9.09283 1.19448 9.35658 0.649917 9.48166L 0.356119 8.48453C 0.736886 8.35942 1.09038 8.16304 1.39777 7.90584C 1.8321 7.55188 2.17678 7.10044 2.4038 6.5882C 2.45239 6.49949 2.48551 6.40314 2.50173 6.3033C 2.49161 6.19586 2.46457 6.0907 2.42161 5.9917L 0 0L 1.29983 0L 1.31764 0.0178059Z"/>
<path id="path4_fill" d="M 2.19013 0L 2.19013 1.86962L 3.8995 1.86962L 3.8995 2.75992L 2.19013 2.75992L 2.19013 6.26769C 2.19013 7.06896 2.42161 7.53191 3.08043 7.53191C 3.31442 7.53574 3.54789 7.5088 3.77486 7.45179L 3.82828 8.34208C 3.48794 8.45999 3.12881 8.51431 2.76882 8.50234C 2.53042 8.51726 2.29161 8.48043 2.06878 8.39437C 1.84595 8.30831 1.64438 8.17506 1.47789 8.00377C 1.11525 7.51873 0.949826 6.91431 1.01494 6.31221L 1.01494 2.75102L 0 2.75102L 0 1.86072L 1.03274 1.86072L 1.03274 0.275992L 2.19013 0Z"/>
<path id="path5_fill" d="M 1.17716 3.57899C 1.153 3.88093 1.19468 4.18451 1.29933 4.46876C 1.40398 4.75301 1.5691 5.01114 1.78329 5.22532C 1.99747 5.43951 2.2556 5.60463 2.53985 5.70928C 2.8241 5.81393 3.12768 5.85561 3.42962 5.83145C 4.04033 5.84511 4.64706 5.72983 5.21021 5.49313L 5.41498 6.38343C 4.72393 6.66809 3.98085 6.80458 3.23375 6.78406C 2.79821 6.81388 2.36138 6.74914 1.95322 6.59427C 1.54505 6.43941 1.17522 6.19809 0.869071 5.88688C 0.562928 5.57566 0.327723 5.2019 0.179591 4.79125C 0.0314584 4.38059 -0.0260962 3.94276 0.0108748 3.50777C 0.0108748 1.54912 1.17716 0 3.0824 0C 5.21911 0 5.75329 1.86962 5.75329 3.06262C 5.76471 3.24644 5.76471 3.43079 5.75329 3.61461L 1.15046 3.61461L 1.17716 3.57899ZM 4.66713 2.6887C 4.70149 2.45067 4.68443 2.20805 4.61709 1.97718C 4.54976 1.74631 4.43372 1.53255 4.2768 1.35031C 4.11987 1.16808 3.92571 1.0216 3.70739 0.920744C 3.48907 0.81989 3.25166 0.767006 3.01118 0.765656C 2.52201 0.801064 2.06371 1.01788 1.72609 1.37362C 1.38847 1.72935 1.19588 2.19835 1.18607 2.6887L 4.66713 2.6887Z"/>
<path id="path6_fill" d="M 0.0534178 2.19228C 0.0534178 1.42663 0.0534178 0.767806 0 0.162404L 1.06836 0.162404L 1.06836 1.43553L 1.12177 1.43553C 1.23391 1.04259 1.4656 0.694314 1.78468 0.439049C 2.10376 0.183783 2.4944 0.034196 2.90237 0.0110538C 3.01466 -0.00368459 3.12839 -0.00368459 3.24068 0.0110538L 3.24068 1.12393C 3.10462 1.10817 2.9672 1.10817 2.83114 1.12393C 2.427 1.13958 2.04237 1.30182 1.7491 1.58035C 1.45583 1.85887 1.27398 2.23462 1.23751 2.63743C 1.20422 2.8196 1.18635 3.00425 1.1841 3.18941L 1.1841 6.65267L 0.00890297 6.65267L 0.00890297 2.20118L 0.0534178 2.19228Z"/>
<path id="path7_fill" d="M 6.03059 2.83565C 6.06715 3.43376 5.92485 4.02921 5.6218 4.54615C 5.31875 5.0631 4.86869 5.47813 4.32893 5.73839C 3.78917 5.99864 3.18416 6.09233 2.59097 6.00753C 1.99778 5.92272 1.44326 5.66326 0.998048 5.26219C 0.552837 4.86113 0.23709 4.33661 0.0910307 3.75546C -0.0550287 3.17431 -0.0247891 2.56283 0.177897 1.99893C 0.380583 1.43503 0.746541 0.944221 1.22915 0.589037C 1.71176 0.233853 2.28918 0.0303686 2.88784 0.00450543C 3.28035 -0.0170932 3.67326 0.0391144 4.04396 0.169896C 4.41467 0.300677 4.75587 0.503453 5.04794 0.766561C 5.34 1.02967 5.57718 1.34792 5.74582 1.70301C 5.91446 2.0581 6.01124 2.44303 6.03059 2.83565L 6.03059 2.83565Z"/>
<path id="path8_fill" d="M 18.6962 7.12238C 10.6836 7.12238 3.64131 4.24672 0 0C 1.41284 3.82041 3.96215 7.1163 7.30479 9.44404C 10.6474 11.7718 14.623 13.0196 18.6962 13.0196C 22.7695 13.0196 26.745 11.7718 30.0877 9.44404C 33.4303 7.1163 35.9796 3.82041 37.3925 4.0486e-13C 33.7601 4.24672 26.7445 7.12238 18.6962 7.12238Z"/>
<path id="path9_fill" d="M 18.6962 5.89725C 26.7089 5.89725 33.7512 8.77291 37.3925 13.0196C 35.9796 9.19922 33.4303 5.90333 30.0877 3.57559C 26.745 1.24785 22.7695 4.0486e-13 18.6962 0C 14.623 4.0486e-13 10.6474 1.24785 7.30479 3.57559C 3.96215 5.90333 1.41284 9.19922 0 13.0196C 3.64131 8.76401 10.648 5.89725 18.6962 5.89725Z"/>
<path id="path10_fill" d="M 7.59576 3.56656C 7.64276 4.31992 7.46442 5.07022 7.08347 5.72186C 6.70251 6.3735 6.13619 6.89698 5.45666 7.22561C 4.77713 7.55424 4.01515 7.67314 3.26781 7.56716C 2.52046 7.46117 1.82158 7.13511 1.26021 6.63051C 0.698839 6.12591 0.300394 5.46561 0.115637 4.73375C -0.0691191 4.00188 -0.0318219 3.23159 0.222777 2.52099C 0.477376 1.8104 0.93775 1.19169 1.54524 0.743685C 2.15274 0.295678 2.87985 0.0386595 3.63394 0.00537589C 4.12793 -0.0210471 4.62229 0.0501173 5.08878 0.214803C 5.55526 0.37949 5.98473 0.63447 6.35264 0.965179C 6.72055 1.29589 7.01971 1.69584 7.233 2.1422C 7.4463 2.58855 7.56957 3.07256 7.59576 3.56656L 7.59576 3.56656Z"/>
<path id="path11_fill" d="M 2.25061 4.37943C 1.81886 4.39135 1.39322 4.27535 1.02722 4.04602C 0.661224 3.81668 0.371206 3.48424 0.193641 3.09052C 0.0160762 2.69679 -0.0411078 2.25935 0.0292804 1.83321C 0.0996686 1.40707 0.294486 1.01125 0.589233 0.695542C 0.883981 0.37983 1.2655 0.158316 1.68581 0.0588577C 2.10611 -0.0406005 2.54644 -0.0135622 2.95143 0.136572C 3.35641 0.286707 3.70796 0.553234 3.96186 0.902636C 4.21577 1.25204 4.3607 1.66872 4.37842 2.10027C 4.39529 2.6838 4.18131 3.25044 3.78293 3.67715C 3.38455 4.10387 2.83392 4.35623 2.25061 4.37943Z"/>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -0,0 +1,138 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "0",
"metadata": {},
"source": [
"# A simple SymPy example"
]
},
{
"cell_type": "markdown",
"id": "1",
"metadata": {},
"source": [
"First we import SymPy and initialize printing:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2",
"metadata": {
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [],
"source": [
"from sympy import diff, init_printing, integrate, sin, symbols"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3",
"metadata": {},
"outputs": [],
"source": [
"init_printing()"
]
},
{
"cell_type": "markdown",
"id": "4",
"metadata": {},
"source": [
"Create a few symbols:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5",
"metadata": {
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [],
"source": [
"x, y, z = symbols(\"x y z\")"
]
},
{
"cell_type": "markdown",
"id": "6",
"metadata": {},
"source": [
"Here is a basic expression:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7",
"metadata": {
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [],
"source": [
"e = x**2 + 2.0 * y + sin(z)\n",
"e"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8",
"metadata": {
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [],
"source": [
"diff(e, x)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9",
"metadata": {
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [],
"source": [
"integrate(e, z)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.10"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,43 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from IPython.display import SVG, display"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"display(SVG(filename=\"Jupyter_logo.svg\"))"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.10"
}
},
"nbformat": 4,
"nbformat_minor": 4
}

View File

@@ -0,0 +1,34 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
import logging
from pathlib import Path
import pytest # type: ignore
from tests.utils.tracked_container import TrackedContainer
LOGGER = logging.getLogger(__name__)
THIS_DIR = Path(__file__).parent.resolve()
@pytest.mark.parametrize("test_file", ["notebook_math", "notebook_svg"])
@pytest.mark.parametrize("output_format", ["pdf", "html"])
def test_nbconvert(
container: TrackedContainer, test_file: str, output_format: str
) -> None:
"""Check if nbconvert is able to convert a notebook file"""
host_data_dir = THIS_DIR / "data"
cont_data_dir = "/home/jovyan/data"
output_dir = "/tmp"
LOGGER.info(
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}"
logs = container.run_and_wait(
timeout=30,
volumes={str(host_data_dir): {"bind": cont_data_dir, "mode": "ro"}},
tty=True,
command=["bash", "-c", command],
)
expected_file = f"{output_dir}/{test_file}.{output_format}"
assert expected_file in logs, f"Expected file {expected_file} not generated"

View File

@@ -0,0 +1,22 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
import logging
from tests.utils.tracked_container import TrackedContainer
LOGGER = logging.getLogger(__name__)
def test_spark_shell(container: TrackedContainer) -> None:
"""Checking if Spark (spark-shell) is running properly"""
logs = container.run_and_wait(
timeout=60,
no_warnings=False,
tty=True,
command=["bash", "-c", 'spark-shell <<< "1+1"'],
)
warnings = TrackedContainer.get_warnings(logs)
assert len(warnings) == 1
assert "Using incubator modules: jdk.incubator.vector" in warnings[0]
assert "res0: Int = 2" in logs, "spark-shell does not work"

View File

@@ -0,0 +1,5 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
import pandas
assert pandas.__version__ == "2.2.2"

View File

@@ -0,0 +1,3 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
import pyspark # noqa: F401

View File

@@ -0,0 +1,5 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
import torch
print(torch.tensor([[1.0, 4.0, 7.0], [4.0, 9.0, 11.0]]))

View File

@@ -0,0 +1,9 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
from tests.shared_checks.R_mimetype_check import check_r_mimetypes
from tests.utils.tracked_container import TrackedContainer
def test_mimetypes(container: TrackedContainer) -> None:
"""Check if Rscript command for mimetypes can be executed"""
check_r_mimetypes(container)

View File

@@ -0,0 +1 @@
print("Hello World")

View File

@@ -0,0 +1,6 @@
# These lines are not sorted by isort on purpose
# see: https://stackoverflow.com/a/53356077/4881441
from setuptools import setup # isort:skip
from Cython.Build import cythonize # isort:skip
setup(ext_modules=cythonize("helloworld.pyx"))

View File

@@ -0,0 +1,27 @@
# type: ignore
# Matplotlib: Create a simple plot example.
# Refs: https://matplotlib.org/stable/gallery/lines_bars_and_markers/simple_plot.html
# Optional test with [Matplotlib Jupyter Integration](https://github.com/matplotlib/ipympl)
# %matplotlib widget
import matplotlib.pyplot as plt
import numpy as np
# Data for plotting
t = np.arange(0.0, 2.0, 0.01)
s = 1 + np.sin(2 * np.pi * t)
fig, ax = plt.subplots()
ax.plot(t, s)
ax.set(
xlabel="time (s)",
ylabel="voltage (mV)",
title="About as simple as it gets, folks",
)
ax.grid()
# Note that the test can be run headless by checking if an image is produced
file_path = "/tmp/test.png"
fig.savefig(file_path)
print(f"File {file_path} saved")

View File

@@ -0,0 +1,25 @@
# Matplotlib: Test tex fonts
import matplotlib
import matplotlib.pyplot as plt
matplotlib.rcParams["pgf.texsystem"] = "pdflatex"
matplotlib.rcParams.update(
{
"font.family": "serif",
"font.size": 18,
"axes.labelsize": 20,
"axes.titlesize": 24,
"figure.titlesize": 28,
}
)
matplotlib.rcParams["text.usetex"] = True
fig, ax = plt.subplots(1, 1)
x = [1, 2]
y = [1, 2]
ax.plot(x, y, label="a label")
ax.legend(fontsize=15)
file_path = "/tmp/test_fonts.png"
fig.savefig(file_path)
print(f"File {file_path} saved")

View File

@@ -0,0 +1,25 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
from pathlib import Path
from tests.utils.tracked_container import TrackedContainer
THIS_DIR = Path(__file__).parent.resolve()
def test_cython(container: TrackedContainer) -> None:
host_data_dir = THIS_DIR / "data/cython"
cont_data_dir = "/home/jovyan/data"
logs = container.run_and_wait(
timeout=10,
volumes={str(host_data_dir): {"bind": cont_data_dir, "mode": "ro"}},
tty=True,
command=[
"bash",
"-c",
# We copy our data to a temporary folder to be able to modify the directory
f"cp -r {cont_data_dir}/ /tmp/test/ && cd /tmp/test && python3 setup.py build_ext",
],
)
assert "building 'helloworld' extension" in logs

View File

@@ -0,0 +1,34 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
import logging
import pytest # type: ignore
from tests.utils.tracked_container import TrackedContainer
LOGGER = logging.getLogger(__name__)
@pytest.mark.skip(reason="Not yet compliant with JupyterLab 4")
@pytest.mark.parametrize(
"extension",
[
"@bokeh/jupyter_bokeh",
"@jupyter-widgets/jupyterlab-manager",
"jupyter-matplotlib",
],
)
def test_check_extension(container: TrackedContainer, extension: str) -> None:
"""Basic check of each extension
The list of installed extensions can be obtained through this command:
$ jupyter labextension list
"""
LOGGER.info(f"Checking the extension: {extension} ...")
container.run_and_wait(
timeout=10,
tty=True,
command=["jupyter", "labextension", "check", extension],
)

View File

@@ -0,0 +1,55 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
import logging
from pathlib import Path
import pytest # type: ignore
from tests.utils.tracked_container import TrackedContainer
LOGGER = logging.getLogger(__name__)
THIS_DIR = Path(__file__).parent.resolve()
@pytest.mark.parametrize(
"test_file,expected_file,description",
[
(
"matplotlib_1.py",
"test.png",
"Test that matplotlib can plot a graph and write it as an image ...",
),
(
"matplotlib_fonts_1.py",
"test_fonts.png",
"Test cm-super latex labels in matplotlib ...",
),
],
)
def test_matplotlib(
container: TrackedContainer, test_file: str, expected_file: str, description: str
) -> None:
"""Various tests performed on matplotlib
- Test that matplotlib is able to plot a graph and write it as an image
- Test matplotlib latex fonts, which depend on the cm-super package
"""
host_data_dir = THIS_DIR / "data/matplotlib"
cont_data_dir = "/home/jovyan/data"
output_dir = "/tmp"
LOGGER.info(description)
running_container = container.run_detached(
volumes={str(host_data_dir): {"bind": cont_data_dir, "mode": "ro"}},
tty=True,
command=["bash", "-c", "sleep infinity"],
)
command = f"python {cont_data_dir}/{test_file}"
cmd = running_container.exec_run(command)
LOGGER.debug(cmd.output.decode("utf-8"))
assert cmd.exit_code == 0, f"Command {command} failed"
# Checking if the file is generated
# https://stackoverflow.com/a/15895594/4413446
command = f"test -s {output_dir}/{expected_file}"
cmd = running_container.exec_run(command)
LOGGER.debug(cmd.output.decode("utf-8"))
assert cmd.exit_code == 0, f"Command {command} failed"

View File

@@ -0,0 +1,8 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
# type: ignore
import numpy as np
import pandas as pd
np.random.seed(0)
print(pd.Series(np.random.randint(0, 7, size=10)).sum())

View File

@@ -0,0 +1,6 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
import tensorflow as tf
print(tf.constant("Hello, TensorFlow"))
print(tf.reduce_sum(tf.random.normal([1000, 1000])))