Skip to content

Commit 6c6fa38

Browse files
committed
Add unknown clusters for derived
Because the derived clusters may not have the direction noted in the table, we can't tell if they're accepted or generated commands until we match them to the base. Keep an unknown list until we merge. Also WARN on unknown clusters that persist after processing.
1 parent 9833396 commit 6c6fa38

File tree

2 files changed

+91
-17
lines changed

2 files changed

+91
-17
lines changed

src/python_testing/TestSpecParsingSupport.py

+47-2
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@
2121
from global_attribute_ids import GlobalAttributeIds
2222
from matter_testing_support import MatterBaseTest, default_matter_test_main
2323
from mobly import asserts
24-
from spec_parsing_support import ClusterParser, XmlCluster, add_cluster_data_from_xml, combine_derived_clusters_with_base
24+
from spec_parsing_support import (ClusterParser, XmlCluster, add_cluster_data_from_xml, check_clusters_for_unknown_commands,
25+
combine_derived_clusters_with_base)
2526

2627
# TODO: improve the test coverage here
2728
# https://github.com/project-chip/connectedhomeip/issues/30958
@@ -175,6 +176,21 @@ def get_access_enum_from_string(access_str: str) -> Clusters.AccessControl.Enums
175176
'</cluster>'
176177
)
177178

179+
CLUSTER_WITH_UNKNOWN_COMMAND = (
180+
'<cluster xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="types types.xsd cluster cluster.xsd" id="0xFFFE" name="Test Unknown Command" revision="1">'
181+
' <revisionHistory>'
182+
' <revision revision="1" summary="Initial version"/>'
183+
' </revisionHistory>'
184+
' <classification hierarchy="base" role="application" picsCode="BASE" scope="Endpoint"/>'
185+
' <commands>'
186+
' <command id="0x00" name="ChangeToMode" direction="commandToClient">'
187+
' <access invokePrivilege="operate"/>'
188+
' <mandatoryConform/>'
189+
' </command>'
190+
' </commands>'
191+
'</cluster>'
192+
)
193+
178194

179195
class TestSpecParsingSupport(MatterBaseTest):
180196
def test_spec_parsing_access(self):
@@ -236,6 +252,14 @@ def test_derived_clusters(self):
236252
asserts.assert_equal(set(derived_clusters["Test Base"].accepted_commands.keys()), set([0]), "Unexpected accepted commands")
237253
asserts.assert_equal(set(derived_clusters["Test Base"].generated_commands.keys()),
238254
set([1]), "Unexpected generated commands")
255+
asserts.assert_equal(str(derived_clusters["Test Base"].accepted_commands[0].conformance),
256+
"M", "Unexpected conformance on base accepted command")
257+
asserts.assert_equal(str(derived_clusters["Test Base"].generated_commands[1].conformance),
258+
"M", "Unexpected conformance on base generated command")
259+
260+
asserts.assert_equal(len(derived_clusters["Test Base"].unknown_commands),
261+
0, "Unexpected number of unknown commands in base")
262+
asserts.assert_equal(len(clusters[0xFFFF].unknown_commands), 2, "Unexpected number of unknown commands in derived cluster")
239263

240264
combine_derived_clusters_with_base(clusters, derived_clusters, ids_by_name)
241265
# Ensure the base-only attribute (1) was added to the derived cluster
@@ -247,8 +271,29 @@ def test_derived_clusters(self):
247271
asserts.assert_equal(str(clusters[0xFFFF].attributes[2].conformance), "X", "Unexpected conformance on attribute 1")
248272
asserts.assert_equal(str(clusters[0xFFFF].attributes[3].conformance), "X", "Unexpected conformance on attribute 1")
249273

