From 52f295a808b81ed7405235beffa0198b5ff1d70d Mon Sep 17 00:00:00 2001 From: Roy Moore Date: Fri, 23 Aug 2024 10:43:58 +0000 Subject: [PATCH 1/6] fix: Reorganize core tests and improve --- core/tests/test_config.py | 49 ++++++++++++++++++++ core/tests/test_core.py | 79 +------------------------------- core/tests/test_image.py | 66 ++++++++++++++++++++++++++ core/tests/test_utils.py | 53 +++++++++++++++++++++ core/tests/test_waiting_utils.py | 14 ++++++ 5 files changed, 184 insertions(+), 77 deletions(-) create mode 100644 core/tests/test_config.py create mode 100644 core/tests/test_image.py create mode 100644 core/tests/test_utils.py create mode 100644 core/tests/test_waiting_utils.py diff --git a/core/tests/test_config.py b/core/tests/test_config.py new file mode 100644 index 000000000..92e73e348 --- /dev/null +++ b/core/tests/test_config.py @@ -0,0 +1,49 @@ +import logging +import pytest + +from pytest import MonkeyPatch, LogCaptureFixture + +from testcontainers.core.config import TestcontainersConfiguration + + +@pytest.mark.parametrize("show_warning, update_value", [(True, True), (False, False)]) +def test_docker_auth_config(caplog: LogCaptureFixture, show_warning: bool, update_value: bool) -> None: + monkeypatch = MonkeyPatch() + logging.captureWarnings(True) + logging.basicConfig() + + if not show_warning: + monkeypatch.setattr("testcontainers.core.config.warning", lambda x: {}) + + monkeypatch.setenv("DOCKER_AUTH_CONFIG", "some_value") + + config = TestcontainersConfiguration() + assert config.docker_auth_config == "some_value" + if show_warning: + assert caplog.text != "" + else: + assert caplog.text == "" + + if update_value: + config.docker_auth_config = "another_value" + assert config.docker_auth_config == "another_value" + + if show_warning: + assert caplog.text != "" + else: + assert caplog.text == "" + + monkeypatch.undo() + + +def test_tc_properties_get_tc_host() -> None: + config = TestcontainersConfiguration() + config.tc_properties = {"tc.host": "some_value"} + assert config.tc_properties_get_tc_host() == "some_value" + + +def test_timeout() -> None: + config = TestcontainersConfiguration() + config.max_tries = 2 + config.sleep_time = 3 + assert config.timeout == 6 diff --git a/core/tests/test_core.py b/core/tests/test_core.py index 8d0c77944..42321e280 100644 --- a/core/tests/test_core.py +++ b/core/tests/test_core.py @@ -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(): @@ -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" diff --git a/core/tests/test_image.py b/core/tests/test_image.py new file mode 100644 index 000000000..ffbbf6136 --- /dev/null +++ b/core/tests/test_image.py @@ -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): + 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]): + 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" diff --git a/core/tests/test_utils.py b/core/tests/test_utils.py new file mode 100644 index 000000000..011343279 --- /dev/null +++ b/core/tests/test_utils.py @@ -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 == {} diff --git a/core/tests/test_waiting_utils.py b/core/tests/test_waiting_utils.py new file mode 100644 index 000000000..f1b752254 --- /dev/null +++ b/core/tests/test_waiting_utils.py @@ -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(): + with DockerContainer("hello-world") as container: + wait_for_logs(container, "Hello from Docker!") + + +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) From 597ce73f91ccab51c7d916ef3ecc46ca80288151 Mon Sep 17 00:00:00 2001 From: Roy Moore Date: Fri, 23 Aug 2024 10:51:33 +0000 Subject: [PATCH 2/6] add missing types --- core/tests/test_waiting_utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/tests/test_waiting_utils.py b/core/tests/test_waiting_utils.py index f1b752254..1e684fc46 100644 --- a/core/tests/test_waiting_utils.py +++ b/core/tests/test_waiting_utils.py @@ -4,11 +4,11 @@ from testcontainers.core.waiting_utils import wait_for_logs -def test_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(): +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) From 89a839dfdbb61292c95513c87eeda234075e4c0d Mon Sep 17 00:00:00 2001 From: Roy Moore Date: Fri, 23 Aug 2024 10:52:33 +0000 Subject: [PATCH 3/6] add missing types --- core/tests/test_image.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/tests/test_image.py b/core/tests/test_image.py index ffbbf6136..da35eda07 100644 --- a/core/tests/test_image.py +++ b/core/tests/test_image.py @@ -12,7 +12,7 @@ @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): +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)) @@ -40,7 +40,7 @@ def test_docker_image(test_image_tag: Optional[str], test_cleanup: bool, check_f @pytest.mark.parametrize("dockerfile_path", [None, Path("subdir/my.Dockerfile")]) -def test_docker_image_with_custom_dockerfile_path(dockerfile_path: Optional[Path]): +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: From e675a5a4f5201a0295d58c1a1d5e8c3b5ec901b9 Mon Sep 17 00:00:00 2001 From: Roy Moore Date: Fri, 23 Aug 2024 21:36:55 +0000 Subject: [PATCH 4/6] simpler test_docker_auth_config --- core/tests/test_config.py | 44 +++++++-------------------------------- 1 file changed, 8 insertions(+), 36 deletions(-) diff --git a/core/tests/test_config.py b/core/tests/test_config.py index 92e73e348..f41674248 100644 --- a/core/tests/test_config.py +++ b/core/tests/test_config.py @@ -1,49 +1,21 @@ -import logging -import pytest +from testcontainers.core.config import TestcontainersConfiguration as TCC -from pytest import MonkeyPatch, LogCaptureFixture -from testcontainers.core.config import TestcontainersConfiguration - - -@pytest.mark.parametrize("show_warning, update_value", [(True, True), (False, False)]) -def test_docker_auth_config(caplog: LogCaptureFixture, show_warning: bool, update_value: bool) -> None: - monkeypatch = MonkeyPatch() - logging.captureWarnings(True) - logging.basicConfig() - - if not show_warning: - monkeypatch.setattr("testcontainers.core.config.warning", lambda x: {}) - - monkeypatch.setenv("DOCKER_AUTH_CONFIG", "some_value") - - config = TestcontainersConfiguration() - assert config.docker_auth_config == "some_value" - if show_warning: - assert caplog.text != "" - else: - assert caplog.text == "" - - if update_value: - config.docker_auth_config = "another_value" - assert config.docker_auth_config == "another_value" - - if show_warning: - assert caplog.text != "" - else: - assert caplog.text == "" - - monkeypatch.undo() +def test_docker_auth_config() -> None: + config = TCC() + assert config.docker_auth_config is None + config.docker_auth_config = "value" + assert config.docker_auth_config == "value" def test_tc_properties_get_tc_host() -> None: - config = TestcontainersConfiguration() + config = TCC() config.tc_properties = {"tc.host": "some_value"} assert config.tc_properties_get_tc_host() == "some_value" def test_timeout() -> None: - config = TestcontainersConfiguration() + config = TCC() config.max_tries = 2 config.sleep_time = 3 assert config.timeout == 6 From 8b7b487ef4ea92812742225a6294a2abb06e3512 Mon Sep 17 00:00:00 2001 From: Roy Moore Date: Sat, 24 Aug 2024 16:40:50 +0000 Subject: [PATCH 5/6] full test_docker_auth_config --- core/tests/test_config.py | 38 +++++++++++++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/core/tests/test_config.py b/core/tests/test_config.py index f41674248..81e746ee0 100644 --- a/core/tests/test_config.py +++ b/core/tests/test_config.py @@ -1,11 +1,39 @@ -from testcontainers.core.config import TestcontainersConfiguration as TCC +from testcontainers.core.config import TestcontainersConfiguration as TCC, _WARNINGS +from pytest import MonkeyPatch, mark, LogCaptureFixture + +import logging + + +@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) -def test_docker_auth_config() -> None: config = TCC() - assert config.docker_auth_config is None - config.docker_auth_config = "value" - assert config.docker_auth_config == "value" + 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: From 96487bea349734c2c9604ef0b005c542ea5e7d38 Mon Sep 17 00:00:00 2001 From: Roy Moore Date: Sat, 24 Aug 2024 16:46:04 +0000 Subject: [PATCH 6/6] full coverage for config --- core/tests/test_config.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/core/tests/test_config.py b/core/tests/test_config.py index 81e746ee0..a6597fd4e 100644 --- a/core/tests/test_config.py +++ b/core/tests/test_config.py @@ -1,8 +1,21 @@ -from testcontainers.core.config import TestcontainersConfiguration as TCC, _WARNINGS +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", ""])