Skip to content

eclipse-mosquitto 2.1.0 fails integration tests #116

Description

@f18m

If we upgrade the mosquitto container in integration_tests/mosquitto_container.py to 2.1.0 or higher all integration tests fail with

ntegration_tests/test_integration_reconn.py::test_mqtt_reconnection FAILED

==================================== ERRORS ====================================
_____________________ ERROR at setup of test_ha_discovery ______________________

request = <SubRequest 'setup' for <Function test_ha_discovery>>

    @pytest.fixture(scope="module", autouse=True)
    def setup(request):
        """
        Fixture to setup and teardown the MQTT broker
        """
>       broker.start()

integration_tests/test_integration_ha_discovery.py:21: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
integration_tests/mosquitto_container.py:36: in start
    super().start()
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/site-packages/testcontainers/mqtt/__init__.py:131: in start
    self._wait()
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/site-packages/testcontainers/mqtt/__init__.py:143: in _wait
    wait_for_logs(self, r"mosquitto version \d+.\d+.\d+ running", timeout=30)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

container = <integration_tests.mosquitto_container.MosquittoContainerEnhanced object at 0x7fe4f58a1d30>
predicate = 'mosquitto version \\d+.\\d+.\\d+ running', timeout = 30
interval = 1, predicate_streams_and = False, raise_on_exit = False

    def wait_for_logs(
        container: WaitStrategyTarget,
        predicate: Union[Callable[[str], bool], str, WaitStrategy],
        timeout: float = testcontainers_config.timeout,
        interval: float = 1,
        predicate_streams_and: bool = False,
        raise_on_exit: bool = False,
        #
    ) -> float:
        """
        Enhanced version of wait_for_logs that supports both old and new interfaces.
    
        This function waits for container logs to satisfy a predicate. It supports
        multiple input types for the predicate and maintains backwards compatibility
        with existing code while adding support for the new WaitStrategy system.
    
        This is a convenience function that can be used for simple log-based waits.
        For more complex scenarios, consider using structured wait strategies directly.
    
        Args:
            container: The DockerContainer to monitor
            predicate: The predicate to check against logs. Can be:
                - A callable function that takes log text and returns bool
                - A string that will be compiled to a regex pattern
                - A WaitStrategy object
            timeout: Maximum time to wait in seconds (default: config.timeout)
            interval: How frequently to check in seconds (default: 1)
            predicate_streams_and: If True, predicate must match both stdout and stderr (default: False)
            raise_on_exit: If True, raise RuntimeError if container exits before predicate matches (default: False)
    
        Returns:
            The time in seconds that was spent waiting
    
        Raises:
            TimeoutError: If the predicate is not satisfied within the timeout period
            RuntimeError: If raise_on_exit is True and container exits before predicate matches
    
        Example:
            # Wait for a simple string
            wait_for_logs(container, "ready for start")
    
            # Wait with custom predicate
            wait_for_logs(container, lambda logs: "database" in logs and "ready" in logs)
    
            # Wait with WaitStrategy
            strategy = LogMessageWaitStrategy("ready")
            wait_for_logs(container, strategy)
    
            # For more complex scenarios, use structured wait strategies directly:
            container.waiting_for(LogMessageWaitStrategy("ready"))
        """
        if isinstance(predicate, WaitStrategy):
            start = time.time()
            predicate.with_startup_timeout(int(timeout)).with_poll_interval(interval)
            predicate.wait_until_ready(container)
            return time.time() - start
        else:
            # Only warn for legacy usage (string or callable predicates, not WaitStrategy objects)
            warnings.warn(
                "The wait_for_logs function with string or callable predicates is deprecated and will be removed in a future version. "
                "Use structured wait strategies instead: "
                "container.waiting_for(LogMessageWaitStrategy('ready')) or "
                "container.waiting_for(LogMessageWaitStrategy(re.compile(r'pattern')))",
                DeprecationWarning,
                stacklevel=2,
            )
    
        # Original implementation for backwards compatibility
        re_predicate: Optional[Callable[[str], Any]] = None
        if timeout is None:
            timeout = testcontainers_config.timeout
        if isinstance(predicate, str):
            re_predicate = re.compile(predicate, re.MULTILINE).search
        elif callable(predicate):
            # some modules like mysql sends the search directly to the predicate
            re_predicate = predicate
        else:
            raise TypeError("Predicate must be a string or callable")
        wrapped = container.get_wrapped_container()
        start = time.time()
        while True:
            duration = time.time() - start
            stdout_b, stderr_b = container.get_logs()
            stdout = stdout_b.decode()
            stderr = stderr_b.decode()
            predicate_result = (
                re_predicate(stdout) or re_predicate(stderr)
                if predicate_streams_and is False
                else re_predicate(stdout) and re_predicate(stderr)
                #
            )
            if predicate_result:
                return duration
            if duration > timeout:
                # Get current logs and status for debugging
                stdout_str, stderr_str = _get_container_logs_for_debugging(container)
                status_info = _get_container_status_info(container)
    
>               raise TimeoutError(
                    f"Container did not emit logs satisfying predicate in {timeout:.3f} seconds. "
                    f"Container status: {status_info['status']}, health: {status_info['health_status']}. "
                    f"Recent stdout: {stdout_str}. "
                    f"Recent stderr: {stderr_str}. "
                    f"Hint: Check if the container is starting correctly and the expected log pattern is being generated. "
                    f"Verify the predicate function or pattern matches the actual log output."
                )
E               TimeoutError: Container did not emit logs satisfying predicate in 30.000 seconds. Container status: exited, health: no health check. Recent stdout: ...1 01:51:32: Saving in-memory database to /data//mosquitto.db.
E               2026-03-01 01:51:32: Error saving in-memory database, unable to open /data//mosquitto.db.new for writing, error No such file or directory
E               . Recent stderr: ... option without a listener will be disabled in the future.
E               2026-03-01 01:51:32: Info: running mosquitto as user: root.
E               2026-03-01 01:51:32: Warning: Mosquitto should not be run as root/administrator.
E               . Hint: Check if the container is starting correctly and the expected log pattern is being generated. Verify the predicate function or pattern matches the actual log output.

which seems to indicate that there are some issues writing to the database. Maybe the new containers default to immutable filesystem and we just need to mount the DB on a Docker volume.
Need to investigate more.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions