Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ jobs:
- name: Install dependencies
run: make install-deps

- name: Pull Docker images for integration tests
run: timeout 300 docker pull eclipse-mosquitto:latest

- name: Run checks
run: make check

Expand Down
5 changes: 0 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
58 changes: 57 additions & 1 deletion tests/integration/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -32,6 +34,20 @@ 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
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)
Expand All @@ -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",
Expand All @@ -63,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()
Expand Down