From dac61d4596e0fe7fdb1b3408eeb7508d8ecbe5d8 Mon Sep 17 00:00:00 2001 From: Teemu R Date: Tue, 5 Dec 2023 02:13:28 +0100 Subject: [PATCH] Move mocked device and status into conftest (#1873) --- miio/tests/conftest.py | 58 ++++++++++++++++ miio/tests/test_descriptorcollection.py | 63 +++++++---------- miio/tests/test_devicestatus.py | 89 ++++++++----------------- 3 files changed, 108 insertions(+), 102 deletions(-) create mode 100644 miio/tests/conftest.py diff --git a/miio/tests/conftest.py b/miio/tests/conftest.py new file mode 100644 index 000000000..def5c2d69 --- /dev/null +++ b/miio/tests/conftest.py @@ -0,0 +1,58 @@ +import pytest + +from ..device import Device +from ..devicestatus import DeviceStatus, action, sensor, setting + + +@pytest.fixture() +def dummy_status(): + """Fixture for a status class with different sensors and settings.""" + + class Status(DeviceStatus): + @property + @sensor("sensor_without_unit") + def sensor_without_unit(self) -> int: + return 1 + + @property + @sensor("sensor_with_unit", unit="V") + def sensor_with_unit(self) -> int: + return 2 + + @property + @setting("setting_without_unit", setter_name="dummy") + def setting_without_unit(self): + return 3 + + @property + @setting("setting_with_unit", unit="V", setter_name="dummy") + def setting_with_unit(self): + return 4 + + @property + @sensor("none_sensor") + def sensor_returning_none(self): + return None + + yield Status() + + +@pytest.fixture() +def dummy_device(mocker, dummy_status): + """Returns a very basic device with patched out I/O and a dummy status.""" + + class DummyDevice(Device): + @action(id="test", name="test") + def test_action(self): + pass + + d = DummyDevice("127.0.0.1", "68ffffffffffffffffffffffffffffff") + d._protocol._device_id = b"12345678" + mocker.patch("miio.Device.send") + mocker.patch("miio.Device.send_handshake") + + patched_status = mocker.patch("miio.Device.status") + patched_status.__annotations__ = {} + patched_status.__annotations__["return"] = dummy_status + + yield d diff --git a/miio/tests/test_descriptorcollection.py b/miio/tests/test_descriptorcollection.py index 0ed97dcfb..a17db0915 100644 --- a/miio/tests/test_descriptorcollection.py +++ b/miio/tests/test_descriptorcollection.py @@ -11,37 +11,20 @@ RangeDescriptor, ValidSettingRange, ) -from miio.devicestatus import action, sensor, setting +from miio.devicestatus import sensor, setting -@pytest.fixture -def dev(mocker): - d = Device("127.0.0.1", "68ffffffffffffffffffffffffffffff") - mocker.patch("miio.Device.send") - mocker.patch("miio.Device.send_handshake") - yield d - - -def test_descriptors_from_device_object(mocker): +def test_descriptors_from_device_object(dummy_device): """Test descriptor collection from device class.""" - class DummyDevice(Device): - @action(id="test", name="test") - def test_action(self): - pass - - dev = DummyDevice("127.0.0.1", "68ffffffffffffffffffffffffffffff") - mocker.patch("miio.Device.send") - mocker.patch("miio.Device.send_handshake") - - coll = DescriptorCollection(device=dev) - coll.descriptors_from_object(DummyDevice()) + coll = DescriptorCollection(device=dummy_device) + coll.descriptors_from_object(dummy_device) assert len(coll) == 1 assert isinstance(coll["test"], ActionDescriptor) -def test_descriptors_from_status_object(dev): - coll = DescriptorCollection(device=dev) +def test_descriptors_from_status_object(dummy_device): + coll = DescriptorCollection(device=dummy_device) class TestStatus(DeviceStatus): @sensor(id="test", name="test sensor") @@ -67,21 +50,21 @@ def test_setting(self): pytest.param(PropertyDescriptor, {"status_attribute": "foo"}), ], ) -def test_add_descriptor(dev: Device, cls, params): +def test_add_descriptor(dummy_device: Device, cls, params): """Test that adding a descriptor works.""" - coll: DescriptorCollection = DescriptorCollection(device=dev) + coll: DescriptorCollection = DescriptorCollection(device=dummy_device) coll.add_descriptor(cls(id="id", name="test name", **params)) assert len(coll) == 1 assert coll["id"] is not None -def test_handle_action_descriptor(mocker, dev): - coll = DescriptorCollection(device=dev) +def test_handle_action_descriptor(mocker, dummy_device): + coll = DescriptorCollection(device=dummy_device) invalid_desc = ActionDescriptor(id="action", name="test name") with pytest.raises(ValueError, match="Neither method or method_name was defined"): coll.add_descriptor(invalid_desc) - mocker.patch.object(dev, "existing_method", create=True) + mocker.patch.object(dummy_device, "existing_method", create=True) # Test method name binding act_with_method_name = ActionDescriptor( @@ -100,8 +83,8 @@ def test_handle_action_descriptor(mocker, dev): coll.add_descriptor(act_with_method_name_missing) -def test_handle_writable_property_descriptor(mocker, dev): - coll = DescriptorCollection(device=dev) +def test_handle_writable_property_descriptor(mocker, dummy_device): + coll = DescriptorCollection(device=dummy_device) data = { "name": "", "status_attribute": "", @@ -111,7 +94,7 @@ def test_handle_writable_property_descriptor(mocker, dev): with pytest.raises(ValueError, match="Neither setter or setter_name was defined"): coll.add_descriptor(invalid) - mocker.patch.object(dev, "existing_method", create=True) + mocker.patch.object(dummy_device, "existing_method", create=True) # Test name binding setter_name_desc = PropertyDescriptor( @@ -128,15 +111,15 @@ def test_handle_writable_property_descriptor(mocker, dev): ) -def test_handle_enum_constraints(dev, mocker): - coll = DescriptorCollection(device=dev) +def test_handle_enum_constraints(dummy_device, mocker): + coll = DescriptorCollection(device=dummy_device) data = { "name": "enum", "status_attribute": "attr", } - mocker.patch.object(dev, "choices_attr", create=True) + mocker.patch.object(dummy_device, "choices_attr", create=True) # Check that error is raised if choices are missing invalid = EnumDescriptor(id="missing", **data) @@ -154,8 +137,8 @@ def test_handle_enum_constraints(dev, mocker): assert coll["with_choices_attr"].choices is not None -def test_handle_range_constraints(dev, mocker): - coll = DescriptorCollection(device=dev) +def test_handle_range_constraints(dummy_device, mocker): + coll = DescriptorCollection(device=dummy_device) data = { "name": "name", @@ -170,7 +153,9 @@ def test_handle_range_constraints(dev, mocker): coll.add_descriptor(desc) assert coll["regular"].max_value == 100 - mocker.patch.object(dev, "range", create=True, new=ValidSettingRange(-1, 1000, 10)) + mocker.patch.object( + dummy_device, "range", create=True, new=ValidSettingRange(-1, 1000, 10) + ) range_attr = RangeDescriptor(id="range_attribute", range_attribute="range", **data) coll.add_descriptor(range_attr) @@ -179,8 +164,8 @@ def test_handle_range_constraints(dev, mocker): assert coll["range_attribute"].step == 10 -def test_duplicate_identifiers(dev): - coll = DescriptorCollection(device=dev) +def test_duplicate_identifiers(dummy_device): + coll = DescriptorCollection(device=dummy_device) for i in range(3): coll.add_descriptor( ActionDescriptor(id="action", name=f"action {i}", method=lambda _: _) diff --git a/miio/tests/test_devicestatus.py b/miio/tests/test_devicestatus.py index 89df77775..5872709e9 100644 --- a/miio/tests/test_devicestatus.py +++ b/miio/tests/test_devicestatus.py @@ -3,7 +3,7 @@ import pytest -from miio import Device, DeviceStatus +from miio import DeviceStatus from miio.descriptors import EnumDescriptor, RangeDescriptor, ValidSettingRange from miio.devicestatus import sensor, setting @@ -109,19 +109,19 @@ def unknown(self): pass status = DecoratedProps() - sensors = status.descriptors() - assert len(sensors) == 3 + descs = status.descriptors() + assert len(descs) == 3 - all_kwargs = sensors["all_kwargs"] + all_kwargs = descs["all_kwargs"] assert all_kwargs.name == "Voltage" assert all_kwargs.unit == "V" - assert sensors["only_name"].name == "Only name" + assert descs["only_name"].name == "Only name" - assert "unknown_kwarg" in sensors["unknown"].extras + assert "unknown_kwarg" in descs["unknown"].extras -def test_setting_decorator_number(mocker): +def test_setting_decorator_number(dummy_device, mocker): """Tests for setting decorator with numbers.""" class Settings(DeviceStatus): @@ -137,24 +137,20 @@ class Settings(DeviceStatus): def level(self) -> int: return 1 - mocker.patch("miio.Device.send") - d = Device("127.0.0.1", "68ffffffffffffffffffffffffffffff") - d._protocol._device_id = b"12345678" - # Patch status to return our class - status = mocker.patch.object(d, "status", return_value=Settings()) + status = mocker.patch.object(dummy_device, "status", return_value=Settings()) status.__annotations__ = {} status.__annotations__["return"] = Settings # Patch to create a new setter as defined in the status class - setter = mocker.patch.object(d, "set_level", create=True) + setter = mocker.patch.object(dummy_device, "set_level", create=True) - settings = d.settings() + settings = dummy_device.settings() assert len(settings) == 1 desc = settings["level"] assert isinstance(desc, RangeDescriptor) - assert getattr(d.status(), desc.status_attribute) == 1 + assert getattr(dummy_device.status(), desc.status_attribute) == 1 assert desc.name == "Level" assert desc.min_value == 0 @@ -165,7 +161,7 @@ def level(self) -> int: setter.assert_called_with(1) -def test_setting_decorator_number_range_attribute(mocker): +def test_setting_decorator_number_range_attribute(mocker, dummy_device): """Tests for setting decorator with range_attribute. This makes sure the range_attribute overrides {min,max}_value and step. @@ -186,26 +182,24 @@ class Settings(DeviceStatus): def level(self) -> int: return 1 - mocker.patch("miio.Device.send") - d = Device("127.0.0.1", "68ffffffffffffffffffffffffffffff") - d._protocol._device_id = b"12345678" - # Patch status to return our class - status = mocker.patch.object(d, "status", return_value=Settings()) + status = mocker.patch.object(dummy_device, "status", return_value=Settings()) status.__annotations__ = {} status.__annotations__["return"] = Settings - mocker.patch.object(d, "valid_range", create=True, new=ValidSettingRange(1, 100, 2)) + mocker.patch.object( + dummy_device, "valid_range", create=True, new=ValidSettingRange(1, 100, 2) + ) # Patch to create a new setter as defined in the status class - setter = mocker.patch.object(d, "set_level", create=True) + setter = mocker.patch.object(dummy_device, "set_level", create=True) - settings = d.settings() + settings = dummy_device.settings() assert len(settings) == 1 desc = settings["level"] assert isinstance(desc, RangeDescriptor) - assert getattr(d.status(), desc.status_attribute) == 1 + assert getattr(dummy_device.status(), desc.status_attribute) == 1 assert desc.name == "Level" assert desc.min_value == 1 @@ -216,7 +210,7 @@ def level(self) -> int: setter.assert_called_with(50) -def test_setting_decorator_enum(mocker): +def test_setting_decorator_enum(dummy_device, mocker): """Tests for setting decorator with enums.""" class TestEnum(Enum): @@ -235,23 +229,19 @@ class Settings(DeviceStatus): def level(self) -> TestEnum: return TestEnum.First - mocker.patch("miio.Device.send") - d = Device("127.0.0.1", "68ffffffffffffffffffffffffffffff") - d._protocol._device_id = b"12345678" - # Patch status to return our class - status = mocker.patch.object(d, "status", return_value=Settings()) + status = mocker.patch.object(dummy_device, "status", return_value=Settings()) status.__annotations__ = {} status.__annotations__["return"] = Settings # Patch to create a new setter as defined in the status class - setter = mocker.patch.object(d, "set_level", create=True) + setter = mocker.patch.object(dummy_device, "set_level", create=True) - settings = d.settings() + settings = dummy_device.settings() assert len(settings) == 1 desc = settings["level"] assert isinstance(desc, EnumDescriptor) - assert getattr(d.status(), desc.status_attribute) == TestEnum.First + assert getattr(dummy_device.status(), desc.status_attribute) == TestEnum.First assert desc.name == "Level" assert len(desc.choices) == 2 @@ -301,36 +291,9 @@ def sub_sensor(self): assert "SubStatus__sub_sensor" in dir(main) -def test_cli_output(): +def test_cli_output(dummy_status): """Test the cli output string.""" - class Status(DeviceStatus): - @property - @sensor("sensor_without_unit") - def sensor_without_unit(self) -> int: - return 1 - - @property - @sensor("sensor_with_unit", unit="V") - def sensor_with_unit(self) -> int: - return 2 - - @property - @setting("setting_without_unit", setter_name="dummy") - def setting_without_unit(self): - return 3 - - @property - @setting("setting_with_unit", unit="V", setter_name="dummy") - def setting_with_unit(self): - return 4 - - @property - @sensor("none_sensor") - def sensor_returning_none(self): - return None - - status = Status() expected_regex = [ "r-- sensor_without_unit (.+?): 1", "r-- sensor_with_unit (.+?): 2 V", @@ -338,5 +301,5 @@ def sensor_returning_none(self): r"rw- setting_with_unit (.+?): 4 V", ] - for idx, line in enumerate(status.__cli_output__.splitlines()): + for idx, line in enumerate(dummy_status.__cli_output__.splitlines()): assert re.match(expected_regex[idx], line) is not None