mirror of
https://github.com/jupyter/docker-stacks.git
synced 2025-10-18 07:22:57 +00:00
Better tests directory structure (#2231)
This commit is contained in:
@@ -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
|
||||
}
|
@@ -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
|
||||
}
|
@@ -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
|
||||
}
|
@@ -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
|
||||
}
|
@@ -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"
|
@@ -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
|
162
tests/image_specific_tests/base-notebook/test_healthcheck.py
Normal file
162
tests/image_specific_tests/base-notebook/test_healthcheck.py
Normal 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"
|
17
tests/image_specific_tests/base-notebook/test_notebook.py
Normal file
17
tests/image_specific_tests/base-notebook/test_notebook.py
Normal 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"
|
17
tests/image_specific_tests/base-notebook/test_pandoc.py
Normal file
17
tests/image_specific_tests/base-notebook/test_pandoc.py
Normal 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
|
@@ -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}"
|
@@ -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")
|
@@ -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)
|
@@ -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)
|
@@ -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"
|
@@ -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"
|
@@ -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"
|
@@ -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")
|
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) Jupyter Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
|
||||
assert False
|
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
# Copyright (c) Jupyter Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
|
||||
export SOME_VAR=123
|
@@ -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"
|
12
tests/image_specific_tests/docker-stacks-foundation/run-hooks-failures/b.py
Executable file
12
tests/image_specific_tests/docker-stacks-foundation/run-hooks-failures/b.py
Executable 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")
|
@@ -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
|
@@ -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"
|
@@ -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"
|
@@ -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
|
@@ -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"
|
@@ -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")
|
@@ -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)
|
@@ -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)
|
@@ -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
|
@@ -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
|
@@ -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}"],
|
||||
)
|
@@ -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
|
8
tests/image_specific_tests/julia-notebook/test_julia.py
Normal file
8
tests/image_specific_tests/julia-notebook/test_julia.py
Normal 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")
|
13
tests/image_specific_tests/julia-notebook/test_pluto.py
Normal file
13
tests/image_specific_tests/julia-notebook/test_pluto.py
Normal 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)
|
@@ -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 |
@@ -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
|
||||
}
|
@@ -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
|
||||
}
|
@@ -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"
|
22
tests/image_specific_tests/pyspark-notebook/test_spark.py
Normal file
22
tests/image_specific_tests/pyspark-notebook/test_spark.py
Normal 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"
|
@@ -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"
|
@@ -0,0 +1,3 @@
|
||||
# Copyright (c) Jupyter Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
import pyspark # noqa: F401
|
@@ -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]]))
|
@@ -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)
|
@@ -0,0 +1 @@
|
||||
print("Hello World")
|
@@ -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"))
|
@@ -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")
|
@@ -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")
|
25
tests/image_specific_tests/scipy-notebook/test_cython.py
Normal file
25
tests/image_specific_tests/scipy-notebook/test_cython.py
Normal 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
|
34
tests/image_specific_tests/scipy-notebook/test_extensions.py
Normal file
34
tests/image_specific_tests/scipy-notebook/test_extensions.py
Normal 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],
|
||||
)
|
55
tests/image_specific_tests/scipy-notebook/test_matplotlib.py
Normal file
55
tests/image_specific_tests/scipy-notebook/test_matplotlib.py
Normal 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"
|
@@ -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())
|
@@ -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])))
|
Reference in New Issue
Block a user