Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Allowing pytest cli arguments for host and port #8

Merged
merged 2 commits into from
Mar 29, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@
/.ruff_cache
/.vagrant
/.venv*
/.github
/build

/dist

.coverage*
coverage.xml
*.egg-info
*.pyc
__pycache__
__pycache__
6 changes: 3 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
=====================

Expand Down Expand Up @@ -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.

Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ dependencies = [
]

[project.entry-points.pytest11]
mqttcliargs = "pytest_mqtt.mqttcliargs"
capmqtt = "pytest_mqtt.capmqtt"
mosquitto = "pytest_mqtt.mosquitto"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't think that new mqttcliargs fixture needs to be exported for public consumption. Now that the new {host,port} information is also available as attributes on MqttClientAdapter, it can easily be accessed through the capmqtt fixture, right?

def test_foo(capmqtt):
    assert capmqtt.mqtt_client.host == "localhost"
    assert capmqtt.mqtt_client.port == 1883

For improved convenience, they could be mirrored / also accessed by additional properties on MqttCaptureFixture, like:

class MqttCaptureFixture:

    @property
    def host(self):
        return self.mqtt_client.host

    @property
    def port(self):
        return self.mqtt_client.port
def test_foo(capmqtt):
    assert capmqtt.host == "localhost"
    assert capmqtt.port == 1883


Expand Down
18 changes: 11 additions & 7 deletions pytest_mqtt/capmqtt.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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):
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead, you may already be able to use the built-in pytestconfig fixture, in order to retrieve configuration options.

def capmqtt(request, pytestconfig):
    host = pytestconfig.getoption("--host")
    port = int(pytestconfig.getoption("--port"))

-- https://docs.pytest.org/en/8.0.x/reference/reference.html#std-fixture-pytestconfig

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
)
Comment on lines 134 to 138
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When acquiring configuration options like this, they can be provided through three different means, see usage of capmqtt_decode_utf8.

Acquiring capmqtt_host and capmqtt_port could be implemented in the very same way, so they are not too special?


result = MqttCaptureFixture(decode_utf8=capmqtt_decode_utf8)
result = MqttCaptureFixture(decode_utf8=capmqtt_decode_utf8, host=host, port=port)
delay()
yield result
result.finalize()
18 changes: 12 additions & 6 deletions pytest_mqtt/mosquitto.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-

# Copyright (c) 2020-2022 Andreas Motl <[email protected]>
# Copyright (c) 2020-2022 Richard Pobering <[email protected]>
#
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -71,16 +75,18 @@
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

Check warning on line 89 in pytest_mqtt/mosquitto.py

View check run for this annotation

Codecov / codecov/patch

pytest_mqtt/mosquitto.py#L89

Added line #L89 was not covered by tests
return

# Spin up Mosquitto container.
Expand Down
10 changes: 10 additions & 0 deletions pytest_mqtt/mqttcliargs.py
Original file line number Diff line number Diff line change
@@ -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))
amotl marked this conversation as resolved.
Show resolved Hide resolved
yield host, port
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about conveying type=int?

We think the mqttcliargs shortcut fixture, for carrying around host/port information like this, should be dissolved completely.

Instead, we suggest to use pytestconfig, for acquiring configuration options. Because default values are already defined in pytest_addoption, there is no need to define them once again redundantly.

3 changes: 3 additions & 0 deletions testing/conftest.py
Original file line number Diff line number Diff line change
@@ -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")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a suggestion about "naming things".

Suggested change
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", default="localhost", help="mqtt host to be connected through")
parser.addoption("--mqtt-port", action="store", default=1883, help="mqtt port to be connected through")

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about conveying type=int?
-- https://docs.python.org/3/library/argparse.html#type

Suggested change
parser.addoption("--mqtt_port", action="store", default=1883, help="mqtt port to be connected through")
parser.addoption("--mqtt_port", action="store", type=int, default=1883, help="mqtt port to be connected through")

6 changes: 5 additions & 1 deletion testing/test_capmqtt.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down