11import json
22import logging
3+ import os
34from contextlib import contextmanager
45from pathlib import Path
56from time import sleep
@@ -35,6 +36,13 @@ def pytest_addoption(parser):
3536def image (request ):
3637 """Get image name. Builds it if needed."""
3738 image_name = request .config .getoption ("--image" )
39+
40+ # If running under pytest-xdist, make image tags unique per worker to avoid
41+ # concurrent "image already exists" collisions during docker build/export.
42+ worker_id = os .environ .get ("PYTEST_XDIST_WORKER" )
43+ if worker_id and worker_id != "master" :
44+ image_name = f"{ image_name } -{ worker_id } "
45+
3846 if request .config .getoption ("--prebuild" ):
3947 for tag in [
4048 "docker-s3" ,
@@ -57,8 +65,6 @@ def image(request):
5765 ]
5866 retcode , stdout , stderr = build .run ()
5967 _logger .log (
60- # Pytest prints warnings if a test fails, so this is a warning if
61- # the build succeeded, to allow debugging the build logs
6268 logging .ERROR if retcode else logging .WARNING ,
6369 "Build logs for COMMAND: %s\n EXIT CODE:%d\n STDOUT:%s\n STDERR:%s" ,
6470 build .bound_command (),
@@ -101,6 +107,31 @@ def _container(tag_name, dbver=None):
101107 return _container
102108
103109
110+ def _normalize_inspect_ip (container_info : dict ) -> dict :
111+ """Ensure container_info['NetworkSettings']['IPAddress'] exists.
112+
113+ Newer Docker versions (and user-defined networks) may leave
114+ NetworkSettings.IPAddress empty / absent. The per-network IP lives in
115+ NetworkSettings.Networks[<name>].IPAddress.
116+ """
117+ ns = container_info .get ("NetworkSettings" ) or {}
118+ # If IPAddress is already present and non-empty, keep it.
119+ ip = ns .get ("IPAddress" )
120+ if ip :
121+ return container_info
122+
123+ networks = ns .get ("Networks" ) or {}
124+ for net_data in networks .values ():
125+ cand = (net_data or {}).get ("IPAddress" )
126+ if cand :
127+ ns ["IPAddress" ] = cand
128+ container_info ["NetworkSettings" ] = ns
129+ return container_info
130+
131+ # No IP found; leave as-is (tests will fail with clearer error later).
132+ return container_info
133+
134+
104135@pytest .fixture
105136def postgres_factory ():
106137 """Give a running postgres container ID."""
@@ -116,6 +147,7 @@ def _container_info(dbver):
116147 f"postgres:{ dbver or pytest .MIN_PG } -alpine" ,
117148 ).strip ()
118149 container_info = json .loads (docker ("container" , "inspect" , container_id ))[0 ]
150+ container_info = _normalize_inspect_ip (container_info )
119151 for attempt in range (10 ):
120152 _logger .debug ("Attempt %d of waiting for postgres to start." , attempt )
121153 try :
0 commit comments