Skip to content

Commit

Permalink
fix(generic): Also catch URLError waiting for ServerContainer (#743)
Browse files Browse the repository at this point in the history
## 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/<redacted>/lib/python3.12/site-packages/testcontainers/generic/server.py:70: in start
    self._connect()
../../../Library/Caches/pypoetry/virtualenvs/<redacted>/lib/python3.12/site-packages/testcontainers/core/waiting_utils.py:59: in wrapper
    return wrapped(*args, **kwargs)
../../../Library/Caches/pypoetry/virtualenvs/<redacted>/lib/python3.12/site-packages/testcontainers/generic/server.py:48: in _connect
    with urlopen(url) as r:
/usr/local/Cellar/[email protected]/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/[email protected]/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/[email protected]/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/[email protected]/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/[email protected]/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 = <urllib.request.HTTPHandler object at 0x1247ea3c0>
http_class = <class 'http.client.HTTPConnection'>
req = <urllib.request.Request object at 0x1247e8c80>, http_conn_args = {}
host = '192.168.106.2:32876'
h = <http.client.HTTPConnection object at 0x1247ea360>
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: <urlopen error [Errno 61] Connection refused>
/usr/local/Cellar/[email protected]/3.12.7/Frameworks/Python.framework/Versions/3.12/lib/python3.12/urllib/request.py:1347: URLError
```
  • Loading branch information
mmwinther authored Nov 26, 2024
1 parent 4ced198 commit 24e354f
Showing 1 changed file with 2 additions and 2 deletions.
4 changes: 2 additions & 2 deletions modules/generic/testcontainers/generic/server.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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()
Expand Down

0 comments on commit 24e354f

Please sign in to comment.