From 557069583d2728d3a7dff3d4706812bd4e4b4c42 Mon Sep 17 00:00:00 2001 From: Niek <100143256+SchoNie@users.noreply.github.com> Date: Thu, 27 Feb 2025 13:37:28 +0100 Subject: [PATCH 1/8] tests: DockerComposer contextmanager improvements -Improve the DockerComposer contextmanager to make sure teardown happens by using try except block. -Move the self. declarations up so we are sure they are set even if an exception occurs. -set self._networks --- test/conftest.py | 39 +++++++++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/test/conftest.py b/test/conftest.py index 2b1ab44..5c1c7e4 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -471,34 +471,57 @@ def connect_to_all_networks() -> List[Network]: class DockerComposer(contextlib.AbstractContextManager): def __init__(self): + logging.debug("DockerComposer __init__") self._networks = None self._docker_compose_files = None self._project_name = None def __exit__(self, *exc_info): + logging.debug("DockerComposer __exit__") self._down() def _down(self): + logging.debug(f"DockerComposer _down {self._docker_compose_files} {self._project_name} {self._networks}") if self._docker_compose_files is None: + logging.debug("docker_compose_files is None, nothing to cleanup") return - for network in self._networks: - disconnect_from_network(network) + if self._networks: + for network in self._networks: + disconnect_from_network(network) docker_compose_down(self._docker_compose_files, self._project_name) self._docker_compose_file = None self._project_name = None + self._networks = [] def compose(self, docker_compose_files: List[str], project_name: str): if docker_compose_files == self._docker_compose_files and project_name == self._project_name: + logging.info(f"Skipping compose: {docker_compose_files} (already running under project {project_name})") + return + if docker_compose_files is None or project_name is None: + logging.info(f"Skipping compose: no compose file specified") return self._down() - if docker_compose_files is None or project_name is None: - return - docker_compose_up(docker_compose_files, project_name) - self._networks = connect_to_all_networks() - wait_for_nginxproxy_to_be_ready() - time.sleep(3) # give time to containers to be ready self._docker_compose_files = docker_compose_files self._project_name = project_name + logging.debug(f"DockerComposer compose {self._docker_compose_files} {self._project_name} {self._networks}") + + try: + docker_compose_up(docker_compose_files, project_name) + self._networks = connect_to_all_networks() + wait_for_nginxproxy_to_be_ready() + time.sleep(3) # give time to containers to be ready + + except docker.errors.APIError as e: + logging.error(f"Docker API error ({e.status_code}): {e.explanation}") + logging.debug(f"Full error message: {str(e)}") + self._down() # Ensure proper cleanup even on failure + pytest.fail(f"Docker Compose setup failed due to Docker API error: {e.explanation}") + + except RuntimeError as e: + logging.error(f"RuntimeEror encountered in: {project_name}") + logging.debug(f"Full error message: {str(e)}") + self._down() # Ensure proper cleanup even on failure + pytest.fail(f"Docker Compose setup failed due to RuntimeError in: {project_name}") ############################################################################### From d87ee36909835a8c7009111ba2be77c6b0d09430 Mon Sep 17 00:00:00 2001 From: Niek <100143256+SchoNie@users.noreply.github.com> Date: Thu, 27 Feb 2025 13:39:07 +0100 Subject: [PATCH 2/8] tests: self._docker_compose_file typo --- test/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/conftest.py b/test/conftest.py index 5c1c7e4..c420fa7 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -489,7 +489,7 @@ class DockerComposer(contextlib.AbstractContextManager): for network in self._networks: disconnect_from_network(network) docker_compose_down(self._docker_compose_files, self._project_name) - self._docker_compose_file = None + self._docker_compose_files = None self._project_name = None self._networks = [] From a6271639adfa3d6ada9cff8f6fac3e5501ed0183 Mon Sep 17 00:00:00 2001 From: Niek <100143256+SchoNie@users.noreply.github.com> Date: Thu, 27 Feb 2025 13:44:30 +0100 Subject: [PATCH 3/8] tests: DockerComposer contextmanager do not fail Change pytest.fail to a logging error because pytest.fail makes DockerComposer ContextManager exit and no _down teardown is executed. --- test/conftest.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/conftest.py b/test/conftest.py index c420fa7..cc8265e 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -321,11 +321,11 @@ def __prepare_and_execute_compose_cmd(compose_files: List[str], project_name: st compose_cmd.write(f" --file {compose_file}") compose_cmd.write(f" {cmd}") - logging.info(compose_cmd.getvalue()) try: subprocess.check_output(shlex.split(compose_cmd.getvalue()), stderr=subprocess.STDOUT) + logging.info(f"Executed '{compose_cmd.getvalue()}'") except subprocess.CalledProcessError as e: - pytest.fail(f"Error while running '{compose_cmd.getvalue()}':\n{e.output}", pytrace=False) + logging.error(f"Error while running '{compose_cmd.getvalue()}'") def docker_compose_up(compose_files: List[str], project_name: str): From 545e9cebb6bb8a5d7ab87be0079b140b3222ab83 Mon Sep 17 00:00:00 2001 From: Niek <100143256+SchoNie@users.noreply.github.com> Date: Thu, 27 Feb 2025 13:46:37 +0100 Subject: [PATCH 4/8] tests: DockerComposer KeyboardInterrupt make sure we teardown on KeyboardInterrupt --- test/conftest.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/conftest.py b/test/conftest.py index cc8265e..e2eeed2 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -511,6 +511,11 @@ class DockerComposer(contextlib.AbstractContextManager): wait_for_nginxproxy_to_be_ready() time.sleep(3) # give time to containers to be ready + except KeyboardInterrupt: + logging.warning("KeyboardInterrupt detected! Force cleanup...") + self._down() # Ensure proper shutdown + raise # Re-raise to allow pytest to exit cleanly + except docker.errors.APIError as e: logging.error(f"Docker API error ({e.status_code}): {e.explanation}") logging.debug(f"Full error message: {str(e)}") From 58e21618b44415cdd9a4066bfa21422e97e4a7b5 Mon Sep 17 00:00:00 2001 From: Niek <100143256+SchoNie@users.noreply.github.com> Date: Thu, 27 Feb 2025 13:53:35 +0100 Subject: [PATCH 5/8] tests: DockerComposer connect_to_network except If running from a container host-network-mode test fail because we cannot connect to host network. This prevents the cleanup. So make sure we always return network for later removal. Explicitly return None for readability --- test/conftest.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/test/conftest.py b/test/conftest.py index e2eeed2..cc3c48d 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -428,9 +428,15 @@ def connect_to_network(network: Network) -> Optional[Network]: # Make sure our container is connected to the nginx-proxy's network, # but avoid connecting to `none` network (not valid) with `test_server-down` tests if network.name not in my_networks and network.name != 'none': - logging.info(f"Connecting to docker network: {network.name}") - network.connect(my_container) - return network + try: + logging.info(f"Connecting to docker network: {network.name}") + network.connect(my_container) + return network + except docker.errors.APIError as e: + logging.warning(f"Failed to connect to network {network.name}: {e}") + return network # Ensure the network is still tracked for later removal + + return None def disconnect_from_network(network: Network = None): From c18795b10e7dd50be96e58b11b6a0cfe7e37415e Mon Sep 17 00:00:00 2001 From: Niek <100143256+SchoNie@users.noreply.github.com> Date: Thu, 27 Feb 2025 13:58:57 +0100 Subject: [PATCH 6/8] tests: variable interpolation comments To make same as in test_wildcard-host.yml --- test/test_custom/test_location-per-vhost.yml | 2 +- test/test_custom/test_per-vhost.yml | 2 +- test/test_htpasswd/test_htpasswd-regex-virtual-host.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/test_custom/test_location-per-vhost.yml b/test/test_custom/test_location-per-vhost.yml index 676712c..07f0817 100644 --- a/test/test_custom/test_location-per-vhost.yml +++ b/test/test_custom/test_location-per-vhost.yml @@ -27,4 +27,4 @@ services: - "83" environment: WEB_PORTS: "83" - VIRTUAL_HOST: ~^regex.*\.nginx-proxy\.example$$ + VIRTUAL_HOST: ~^regex.*\.nginx-proxy\.example$$ # we need to double the `$` because of docker compose variable interpolation diff --git a/test/test_custom/test_per-vhost.yml b/test/test_custom/test_per-vhost.yml index 3680358..317e208 100644 --- a/test/test_custom/test_per-vhost.yml +++ b/test/test_custom/test_per-vhost.yml @@ -27,4 +27,4 @@ services: - "83" environment: WEB_PORTS: "83" - VIRTUAL_HOST: ~^regex.*\.nginx-proxy\.example$$ + VIRTUAL_HOST: ~^regex.*\.nginx-proxy\.example$$ # we need to double the `$` because of docker compose variable interpolation diff --git a/test/test_htpasswd/test_htpasswd-regex-virtual-host.yml b/test/test_htpasswd/test_htpasswd-regex-virtual-host.yml index ba9d9ef..9eac0d1 100644 --- a/test/test_htpasswd/test_htpasswd-regex-virtual-host.yml +++ b/test/test_htpasswd/test_htpasswd-regex-virtual-host.yml @@ -5,4 +5,4 @@ services: - "80" environment: WEB_PORTS: "80" - VIRTUAL_HOST: ~^regex.*\.nginx-proxy\.example$$ + VIRTUAL_HOST: ~^regex.*\.nginx-proxy\.example$$ # we need to double the `$` because of docker compose variable interpolation From 15e6fa8e07d0e7e2eba2509e14069efdb28a4a81 Mon Sep 17 00:00:00 2001 From: Niek <100143256+SchoNie@users.noreply.github.com> Date: Thu, 27 Feb 2025 14:18:37 +0100 Subject: [PATCH 7/8] tests: skip test_proxy-host-network-mode when PYTEST_RUNNING_IN_CONTAINER Connecting to host network not supported when pytest is running in container --- .../test_proxy-host-network-mode.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/test_host-network-mode/test_proxy-host-network-mode.py b/test/test_host-network-mode/test_proxy-host-network-mode.py index fd7208e..5e6e72b 100644 --- a/test/test_host-network-mode/test_proxy-host-network-mode.py +++ b/test/test_host-network-mode/test_proxy-host-network-mode.py @@ -1,5 +1,15 @@ # Note: on Docker Desktop, host networking must be manually enabled. # See https://docs.docker.com/engine/network/drivers/host/ +import os + +import pytest + +PYTEST_RUNNING_IN_CONTAINER = os.environ.get('PYTEST_RUNNING_IN_CONTAINER') == "1" + +pytestmark = pytest.mark.skipif( + PYTEST_RUNNING_IN_CONTAINER, + reason="Connecting to host network not supported when pytest is running in container" +) def test_forwards_to_host_network_container_1(docker_compose, nginxproxy): r = nginxproxy.get("http://host-network-1.nginx-proxy.tld:8888/port") From 005b0bdd62069718a233190e90faa9f283a3efc7 Mon Sep 17 00:00:00 2001 From: Niek <100143256+SchoNie@users.noreply.github.com> Date: Fri, 28 Feb 2025 12:18:07 +0100 Subject: [PATCH 8/8] chore: fix whitespace --- test/conftest.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/conftest.py b/test/conftest.py index cc3c48d..15d18bc 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -169,7 +169,7 @@ def container_ip(container: Container) -> str: net_info = container.attrs["NetworkSettings"]["Networks"] if "bridge" in net_info: return net_info["bridge"]["IPAddress"] - + # container is running in host network mode if "host" in net_info: return "127.0.0.1" @@ -186,7 +186,7 @@ def container_ipv6(container: Container) -> str: net_info = container.attrs["NetworkSettings"]["Networks"] if "bridge" in net_info: return net_info["bridge"]["GlobalIPv6Address"] - + # container is running in host network mode if "host" in net_info: return "::1" @@ -527,7 +527,7 @@ class DockerComposer(contextlib.AbstractContextManager): logging.debug(f"Full error message: {str(e)}") self._down() # Ensure proper cleanup even on failure pytest.fail(f"Docker Compose setup failed due to Docker API error: {e.explanation}") - + except RuntimeError as e: logging.error(f"RuntimeEror encountered in: {project_name}") logging.debug(f"Full error message: {str(e)}")