Prechádzať zdrojové kódy

Merge pull request #2591 from SchoNie/tests-contextmanager-fixes

tests: DockerComposer contextmanager improvements
Nicolas Duchon 2 mesiacov pred
rodič
commit
1842acba7d

+ 49 - 15
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"
@@ -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):
@@ -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):
@@ -471,34 +477,62 @@ 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._docker_compose_files = 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
-        self._down()
         if docker_compose_files is None or project_name is None:
+            logging.info(f"Skipping compose: no compose file specified")
             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._down()
         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 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)}")
+            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}")
 
 
 ###############################################################################

+ 1 - 1
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

+ 1 - 1
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

+ 10 - 0
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")

+ 1 - 1
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