From 24e354f3bfa5912eaf7877da9442a885d7872f1a Mon Sep 17 00:00:00 2001 From: Miles Mason Winther <42948872+mmwinther@users.noreply.github.com> Date: Tue, 26 Nov 2024 18:55:49 +0100 Subject: [PATCH] fix(generic): Also catch URLError waiting for ServerContainer (#743) ## Problem summary There was a race condition here because the server hasn't always started before we send a request to it. This was causing a lot of unnecessary test failures. This change makes use of `ServerContainer` much more reliable. ## Example Traceback ``` ../../../Library/Caches/pypoetry/virtualenvs//lib/python3.12/site-packages/testcontainers/generic/server.py:70: in start self._connect() ../../../Library/Caches/pypoetry/virtualenvs//lib/python3.12/site-packages/testcontainers/core/waiting_utils.py:59: in wrapper return wrapped(*args, **kwargs) ../../../Library/Caches/pypoetry/virtualenvs//lib/python3.12/site-packages/testcontainers/generic/server.py:48: in _connect with urlopen(url) as r: /usr/local/Cellar/python@3.12/3.12.7/Frameworks/Python.framework/Versions/3.12/lib/python3.12/urllib/request.py:215: in urlopen return opener.open(url, data, timeout) /usr/local/Cellar/python@3.12/3.12.7/Frameworks/Python.framework/Versions/3.12/lib/python3.12/urllib/request.py:515: in open response = self._open(req, data) /usr/local/Cellar/python@3.12/3.12.7/Frameworks/Python.framework/Versions/3.12/lib/python3.12/urllib/request.py:532: in _open result = self._call_chain(self.handle_open, protocol, protocol + /usr/local/Cellar/python@3.12/3.12.7/Frameworks/Python.framework/Versions/3.12/lib/python3.12/urllib/request.py:492: in _call_chain result = func(*args) /usr/local/Cellar/python@3.12/3.12.7/Frameworks/Python.framework/Versions/3.12/lib/python3.12/urllib/request.py:1373: in http_open return self.do_open(http.client.HTTPConnection, req) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = http_class = req = , http_conn_args = {} host = '192.168.106.2:32876' h = headers = {'Connection': 'close', 'Host': '192.168.106.2:32876', 'User-Agent': 'Python-urllib/3.12'} def do_open(self, http_class, req, **http_conn_args): """Return an HTTPResponse object for the request, using http_class. http_class must implement the HTTPConnection API from http.client. """ host = req.host if not host: raise URLError('no host given') # will parse host:port h = http_class(host, timeout=req.timeout, **http_conn_args) h.set_debuglevel(self._debuglevel) headers = dict(req.unredirected_hdrs) headers.update({k: v for k, v in req.headers.items() if k not in headers}) # TODO(jhylton): Should this be redesigned to handle # persistent connections? # We want to make an HTTP/1.1 request, but the addinfourl # class isn't prepared to deal with a persistent connection. # It will try to read all remaining data from the socket, # which will block while the server waits for the next request. # So make sure the connection gets closed after the (only) # request. headers["Connection"] = "close" headers = {name.title(): val for name, val in headers.items()} if req._tunnel_host: tunnel_headers = {} proxy_auth_hdr = "Proxy-Authorization" if proxy_auth_hdr in headers: tunnel_headers[proxy_auth_hdr] = headers[proxy_auth_hdr] # Proxy-Authorization should not be sent to origin # server. del headers[proxy_auth_hdr] h.set_tunnel(req._tunnel_host, headers=tunnel_headers) try: try: h.request(req.get_method(), req.selector, req.data, headers, encode_chunked=req.has_header('Transfer-encoding')) except OSError as err: # timeout error > raise URLError(err) E urllib.error.URLError: /usr/local/Cellar/python@3.12/3.12.7/Frameworks/Python.framework/Versions/3.12/lib/python3.12/urllib/request.py:1347: URLError ``` --- modules/generic/testcontainers/generic/server.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/generic/testcontainers/generic/server.py b/modules/generic/testcontainers/generic/server.py index ce61e9b7..fe990f17 100644 --- a/modules/generic/testcontainers/generic/server.py +++ b/modules/generic/testcontainers/generic/server.py @@ -1,5 +1,5 @@ from typing import Union -from urllib.error import HTTPError +from urllib.error import HTTPError, URLError from urllib.request import urlopen import httpx @@ -40,7 +40,7 @@ def __init__(self, port: int, image: Union[str, DockerImage]) -> None: self.internal_port = port self.with_exposed_ports(self.internal_port) - @wait_container_is_ready(HTTPError) + @wait_container_is_ready(HTTPError, URLError) def _connect(self) -> None: # noinspection HttpUrlsUsage url = self._create_connection_url()