From 8b625b67dbe1b7944fa7831de11fcb3866d0dae7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Fern=C3=A1ndez=20Mart=C3=ADnez?= Date: Mon, 27 Nov 2023 13:16:45 +0100 Subject: [PATCH 1/2] =?UTF-8?q?=E2=9C=A8=20Allowing=20pytest=20cli=20argum?= =?UTF-8?q?ents=20for=20host=20and=20port?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 4 +++- README.rst | 6 +++--- pyproject.toml | 1 + pytest_mqtt/capmqtt.py | 18 +++++++++++------- pytest_mqtt/mosquitto.py | 18 ++++++++++++------ pytest_mqtt/mqttcliargs.py | 10 ++++++++++ testing/conftest.py | 3 +++ testing/test_capmqtt.py | 6 +++++- 8 files changed, 48 insertions(+), 18 deletions(-) create mode 100644 pytest_mqtt/mqttcliargs.py create mode 100644 testing/conftest.py diff --git a/.gitignore b/.gitignore index 675cdcb..96282cb 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,8 @@ /.ruff_cache /.vagrant /.venv* +/.github +/build /dist @@ -11,4 +13,4 @@ coverage.xml *.egg-info *.pyc -__pycache__ +__pycache__ \ No newline at end of file diff --git a/README.rst b/README.rst index ae5957d..340fc06 100644 --- a/README.rst +++ b/README.rst @@ -47,6 +47,9 @@ fixtures for ``pytest``. It has been conceived for the fine Capture MQTT messages, using the `Paho MQTT Python Client`_, in the spirit of ``caplog`` and ``capsys``. It can also be used to publish MQTT messages. +MQTT server host and port are configurable via pytest cli arguments: +``mqtt_host`` and ``mqtt_port`` (defaults to ``localhost`` and ``1883``) + ``mosquitto`` fixture ===================== @@ -105,9 +108,6 @@ The ``capmqtt_decode_utf8`` setting can be enabled in three ways. Issues ****** -- Both fixtures currently do not support changing the MQTT broker hostname and - port number differently than ``localhost:1883``. - - The ``mosquitto`` fixture currently does not support either authentication or encryption. diff --git a/pyproject.toml b/pyproject.toml index b54b4f7..3fa9e3f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,6 +61,7 @@ dependencies = [ ] [project.entry-points.pytest11] +mqttcliargs = "pytest_mqtt.mqttcliargs" capmqtt = "pytest_mqtt.capmqtt" mosquitto = "pytest_mqtt.mosquitto" diff --git a/pytest_mqtt/capmqtt.py b/pytest_mqtt/capmqtt.py index 3311759..01befec 100644 --- a/pytest_mqtt/capmqtt.py +++ b/pytest_mqtt/capmqtt.py @@ -27,10 +27,12 @@ class MqttClientAdapter(threading.Thread): - def __init__(self, on_message_callback: t.Optional[t.Callable] = None): + def __init__(self, on_message_callback: t.Optional[t.Callable] = None, host: str = "localhost", port: int = 1883): super().__init__() self.client: mqtt.Client = mqtt.Client() self.on_message_callback = on_message_callback + self.host = host + self.port = int(port) self.setup() def setup(self): @@ -43,7 +45,7 @@ def setup(self): client.on_message = self.on_message_callback logger.debug("[PYTEST] Connecting to MQTT broker") - client.connect("localhost", port=1883) + client.connect(host=self.host, port=self.port) client.subscribe("#") def run(self): @@ -75,12 +77,12 @@ def publish(self, topic: str, payload: str, **kwargs) -> mqtt.MQTTMessageInfo: class MqttCaptureFixture: """Provides access and control of log capturing.""" - def __init__(self, decode_utf8: t.Optional[bool]) -> None: + def __init__(self, decode_utf8: t.Optional[bool], host: str = "localhost", port: int = 1883) -> None: """Creates a new funcarg.""" self._buffer: t.List[MqttMessage] = [] self._decode_utf8: bool = decode_utf8 - self.mqtt_client = MqttClientAdapter(on_message_callback=self.on_message) + self.mqtt_client = MqttClientAdapter(on_message_callback=self.on_message, host=host, port=port) self.mqtt_client.start() # time.sleep(0.1) @@ -119,20 +121,22 @@ def publish(self, topic: str, payload: str, **kwargs) -> mqtt.MQTTMessageInfo: @pytest.fixture(scope="function") -def capmqtt(request): +def capmqtt(request, mqttcliargs): """Access and control MQTT messages.""" # Configure `capmqtt` fixture, obtaining the `capmqtt_decode_utf8` setting from # either a global or module-wide setting, or from a test case marker. # https://docs.pytest.org/en/7.1.x/how-to/fixtures.html#fixtures-can-introspect-the-requesting-test-context # https://docs.pytest.org/en/7.1.x/how-to/fixtures.html#using-markers-to-pass-data-to-fixtures + + host, port = mqttcliargs + capmqtt_decode_utf8 = ( getattr(request.config.option, "capmqtt_decode_utf8", False) or getattr(request.module, "capmqtt_decode_utf8", False) or request.node.get_closest_marker("capmqtt_decode_utf8") is not None ) - - result = MqttCaptureFixture(decode_utf8=capmqtt_decode_utf8) + result = MqttCaptureFixture(decode_utf8=capmqtt_decode_utf8, host=host, port=port) delay() yield result result.finalize() diff --git a/pytest_mqtt/mosquitto.py b/pytest_mqtt/mosquitto.py index 6726924..0c26ba3 100644 --- a/pytest_mqtt/mosquitto.py +++ b/pytest_mqtt/mosquitto.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + # Copyright (c) 2020-2022 Andreas Motl # Copyright (c) 2020-2022 Richard Pobering # @@ -36,7 +37,10 @@ class Mosquitto(BaseImage): name = "mosquitto" - port = 1883 + + def __init__(self, host: str = "localhost", port: int = 1883) -> None: + self.host = host + self.port = port def check(self): # TODO: Add real implementation. @@ -71,16 +75,18 @@ def run(self): mosquitto_image = Mosquitto() -def is_mosquitto_running() -> bool: - return probe_tcp_connect("localhost", 1883) +def is_mosquitto_running(host: str, port: int) -> bool: + return probe_tcp_connect(host, port) @pytest.fixture(scope="session") -def mosquitto(): +def mosquitto(request, mqttcliargs): + + host, port = mqttcliargs # Gracefully skip spinning up the Docker container if Mosquitto is already running. - if is_mosquitto_running(): - yield "localhost", 1883 + if is_mosquitto_running(host, port): + yield host, port return # Spin up Mosquitto container. diff --git a/pytest_mqtt/mqttcliargs.py b/pytest_mqtt/mqttcliargs.py new file mode 100644 index 0000000..c19207f --- /dev/null +++ b/pytest_mqtt/mqttcliargs.py @@ -0,0 +1,10 @@ +import typing as t + +import pytest + + +@pytest.fixture(scope="session") +def mqttcliargs(request) -> t.Tuple[str, int]: + host = request.config.getoption("--mqtt_host", "localhost") + port = int(request.config.getoption("--mqtt_port", 1883)) + yield host, port diff --git a/testing/conftest.py b/testing/conftest.py new file mode 100644 index 0000000..382f9ce --- /dev/null +++ b/testing/conftest.py @@ -0,0 +1,3 @@ +def pytest_addoption(parser) -> None: + parser.addoption("--mqtt_host", action="store", default="localhost", help="mqtt host to be connected through") + parser.addoption("--mqtt_port", action="store", default=1883, help="mqtt port to be connected through") diff --git a/testing/test_capmqtt.py b/testing/test_capmqtt.py index 88fd4c4..4eba66d 100644 --- a/testing/test_capmqtt.py +++ b/testing/test_capmqtt.py @@ -3,9 +3,13 @@ def test_mqtt_client_adapter(mosquitto): - mqtt_client = MqttClientAdapter() + host, port = mosquitto + mqtt_client = MqttClientAdapter(host=host, port=port) mqtt_client.start() + assert mqtt_client.client._host == host + assert mqtt_client.client._port == int(port) + # Submit MQTT message. message_info = mqtt_client.publish("foo", "bar") message_info.wait_for_publish(timeout=0.5) From 6d945247f7d89e1d2bd8683b9c24c8bfc87d98a1 Mon Sep 17 00:00:00 2001 From: Andreas Motl Date: Fri, 29 Mar 2024 03:17:08 +0100 Subject: [PATCH 2/2] Accept command line options `--mqtt-host` and `--mqtt-port` ... in order to connect to an MQTT broker on a different endpoint than `localhost:1883`. --- CHANGES.rst | 3 +++ README.rst | 2 +- pyproject.toml | 1 - pytest_mqtt/capmqtt.py | 6 +++--- pytest_mqtt/model.py | 6 ++++++ pytest_mqtt/mosquitto.py | 5 +++-- pytest_mqtt/mqttcliargs.py | 10 ---------- testing/conftest.py | 17 +++++++++++++++-- 8 files changed, 31 insertions(+), 19 deletions(-) delete mode 100644 pytest_mqtt/mqttcliargs.py diff --git a/CHANGES.rst b/CHANGES.rst index 252fbd9..648cc0e 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ pytest-mqtt changelog in progress =========== +- Accept command line options ``--mqtt-host`` and ``--mqtt-port``, + in order to connect to an MQTT broker on a different endpoint + than ``localhost:1883``. Thanks, @zedfmario. 2023-08-03 0.3.1 diff --git a/README.rst b/README.rst index 340fc06..461b34e 100644 --- a/README.rst +++ b/README.rst @@ -48,7 +48,7 @@ Capture MQTT messages, using the `Paho MQTT Python Client`_, in the spirit of ``caplog`` and ``capsys``. It can also be used to publish MQTT messages. MQTT server host and port are configurable via pytest cli arguments: -``mqtt_host`` and ``mqtt_port`` (defaults to ``localhost`` and ``1883``) +``--mqtt-host`` and ``--mqtt-port``. Default values are ``localhost``/``1883``. ``mosquitto`` fixture ===================== diff --git a/pyproject.toml b/pyproject.toml index 3fa9e3f..b54b4f7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,7 +61,6 @@ dependencies = [ ] [project.entry-points.pytest11] -mqttcliargs = "pytest_mqtt.mqttcliargs" capmqtt = "pytest_mqtt.capmqtt" mosquitto = "pytest_mqtt.mosquitto" diff --git a/pytest_mqtt/capmqtt.py b/pytest_mqtt/capmqtt.py index 01befec..7cf7836 100644 --- a/pytest_mqtt/capmqtt.py +++ b/pytest_mqtt/capmqtt.py @@ -20,7 +20,7 @@ import paho.mqtt.client as mqtt import pytest -from pytest_mqtt.model import MqttMessage +from pytest_mqtt.model import MqttMessage, MqttSettings from pytest_mqtt.util import delay logger = logging.getLogger(__name__) @@ -121,7 +121,7 @@ def publish(self, topic: str, payload: str, **kwargs) -> mqtt.MQTTMessageInfo: @pytest.fixture(scope="function") -def capmqtt(request, mqttcliargs): +def capmqtt(request, mqtt_settings: MqttSettings): """Access and control MQTT messages.""" # Configure `capmqtt` fixture, obtaining the `capmqtt_decode_utf8` setting from @@ -129,7 +129,7 @@ def capmqtt(request, mqttcliargs): # https://docs.pytest.org/en/7.1.x/how-to/fixtures.html#fixtures-can-introspect-the-requesting-test-context # https://docs.pytest.org/en/7.1.x/how-to/fixtures.html#using-markers-to-pass-data-to-fixtures - host, port = mqttcliargs + host, port = mqtt_settings.host, mqtt_settings.port capmqtt_decode_utf8 = ( getattr(request.config.option, "capmqtt_decode_utf8", False) diff --git a/pytest_mqtt/model.py b/pytest_mqtt/model.py index ac004aa..d30989b 100644 --- a/pytest_mqtt/model.py +++ b/pytest_mqtt/model.py @@ -11,3 +11,9 @@ class MqttMessage: topic: str payload: t.Union[str, bytes] userdata: t.Optional[t.Union[t.Dict, None]] + + +@dataclasses.dataclass +class MqttSettings: + host: str + port: int diff --git a/pytest_mqtt/mosquitto.py b/pytest_mqtt/mosquitto.py index 0c26ba3..debe9ae 100644 --- a/pytest_mqtt/mosquitto.py +++ b/pytest_mqtt/mosquitto.py @@ -21,6 +21,7 @@ from pytest_docker_fixtures import images from pytest_docker_fixtures.containers._base import BaseImage +from pytest_mqtt.model import MqttSettings from pytest_mqtt.util import probe_tcp_connect images.settings["mosquitto"] = { @@ -80,9 +81,9 @@ def is_mosquitto_running(host: str, port: int) -> bool: @pytest.fixture(scope="session") -def mosquitto(request, mqttcliargs): +def mosquitto(mqtt_settings: MqttSettings): - host, port = mqttcliargs + host, port = mqtt_settings.host, mqtt_settings.port # Gracefully skip spinning up the Docker container if Mosquitto is already running. if is_mosquitto_running(host, port): diff --git a/pytest_mqtt/mqttcliargs.py b/pytest_mqtt/mqttcliargs.py deleted file mode 100644 index c19207f..0000000 --- a/pytest_mqtt/mqttcliargs.py +++ /dev/null @@ -1,10 +0,0 @@ -import typing as t - -import pytest - - -@pytest.fixture(scope="session") -def mqttcliargs(request) -> t.Tuple[str, int]: - host = request.config.getoption("--mqtt_host", "localhost") - port = int(request.config.getoption("--mqtt_port", 1883)) - yield host, port diff --git a/testing/conftest.py b/testing/conftest.py index 382f9ce..f2c67de 100644 --- a/testing/conftest.py +++ b/testing/conftest.py @@ -1,3 +1,16 @@ +import pytest + +from pytest_mqtt.model import MqttSettings + + def pytest_addoption(parser) -> None: - parser.addoption("--mqtt_host", action="store", default="localhost", help="mqtt host to be connected through") - parser.addoption("--mqtt_port", action="store", default=1883, help="mqtt port to be connected through") + parser.addoption("--mqtt-host", action="store", type=str, default="localhost", help="MQTT host name") + parser.addoption("--mqtt-port", action="store", type=int, default=1883, help="MQTT port number") + + +@pytest.fixture(scope="session") +def mqtt_settings(pytestconfig) -> MqttSettings: + return MqttSettings( + host=pytestconfig.getoption("--mqtt-host"), + port=pytestconfig.getoption("--mqtt-port"), + )