mirror of
https://github.com/espressif/esp-matter.git
synced 2026-04-27 19:13:13 +00:00
42075d5c75
- data_model/legacy/: moved old data model to this folder - data_model/generated/: contain the automatically generated data model - tools/data_model_gen: contains the script to generate the data model
428 lines
15 KiB
Python
428 lines
15 KiB
Python
# Copyright 2026 Espressif Systems (Shanghai) PTE LTD
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
"""
|
|
Tests for code_generation/elements.py — Cluster, Attribute, Command, Event, Feature, Device
|
|
used during Jinja template rendering.
|
|
"""
|
|
|
|
import unittest
|
|
import sys
|
|
import os
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
from code_generation.elements import ( # noqa: E402
|
|
Cluster,
|
|
Attribute,
|
|
Command,
|
|
Event,
|
|
Feature,
|
|
Device,
|
|
get_choice_group,
|
|
get_id_name_lambda,
|
|
)
|
|
from code_generation.conformance_codegen import ConformanceDecision # noqa: E402
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Helpers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def _make_attr(name, id_, type_="uint8", mandatory=True, default="0"):
|
|
a = Attribute(
|
|
name=name, id=id_, type_=type_, is_mandatory=mandatory, default_value=default
|
|
)
|
|
a.converted_type = "uint8_t"
|
|
a._flag = "ATTRIBUTE_FLAG_NONE"
|
|
return a
|
|
|
|
|
|
def _make_cmd(name, id_, mandatory=True, direction="commandToServer", response="Y"):
|
|
c = Command(
|
|
name=name,
|
|
id=id_,
|
|
is_mandatory=mandatory,
|
|
direction=direction,
|
|
response=response,
|
|
)
|
|
c._flag = "COMMAND_FLAG_ACCEPTED"
|
|
c.has_callback = True
|
|
return c
|
|
|
|
|
|
def _make_event(name, id_, mandatory=True):
|
|
return Event(name=name, id=id_, is_mandatory=mandatory)
|
|
|
|
|
|
def _make_feature(name, id_, code="XX", mandatory=False, conformance=None):
|
|
f = Feature(name=name, id=id_, code=code, is_mandatory=mandatory)
|
|
if conformance:
|
|
f.conformance = conformance
|
|
return f
|
|
|
|
|
|
def _make_cluster(name="TestCluster", id_="0x0001", revision=1):
|
|
return Cluster(name=name, id=id_, revision=revision, is_mandatory=True)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Attribute (codegen)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestCodegenAttribute(unittest.TestCase):
|
|
"""Test code_generation.elements.Attribute."""
|
|
|
|
def test_basic_creation(self):
|
|
a = _make_attr("Temp", "0x0001")
|
|
self.assertEqual(a.get_id(), "0x0001")
|
|
self.assertTrue(a.is_mandatory)
|
|
|
|
def test_get_flag(self):
|
|
a = _make_attr("Temp", "0x0001")
|
|
a._flag = "ATTRIBUTE_FLAG_WRITABLE | ATTRIBUTE_FLAG_NULLABLE"
|
|
self.assertIn("WRITABLE", a.get_flag())
|
|
|
|
def test_get_type(self):
|
|
a = _make_attr("Temp", "0x0001")
|
|
a.converted_type = "int16_t"
|
|
self.assertEqual(a.get_type(), "int16_t")
|
|
|
|
def test_get_default_value(self):
|
|
a = _make_attr("Temp", "0x0001", default="42")
|
|
self.assertEqual(a.get_default_value(), "42")
|
|
|
|
def test_get_default_value_type_small(self):
|
|
a = _make_attr("Temp", "0x0001", default="10")
|
|
self.assertEqual(a.get_default_value_type(), "uint8_t")
|
|
|
|
def test_get_default_value_type_medium(self):
|
|
a = _make_attr("Temp", "0x0001", default="1000")
|
|
self.assertEqual(a.get_default_value_type(), "uint16_t")
|
|
|
|
def test_get_default_value_type_large(self):
|
|
a = _make_attr("Temp", "0x0001", default="100000")
|
|
self.assertEqual(a.get_default_value_type(), "uint32_t")
|
|
|
|
def test_get_default_value_type_non_numeric(self):
|
|
a = _make_attr("Temp", "0x0001", default="abc")
|
|
self.assertEqual(a.get_default_value_type(), "uint32_t")
|
|
|
|
def test_min_max_value(self):
|
|
a = _make_attr("Temp", "0x0001")
|
|
a.min_value = -100
|
|
a.max_value = 200
|
|
self.assertEqual(a.get_min_value(), -100)
|
|
self.assertEqual(a.get_max_value(), 200)
|
|
|
|
def test_max_value_falls_back_to_default(self):
|
|
a = _make_attr("Temp", "0x0001", default="50")
|
|
a.max_value = None
|
|
self.assertEqual(a.get_max_value(), "50")
|
|
|
|
def test_conformance_condition_default_none(self):
|
|
a = _make_attr("Temp", "0x0001")
|
|
self.assertIsNone(a.get_conformance_condition())
|
|
|
|
def test_is_internally_managed_default(self):
|
|
a = _make_attr("Temp", "0x0001")
|
|
self.assertFalse(a.is_internally_managed)
|
|
|
|
def test_is_complex_default(self):
|
|
a = _make_attr("Temp", "0x0001")
|
|
self.assertFalse(a.is_complex)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Command (codegen)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestCodegenCommand(unittest.TestCase):
|
|
"""Test code_generation.elements.Command."""
|
|
|
|
def test_basic(self):
|
|
c = _make_cmd("Off", "0x0001")
|
|
self.assertEqual(c.get_flag(), "COMMAND_FLAG_ACCEPTED")
|
|
self.assertTrue(c.has_callback)
|
|
|
|
def test_conformance_condition_default(self):
|
|
c = _make_cmd("Off", "0x0001")
|
|
self.assertIsNone(c.get_conformance_condition())
|
|
|
|
def test_fabric_scoped_default(self):
|
|
c = _make_cmd("Off", "0x0001")
|
|
self.assertFalse(c.is_fabric_scoped)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Event (codegen)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestCodegenEvent(unittest.TestCase):
|
|
"""Test code_generation.elements.Event."""
|
|
|
|
def test_basic(self):
|
|
e = _make_event("StateChange", "0x0001")
|
|
self.assertEqual(e.priority, "Info")
|
|
|
|
def test_conformance_condition_default(self):
|
|
e = _make_event("StateChange", "0x0001")
|
|
self.assertIsNone(e.get_conformance_condition())
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Feature (codegen)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestCodegenFeature(unittest.TestCase):
|
|
"""Test code_generation.elements.Feature."""
|
|
|
|
def test_basic(self):
|
|
f = _make_feature("Lighting", "0x0001", code="LT")
|
|
self.assertEqual(f.code, "LT")
|
|
|
|
def test_get_attributes_sorted(self):
|
|
f = _make_feature("Lighting", "0x0001", code="LT")
|
|
f.attributes = [_make_attr("B", "0x0002"), _make_attr("A", "0x0001")]
|
|
attrs = f.get_attributes()
|
|
self.assertEqual(attrs[0].get_id(), "0x0001")
|
|
|
|
def test_get_externally_managed_attributes(self):
|
|
f = _make_feature("Lighting", "0x0001", code="LT")
|
|
a1 = _make_attr("Ext", "0x0001")
|
|
a1.is_internally_managed = False
|
|
a2 = _make_attr("Int", "0x0002")
|
|
a2.is_internally_managed = True
|
|
f.attributes = [a1, a2]
|
|
ext = f.get_externally_managed_attributes()
|
|
self.assertEqual(len(ext), 1)
|
|
self.assertEqual(ext[0].get_id(), "0x0001")
|
|
|
|
def test_get_commands_sorted(self):
|
|
f = _make_feature("Lighting", "0x0001", code="LT")
|
|
f.commands = [_make_cmd("B", "0x0002"), _make_cmd("A", "0x0001")]
|
|
cmds = f.get_commands()
|
|
self.assertEqual(cmds[0].get_id(), "0x0001")
|
|
|
|
def test_get_events_sorted(self):
|
|
f = _make_feature("Lighting", "0x0001", code="LT")
|
|
f.events = [_make_event("B", "0x0002"), _make_event("A", "0x0001")]
|
|
evts = f.get_events()
|
|
self.assertEqual(evts[0].get_id(), "0x0001")
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Cluster (codegen)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestCodegenCluster(unittest.TestCase):
|
|
"""Test code_generation.elements.Cluster — sorting, mandatory, choice groups."""
|
|
|
|
def test_get_attributes_sorted(self):
|
|
c = _make_cluster()
|
|
c.attributes = [_make_attr("B", "0x0002"), _make_attr("A", "0x0001")]
|
|
self.assertEqual(c.get_attributes()[0].get_id(), "0x0001")
|
|
|
|
def test_get_commands_sorted(self):
|
|
c = _make_cluster()
|
|
c.commands = [_make_cmd("B", "0x0002"), _make_cmd("A", "0x0001")]
|
|
self.assertEqual(c.get_commands()[0].get_id(), "0x0001")
|
|
|
|
def test_get_events_sorted(self):
|
|
c = _make_cluster()
|
|
c.events = [_make_event("B", "0x0002"), _make_event("A", "0x0001")]
|
|
self.assertEqual(c.get_events()[0].get_id(), "0x0001")
|
|
|
|
def test_get_features_sorted(self):
|
|
c = _make_cluster()
|
|
c.features = [
|
|
_make_feature("B", "0x0002", code="BB"),
|
|
_make_feature("A", "0x0001", code="AA"),
|
|
]
|
|
self.assertEqual(c.get_features()[0].get_id(), "0x0001")
|
|
|
|
def test_get_mandatory_attributes(self):
|
|
c = _make_cluster()
|
|
a1 = _make_attr("M", "0x0001", mandatory=True)
|
|
a2 = _make_attr("O", "0x0002", mandatory=False)
|
|
c.attributes = [a1, a2]
|
|
mandatory = c.get_mandatory_attributes()
|
|
self.assertEqual(len(mandatory), 1)
|
|
self.assertEqual(mandatory[0].get_id(), "0x0001")
|
|
|
|
def test_get_mandatory_commands(self):
|
|
c = _make_cluster()
|
|
c1 = _make_cmd("M", "0x0001", mandatory=True)
|
|
c2 = _make_cmd("O", "0x0002", mandatory=False)
|
|
c.commands = [c1, c2]
|
|
mandatory = c.get_mandatory_commands()
|
|
self.assertEqual(len(mandatory), 1)
|
|
|
|
def test_get_mandatory_events(self):
|
|
c = _make_cluster()
|
|
e1 = _make_event("M", "0x0001", mandatory=True)
|
|
e2 = _make_event("O", "0x0002", mandatory=False)
|
|
c.events = [e1, e2]
|
|
mandatory = c.get_mandatory_events()
|
|
self.assertEqual(len(mandatory), 1)
|
|
|
|
def test_has_choice_features_false(self):
|
|
c = _make_cluster()
|
|
self.assertFalse(c.has_choice_features())
|
|
|
|
def test_get_cluster_init_callback(self):
|
|
c = _make_cluster(name="OnOff")
|
|
cb = c.get_cluster_init_callback()
|
|
self.assertIn("OnOff", cb)
|
|
self.assertIn("Init", cb)
|
|
|
|
def test_get_cluster_shutdown_callback(self):
|
|
c = _make_cluster(name="OnOff")
|
|
cb = c.get_cluster_shutdown_callback()
|
|
self.assertIn("OnOff", cb)
|
|
self.assertIn("Shutdown", cb)
|
|
|
|
def test_get_response_command(self):
|
|
c = _make_cluster()
|
|
cmd = _make_cmd("GetResponse", "0x0001")
|
|
c.commands = [cmd]
|
|
self.assertEqual(c.get_response_command("GetResponse"), cmd)
|
|
self.assertIsNone(c.get_response_command("Unknown"))
|
|
|
|
def test_get_destroyable_elements_empty(self):
|
|
c = _make_cluster()
|
|
c.attributes = [_make_attr("A", "0x0001")]
|
|
result = c.get_destroyable_elements("lighting")
|
|
self.assertEqual(result["attributes"], [])
|
|
self.assertEqual(result["commands"], [])
|
|
self.assertEqual(result["events"], [])
|
|
|
|
def test_get_independent_features_all_independent(self):
|
|
c = _make_cluster()
|
|
f1 = _make_feature("A", "0x0001", code="AA")
|
|
f2 = _make_feature("B", "0x0002", code="BB")
|
|
c.features = [f1, f2]
|
|
independent = c.get_independent_features()
|
|
self.assertEqual(len(independent), 2)
|
|
|
|
def test_standalone_choice_groups_empty(self):
|
|
c = _make_cluster()
|
|
self.assertEqual(c.get_standalone_choice_groups(), [])
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Device (codegen)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestCodegenDevice(unittest.TestCase):
|
|
"""Test code_generation.elements.Device."""
|
|
|
|
def test_basic(self):
|
|
d = Device(id="0x0100", name="On/Off Light", revision=3)
|
|
self.assertEqual(d.get_device_type_id(), "0x0100")
|
|
self.assertEqual(d.get_device_type_version(), 3)
|
|
|
|
def test_get_clusters_sorted(self):
|
|
d = Device(id="0x0100", name="Test", revision=1)
|
|
c1 = _make_cluster("B", "0x0006")
|
|
c1.server_cluster = True
|
|
c2 = _make_cluster("A", "0x0003")
|
|
c2.server_cluster = True
|
|
d.clusters = [c1, c2]
|
|
self.assertEqual(d.get_clusters()[0].get_id(), "0x0003")
|
|
|
|
def test_get_mandatory_clusters(self):
|
|
d = Device(id="0x0100", name="Test", revision=1)
|
|
c1 = _make_cluster("M", "0x0001")
|
|
c1.is_mandatory = True
|
|
c2 = Cluster(name="O", id="0x0002", revision=1, is_mandatory=False)
|
|
d.clusters = [c1, c2]
|
|
self.assertEqual(len(d.get_mandatory_clusters()), 1)
|
|
|
|
def test_get_unique_clusters_deduplicates(self):
|
|
d = Device(id="0x0100", name="Test", revision=1)
|
|
c1 = _make_cluster("Same", "0x0006")
|
|
c1.server_cluster = True
|
|
c2 = _make_cluster("Same", "0x0006")
|
|
c2.server_cluster = False
|
|
d.clusters = [c1, c2]
|
|
unique = d.get_unique_clusters()
|
|
self.assertEqual(len(unique), 1)
|
|
|
|
def test_binding_cluster_available(self):
|
|
d = Device(id="0x0100", name="Test", revision=1)
|
|
c = _make_cluster("Test", "0x0006")
|
|
c.client_cluster = True
|
|
c.is_mandatory = True
|
|
d.clusters = [c]
|
|
self.assertTrue(d.binding_cluster_available())
|
|
|
|
def test_no_binding_cluster(self):
|
|
d = Device(id="0x0100", name="Test", revision=1)
|
|
c = _make_cluster("Test", "0x0006")
|
|
c.server_cluster = True
|
|
c.client_cluster = False
|
|
c.is_mandatory = True
|
|
d.clusters = [c]
|
|
self.assertFalse(d.binding_cluster_available())
|
|
|
|
def test_filename(self):
|
|
d = Device(id="0x0100", name="Temperature Sensor", revision=1)
|
|
self.assertTrue(d.filename.endswith("_device"))
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Deserializer roundtrip helpers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestGetIdNameLambda(unittest.TestCase):
|
|
"""Test sorting helper."""
|
|
|
|
def test_sorts_correctly(self):
|
|
items = [
|
|
_make_attr("C", "0x0003"),
|
|
_make_attr("A", "0x0001"),
|
|
_make_attr("B", "0x0002"),
|
|
]
|
|
sorted_items = sorted(items, key=get_id_name_lambda())
|
|
self.assertEqual(
|
|
[i.get_id() for i in sorted_items], ["0x0001", "0x0002", "0x0003"]
|
|
)
|
|
|
|
|
|
class TestGetChoiceGroup(unittest.TestCase):
|
|
"""Test get_choice_group() utility."""
|
|
|
|
def test_empty_features(self):
|
|
result = get_choice_group("mandatory_parent", ConformanceDecision.OTHERWISE, [])
|
|
self.assertEqual(result, [])
|
|
|
|
def test_no_matching_conformance(self):
|
|
f = _make_feature("A", "0x0001", code="AA")
|
|
result = get_choice_group(
|
|
"mandatory_parent", ConformanceDecision.OTHERWISE, [f]
|
|
)
|
|
self.assertEqual(result, [])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|