274+
# Ensure both the accepted and generated command overrides work
275+
asserts.assert_true(set(clusters[0xFFFF].accepted_commands.keys()),
276+
set([0]), "Unexpected accepted command list after merge")
277+
asserts.assert_true(set(clusters[0xFFFF].generated_commands.keys()), set([1]),
278+
"Unexpected generated command list after merge")
279+
asserts.assert_equal(str(clusters[0xFFFF].accepted_commands[0].conformance),
280+
"X", "Unexpected conformance on accepted commands")
281+
asserts.assert_equal(str(clusters[0xFFFF].generated_commands[1].conformance),
282+
"X", "Unexpected conformance on generated commands")
283+
asserts.assert_equal(len(clusters[0xFFFF].unknown_commands), 0, "Unexpected number of unknown commands after merge")
284+
250285
def test_missing_command_direction(self):
251-
pass
286+
clusters: dict[int, XmlCluster] = {}
287+
derived_clusters: dict[str, XmlCluster] = {}
288+
ids_by_name: dict[str, int] = {}
289+
problems: list[ProblemNotice] = []
290+
cluster_xml = ElementTree.fromstring(CLUSTER_WITH_UNKNOWN_COMMAND)
291+
292+
add_cluster_data_from_xml(cluster_xml, clusters, derived_clusters, ids_by_name, problems)
293+
check_clusters_for_unknown_commands(clusters, problems)
294+
asserts.assert_equal(len(problems), 1, "Unexpected number of problems found")
295+
asserts.assert_equal(problems[0].location.cluster_id, 0xFFFE, "Unexpected problem location (cluster id)")
296+
asserts.assert_equal(problems[0].location.command_id, 0, "Unexpected problem location (command id)")
252297

253298

254299
if __name__ == "__main__":

src/python_testing/spec_parsing_support.py

+44-15
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ def __str__(self):
7676

7777
@dataclass
7878
class XmlCommand:
79+
id: int
7980
name: str
8081
conformance: Callable[[uint], ConformanceDecision]
8182

@@ -100,6 +101,7 @@ class XmlCluster:
100101
attributes: dict[uint, XmlAttribute]
101102
accepted_commands: dict[uint, XmlCommand]
102103
generated_commands: dict[uint, XmlCommand]
104+
unknown_commands: list[XmlCommand]
103105
events: dict[uint, XmlEvent]
104106
pics: str
105107

@@ -346,27 +348,37 @@ def parse_attributes(self) -> dict[uint, XmlAttribute]:
346348
), read_access=Clusters.AccessControl.Enums.AccessControlEntryPrivilegeEnum.kView, write_access=Clusters.AccessControl.Enums.AccessControlEntryPrivilegeEnum.kUnknownEnumValue, write_optional=False)
347349
return attributes
348350

349-
def parse_commands(self, command_type: CommandType) -> dict[uint, XmlAttribute]:
350-
commands = {}
351+
def get_command_direction(self, element: ElementTree.Element) -> CommandType:
352+
try:
353+
if element.attrib['direction'].lower() == 'responsefromserver':
354+
return CommandType.GENERATED
355+
if element.attrib['direction'].lower() == 'commandtoclient':
356+
return CommandType.UNKNOWN
357+
except KeyError:
358+
return CommandType.ACCEPTED
359+
360+
def parse_unknown_commands(self) -> list[XmlCommand]:
361+
commands = []
351362
for element, conformance_xml, access_xml in self.command_elements:
363+
if self.get_command_direction(element) != CommandType.UNKNOWN:
364+
continue
352365
code = int(element.attrib['id'], 0)
353-
dir = CommandType.ACCEPTED
354-
try:
355-
if element.attrib['direction'].lower() == 'responsefromserver':
356-
dir = CommandType.GENERATED
357-
if element.attrib['direction'].lower() == 'commandtoclient':
358-
dir = CommandType.UNKNOWN
359-
except KeyError:
360-
pass
361-
if dir != command_type:
366+
conformance = self.parse_conformance(conformance_xml)
367+
commands.append(XmlCommand(id=code, name=element.attrib['name'], conformance=conformance))
368+
return commands
369+
370+
def parse_commands(self, command_type: CommandType) -> dict[uint, XmlCommand]:
371+
commands = {}
372+
for element, conformance_xml, access_xml in self.command_elements:
373+
if self.get_command_direction(element) != command_type:
362374
continue
363375
code = int(element.attrib['id'], 0)
364376
conformance = self.parse_conformance(conformance_xml)
365377
if conformance is None:
366378
continue
367379
if code in commands:
368380
conformance = or_operation([conformance, commands[code].conformance])
369-
commands[code] = XmlCommand(name=element.attrib['name'], conformance=conformance)
381+
commands[code] = XmlCommand(id=code, name=element.attrib['name'], conformance=conformance)
370382
return commands
371383

