Skip to content

Commit

Permalink
fix(core): Reorganize core tests and improve (#693)
Browse files Browse the repository at this point in the history
Found some loss ends while working on fixing the typing.

The idea in this PR is to reorganize the tests to **better reflect** the
use case (not all just in `test_core`)
This should help help avoid later issues when we fix more typing
(allowing better isolation/management)
Also took this opportunity to **add missing tests** for `utils` and
`config`

So now we have:
1. `test_image` - stripped from `test_core`
2. `test_waiting_utils` - stripped from `test_core`
3. a much cleaner `test_core`
4. `test_utils` - new
5. `test_config` - new

```bash
---------- coverage: platform linux, python 3.10.12-final-0 ----------
Name                                        Stmts   Miss Branch BrPart  Cover   Missing
---------------------------------------------------------------------------------------
core/testcontainers/core/__init__.py            0      0      0      0   100%
core/testcontainers/core/auth.py               31      0     12      0   100%
core/testcontainers/core/config.py             59      0     20      0   100%
core/testcontainers/core/container.py         159     23     40      9    80%   20, 61-62, 82-84, 121->123, 135, 138, 158-162, 186, 191, 244->260, 250-259, 261
core/testcontainers/core/docker_client.py     118     18     32      9    79%   63-65, 96, 134->133, 138-139, 141, 152, 161, 179, 196, 200-201, 204-208
core/testcontainers/core/exceptions.py          3      0      0      0   100%
core/testcontainers/core/generic.py            28     28      0      0     0%   13-82
core/testcontainers/core/image.py              47      2      6      2    92%   10, 68
core/testcontainers/core/labels.py             23      0      8      0   100%
core/testcontainers/core/network.py            20      1      0      0    95%   30
core/testcontainers/core/utils.py              46      9     10      2    77%   27->exit, 63-70, 79
core/testcontainers/core/version.py            20      0     12      0   100%
core/testcontainers/core/waiting_utils.py      48      8     14      4    81%   26, 60-67, 77, 104->106, 122
---------------------------------------------------------------------------------------
TOTAL                                         602     89    154     26    83%

```
  • Loading branch information
Tranquility2 authored Sep 8, 2024
1 parent 794a22e commit f1665f3
Show file tree
Hide file tree
Showing 5 changed files with 197 additions and 77 deletions.
62 changes: 62 additions & 0 deletions core/tests/test_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
from testcontainers.core.config import TestcontainersConfiguration as TCC, TC_FILE

from pytest import MonkeyPatch, mark, LogCaptureFixture

import logging
import tempfile


def test_read_tc_properties(monkeypatch: MonkeyPatch) -> None:
with tempfile.TemporaryDirectory() as tmpdirname:
file = f"{tmpdirname}/{TC_FILE}"
with open(file, "w") as f:
f.write("tc.host=some_value\n")

monkeypatch.setattr("testcontainers.core.config.TC_GLOBAL", file)

config = TCC()
assert config.tc_properties == {"tc.host": "some_value"}


@mark.parametrize("docker_auth_config_env", ["key=value", ""])
@mark.parametrize("warning_dict", [{}, {"key": "value"}, {"DOCKER_AUTH_CONFIG": "TEST"}])
@mark.parametrize("warning_dict_post", [{}, {"key": "value"}, {"DOCKER_AUTH_CONFIG": "TEST"}])
def test_docker_auth_config(
caplog: LogCaptureFixture,
monkeypatch: MonkeyPatch,
docker_auth_config_env: str,
warning_dict: dict[str, str],
warning_dict_post: dict[str, str],
) -> None:
monkeypatch.setattr("testcontainers.core.config._WARNINGS", warning_dict)
monkeypatch.setenv("DOCKER_AUTH_CONFIG", docker_auth_config_env)
caplog.set_level(logging.WARNING)

config = TCC()
if not docker_auth_config_env:
assert config.docker_auth_config == ""
assert caplog.text == ""
else:
assert config.docker_auth_config == docker_auth_config_env

if "DOCKER_AUTH_CONFIG" in warning_dict:
assert warning_dict["DOCKER_AUTH_CONFIG"] in caplog.text

if warning_dict == {}:
monkeypatch.setattr("testcontainers.core.config._WARNINGS", warning_dict_post)

config.docker_auth_config = "new_value"
assert config.docker_auth_config == "new_value"


def test_tc_properties_get_tc_host() -> None:
config = TCC()
config.tc_properties = {"tc.host": "some_value"}
assert config.tc_properties_get_tc_host() == "some_value"


def test_timeout() -> None:
config = TCC()
config.max_tries = 2
config.sleep_time = 3
assert config.timeout == 6
79 changes: 2 additions & 77 deletions core/tests/test_core.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,4 @@
import pytest
import tempfile
import random
import os

from pathlib import Path
from typing import Optional

from testcontainers.core.container import DockerContainer
from testcontainers.core.image import DockerImage
from testcontainers.core.waiting_utils import wait_for_logs


def test_timeout_is_raised_when_waiting_for_logs():
with pytest.raises(TimeoutError), DockerContainer("alpine").with_command("sleep 2") as container:
wait_for_logs(container, "Hello from Docker!", timeout=1e-3)


def test_garbage_collection_is_defensive():
Expand All @@ -26,69 +11,9 @@ def test_garbage_collection_is_defensive():
del container


def test_wait_for_hello():
with DockerContainer("hello-world") as container:
wait_for_logs(container, "Hello from Docker!")


def test_can_get_logs():
def test_get_logs():
with DockerContainer("hello-world") as container:
wait_for_logs(container, "Hello from Docker!")
stdout, stderr = container.get_logs()
assert isinstance(stdout, bytes)
assert isinstance(stderr, bytes)
assert stdout, "There should be something on stdout"


@pytest.mark.parametrize("test_cleanup", [True, False])
@pytest.mark.parametrize("test_image_tag", [None, "test-image:latest"])
def test_docker_image(test_image_tag: Optional[str], test_cleanup: bool, check_for_image):
with tempfile.TemporaryDirectory() as temp_directory:
# It's important to use a random string to avoid image caching
random_string = "Hello from Docker Image! " + str(random.randint(0, 1000))
with open(f"{temp_directory}/Dockerfile", "w") as f:
f.write(
f"""
FROM alpine:latest
CMD echo "{random_string}"
"""
)
with DockerImage(path=temp_directory, tag=test_image_tag, clean_up=test_cleanup) as image:
image_short_id = image.short_id
assert image.tag is test_image_tag, f"Expected {test_image_tag}, got {image.tag}"
assert image.short_id is not None, "Short ID should not be None"
logs = image.get_logs()
assert isinstance(logs, list), "Logs should be a list"
assert logs[0] == {"stream": "Step 1/2 : FROM alpine:latest"}
assert logs[3] == {"stream": f'Step 2/2 : CMD echo "{random_string}"'}
with DockerContainer(str(image)) as container:
assert container._container.image.short_id.endswith(image_short_id), "Image ID mismatch"
assert container.get_logs() == ((random_string + "\n").encode(), b""), "Container logs mismatch"

check_for_image(image_short_id, test_cleanup)


@pytest.mark.parametrize("dockerfile_path", [None, Path("subdir/my.Dockerfile")])
def test_docker_image_with_custom_dockerfile_path(dockerfile_path: Optional[Path]):
with tempfile.TemporaryDirectory() as temp_directory:
temp_dir_path = Path(temp_directory)
if dockerfile_path:
os.makedirs(temp_dir_path / dockerfile_path.parent, exist_ok=True)
dockerfile_rel_path = dockerfile_path
dockerfile_kwargs = {"dockerfile_path": dockerfile_path}
else:
dockerfile_rel_path = Path("Dockerfile") # default
dockerfile_kwargs = {}

with open(temp_dir_path / dockerfile_rel_path, "x") as f:
f.write(
f"""
FROM alpine:latest
CMD echo "Hello world!"
"""
)
with DockerImage(path=temp_directory, tag="test", clean_up=True, no_cache=True, **dockerfile_kwargs) as image:
image_short_id = image.short_id
with DockerContainer(str(image)) as container:
assert container._container.image.short_id.endswith(image_short_id), "Image ID mismatch"
assert container.get_logs() == (("Hello world!\n").encode(), b""), "Container logs mismatch"
assert "Hello from Docker".encode() in stdout, "There should be something on stdout"
66 changes: 66 additions & 0 deletions core/tests/test_image.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import pytest
import tempfile
import random
import os

from pathlib import Path
from typing import Optional

from testcontainers.core.container import DockerContainer
from testcontainers.core.image import DockerImage


@pytest.mark.parametrize("test_cleanup", [True, False])
@pytest.mark.parametrize("test_image_tag", [None, "test-image:latest"])
def test_docker_image(test_image_tag: Optional[str], test_cleanup: bool, check_for_image) -> None:
with tempfile.TemporaryDirectory() as temp_directory:
# It's important to use a random string to avoid image caching
random_string = "Hello from Docker Image! " + str(random.randint(0, 1000))
with open(f"{temp_directory}/Dockerfile", "w") as f:
f.write(
f"""
FROM alpine:latest
CMD echo "{random_string}"
"""
)
with DockerImage(path=temp_directory, tag=test_image_tag, clean_up=test_cleanup) as image:
image_short_id = image.short_id
assert image.tag is test_image_tag, f"Expected {test_image_tag}, got {image.tag}"
assert image.short_id is not None, "Short ID should not be None"
assert image.get_wrapped_image() is not None
logs = image.get_logs()
assert isinstance(logs, list), "Logs should be a list"
assert logs[0] == {"stream": "Step 1/2 : FROM alpine:latest"}
assert logs[3] == {"stream": f'Step 2/2 : CMD echo "{random_string}"'}
with DockerContainer(str(image)) as container:
assert container._container.image.short_id.endswith(image_short_id), "Image ID mismatch"
assert container.get_logs() == ((random_string + "\n").encode(), b""), "Container logs mismatch"

check_for_image(image_short_id, test_cleanup)


@pytest.mark.parametrize("dockerfile_path", [None, Path("subdir/my.Dockerfile")])
def test_docker_image_with_custom_dockerfile_path(dockerfile_path: Optional[Path]) -> None:
with tempfile.TemporaryDirectory() as temp_directory:
temp_dir_path = Path(temp_directory)
if dockerfile_path:
os.makedirs(temp_dir_path / dockerfile_path.parent, exist_ok=True)
dockerfile_rel_path = dockerfile_path
dockerfile_kwargs = {"dockerfile_path": dockerfile_path}
else:
dockerfile_rel_path = Path("Dockerfile") # default
dockerfile_kwargs = {}

with open(temp_dir_path / dockerfile_rel_path, "x") as f:
f.write(
f"""
FROM alpine:latest
CMD echo "Hello world!"
"""
)
with DockerImage(path=temp_directory, tag="test", clean_up=True, no_cache=True, **dockerfile_kwargs) as image:
image_short_id = image.short_id
assert image.get_wrapped_image() is not None
with DockerContainer(str(image)) as container:
assert container._container.image.short_id.endswith(image_short_id), "Image ID mismatch"
assert container.get_logs() == (("Hello world!\n").encode(), b""), "Container logs mismatch"
53 changes: 53 additions & 0 deletions core/tests/test_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
from pytest import MonkeyPatch, raises, mark

from testcontainers.core import utils


def test_setup_logger() -> None:
assert utils.setup_logger("test") is not None


@mark.parametrize("platform, expected", [("linux", "linux"), ("linux2", "linux"), ("darwin", "mac"), ("win32", "win")])
def test_os_name(monkeypatch: MonkeyPatch, platform: str, expected: str) -> None:
assert utils.os_name() is not None
monkeypatch.setattr("sys.platform", platform)
assert utils.os_name() == expected


def test_is_mac(monkeypatch: MonkeyPatch) -> None:
monkeypatch.setattr("testcontainers.core.utils.os_name", lambda: "mac")
assert utils.is_mac()


def test_is_linux(monkeypatch: MonkeyPatch) -> None:
monkeypatch.setattr("testcontainers.core.utils.os_name", lambda: "linux")
assert utils.is_linux()


def test_is_windows(monkeypatch: MonkeyPatch) -> None:
monkeypatch.setattr("testcontainers.core.utils.os_name", lambda: "win")
assert utils.is_windows()


def test_is_arm(monkeypatch: MonkeyPatch) -> None:
assert not utils.is_arm()
monkeypatch.setattr("platform.machine", lambda: "arm64")
assert utils.is_arm()
monkeypatch.setattr("platform.machine", lambda: "aarch64")
assert utils.is_arm()


def test_inside_container(monkeypatch: MonkeyPatch) -> None:
assert not utils.inside_container()
monkeypatch.setattr("os.path.exists", lambda _: True)
assert utils.inside_container()


def test_raise_for_deprecated_parameters() -> None:
kwargs = {"key": "value"}
current = "key"
replacement = "new_key"
with raises(ValueError) as e:
result = utils.raise_for_deprecated_parameter(kwargs, current, replacement)
assert str(e.value) == "Parameter 'deprecated' is deprecated and should be replaced by 'replacement'."
assert result == {}
14 changes: 14 additions & 0 deletions core/tests/test_waiting_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import pytest

from testcontainers.core.container import DockerContainer
from testcontainers.core.waiting_utils import wait_for_logs


def test_wait_for_logs() -> None:
with DockerContainer("hello-world") as container:
wait_for_logs(container, "Hello from Docker!")


def test_timeout_is_raised_when_waiting_for_logs() -> None:
with pytest.raises(TimeoutError), DockerContainer("alpine").with_command("sleep 2") as container:
wait_for_logs(container, "Hello from Docker!", timeout=1e-3)

0 comments on commit f1665f3

Please sign in to comment.