From d6addbd2993709997683c3be67eb5241a38105e7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 30 Jan 2026 14:02:36 +0000 Subject: [PATCH 1/3] Initial plan From 929a2831b1e304b387652badf3b59edd687c07b8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 30 Jan 2026 14:04:49 +0000 Subject: [PATCH 2/3] Fix integration tests timeout by pre-pulling Docker image - Add Docker image pre-pull step to CI workflow before running tests - Add explicit image pull with error handling in test fixtures - Remove CI skip condition for integration tests - Improve error messages for Docker-related failures Co-authored-by: rnovatorov <20299819+rnovatorov@users.noreply.github.com> --- .github/workflows/ci.yml | 3 +++ Makefile | 5 ----- tests/integration/conftest.py | 18 +++++++++++++++++- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b05ea16..46cf143 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,6 +27,9 @@ jobs: - name: Install dependencies run: make install-deps + - name: Pull Docker images for integration tests + run: docker pull eclipse-mosquitto:latest + - name: Run checks run: make check diff --git a/Makefile b/Makefile index cb205df..a704f47 100644 --- a/Makefile +++ b/Makefile @@ -42,12 +42,7 @@ test-unit: .PHONY: test-integration test-integration: -# TODO: Figure out why integration tests time out in CI environment. -ifdef CI - $(warning skipping integration tests in CI environment) -else pipenv run pytest -vv --capture=no tests/integration -endif .PHONY: get-pipenv get-pipenv: diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 52549f2..7abd2e5 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -32,6 +32,22 @@ def fixture_mosquitto_container( docker_client: docker.DockerClient, ) -> Generator[docker.models.containers.Container, None, None]: name = "enapter-python-sdk-integration-tests-mosquitto" + image = os.getenv("MOSQUITTO_IMAGE", "eclipse-mosquitto:latest") + + # Ensure the image is available locally + try: + docker_client.images.get(image) + except docker.errors.ImageNotFound: + # Pull the image if not available locally + # This is handled by the CI workflow, but we keep it here for local testing + try: + docker_client.images.pull(image) + except docker.errors.APIError as e: + raise RuntimeError( + f"Failed to pull Docker image {image}. " + f"Please ensure Docker is running and you have network connectivity. " + f"Error: {e}" + ) from e try: old_mosquitto = docker_client.containers.get(name) @@ -41,7 +57,7 @@ def fixture_mosquitto_container( old_mosquitto.remove(force=True) mosquitto = docker_client.containers.run( - os.getenv("MOSQUITTO_IMAGE", "eclipse-mosquitto:latest"), + image, ["mosquitto", "-c", "/mosquitto-no-auth.conf"], name=name, network="bridge", From 0259d895a110df90c30d9474ca486afcd6e6fee2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 30 Jan 2026 14:09:16 +0000 Subject: [PATCH 3/3] Add timeout handling for Docker operations - Add 300-second timeout to docker pull in CI workflow - Implement pull_docker_image_with_timeout helper with proper timeout - Add better error handling for Docker daemon issues - Address code review feedback Co-authored-by: rnovatorov <20299819+rnovatorov@users.noreply.github.com> --- .github/workflows/ci.yml | 2 +- tests/integration/conftest.py | 56 ++++++++++++++++++++++++++++++----- 2 files changed, 49 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 46cf143..98b9bad 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,7 +28,7 @@ jobs: run: make install-deps - name: Pull Docker images for integration tests - run: docker pull eclipse-mosquitto:latest + run: timeout 300 docker pull eclipse-mosquitto:latest - name: Run checks run: make check diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 7abd2e5..d3494e1 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -10,6 +10,8 @@ import enapter MOSQUITTO_PORT = "1883/tcp" +# Timeout for Docker image pull operations (in seconds) +DOCKER_PULL_TIMEOUT = 300 # 5 minutes @pytest.fixture(name="enapter_mqtt_client") @@ -40,14 +42,12 @@ def fixture_mosquitto_container( except docker.errors.ImageNotFound: # Pull the image if not available locally # This is handled by the CI workflow, but we keep it here for local testing - try: - docker_client.images.pull(image) - except docker.errors.APIError as e: - raise RuntimeError( - f"Failed to pull Docker image {image}. " - f"Please ensure Docker is running and you have network connectivity. " - f"Error: {e}" - ) from e + pull_docker_image_with_timeout(docker_client, image) + except docker.errors.APIError as e: + raise RuntimeError( + f"Failed to access Docker daemon or image {image}. " + f"Please ensure Docker is running. Error: {e}" + ) from e try: old_mosquitto = docker_client.containers.get(name) @@ -79,6 +79,46 @@ def random_unused_port() -> int: return addr[1] +def pull_docker_image_with_timeout( + docker_client: docker.DockerClient, image: str, timeout: int = DOCKER_PULL_TIMEOUT +) -> None: + """Pull a Docker image with a timeout. + + Args: + docker_client: Docker client instance + image: Image name to pull + timeout: Timeout in seconds (default: DOCKER_PULL_TIMEOUT) + + Raises: + RuntimeError: If the image pull fails or times out + TimeoutError: If the operation exceeds the timeout + """ + import concurrent.futures + + def _pull_image() -> None: + try: + docker_client.images.pull(image) + except docker.errors.APIError as e: + raise RuntimeError( + f"Failed to pull Docker image {image}. " + f"Please ensure Docker is running and you have network connectivity. " + f"Error: {e}" + ) from e + + with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor: + future = executor.submit(_pull_image) + try: + future.result(timeout=timeout) + except concurrent.futures.TimeoutError as e: + raise TimeoutError( + f"Timeout while pulling Docker image {image} after {timeout} seconds. " + f"Please check your network connection or increase the timeout." + ) from e + except Exception: + # Re-raise any other exceptions from the pull operation + raise + + @pytest.fixture(name="docker_client", scope="session") def fixture_docker_client() -> Generator[docker.DockerClient, None, None]: docker_client = docker.from_env()