Skip to content

Commit f1665f3

Browse files
authored
fix(core): Reorganize core tests and improve (#693)
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% ```
1 parent 794a22e commit f1665f3

File tree

5 files changed

+197
-77
lines changed

5 files changed

+197
-77
lines changed

core/tests/test_config.py

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
from testcontainers.core.config import TestcontainersConfiguration as TCC, TC_FILE
2+
3+
from pytest import MonkeyPatch, mark, LogCaptureFixture
4+
5+
import logging
6+
import tempfile
7+
8+
9+
def test_read_tc_properties(monkeypatch: MonkeyPatch) -> None:
10+
with tempfile.TemporaryDirectory() as tmpdirname:
11+
file = f"{tmpdirname}/{TC_FILE}"
12+
with open(file, "w") as f:
13+
f.write("tc.host=some_value\n")
14+
15+
monkeypatch.setattr("testcontainers.core.config.TC_GLOBAL", file)
16+
17+
config = TCC()
18+
assert config.tc_properties == {"tc.host": "some_value"}
19+
20+
21+
@mark.parametrize("docker_auth_config_env", ["key=value", ""])
22+
@mark.parametrize("warning_dict", [{}, {"key": "value"}, {"DOCKER_AUTH_CONFIG": "TEST"}])
23+
@mark.parametrize("warning_dict_post", [{}, {"key": "value"}, {"DOCKER_AUTH_CONFIG": "TEST"}])
24+
def test_docker_auth_config(
25+
caplog: LogCaptureFixture,
26+
monkeypatch: MonkeyPatch,
27+
docker_auth_config_env: str,
28+
warning_dict: dict[str, str],
29+
warning_dict_post: dict[str, str],
30+
) -> None:
31+
monkeypatch.setattr("testcontainers.core.config._WARNINGS", warning_dict)
32+
monkeypatch.setenv("DOCKER_AUTH_CONFIG", docker_auth_config_env)
33+
caplog.set_level(logging.WARNING)
34+
35+
config = TCC()
36+
if not docker_auth_config_env:
37+
assert config.docker_auth_config == ""
38+
assert caplog.text == ""
39+
else:
40+
assert config.docker_auth_config == docker_auth_config_env
41+
42+
if "DOCKER_AUTH_CONFIG" in warning_dict:
43+
assert warning_dict["DOCKER_AUTH_CONFIG"] in caplog.text
44+
45+
if warning_dict == {}:
46+
monkeypatch.setattr("testcontainers.core.config._WARNINGS", warning_dict_post)
47+
48+
config.docker_auth_config = "new_value"
49+
assert config.docker_auth_config == "new_value"
50+
51+
52+
def test_tc_properties_get_tc_host() -> None:
53+
config = TCC()
54+
config.tc_properties = {"tc.host": "some_value"}
55+
assert config.tc_properties_get_tc_host() == "some_value"
56+
57+
58+
def test_timeout() -> None:
59+
config = TCC()
60+
config.max_tries = 2
61+
config.sleep_time = 3
62+
assert config.timeout == 6

core/tests/test_core.py

+2-77
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,4 @@
1-
import pytest
2-
import tempfile
3-
import random
4-
import os
5-
6-
from pathlib import Path
7-
from typing import Optional
8-
91
from testcontainers.core.container import DockerContainer
10-
from testcontainers.core.image import DockerImage
11-
from testcontainers.core.waiting_utils import wait_for_logs
12-
13-
14-
def test_timeout_is_raised_when_waiting_for_logs():
15-
with pytest.raises(TimeoutError), DockerContainer("alpine").with_command("sleep 2") as container:
16-
wait_for_logs(container, "Hello from Docker!", timeout=1e-3)
172

183

194
def test_garbage_collection_is_defensive():
@@ -26,69 +11,9 @@ def test_garbage_collection_is_defensive():
2611
del container
2712

2813

29-
def test_wait_for_hello():
30-
with DockerContainer("hello-world") as container:
31-
wait_for_logs(container, "Hello from Docker!")
32-
33-
34-
def test_can_get_logs():
14+
def test_get_logs():
3515
with DockerContainer("hello-world") as container:
36-
wait_for_logs(container, "Hello from Docker!")
3716
stdout, stderr = container.get_logs()
3817
assert isinstance(stdout, bytes)
3918
assert isinstance(stderr, bytes)
40-
assert stdout, "There should be something on stdout"
41-
42-
43-
@pytest.mark.parametrize("test_cleanup", [True, False])
44-
@pytest.mark.parametrize("test_image_tag", [None, "test-image:latest"])
45-
def test_docker_image(test_image_tag: Optional[str], test_cleanup: bool, check_for_image):
46-
with tempfile.TemporaryDirectory() as temp_directory:
47-
# It's important to use a random string to avoid image caching
48-
random_string = "Hello from Docker Image! " + str(random.randint(0, 1000))
49-
with open(f"{temp_directory}/Dockerfile", "w") as f:
50-
f.write(
51-
f"""
52-
FROM alpine:latest
53-
CMD echo "{random_string}"
54-
"""
55-
)
56-
with DockerImage(path=temp_directory, tag=test_image_tag, clean_up=test_cleanup) as image:
57-
image_short_id = image.short_id
58-
assert image.tag is test_image_tag, f"Expected {test_image_tag}, got {image.tag}"
59-
assert image.short_id is not None, "Short ID should not be None"
60-
logs = image.get_logs()
61-
assert isinstance(logs, list), "Logs should be a list"
62-
assert logs[0] == {"stream": "Step 1/2 : FROM alpine:latest"}
63-
assert logs[3] == {"stream": f'Step 2/2 : CMD echo "{random_string}"'}
64-
with DockerContainer(str(image)) as container:
65-
assert container._container.image.short_id.endswith(image_short_id), "Image ID mismatch"
66-
assert container.get_logs() == ((random_string + "\n").encode(), b""), "Container logs mismatch"
67-
68-
check_for_image(image_short_id, test_cleanup)
69-
70-
71-
@pytest.mark.parametrize("dockerfile_path", [None, Path("subdir/my.Dockerfile")])
72-
def test_docker_image_with_custom_dockerfile_path(dockerfile_path: Optional[Path]):
73-
with tempfile.TemporaryDirectory() as temp_directory:
74-
temp_dir_path = Path(temp_directory)
75-
if dockerfile_path:
76-
os.makedirs(temp_dir_path / dockerfile_path.parent, exist_ok=True)
77-
dockerfile_rel_path = dockerfile_path
78-
dockerfile_kwargs = {"dockerfile_path": dockerfile_path}
79-
else:
80-
dockerfile_rel_path = Path("Dockerfile") # default
81-
dockerfile_kwargs = {}
82-
83-
with open(temp_dir_path / dockerfile_rel_path, "x") as f:
84-
f.write(
85-
f"""
86-
FROM alpine:latest
87-
CMD echo "Hello world!"
88-
"""
89-
)
90-
with DockerImage(path=temp_directory, tag="test", clean_up=True, no_cache=True, **dockerfile_kwargs) as image:
91-
image_short_id = image.short_id
92-
with DockerContainer(str(image)) as container:
93-
assert container._container.image.short_id.endswith(image_short_id), "Image ID mismatch"
94-
assert container.get_logs() == (("Hello world!\n").encode(), b""), "Container logs mismatch"
19+
assert "Hello from Docker".encode() in stdout, "There should be something on stdout"

core/tests/test_image.py

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import pytest
2+
import tempfile
3+
import random
4+
import os
5+
6+
from pathlib import Path
7+
from typing import Optional
8+
9+
from testcontainers.core.container import DockerContainer
10+
from testcontainers.core.image import DockerImage
11+
12+
13+
@pytest.mark.parametrize("test_cleanup", [True, False])
14+
@pytest.mark.parametrize("test_image_tag", [None, "test-image:latest"])
15+
def test_docker_image(test_image_tag: Optional[str], test_cleanup: bool, check_for_image) -> None:
16+
with tempfile.TemporaryDirectory() as temp_directory:
17+
# It's important to use a random string to avoid image caching
18+
random_string = "Hello from Docker Image! " + str(random.randint(0, 1000))
19+
with open(f"{temp_directory}/Dockerfile", "w") as f:
20+
f.write(
21+
f"""
22+
FROM alpine:latest
23+
CMD echo "{random_string}"
24+
"""
25+
)
26+
with DockerImage(path=temp_directory, tag=test_image_tag, clean_up=test_cleanup) as image:
27+
image_short_id = image.short_id
28+
assert image.tag is test_image_tag, f"Expected {test_image_tag}, got {image.tag}"
29+
assert image.short_id is not None, "Short ID should not be None"
30+
assert image.get_wrapped_image() is not None
31+
logs = image.get_logs()
32+
assert isinstance(logs, list), "Logs should be a list"
33+
assert logs[0] == {"stream": "Step 1/2 : FROM alpine:latest"}
34+
assert logs[3] == {"stream": f'Step 2/2 : CMD echo "{random_string}"'}
35+
with DockerContainer(str(image)) as container:
36+
assert container._container.image.short_id.endswith(image_short_id), "Image ID mismatch"
37+
assert container.get_logs() == ((random_string + "\n").encode(), b""), "Container logs mismatch"
38+
39+
check_for_image(image_short_id, test_cleanup)
40+
41+
42+
@pytest.mark.parametrize("dockerfile_path", [None, Path("subdir/my.Dockerfile")])
43+
def test_docker_image_with_custom_dockerfile_path(dockerfile_path: Optional[Path]) -> None:
44+
with tempfile.TemporaryDirectory() as temp_directory:
45+
temp_dir_path = Path(temp_directory)
46+
if dockerfile_path:
47+
os.makedirs(temp_dir_path / dockerfile_path.parent, exist_ok=True)
48+
dockerfile_rel_path = dockerfile_path
49+
dockerfile_kwargs = {"dockerfile_path": dockerfile_path}
50+
else:
51+
dockerfile_rel_path = Path("Dockerfile") # default
52+
dockerfile_kwargs = {}
53+
54+
with open(temp_dir_path / dockerfile_rel_path, "x") as f:
55+
f.write(
56+
f"""
57+
FROM alpine:latest
58+
CMD echo "Hello world!"
59+
"""
60+
)
61+
with DockerImage(path=temp_directory, tag="test", clean_up=True, no_cache=True, **dockerfile_kwargs) as image:
62+
image_short_id = image.short_id
63+
assert image.get_wrapped_image() is not None
64+
with DockerContainer(str(image)) as container:
65+
assert container._container.image.short_id.endswith(image_short_id), "Image ID mismatch"
66+
assert container.get_logs() == (("Hello world!\n").encode(), b""), "Container logs mismatch"

core/tests/test_utils.py

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
from pytest import MonkeyPatch, raises, mark
2+
3+
from testcontainers.core import utils
4+
5+
6+
def test_setup_logger() -> None:
7+
assert utils.setup_logger("test") is not None
8+
9+
10+
@mark.parametrize("platform, expected", [("linux", "linux"), ("linux2", "linux"), ("darwin", "mac"), ("win32", "win")])
11+
def test_os_name(monkeypatch: MonkeyPatch, platform: str, expected: str) -> None:
12+
assert utils.os_name() is not None
13+
monkeypatch.setattr("sys.platform", platform)
14+
assert utils.os_name() == expected
15+
16+
17+
def test_is_mac(monkeypatch: MonkeyPatch) -> None:
18+
monkeypatch.setattr("testcontainers.core.utils.os_name", lambda: "mac")
19+
assert utils.is_mac()
20+
21+
22+
def test_is_linux(monkeypatch: MonkeyPatch) -> None:
23+
monkeypatch.setattr("testcontainers.core.utils.os_name", lambda: "linux")
24+
assert utils.is_linux()
25+
26+
27+
def test_is_windows(monkeypatch: MonkeyPatch) -> None:
28+
monkeypatch.setattr("testcontainers.core.utils.os_name", lambda: "win")
29+
assert utils.is_windows()
30+
31+
32+
def test_is_arm(monkeypatch: MonkeyPatch) -> None:
33+
assert not utils.is_arm()
34+
monkeypatch.setattr("platform.machine", lambda: "arm64")
35+
assert utils.is_arm()
36+
monkeypatch.setattr("platform.machine", lambda: "aarch64")
37+
assert utils.is_arm()
38+
39+
40+
def test_inside_container(monkeypatch: MonkeyPatch) -> None:
41+
assert not utils.inside_container()
42+
monkeypatch.setattr("os.path.exists", lambda _: True)
43+
assert utils.inside_container()
44+
45+
46+
def test_raise_for_deprecated_parameters() -> None:
47+
kwargs = {"key": "value"}
48+
current = "key"
49+
replacement = "new_key"
50+
with raises(ValueError) as e:
51+
result = utils.raise_for_deprecated_parameter(kwargs, current, replacement)
52+
assert str(e.value) == "Parameter 'deprecated' is deprecated and should be replaced by 'replacement'."
53+
assert result == {}

core/tests/test_waiting_utils.py

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import pytest
2+
3+
from testcontainers.core.container import DockerContainer
4+
from testcontainers.core.waiting_utils import wait_for_logs
5+
6+
7+
def test_wait_for_logs() -> None:
8+
with DockerContainer("hello-world") as container:
9+
wait_for_logs(container, "Hello from Docker!")
10+
11+
12+
def test_timeout_is_raised_when_waiting_for_logs() -> None:
13+
with pytest.raises(TimeoutError), DockerContainer("alpine").with_command("sleep 2") as container:
14+
wait_for_logs(container, "Hello from Docker!", timeout=1e-3)

0 commit comments

Comments
 (0)