372384
def parse_events(self) -> dict[uint, XmlAttribute]:
@@ -393,6 +405,7 @@ def create_cluster(self) -> XmlCluster:
393405
attributes=self.parse_attributes(),
394406
accepted_commands=self.parse_commands(CommandType.ACCEPTED),
395407
generated_commands=self.parse_commands(CommandType.GENERATED),
408+
unknown_commands=self.parse_unknown_commands(),
396409
events=self.parse_events(), pics=self._pics)
397410

398411
def get_problems(self) -> list[ProblemNotice]:
@@ -423,6 +436,13 @@ def add_cluster_data_from_xml(xml: ElementTree.Element, clusters: dict[int, XmlC
423436
derived_clusters[name] = new
424437

425438

439+
def check_clusters_for_unknown_commands(clusters: dict[int, XmlCluster], problems: list[ProblemNotice]):
440+
for id, cluster in clusters.items():
441+
for cmd in cluster.unknown_commands:
442+
problems.append(ProblemNotice(test_name="Spec XML parsing", location=CommandPathLocation(
443+
endpoint_id=0, cluster_id=id, command_id=cmd.id), severity=ProblemSeverity.WARNING, problem="Command with unknown direction"))
444+
445+
426446
def build_xml_clusters() -> tuple[list[XmlCluster], list[ProblemNotice]]:
427447
dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', '..', 'data_model', 'clusters')
428448
clusters: dict[int, XmlCluster] = {}
@@ -489,6 +509,8 @@ def remove_problem(location: typing.Union[CommandPathLocation, FeaturePathLocati
489509
clusters[acl_id].attributes[Clusters.AccessControl.Attributes.Acl.attribute_id].write_access = Clusters.AccessControl.Enums.AccessControlEntryPrivilegeEnum.kAdminister
490510
clusters[acl_id].attributes[Clusters.AccessControl.Attributes.Extension.attribute_id].write_access = Clusters.AccessControl.Enums.AccessControlEntryPrivilegeEnum.kAdminister
491511

512+
check_clusters_for_unknown_commands(clusters, problems)
513+
492514
return clusters, problems
493515

494516

@@ -522,7 +544,6 @@ def combine_attributes(base: dict[uint, XmlAttribute], derived: dict[uint, XmlAt
522544
for id, c in xml_clusters.items():
523545
if c.derived:
524546
base_name = c.derived
525-
print(f"fixing cluster {base_name}")
526547
if base_name in ids_by_name:
527548
base = xml_clusters[ids_by_name[c.derived]]
528549
else:
@@ -543,9 +564,17 @@ def combine_attributes(base: dict[uint, XmlAttribute], derived: dict[uint, XmlAt
543564
generated_commands.update(c.generated_commands)
544565
events = deepcopy(base.events)
545566
events.update(c.events)
567+
unknown_commands = deepcopy(base.unknown_commands)
568+
for cmd in c.unknown_commands:
569+
if cmd.id in accepted_commands.keys() and cmd.name == accepted_commands[cmd.id].name:
570+
accepted_commands[cmd.id].conformance = cmd.conformance
571+
elif cmd.id in generated_commands.keys() and cmd.name == generated_commands[cmd.id].name:
572+
generated_commands[cmd.id].conformance = cmd.conformance
573+
else:
574+
unknown_commands.append(cmd)
575+
546576
new = XmlCluster(revision=c.revision, derived=c.derived, name=c.name,
547577
feature_map=feature_map, attribute_map=attribute_map, command_map=command_map,
548578
features=features, attributes=attributes, accepted_commands=accepted_commands,
549-
generated_commands=generated_commands, events=events, pics=c.pics)
550-
print(new)
579+
generated_commands=generated_commands, unknown_commands=unknown_commands, events=events, pics=c.pics)
551580
xml_clusters[id] = new

0 commit comments

Comments
 (0)