Skip to content

Commit a72e784

Browse files
committed
Better tests directory structure
1 parent 80f4426 commit a72e784

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

67 files changed

+174
-148
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions

tests/conftest.py

Lines changed: 5 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,15 @@
11
# Copyright (c) Jupyter Development Team.
22
# Distributed under the terms of the Modified BSD License.
3-
import logging
43
import os
5-
import socket
6-
from contextlib import closing
7-
from typing import Any
4+
from collections.abc import Generator
85

96
import docker
107
import pytest # type: ignore
118
import requests
12-
from docker.models.containers import Container
139
from requests.adapters import HTTPAdapter
1410
from urllib3.util.retry import Retry
1511

16-
LOGGER = logging.getLogger(__name__)
17-
18-
19-
def find_free_port() -> str:
20-
"""Returns the available host port. Can be called in multiple threads/processes."""
21-
with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s:
22-
s.bind(("", 0))
23-
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
24-
return s.getsockname()[1] # type: ignore
25-
26-
27-
def get_health(container: Container) -> str:
28-
api_client = docker.APIClient()
29-
inspect_results = api_client.inspect_container(container.name)
30-
return inspect_results["State"]["Health"]["Status"] # type: ignore
12+
from tests.utils.tracked_container import TrackedContainer
3113

3214

3315
@pytest.fixture(scope="session")
@@ -52,95 +34,10 @@ def image_name() -> str:
5234
return os.environ["TEST_IMAGE"]
5335

5436

55-
class TrackedContainer:
56-
"""Wrapper that collects docker container configuration and delays
57-
container creation/execution.
58-
59-
Parameters
60-
----------
61-
docker_client: docker.DockerClient
62-
Docker client instance
63-
image_name: str
64-
Name of the docker image to launch
65-
**kwargs: dict, optional
66-
Default keyword arguments to pass to docker.DockerClient.containers.run
67-
"""
68-
69-
def __init__(
70-
self,
71-
docker_client: docker.DockerClient,
72-
image_name: str,
73-
**kwargs: Any,
74-
):
75-
self.container: Container | None = None
76-
self.docker_client: docker.DockerClient = docker_client
77-
self.image_name: str = image_name
78-
self.kwargs: Any = kwargs
79-
80-
def run_detached(self, **kwargs: Any) -> Container:
81-
"""Runs a docker container using the pre-configured image name
82-
and a mix of the pre-configured container options and those passed
83-
to this method.
84-
85-
Keeps track of the docker.Container instance spawned to kill it
86-
later.
87-
88-
Parameters
89-
----------
90-
**kwargs: dict, optional
91-
Keyword arguments to pass to docker.DockerClient.containers.run
92-
extending and/or overriding key/value pairs passed to the constructor
93-
94-
Returns
95-
-------
96-
docker.Container
97-
"""
98-
all_kwargs = self.kwargs | kwargs
99-
LOGGER.info(f"Running {self.image_name} with args {all_kwargs} ...")
100-
self.container = self.docker_client.containers.run(
101-
self.image_name,
102-
**all_kwargs,
103-
)
104-
return self.container
105-
106-
def run_and_wait(
107-
self,
108-
timeout: int,
109-
no_warnings: bool = True,
110-
no_errors: bool = True,
111-
no_failure: bool = True,
112-
**kwargs: Any,
113-
) -> str:
114-
running_container = self.run_detached(**kwargs)
115-
rv = running_container.wait(timeout=timeout)
116-
logs = running_container.logs().decode("utf-8")
117-
assert isinstance(logs, str)
118-
LOGGER.debug(logs)
119-
assert no_warnings == (not self.get_warnings(logs))
120-
assert no_errors == (not self.get_errors(logs))
121-
assert no_failure == (rv["StatusCode"] == 0)
122-
return logs
123-
124-
@staticmethod
125-
def get_errors(logs: str) -> list[str]:
126-
return TrackedContainer._lines_starting_with(logs, "ERROR")
127-
128-
@staticmethod
129-
def get_warnings(logs: str) -> list[str]:
130-
return TrackedContainer._lines_starting_with(logs, "WARNING")
131-
132-
@staticmethod
133-
def _lines_starting_with(logs: str, pattern: str) -> list[str]:
134-
return [line for line in logs.splitlines() if line.startswith(pattern)]
135-
136-
def remove(self) -> None:
137-
"""Kills and removes the tracked docker container."""
138-
if self.container:
139-
self.container.remove(force=True)
140-
141-
14237
@pytest.fixture(scope="function")
143-
def container(docker_client: docker.DockerClient, image_name: str) -> Container:
38+
def container(
39+
docker_client: docker.DockerClient, image_name: str
40+
) -> Generator[TrackedContainer]:
14441
"""Notebook container with initial configuration appropriate for testing
14542
(e.g., HTTP port exposed to the host for HTTP calls).
14643

tests/hierarchy/__init__.py

Whitespace-only changes.

tests/images_hierarchy.py renamed to tests/hierarchy/images_hierarchy.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
from pathlib import Path
44

55
THIS_DIR = Path(__file__).parent.resolve()
6+
IMAGE_SPECIFIC_TESTS_DIR = THIS_DIR.parent / "image_specific_tests"
7+
8+
assert IMAGE_SPECIFIC_TESTS_DIR.exists(), f"{IMAGE_SPECIFIC_TESTS_DIR} does not exist."
69

710
# Please, take a look at the hierarchy of the images here:
811
# https://jupyter-docker-stacks.readthedocs.io/en/latest/using/selecting.html#image-relationships
@@ -28,6 +31,8 @@ def get_test_dirs(
2831
return []
2932

3033
test_dirs = get_test_dirs(ALL_IMAGES[short_image_name])
31-
if (current_image_tests_dir := THIS_DIR / short_image_name).exists():
34+
if (
35+
current_image_tests_dir := IMAGE_SPECIFIC_TESTS_DIR / short_image_name
36+
).exists():
3237
test_dirs.append(current_image_tests_dir)
3338
return test_dirs

tests/all-spark-notebook/test_spark_notebooks.py renamed to tests/image_specific_tests/all-spark-notebook/test_spark_notebooks.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import pytest # type: ignore
77

8-
from tests.conftest import TrackedContainer
8+
from tests.utils.tracked_container import TrackedContainer
99

1010
LOGGER = logging.getLogger(__name__)
1111
THIS_DIR = Path(__file__).parent.resolve()

tests/base-notebook/test_container_options.py renamed to tests/image_specific_tests/base-notebook/test_container_options.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
import pytest # type: ignore
77
import requests
88

9-
from tests.conftest import TrackedContainer, find_free_port
9+
from tests.utils.find_free_port import find_free_port
10+
from tests.utils.tracked_container import TrackedContainer
1011

1112
LOGGER = logging.getLogger(__name__)
1213

0 commit comments

Comments
 (0)