From 43608ba81386339d1e270c71402a904cb9f83939 Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Fri, 21 Feb 2025 13:34:24 +0000 Subject: [PATCH] Better tests directory structure --- tests/conftest.py | 113 +----------------- tests/hierarchy/__init__.py | 0 tests/{ => hierarchy}/images_hierarchy.py | 7 +- .../all-spark-notebook/data/issue_1168.ipynb | 0 .../data/local_pyspark.ipynb | 0 .../data/local_sparkR.ipynb | 0 .../data/local_sparklyr.ipynb | 0 .../test_spark_notebooks.py | 2 +- .../base-notebook/test_container_options.py | 3 +- .../base-notebook/test_healthcheck.py | 3 +- .../base-notebook/test_notebook.py | 3 +- .../base-notebook/test_pandoc.py | 2 +- .../base-notebook/test_start_container.py | 3 +- .../test_julia_datascience.py | 4 +- .../datascience-notebook/test_mimetypes.py | 4 +- .../test_pluto_datascience.py | 4 +- .../run-hooks-change/a.sh | 0 .../run-hooks-change/b.sh | 0 .../run-hooks-change/c.sh | 0 .../run-hooks-executables/executable.py | 0 .../run-hooks-executables/non_executable.py | 0 .../run-hooks-executables/run-me.sh | 0 .../run-hooks-failures/a.sh | 0 .../run-hooks-failures/b.py | 0 .../run-hooks-failures/c.sh | 0 .../run-hooks-failures/d.sh | 0 .../run-hooks-unset/a.sh | 0 .../run-hooks-unset/b.sh | 0 .../run-hooks-unset/c.sh | 0 .../docker-stacks-foundation/test_outdated.py | 4 +- .../test_package_managers.py | 4 +- .../docker-stacks-foundation/test_packages.py | 4 +- .../test_python_version.py | 2 +- .../test_run_hooks.py | 2 +- .../docker-stacks-foundation/test_units.py | 4 +- .../test_user_options.py | 2 +- .../julia-notebook/test_julia.py | 4 +- .../julia-notebook/test_pluto.py | 4 +- .../minimal-notebook/data/Jupyter_logo.svg | 0 .../minimal-notebook/data/notebook_math.ipynb | 0 .../minimal-notebook/data/notebook_svg.ipynb | 0 .../minimal-notebook/test_nbconvert.py | 2 +- .../pyspark-notebook/test_spark.py | 2 +- .../units/unit_pandas_version.py | 0 .../pyspark-notebook/units/unit_spark.py | 0 .../pytorch-notebook/units/unit_pytorch.py | 0 .../r-notebook/test_R_mimetypes.py | 4 +- .../scipy-notebook/data/cython/helloworld.pyx | 0 .../scipy-notebook/data/cython/setup.py | 0 .../data/matplotlib/matplotlib_1.py | 0 .../data/matplotlib/matplotlib_fonts_1.py | 0 .../scipy-notebook/test_cython.py | 2 +- .../scipy-notebook/test_extensions.py | 2 +- .../scipy-notebook/test_matplotlib.py | 2 +- .../scipy-notebook/units/unit_pandas.py | 0 .../units/unit_tensorflow.py | 0 tests/run_tests.py | 2 +- tests/{ => shared_checks}/R_mimetype_check.py | 2 +- tests/shared_checks/__init__.py | 0 tests/{ => shared_checks}/pluto_check.py | 3 +- tests/utils/__init__.py | 0 .../conda_package_helper.py} | 2 +- tests/utils/find_free_port.py | 12 ++ tests/utils/get_container_health.py | 10 ++ tests/{ => utils}/run_command.py | 2 +- tests/utils/tracked_container.py | 96 +++++++++++++++ 66 files changed, 173 insertions(+), 148 deletions(-) create mode 100644 tests/hierarchy/__init__.py rename tests/{ => hierarchy}/images_hierarchy.py (80%) rename tests/{ => image_specific_tests}/all-spark-notebook/data/issue_1168.ipynb (100%) rename tests/{ => image_specific_tests}/all-spark-notebook/data/local_pyspark.ipynb (100%) rename tests/{ => image_specific_tests}/all-spark-notebook/data/local_sparkR.ipynb (100%) rename tests/{ => image_specific_tests}/all-spark-notebook/data/local_sparklyr.ipynb (100%) rename tests/{ => image_specific_tests}/all-spark-notebook/test_spark_notebooks.py (96%) rename tests/{ => image_specific_tests}/base-notebook/test_container_options.py (97%) rename tests/{ => image_specific_tests}/base-notebook/test_healthcheck.py (97%) rename tests/{ => image_specific_tests}/base-notebook/test_notebook.py (82%) rename tests/{ => image_specific_tests}/base-notebook/test_pandoc.py (88%) rename tests/{ => image_specific_tests}/base-notebook/test_start_container.py (96%) rename tests/{ => image_specific_tests}/datascience-notebook/test_julia_datascience.py (65%) rename tests/{ => image_specific_tests}/datascience-notebook/test_mimetypes.py (67%) rename tests/{ => image_specific_tests}/datascience-notebook/test_pluto_datascience.py (71%) rename tests/{ => image_specific_tests}/docker-stacks-foundation/run-hooks-change/a.sh (100%) rename tests/{ => image_specific_tests}/docker-stacks-foundation/run-hooks-change/b.sh (100%) rename tests/{ => image_specific_tests}/docker-stacks-foundation/run-hooks-change/c.sh (100%) rename tests/{ => image_specific_tests}/docker-stacks-foundation/run-hooks-executables/executable.py (100%) rename tests/{ => image_specific_tests}/docker-stacks-foundation/run-hooks-executables/non_executable.py (100%) rename tests/{ => image_specific_tests}/docker-stacks-foundation/run-hooks-executables/run-me.sh (100%) rename tests/{ => image_specific_tests}/docker-stacks-foundation/run-hooks-failures/a.sh (100%) rename tests/{ => image_specific_tests}/docker-stacks-foundation/run-hooks-failures/b.py (100%) rename tests/{ => image_specific_tests}/docker-stacks-foundation/run-hooks-failures/c.sh (100%) rename tests/{ => image_specific_tests}/docker-stacks-foundation/run-hooks-failures/d.sh (100%) rename tests/{ => image_specific_tests}/docker-stacks-foundation/run-hooks-unset/a.sh (100%) rename tests/{ => image_specific_tests}/docker-stacks-foundation/run-hooks-unset/b.sh (100%) rename tests/{ => image_specific_tests}/docker-stacks-foundation/run-hooks-unset/c.sh (100%) rename tests/{ => image_specific_tests}/docker-stacks-foundation/test_outdated.py (84%) rename tests/{ => image_specific_tests}/docker-stacks-foundation/test_package_managers.py (82%) rename tests/{ => image_specific_tests}/docker-stacks-foundation/test_packages.py (98%) rename tests/{ => image_specific_tests}/docker-stacks-foundation/test_python_version.py (94%) rename tests/{ => image_specific_tests}/docker-stacks-foundation/test_run_hooks.py (98%) rename tests/{ => image_specific_tests}/docker-stacks-foundation/test_units.py (91%) rename tests/{ => image_specific_tests}/docker-stacks-foundation/test_user_options.py (99%) rename tests/{ => image_specific_tests}/julia-notebook/test_julia.py (65%) rename tests/{ => image_specific_tests}/julia-notebook/test_pluto.py (71%) rename tests/{ => image_specific_tests}/minimal-notebook/data/Jupyter_logo.svg (100%) rename tests/{ => image_specific_tests}/minimal-notebook/data/notebook_math.ipynb (100%) rename tests/{ => image_specific_tests}/minimal-notebook/data/notebook_svg.ipynb (100%) rename tests/{ => image_specific_tests}/minimal-notebook/test_nbconvert.py (95%) rename tests/{ => image_specific_tests}/pyspark-notebook/test_spark.py (91%) rename tests/{ => image_specific_tests}/pyspark-notebook/units/unit_pandas_version.py (100%) rename tests/{ => image_specific_tests}/pyspark-notebook/units/unit_spark.py (100%) rename tests/{ => image_specific_tests}/pytorch-notebook/units/unit_pytorch.py (100%) rename tests/{ => image_specific_tests}/r-notebook/test_R_mimetypes.py (67%) rename tests/{ => image_specific_tests}/scipy-notebook/data/cython/helloworld.pyx (100%) rename tests/{ => image_specific_tests}/scipy-notebook/data/cython/setup.py (100%) rename tests/{ => image_specific_tests}/scipy-notebook/data/matplotlib/matplotlib_1.py (100%) rename tests/{ => image_specific_tests}/scipy-notebook/data/matplotlib/matplotlib_fonts_1.py (100%) rename tests/{ => image_specific_tests}/scipy-notebook/test_cython.py (92%) rename tests/{ => image_specific_tests}/scipy-notebook/test_extensions.py (93%) rename tests/{ => image_specific_tests}/scipy-notebook/test_matplotlib.py (96%) rename tests/{ => image_specific_tests}/scipy-notebook/units/unit_pandas.py (100%) rename tests/{ => image_specific_tests}/tensorflow-notebook/units/unit_tensorflow.py (100%) rename tests/{ => shared_checks}/R_mimetype_check.py (93%) create mode 100644 tests/shared_checks/__init__.py rename tests/{ => shared_checks}/pluto_check.py (87%) create mode 100644 tests/utils/__init__.py rename tests/{package_helper.py => utils/conda_package_helper.py} (99%) create mode 100644 tests/utils/find_free_port.py create mode 100644 tests/utils/get_container_health.py rename tests/{ => utils}/run_command.py (89%) create mode 100644 tests/utils/tracked_container.py diff --git a/tests/conftest.py b/tests/conftest.py index 6df248a5e9..894ad83ce3 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,33 +1,15 @@ # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. -import logging import os -import socket -from contextlib import closing -from typing import Any +from collections.abc import Generator import docker import pytest # type: ignore import requests -from docker.models.containers import Container from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry -LOGGER = logging.getLogger(__name__) - - -def find_free_port() -> str: - """Returns the available host port. Can be called in multiple threads/processes.""" - with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s: - s.bind(("", 0)) - s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - return s.getsockname()[1] # type: ignore - - -def get_health(container: Container) -> str: - api_client = docker.APIClient() - inspect_results = api_client.inspect_container(container.name) - return inspect_results["State"]["Health"]["Status"] # type: ignore +from tests.utils.tracked_container import TrackedContainer @pytest.fixture(scope="session") @@ -52,95 +34,10 @@ def image_name() -> str: return os.environ["TEST_IMAGE"] -class TrackedContainer: - """Wrapper that collects docker container configuration and delays - container creation/execution. - - Parameters - ---------- - docker_client: docker.DockerClient - Docker client instance - image_name: str - Name of the docker image to launch - **kwargs: dict, optional - Default keyword arguments to pass to docker.DockerClient.containers.run - """ - - def __init__( - self, - docker_client: docker.DockerClient, - image_name: str, - **kwargs: Any, - ): - self.container: Container | None = None - self.docker_client: docker.DockerClient = docker_client - self.image_name: str = image_name - self.kwargs: Any = kwargs - - def run_detached(self, **kwargs: Any) -> Container: - """Runs a docker container using the pre-configured image name - and a mix of the pre-configured container options and those passed - to this method. - - Keeps track of the docker.Container instance spawned to kill it - later. - - Parameters - ---------- - **kwargs: dict, optional - Keyword arguments to pass to docker.DockerClient.containers.run - extending and/or overriding key/value pairs passed to the constructor - - Returns - ------- - docker.Container - """ - all_kwargs = self.kwargs | kwargs - LOGGER.info(f"Running {self.image_name} with args {all_kwargs} ...") - self.container = self.docker_client.containers.run( - self.image_name, - **all_kwargs, - ) - return self.container - - def run_and_wait( - self, - timeout: int, - no_warnings: bool = True, - no_errors: bool = True, - no_failure: bool = True, - **kwargs: Any, - ) -> str: - running_container = self.run_detached(**kwargs) - rv = running_container.wait(timeout=timeout) - logs = running_container.logs().decode("utf-8") - assert isinstance(logs, str) - LOGGER.debug(logs) - assert no_warnings == (not self.get_warnings(logs)) - assert no_errors == (not self.get_errors(logs)) - assert no_failure == (rv["StatusCode"] == 0) - return logs - - @staticmethod - def get_errors(logs: str) -> list[str]: - return TrackedContainer._lines_starting_with(logs, "ERROR") - - @staticmethod - def get_warnings(logs: str) -> list[str]: - return TrackedContainer._lines_starting_with(logs, "WARNING") - - @staticmethod - def _lines_starting_with(logs: str, pattern: str) -> list[str]: - return [line for line in logs.splitlines() if line.startswith(pattern)] - - def remove(self) -> None: - """Kills and removes the tracked docker container.""" - if self.container: - self.container.remove(force=True) - - @pytest.fixture(scope="function") -def container(docker_client: docker.DockerClient, image_name: str) -> Container: +def container( + docker_client: docker.DockerClient, image_name: str +) -> Generator[TrackedContainer]: """Notebook container with initial configuration appropriate for testing (e.g., HTTP port exposed to the host for HTTP calls). diff --git a/tests/hierarchy/__init__.py b/tests/hierarchy/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/images_hierarchy.py b/tests/hierarchy/images_hierarchy.py similarity index 80% rename from tests/images_hierarchy.py rename to tests/hierarchy/images_hierarchy.py index df583bd9cf..ee7e0e4b5a 100644 --- a/tests/images_hierarchy.py +++ b/tests/hierarchy/images_hierarchy.py @@ -3,6 +3,9 @@ from pathlib import Path THIS_DIR = Path(__file__).parent.resolve() +IMAGE_SPECIFIC_TESTS_DIR = THIS_DIR.parent / "image_specific_tests" + +assert IMAGE_SPECIFIC_TESTS_DIR.exists(), f"{IMAGE_SPECIFIC_TESTS_DIR} does not exist." # Please, take a look at the hierarchy of the images here: # https://jupyter-docker-stacks.readthedocs.io/en/latest/using/selecting.html#image-relationships @@ -28,6 +31,8 @@ def get_test_dirs( return [] test_dirs = get_test_dirs(ALL_IMAGES[short_image_name]) - if (current_image_tests_dir := THIS_DIR / short_image_name).exists(): + if ( + current_image_tests_dir := IMAGE_SPECIFIC_TESTS_DIR / short_image_name + ).exists(): test_dirs.append(current_image_tests_dir) return test_dirs diff --git a/tests/all-spark-notebook/data/issue_1168.ipynb b/tests/image_specific_tests/all-spark-notebook/data/issue_1168.ipynb similarity index 100% rename from tests/all-spark-notebook/data/issue_1168.ipynb rename to tests/image_specific_tests/all-spark-notebook/data/issue_1168.ipynb diff --git a/tests/all-spark-notebook/data/local_pyspark.ipynb b/tests/image_specific_tests/all-spark-notebook/data/local_pyspark.ipynb similarity index 100% rename from tests/all-spark-notebook/data/local_pyspark.ipynb rename to tests/image_specific_tests/all-spark-notebook/data/local_pyspark.ipynb diff --git a/tests/all-spark-notebook/data/local_sparkR.ipynb b/tests/image_specific_tests/all-spark-notebook/data/local_sparkR.ipynb similarity index 100% rename from tests/all-spark-notebook/data/local_sparkR.ipynb rename to tests/image_specific_tests/all-spark-notebook/data/local_sparkR.ipynb diff --git a/tests/all-spark-notebook/data/local_sparklyr.ipynb b/tests/image_specific_tests/all-spark-notebook/data/local_sparklyr.ipynb similarity index 100% rename from tests/all-spark-notebook/data/local_sparklyr.ipynb rename to tests/image_specific_tests/all-spark-notebook/data/local_sparklyr.ipynb diff --git a/tests/all-spark-notebook/test_spark_notebooks.py b/tests/image_specific_tests/all-spark-notebook/test_spark_notebooks.py similarity index 96% rename from tests/all-spark-notebook/test_spark_notebooks.py rename to tests/image_specific_tests/all-spark-notebook/test_spark_notebooks.py index 81b172846a..c998a1b6b0 100644 --- a/tests/all-spark-notebook/test_spark_notebooks.py +++ b/tests/image_specific_tests/all-spark-notebook/test_spark_notebooks.py @@ -5,7 +5,7 @@ import pytest # type: ignore -from tests.conftest import TrackedContainer +from tests.utils.tracked_container import TrackedContainer LOGGER = logging.getLogger(__name__) THIS_DIR = Path(__file__).parent.resolve() diff --git a/tests/base-notebook/test_container_options.py b/tests/image_specific_tests/base-notebook/test_container_options.py similarity index 97% rename from tests/base-notebook/test_container_options.py rename to tests/image_specific_tests/base-notebook/test_container_options.py index b330c1ecfe..8981da85e0 100644 --- a/tests/base-notebook/test_container_options.py +++ b/tests/image_specific_tests/base-notebook/test_container_options.py @@ -6,7 +6,8 @@ import pytest # type: ignore import requests -from tests.conftest import TrackedContainer, find_free_port +from tests.utils.find_free_port import find_free_port +from tests.utils.tracked_container import TrackedContainer LOGGER = logging.getLogger(__name__) diff --git a/tests/base-notebook/test_healthcheck.py b/tests/image_specific_tests/base-notebook/test_healthcheck.py similarity index 97% rename from tests/base-notebook/test_healthcheck.py rename to tests/image_specific_tests/base-notebook/test_healthcheck.py index c3684b183a..0ed7494cf5 100644 --- a/tests/base-notebook/test_healthcheck.py +++ b/tests/image_specific_tests/base-notebook/test_healthcheck.py @@ -5,7 +5,8 @@ import pytest # type: ignore -from tests.conftest import TrackedContainer, get_health +from tests.utils.get_container_health import get_health +from tests.utils.tracked_container import TrackedContainer LOGGER = logging.getLogger(__name__) diff --git a/tests/base-notebook/test_notebook.py b/tests/image_specific_tests/base-notebook/test_notebook.py similarity index 82% rename from tests/base-notebook/test_notebook.py rename to tests/image_specific_tests/base-notebook/test_notebook.py index 3985990eb0..e19be259d9 100644 --- a/tests/base-notebook/test_notebook.py +++ b/tests/image_specific_tests/base-notebook/test_notebook.py @@ -2,7 +2,8 @@ # Distributed under the terms of the Modified BSD License. import requests -from tests.conftest import TrackedContainer, find_free_port +from tests.utils.find_free_port import find_free_port +from tests.utils.tracked_container import TrackedContainer def test_secured_server( diff --git a/tests/base-notebook/test_pandoc.py b/tests/image_specific_tests/base-notebook/test_pandoc.py similarity index 88% rename from tests/base-notebook/test_pandoc.py rename to tests/image_specific_tests/base-notebook/test_pandoc.py index 3c828a3f0b..3420e4f2c0 100644 --- a/tests/base-notebook/test_pandoc.py +++ b/tests/image_specific_tests/base-notebook/test_pandoc.py @@ -2,7 +2,7 @@ # Distributed under the terms of the Modified BSD License. import logging -from tests.conftest import TrackedContainer +from tests.utils.tracked_container import TrackedContainer LOGGER = logging.getLogger(__name__) diff --git a/tests/base-notebook/test_start_container.py b/tests/image_specific_tests/base-notebook/test_start_container.py similarity index 96% rename from tests/base-notebook/test_start_container.py rename to tests/image_specific_tests/base-notebook/test_start_container.py index e938f78a08..4e83eeb11c 100644 --- a/tests/base-notebook/test_start_container.py +++ b/tests/image_specific_tests/base-notebook/test_start_container.py @@ -6,7 +6,8 @@ import pytest # type: ignore import requests -from tests.conftest import TrackedContainer, find_free_port +from tests.utils.find_free_port import find_free_port +from tests.utils.tracked_container import TrackedContainer LOGGER = logging.getLogger(__name__) diff --git a/tests/datascience-notebook/test_julia_datascience.py b/tests/image_specific_tests/datascience-notebook/test_julia_datascience.py similarity index 65% rename from tests/datascience-notebook/test_julia_datascience.py rename to tests/image_specific_tests/datascience-notebook/test_julia_datascience.py index 3b1a55b658..5b0a3eb8eb 100644 --- a/tests/datascience-notebook/test_julia_datascience.py +++ b/tests/image_specific_tests/datascience-notebook/test_julia_datascience.py @@ -1,7 +1,7 @@ # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. -from tests.conftest import TrackedContainer -from tests.run_command import run_command +from tests.utils.run_command import run_command +from tests.utils.tracked_container import TrackedContainer def test_julia(container: TrackedContainer) -> None: diff --git a/tests/datascience-notebook/test_mimetypes.py b/tests/image_specific_tests/datascience-notebook/test_mimetypes.py similarity index 67% rename from tests/datascience-notebook/test_mimetypes.py rename to tests/image_specific_tests/datascience-notebook/test_mimetypes.py index 4fd86473de..cf555e5c46 100644 --- a/tests/datascience-notebook/test_mimetypes.py +++ b/tests/image_specific_tests/datascience-notebook/test_mimetypes.py @@ -1,7 +1,7 @@ # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. -from tests.conftest import TrackedContainer -from tests.R_mimetype_check import check_r_mimetypes +from tests.shared_checks.R_mimetype_check import check_r_mimetypes +from tests.utils.tracked_container import TrackedContainer def test_mimetypes(container: TrackedContainer) -> None: diff --git a/tests/datascience-notebook/test_pluto_datascience.py b/tests/image_specific_tests/datascience-notebook/test_pluto_datascience.py similarity index 71% rename from tests/datascience-notebook/test_pluto_datascience.py rename to tests/image_specific_tests/datascience-notebook/test_pluto_datascience.py index 27c4aaf0d3..8b60a3191b 100644 --- a/tests/datascience-notebook/test_pluto_datascience.py +++ b/tests/image_specific_tests/datascience-notebook/test_pluto_datascience.py @@ -2,8 +2,8 @@ # Distributed under the terms of the Modified BSD License. import requests -from tests.conftest import TrackedContainer -from tests.pluto_check import check_pluto_proxy +from tests.shared_checks.pluto_check import check_pluto_proxy +from tests.utils.tracked_container import TrackedContainer def test_pluto_proxy( diff --git a/tests/docker-stacks-foundation/run-hooks-change/a.sh b/tests/image_specific_tests/docker-stacks-foundation/run-hooks-change/a.sh similarity index 100% rename from tests/docker-stacks-foundation/run-hooks-change/a.sh rename to tests/image_specific_tests/docker-stacks-foundation/run-hooks-change/a.sh diff --git a/tests/docker-stacks-foundation/run-hooks-change/b.sh b/tests/image_specific_tests/docker-stacks-foundation/run-hooks-change/b.sh similarity index 100% rename from tests/docker-stacks-foundation/run-hooks-change/b.sh rename to tests/image_specific_tests/docker-stacks-foundation/run-hooks-change/b.sh diff --git a/tests/docker-stacks-foundation/run-hooks-change/c.sh b/tests/image_specific_tests/docker-stacks-foundation/run-hooks-change/c.sh similarity index 100% rename from tests/docker-stacks-foundation/run-hooks-change/c.sh rename to tests/image_specific_tests/docker-stacks-foundation/run-hooks-change/c.sh diff --git a/tests/docker-stacks-foundation/run-hooks-executables/executable.py b/tests/image_specific_tests/docker-stacks-foundation/run-hooks-executables/executable.py similarity index 100% rename from tests/docker-stacks-foundation/run-hooks-executables/executable.py rename to tests/image_specific_tests/docker-stacks-foundation/run-hooks-executables/executable.py diff --git a/tests/docker-stacks-foundation/run-hooks-executables/non_executable.py b/tests/image_specific_tests/docker-stacks-foundation/run-hooks-executables/non_executable.py similarity index 100% rename from tests/docker-stacks-foundation/run-hooks-executables/non_executable.py rename to tests/image_specific_tests/docker-stacks-foundation/run-hooks-executables/non_executable.py diff --git a/tests/docker-stacks-foundation/run-hooks-executables/run-me.sh b/tests/image_specific_tests/docker-stacks-foundation/run-hooks-executables/run-me.sh similarity index 100% rename from tests/docker-stacks-foundation/run-hooks-executables/run-me.sh rename to tests/image_specific_tests/docker-stacks-foundation/run-hooks-executables/run-me.sh diff --git a/tests/docker-stacks-foundation/run-hooks-failures/a.sh b/tests/image_specific_tests/docker-stacks-foundation/run-hooks-failures/a.sh similarity index 100% rename from tests/docker-stacks-foundation/run-hooks-failures/a.sh rename to tests/image_specific_tests/docker-stacks-foundation/run-hooks-failures/a.sh diff --git a/tests/docker-stacks-foundation/run-hooks-failures/b.py b/tests/image_specific_tests/docker-stacks-foundation/run-hooks-failures/b.py similarity index 100% rename from tests/docker-stacks-foundation/run-hooks-failures/b.py rename to tests/image_specific_tests/docker-stacks-foundation/run-hooks-failures/b.py diff --git a/tests/docker-stacks-foundation/run-hooks-failures/c.sh b/tests/image_specific_tests/docker-stacks-foundation/run-hooks-failures/c.sh similarity index 100% rename from tests/docker-stacks-foundation/run-hooks-failures/c.sh rename to tests/image_specific_tests/docker-stacks-foundation/run-hooks-failures/c.sh diff --git a/tests/docker-stacks-foundation/run-hooks-failures/d.sh b/tests/image_specific_tests/docker-stacks-foundation/run-hooks-failures/d.sh similarity index 100% rename from tests/docker-stacks-foundation/run-hooks-failures/d.sh rename to tests/image_specific_tests/docker-stacks-foundation/run-hooks-failures/d.sh diff --git a/tests/docker-stacks-foundation/run-hooks-unset/a.sh b/tests/image_specific_tests/docker-stacks-foundation/run-hooks-unset/a.sh similarity index 100% rename from tests/docker-stacks-foundation/run-hooks-unset/a.sh rename to tests/image_specific_tests/docker-stacks-foundation/run-hooks-unset/a.sh diff --git a/tests/docker-stacks-foundation/run-hooks-unset/b.sh b/tests/image_specific_tests/docker-stacks-foundation/run-hooks-unset/b.sh similarity index 100% rename from tests/docker-stacks-foundation/run-hooks-unset/b.sh rename to tests/image_specific_tests/docker-stacks-foundation/run-hooks-unset/b.sh diff --git a/tests/docker-stacks-foundation/run-hooks-unset/c.sh b/tests/image_specific_tests/docker-stacks-foundation/run-hooks-unset/c.sh similarity index 100% rename from tests/docker-stacks-foundation/run-hooks-unset/c.sh rename to tests/image_specific_tests/docker-stacks-foundation/run-hooks-unset/c.sh diff --git a/tests/docker-stacks-foundation/test_outdated.py b/tests/image_specific_tests/docker-stacks-foundation/test_outdated.py similarity index 84% rename from tests/docker-stacks-foundation/test_outdated.py rename to tests/image_specific_tests/docker-stacks-foundation/test_outdated.py index 6de18d3dbc..9c9a3419dd 100644 --- a/tests/docker-stacks-foundation/test_outdated.py +++ b/tests/image_specific_tests/docker-stacks-foundation/test_outdated.py @@ -4,8 +4,8 @@ import pytest # type: ignore -from tests.conftest import TrackedContainer -from tests.package_helper import CondaPackageHelper +from tests.utils.conda_package_helper import CondaPackageHelper +from tests.utils.tracked_container import TrackedContainer LOGGER = logging.getLogger(__name__) diff --git a/tests/docker-stacks-foundation/test_package_managers.py b/tests/image_specific_tests/docker-stacks-foundation/test_package_managers.py similarity index 82% rename from tests/docker-stacks-foundation/test_package_managers.py rename to tests/image_specific_tests/docker-stacks-foundation/test_package_managers.py index 29e0b64979..2bf4537113 100644 --- a/tests/docker-stacks-foundation/test_package_managers.py +++ b/tests/image_specific_tests/docker-stacks-foundation/test_package_managers.py @@ -2,8 +2,8 @@ # Distributed under the terms of the Modified BSD License. import pytest # type: ignore -from tests.conftest import TrackedContainer -from tests.run_command import run_command +from tests.utils.run_command import run_command +from tests.utils.tracked_container import TrackedContainer @pytest.mark.parametrize( diff --git a/tests/docker-stacks-foundation/test_packages.py b/tests/image_specific_tests/docker-stacks-foundation/test_packages.py similarity index 98% rename from tests/docker-stacks-foundation/test_packages.py rename to tests/image_specific_tests/docker-stacks-foundation/test_packages.py index d2d2f1935a..2a4301eea0 100644 --- a/tests/docker-stacks-foundation/test_packages.py +++ b/tests/image_specific_tests/docker-stacks-foundation/test_packages.py @@ -44,8 +44,8 @@ import pytest # type: ignore -from tests.conftest import TrackedContainer -from tests.package_helper import CondaPackageHelper +from tests.utils.conda_package_helper import CondaPackageHelper +from tests.utils.tracked_container import TrackedContainer LOGGER = logging.getLogger(__name__) diff --git a/tests/docker-stacks-foundation/test_python_version.py b/tests/image_specific_tests/docker-stacks-foundation/test_python_version.py similarity index 94% rename from tests/docker-stacks-foundation/test_python_version.py rename to tests/image_specific_tests/docker-stacks-foundation/test_python_version.py index bf81df8356..65ea6010c1 100644 --- a/tests/docker-stacks-foundation/test_python_version.py +++ b/tests/image_specific_tests/docker-stacks-foundation/test_python_version.py @@ -2,7 +2,7 @@ # Distributed under the terms of the Modified BSD License. import logging -from tests.conftest import TrackedContainer +from tests.utils.tracked_container import TrackedContainer LOGGER = logging.getLogger(__name__) EXPECTED_PYTHON_VERSION = "3.12" diff --git a/tests/docker-stacks-foundation/test_run_hooks.py b/tests/image_specific_tests/docker-stacks-foundation/test_run_hooks.py similarity index 98% rename from tests/docker-stacks-foundation/test_run_hooks.py rename to tests/image_specific_tests/docker-stacks-foundation/test_run_hooks.py index 87467f9fb3..22806069a0 100644 --- a/tests/docker-stacks-foundation/test_run_hooks.py +++ b/tests/image_specific_tests/docker-stacks-foundation/test_run_hooks.py @@ -3,7 +3,7 @@ import logging from pathlib import Path -from tests.conftest import TrackedContainer +from tests.utils.tracked_container import TrackedContainer LOGGER = logging.getLogger(__name__) THIS_DIR = Path(__file__).parent.resolve() diff --git a/tests/docker-stacks-foundation/test_units.py b/tests/image_specific_tests/docker-stacks-foundation/test_units.py similarity index 91% rename from tests/docker-stacks-foundation/test_units.py rename to tests/image_specific_tests/docker-stacks-foundation/test_units.py index cfdbc83dd8..5e8bb64da3 100644 --- a/tests/docker-stacks-foundation/test_units.py +++ b/tests/image_specific_tests/docker-stacks-foundation/test_units.py @@ -2,8 +2,8 @@ # Distributed under the terms of the Modified BSD License. import logging -from tests.conftest import TrackedContainer -from tests.images_hierarchy import get_test_dirs +from tests.hierarchy.images_hierarchy import get_test_dirs +from tests.utils.tracked_container import TrackedContainer LOGGER = logging.getLogger(__name__) diff --git a/tests/docker-stacks-foundation/test_user_options.py b/tests/image_specific_tests/docker-stacks-foundation/test_user_options.py similarity index 99% rename from tests/docker-stacks-foundation/test_user_options.py rename to tests/image_specific_tests/docker-stacks-foundation/test_user_options.py index 1ef6ada35a..33382f903e 100644 --- a/tests/docker-stacks-foundation/test_user_options.py +++ b/tests/image_specific_tests/docker-stacks-foundation/test_user_options.py @@ -6,7 +6,7 @@ import pytest # type: ignore -from tests.conftest import TrackedContainer +from tests.utils.tracked_container import TrackedContainer LOGGER = logging.getLogger(__name__) diff --git a/tests/julia-notebook/test_julia.py b/tests/image_specific_tests/julia-notebook/test_julia.py similarity index 65% rename from tests/julia-notebook/test_julia.py rename to tests/image_specific_tests/julia-notebook/test_julia.py index 3b1a55b658..5b0a3eb8eb 100644 --- a/tests/julia-notebook/test_julia.py +++ b/tests/image_specific_tests/julia-notebook/test_julia.py @@ -1,7 +1,7 @@ # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. -from tests.conftest import TrackedContainer -from tests.run_command import run_command +from tests.utils.run_command import run_command +from tests.utils.tracked_container import TrackedContainer def test_julia(container: TrackedContainer) -> None: diff --git a/tests/julia-notebook/test_pluto.py b/tests/image_specific_tests/julia-notebook/test_pluto.py similarity index 71% rename from tests/julia-notebook/test_pluto.py rename to tests/image_specific_tests/julia-notebook/test_pluto.py index 27c4aaf0d3..8b60a3191b 100644 --- a/tests/julia-notebook/test_pluto.py +++ b/tests/image_specific_tests/julia-notebook/test_pluto.py @@ -2,8 +2,8 @@ # Distributed under the terms of the Modified BSD License. import requests -from tests.conftest import TrackedContainer -from tests.pluto_check import check_pluto_proxy +from tests.shared_checks.pluto_check import check_pluto_proxy +from tests.utils.tracked_container import TrackedContainer def test_pluto_proxy( diff --git a/tests/minimal-notebook/data/Jupyter_logo.svg b/tests/image_specific_tests/minimal-notebook/data/Jupyter_logo.svg similarity index 100% rename from tests/minimal-notebook/data/Jupyter_logo.svg rename to tests/image_specific_tests/minimal-notebook/data/Jupyter_logo.svg diff --git a/tests/minimal-notebook/data/notebook_math.ipynb b/tests/image_specific_tests/minimal-notebook/data/notebook_math.ipynb similarity index 100% rename from tests/minimal-notebook/data/notebook_math.ipynb rename to tests/image_specific_tests/minimal-notebook/data/notebook_math.ipynb diff --git a/tests/minimal-notebook/data/notebook_svg.ipynb b/tests/image_specific_tests/minimal-notebook/data/notebook_svg.ipynb similarity index 100% rename from tests/minimal-notebook/data/notebook_svg.ipynb rename to tests/image_specific_tests/minimal-notebook/data/notebook_svg.ipynb diff --git a/tests/minimal-notebook/test_nbconvert.py b/tests/image_specific_tests/minimal-notebook/test_nbconvert.py similarity index 95% rename from tests/minimal-notebook/test_nbconvert.py rename to tests/image_specific_tests/minimal-notebook/test_nbconvert.py index 9c1c017be7..54c1cecdaf 100644 --- a/tests/minimal-notebook/test_nbconvert.py +++ b/tests/image_specific_tests/minimal-notebook/test_nbconvert.py @@ -5,7 +5,7 @@ import pytest # type: ignore -from tests.conftest import TrackedContainer +from tests.utils.tracked_container import TrackedContainer LOGGER = logging.getLogger(__name__) THIS_DIR = Path(__file__).parent.resolve() diff --git a/tests/pyspark-notebook/test_spark.py b/tests/image_specific_tests/pyspark-notebook/test_spark.py similarity index 91% rename from tests/pyspark-notebook/test_spark.py rename to tests/image_specific_tests/pyspark-notebook/test_spark.py index 2ba32cc9fb..8ac720d11f 100644 --- a/tests/pyspark-notebook/test_spark.py +++ b/tests/image_specific_tests/pyspark-notebook/test_spark.py @@ -2,7 +2,7 @@ # Distributed under the terms of the Modified BSD License. import logging -from tests.conftest import TrackedContainer +from tests.utils.tracked_container import TrackedContainer LOGGER = logging.getLogger(__name__) diff --git a/tests/pyspark-notebook/units/unit_pandas_version.py b/tests/image_specific_tests/pyspark-notebook/units/unit_pandas_version.py similarity index 100% rename from tests/pyspark-notebook/units/unit_pandas_version.py rename to tests/image_specific_tests/pyspark-notebook/units/unit_pandas_version.py diff --git a/tests/pyspark-notebook/units/unit_spark.py b/tests/image_specific_tests/pyspark-notebook/units/unit_spark.py similarity index 100% rename from tests/pyspark-notebook/units/unit_spark.py rename to tests/image_specific_tests/pyspark-notebook/units/unit_spark.py diff --git a/tests/pytorch-notebook/units/unit_pytorch.py b/tests/image_specific_tests/pytorch-notebook/units/unit_pytorch.py similarity index 100% rename from tests/pytorch-notebook/units/unit_pytorch.py rename to tests/image_specific_tests/pytorch-notebook/units/unit_pytorch.py diff --git a/tests/r-notebook/test_R_mimetypes.py b/tests/image_specific_tests/r-notebook/test_R_mimetypes.py similarity index 67% rename from tests/r-notebook/test_R_mimetypes.py rename to tests/image_specific_tests/r-notebook/test_R_mimetypes.py index 4fd86473de..cf555e5c46 100644 --- a/tests/r-notebook/test_R_mimetypes.py +++ b/tests/image_specific_tests/r-notebook/test_R_mimetypes.py @@ -1,7 +1,7 @@ # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. -from tests.conftest import TrackedContainer -from tests.R_mimetype_check import check_r_mimetypes +from tests.shared_checks.R_mimetype_check import check_r_mimetypes +from tests.utils.tracked_container import TrackedContainer def test_mimetypes(container: TrackedContainer) -> None: diff --git a/tests/scipy-notebook/data/cython/helloworld.pyx b/tests/image_specific_tests/scipy-notebook/data/cython/helloworld.pyx similarity index 100% rename from tests/scipy-notebook/data/cython/helloworld.pyx rename to tests/image_specific_tests/scipy-notebook/data/cython/helloworld.pyx diff --git a/tests/scipy-notebook/data/cython/setup.py b/tests/image_specific_tests/scipy-notebook/data/cython/setup.py similarity index 100% rename from tests/scipy-notebook/data/cython/setup.py rename to tests/image_specific_tests/scipy-notebook/data/cython/setup.py diff --git a/tests/scipy-notebook/data/matplotlib/matplotlib_1.py b/tests/image_specific_tests/scipy-notebook/data/matplotlib/matplotlib_1.py similarity index 100% rename from tests/scipy-notebook/data/matplotlib/matplotlib_1.py rename to tests/image_specific_tests/scipy-notebook/data/matplotlib/matplotlib_1.py diff --git a/tests/scipy-notebook/data/matplotlib/matplotlib_fonts_1.py b/tests/image_specific_tests/scipy-notebook/data/matplotlib/matplotlib_fonts_1.py similarity index 100% rename from tests/scipy-notebook/data/matplotlib/matplotlib_fonts_1.py rename to tests/image_specific_tests/scipy-notebook/data/matplotlib/matplotlib_fonts_1.py diff --git a/tests/scipy-notebook/test_cython.py b/tests/image_specific_tests/scipy-notebook/test_cython.py similarity index 92% rename from tests/scipy-notebook/test_cython.py rename to tests/image_specific_tests/scipy-notebook/test_cython.py index 092271ba5c..1f1f876978 100644 --- a/tests/scipy-notebook/test_cython.py +++ b/tests/image_specific_tests/scipy-notebook/test_cython.py @@ -2,7 +2,7 @@ # Distributed under the terms of the Modified BSD License. from pathlib import Path -from tests.conftest import TrackedContainer +from tests.utils.tracked_container import TrackedContainer THIS_DIR = Path(__file__).parent.resolve() diff --git a/tests/scipy-notebook/test_extensions.py b/tests/image_specific_tests/scipy-notebook/test_extensions.py similarity index 93% rename from tests/scipy-notebook/test_extensions.py rename to tests/image_specific_tests/scipy-notebook/test_extensions.py index d90cc62290..d64b1b20b8 100644 --- a/tests/scipy-notebook/test_extensions.py +++ b/tests/image_specific_tests/scipy-notebook/test_extensions.py @@ -4,7 +4,7 @@ import pytest # type: ignore -from tests.conftest import TrackedContainer +from tests.utils.tracked_container import TrackedContainer LOGGER = logging.getLogger(__name__) diff --git a/tests/scipy-notebook/test_matplotlib.py b/tests/image_specific_tests/scipy-notebook/test_matplotlib.py similarity index 96% rename from tests/scipy-notebook/test_matplotlib.py rename to tests/image_specific_tests/scipy-notebook/test_matplotlib.py index e96bc8c859..e17c4ff768 100644 --- a/tests/scipy-notebook/test_matplotlib.py +++ b/tests/image_specific_tests/scipy-notebook/test_matplotlib.py @@ -5,7 +5,7 @@ import pytest # type: ignore -from tests.conftest import TrackedContainer +from tests.utils.tracked_container import TrackedContainer LOGGER = logging.getLogger(__name__) THIS_DIR = Path(__file__).parent.resolve() diff --git a/tests/scipy-notebook/units/unit_pandas.py b/tests/image_specific_tests/scipy-notebook/units/unit_pandas.py similarity index 100% rename from tests/scipy-notebook/units/unit_pandas.py rename to tests/image_specific_tests/scipy-notebook/units/unit_pandas.py diff --git a/tests/tensorflow-notebook/units/unit_tensorflow.py b/tests/image_specific_tests/tensorflow-notebook/units/unit_tensorflow.py similarity index 100% rename from tests/tensorflow-notebook/units/unit_tensorflow.py rename to tests/image_specific_tests/tensorflow-notebook/units/unit_tensorflow.py diff --git a/tests/run_tests.py b/tests/run_tests.py index 5f230c466a..b17f511ace 100755 --- a/tests/run_tests.py +++ b/tests/run_tests.py @@ -6,7 +6,7 @@ import plumbum -from tests.images_hierarchy import get_test_dirs +from tests.hierarchy.images_hierarchy import get_test_dirs python3 = plumbum.local["python3"] diff --git a/tests/R_mimetype_check.py b/tests/shared_checks/R_mimetype_check.py similarity index 93% rename from tests/R_mimetype_check.py rename to tests/shared_checks/R_mimetype_check.py index ba90fa51d6..52ae4e94ea 100644 --- a/tests/R_mimetype_check.py +++ b/tests/shared_checks/R_mimetype_check.py @@ -2,7 +2,7 @@ # Distributed under the terms of the Modified BSD License. import logging -from tests.conftest import TrackedContainer +from tests.utils.tracked_container import TrackedContainer LOGGER = logging.getLogger(__name__) diff --git a/tests/shared_checks/__init__.py b/tests/shared_checks/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/pluto_check.py b/tests/shared_checks/pluto_check.py similarity index 87% rename from tests/pluto_check.py rename to tests/shared_checks/pluto_check.py index 48116db472..3dbe043cfe 100644 --- a/tests/pluto_check.py +++ b/tests/shared_checks/pluto_check.py @@ -6,7 +6,8 @@ import requests -from tests.conftest import TrackedContainer, find_free_port +from tests.utils.find_free_port import find_free_port +from tests.utils.tracked_container import TrackedContainer LOGGER = logging.getLogger(__name__) diff --git a/tests/utils/__init__.py b/tests/utils/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/package_helper.py b/tests/utils/conda_package_helper.py similarity index 99% rename from tests/package_helper.py rename to tests/utils/conda_package_helper.py index b5e0e41549..34dcd84c87 100644 --- a/tests/package_helper.py +++ b/tests/utils/conda_package_helper.py @@ -32,7 +32,7 @@ from docker.models.containers import Container from tabulate import tabulate -from tests.conftest import TrackedContainer +from tests.utils.tracked_container import TrackedContainer LOGGER = logging.getLogger(__name__) diff --git a/tests/utils/find_free_port.py b/tests/utils/find_free_port.py new file mode 100644 index 0000000000..0ae1930dc4 --- /dev/null +++ b/tests/utils/find_free_port.py @@ -0,0 +1,12 @@ +# Copyright (c) Jupyter Development Team. +# Distributed under the terms of the Modified BSD License. +import socket +from contextlib import closing + + +def find_free_port() -> str: + """Returns the available host port. Can be called in multiple threads/processes.""" + with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s: + s.bind(("", 0)) + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + return s.getsockname()[1] # type: ignore diff --git a/tests/utils/get_container_health.py b/tests/utils/get_container_health.py new file mode 100644 index 0000000000..ec5807fc53 --- /dev/null +++ b/tests/utils/get_container_health.py @@ -0,0 +1,10 @@ +# Copyright (c) Jupyter Development Team. +# Distributed under the terms of the Modified BSD License. +import docker +from docker.models.containers import Container + + +def get_health(container: Container) -> str: + api_client = docker.APIClient() + inspect_results = api_client.inspect_container(container.name) + return inspect_results["State"]["Health"]["Status"] # type: ignore diff --git a/tests/run_command.py b/tests/utils/run_command.py similarity index 89% rename from tests/run_command.py rename to tests/utils/run_command.py index 48e3cc07cd..fbcca1c887 100644 --- a/tests/run_command.py +++ b/tests/utils/run_command.py @@ -2,7 +2,7 @@ # Distributed under the terms of the Modified BSD License. import logging -from tests.conftest import TrackedContainer +from tests.utils.tracked_container import TrackedContainer LOGGER = logging.getLogger(__name__) diff --git a/tests/utils/tracked_container.py b/tests/utils/tracked_container.py new file mode 100644 index 0000000000..6942b4be8b --- /dev/null +++ b/tests/utils/tracked_container.py @@ -0,0 +1,96 @@ +# Copyright (c) Jupyter Development Team. +# Distributed under the terms of the Modified BSD License. +import logging +from typing import Any + +import docker +from docker.models.containers import Container + +LOGGER = logging.getLogger(__name__) + + +class TrackedContainer: + """Wrapper that collects docker container configuration and delays + container creation/execution. + + Parameters + ---------- + docker_client: docker.DockerClient + Docker client instance + image_name: str + Name of the docker image to launch + **kwargs: dict, optional + Default keyword arguments to pass to docker.DockerClient.containers.run + """ + + def __init__( + self, + docker_client: docker.DockerClient, + image_name: str, + **kwargs: Any, + ): + self.container: Container | None = None + self.docker_client: docker.DockerClient = docker_client + self.image_name: str = image_name + self.kwargs: Any = kwargs + + def run_detached(self, **kwargs: Any) -> Container: + """Runs a docker container using the pre-configured image name + and a mix of the pre-configured container options and those passed + to this method. + + Keeps track of the docker.Container instance spawned to kill it + later. + + Parameters + ---------- + **kwargs: dict, optional + Keyword arguments to pass to docker.DockerClient.containers.run + extending and/or overriding key/value pairs passed to the constructor + + Returns + ------- + docker.Container + """ + all_kwargs = self.kwargs | kwargs + LOGGER.info(f"Running {self.image_name} with args {all_kwargs} ...") + self.container = self.docker_client.containers.run( + self.image_name, + **all_kwargs, + ) + return self.container + + def run_and_wait( + self, + timeout: int, + no_warnings: bool = True, + no_errors: bool = True, + no_failure: bool = True, + **kwargs: Any, + ) -> str: + running_container = self.run_detached(**kwargs) + rv = running_container.wait(timeout=timeout) + logs = running_container.logs().decode("utf-8") + assert isinstance(logs, str) + LOGGER.debug(logs) + assert no_warnings == (not self.get_warnings(logs)) + assert no_errors == (not self.get_errors(logs)) + assert no_failure == (rv["StatusCode"] == 0) + return logs + + @staticmethod + def get_errors(logs: str) -> list[str]: + return TrackedContainer._lines_starting_with(logs, "ERROR") + + @staticmethod + def get_warnings(logs: str) -> list[str]: + return TrackedContainer._lines_starting_with(logs, "WARNING") + + @staticmethod + def _lines_starting_with(logs: str, pattern: str) -> list[str]: + return [line for line in logs.splitlines() if line.startswith(pattern)] + + def remove(self) -> None: + """Kills and removes the tracked docker container.""" + if self.container: + self.container.remove(force=True)