Skip to content

Commit 4958746

Browse files
cecilletehampson
andauthored
Python testing: Fix spec parsing for derived cluster commands (#32028)
* Pythin XML parsing: move some pieces out, add test Attemping to make some of this more testable in order to test derived cluster merging for commands. Commands handled separately in the next commit. * 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. * Fix linter * Update src/python_testing/TestSpecParsingSupport.py Co-authored-by: Terence Hampson <thampson@google.com> * address review comments * Update src/python_testing/TestSpecParsingSupport.py Co-authored-by: Terence Hampson <thampson@google.com> --------- Co-authored-by: Terence Hampson <thampson@google.com>
1 parent a0f446b commit 4958746

File tree

2 files changed

+361
-114
lines changed

2 files changed

+361
-114
lines changed

src/python_testing/TestSpecParsingSupport.py

+192-2
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,10 @@
1919

2020
import chip.clusters as Clusters
2121
from global_attribute_ids import GlobalAttributeIds
22-
from matter_testing_support import MatterBaseTest, default_matter_test_main
22+
from matter_testing_support import MatterBaseTest, ProblemNotice, default_matter_test_main
2323
from mobly import asserts
24-
from spec_parsing_support import ClusterParser, XmlCluster
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
@@ -77,6 +78,120 @@ def get_access_enum_from_string(access_str: str) -> Clusters.AccessControl.Enums
7778
asserts.fail("Unknown access string")
7879

7980

81+
BASE_CLUSTER_XML_STR = (
82+
'<cluster xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="types types.xsd cluster cluster.xsd" id="" name="Test Base" revision="1">'
83+
' <revisionHistory>'
84+
' <revision revision="1" summary="Initial version"/>'
85+
' </revisionHistory>'
86+
' <classification hierarchy="base" role="application" picsCode="BASE" scope="Endpoint"/>'
87+
' <features>'
88+
' <feature bit="0" code="DEPONOFF" name="OnOff" summary="Dependency with the OnOff cluster">'
89+
' <optionalConform/>'
90+
' </feature>'
91+
' </features>'
92+
' <attributes>'
93+
' <attribute id="0x0000" name="SupportedModes" type="list" default="MS">'
94+
' <entry type="ModeOptionStruct"/>'
95+
' <access read="true" readPrivilege="view"/>'
96+
' <quality changeOmitted="false" nullable="false" scene="false" persistence="fixed" reportable="false"/>'
97+
' <mandatoryConform/>'
98+
' <constraint type="countBetween" from="2" to="255"/>'
99+
' </attribute>'
100+
' <attribute id="0x0001" name="CurrentMode" type="uint8" default="MS">'
101+
' <access read="true" readPrivilege="view"/>'
102+
' <quality changeOmitted="false" nullable="false" scene="true" persistence="nonVolatile" reportable="false"/>'
103+
' <mandatoryConform/>'
104+
' <constraint type="desc"/>'
105+
' </attribute>'
106+
' <attribute id="0x0002" name="StartUpMode" type="uint8" default="MS">'
107+
' <access read="true" write="true" readPrivilege="view" writePrivilege="operate"/>'
108+
' <quality changeOmitted="false" nullable="true" scene="false" persistence="nonVolatile" reportable="false"/>'
109+
' <optionalConform/>'
110+
' <constraint type="desc"/>'
111+
' </attribute>'
112+
' <attribute id="0x0003" name="OnMode" type="uint8" default="null">'
113+
' <access read="true" write="true" readPrivilege="view" writePrivilege="operate"/>'
114+
' <quality changeOmitted="false" nullable="true" scene="false" persistence="nonVolatile" reportable="false"/>'
115+
' <mandatoryConform>'
116+
' <feature name="DEPONOFF"/>'
117+
' </mandatoryConform>'
118+
' <constraint type="desc"/>'
119+
' </attribute>'
120+
' </attributes>'
121+
' <commands>'
122+
' <command id="0x00" name="ChangeToMode" response="ChangeToModeResponse">'
123+
' <access invokePrivilege="operate"/>'
124+
' <mandatoryConform/>'
125+
' <field id="0" name="NewMode" type="uint8">'
126+
' <mandatoryConform/>'
127+
' <constraint type="desc"/>'
128+
' </field>'
129+
' </command>'
130+
' <command id="0x01" name="ChangeToModeResponse" direction="responseFromServer">'
131+
' <access invokePrivilege="operate"/>'
132+
' <mandatoryConform/>'
133+
' <field id="0" name="Status" type="enum8">'
134+
' <enum>'
135+
' <item from="0x00" to="0x3F" name="CommonCodes" summary="Common standard values defined in the generic Mode Base cluster specification.">'
136+
' <mandatoryConform/>'
137+
' </item>'
138+
' </enum>'
139+
' <mandatoryConform/>'
140+
' <constraint type="desc"/>'
141+
' </field>'
142+
' <field id="1" name="StatusText" type="string">'
143+
' <constraint type="maxLength" value="64"/>'
144+
' </field>'
145+
' </command>'
146+
' </commands>'
147+
'</cluster>')
148+
149+
DERIVED_CLUSTER_XML_STR = (
150+
'<cluster xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="types types.xsd cluster cluster.xsd" id="0xFFFF" name="Test Derived" revision="1">'
151+
' <revisionHistory>'
152+
' <revision revision="1" summary="Initial Release"/>'
153+
' </revisionHistory>'
154+
' <classification hierarchy="derived" baseCluster="Test Base" role="application" picsCode="MWOM" scope="Endpoint"/>'
155+
' <attributes>'
156+
' <attribute id="0x0000" name="SupportedModes">'
157+
' <mandatoryConform/>'
158+
' </attribute>'
159+
' <attribute id="0x0002" name="StartUpMode">'
160+
' <disallowConform/>'
161+
' </attribute>'
162+
' <attribute id="0x0003" name="OnMode">'
163+
' <disallowConform/>'
164+
' </attribute>'
165+
' </attributes>'
166+
' <commands>'
167+
' <command id="0x00" name="ChangeToMode" direction="commandToClient">'
168+
' <access invokePrivilege="operate"/>'
169+
' <disallowConform/>'
170+
' </command>'
171+
' <command id="0x01" name="ChangeToModeResponse" direction="commandToClient">'
172+
' <access invokePrivilege="operate"/>'
173+
' <disallowConform/>'
174+
' </command>'
175+
' </commands>'
176+
'</cluster>'
177+
)
178+
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+
194+
80195
class TestSpecParsingSupport(MatterBaseTest):
81196
def test_spec_parsing_access(self):
82197
strs = [None, 'view', 'operate', 'manage', 'admin']
@@ -106,6 +221,81 @@ def test_write_optional(self):
106221
asserts.assert_equal(xml_cluster.attributes[ATTRIBUTE_ID].write_optional,
107222
write_support == 'optional', "Unexpected write_optional value")
108223

224+
def test_derived_clusters(self):
225+
clusters: dict[int, XmlCluster] = {}
226+
pure_base_clusters: dict[str, XmlCluster] = {}
227+
ids_by_name: dict[str, int] = {}
228+
problems: list[ProblemNotice] = []
229+
base_cluster_xml = ElementTree.fromstring(BASE_CLUSTER_XML_STR)
230+
derived_cluster_xml = ElementTree.fromstring(DERIVED_CLUSTER_XML_STR)
231+
expected_global_attrs = [GlobalAttributeIds.FEATURE_MAP_ID, GlobalAttributeIds.ATTRIBUTE_LIST_ID,
232+
GlobalAttributeIds.ACCEPTED_COMMAND_LIST_ID, GlobalAttributeIds.GENERATED_COMMAND_LIST_ID, GlobalAttributeIds.CLUSTER_REVISION_ID]
233+
234+
add_cluster_data_from_xml(base_cluster_xml, clusters, pure_base_clusters, ids_by_name, problems)
235+
add_cluster_data_from_xml(derived_cluster_xml, clusters, pure_base_clusters, ids_by_name, problems)
236+
237+
asserts.assert_equal(len(clusters), 1, "Unexpected number of clusters")
238+
asserts.assert_equal(len(pure_base_clusters), 1, "Unexpected number of pure base clusters")
239+
asserts.assert_equal(len(ids_by_name), 1, "Unexpected number of IDs per name")
240+
asserts.assert_equal(len(problems), 0, "Unexpected number of problems")
241+
asserts.assert_equal(ids_by_name["Test Derived"], 0xFFFF, "Test derived name not added to IDs")
242+
243+
asserts.assert_true(0xFFFF in clusters, "Derived ID not found in clusters")
244+
asserts.assert_equal(set(clusters[0xFFFF].attributes.keys()), set(
245+
[0, 2, 3] + expected_global_attrs), "Unexpected attribute list")
246+
asserts.assert_equal(set(clusters[0xFFFF].accepted_commands.keys()), set([]), "Unexpected accepted commands")
247+
asserts.assert_equal(set(clusters[0xFFFF].generated_commands.keys()), set([]), "Unexpected generated commands")
248+
249+
asserts.assert_true("Test Base" in pure_base_clusters, "Base ID not found in derived clusters")
250+
asserts.assert_equal(set(pure_base_clusters["Test Base"].attributes.keys()), set(
251+
[0, 1, 2, 3] + expected_global_attrs), "Unexpected attribute list")
252+
asserts.assert_equal(set(pure_base_clusters["Test Base"].accepted_commands.keys()),
253+
set([0]), "Unexpected accepted commands")
254+
asserts.assert_equal(set(pure_base_clusters["Test Base"].generated_commands.keys()),
255+
set([1]), "Unexpected generated commands")
256+
asserts.assert_equal(str(pure_base_clusters["Test Base"].accepted_commands[0].conformance),
257+
"M", "Unexpected conformance on base accepted command")
258+
asserts.assert_equal(str(pure_base_clusters["Test Base"].generated_commands[1].conformance),
259+
"M", "Unexpected conformance on base generated command")
260+
261+
asserts.assert_equal(len(pure_base_clusters["Test Base"].unknown_commands),
262+
0, "Unexpected number of unknown commands in base")
263+
asserts.assert_equal(len(clusters[0xFFFF].unknown_commands), 2, "Unexpected number of unknown commands in derived cluster")
264+
265+
combine_derived_clusters_with_base(clusters, pure_base_clusters, ids_by_name)
266+
# Ensure the base-only attribute (1) was added to the derived cluster
267+
asserts.assert_equal(set(clusters[0xFFFF].attributes.keys()), set(
268+
[0, 1, 2, 3] + expected_global_attrs), "Unexpected attribute list")
269+
# Ensure the conformance overrides from the derived cluster are on the attributes
270+
asserts.assert_equal(str(clusters[0xFFFF].attributes[0].conformance), "M", "Unexpected conformance on attribute 0")
271+
asserts.assert_equal(str(clusters[0xFFFF].attributes[1].conformance), "M", "Unexpected conformance on attribute 1")
272+
asserts.assert_equal(str(clusters[0xFFFF].attributes[2].conformance), "X", "Unexpected conformance on attribute 2")
273+
asserts.assert_equal(str(clusters[0xFFFF].attributes[3].conformance), "X", "Unexpected conformance on attribute 3")
274+
275+
# Ensure both the accepted and generated command overrides work
276+
asserts.assert_true(set(clusters[0xFFFF].accepted_commands.keys()),
277+
set([0]), "Unexpected accepted command list after merge")
278+
asserts.assert_true(set(clusters[0xFFFF].generated_commands.keys()), set([1]),
279+
"Unexpected generated command list after merge")
280+
asserts.assert_equal(str(clusters[0xFFFF].accepted_commands[0].conformance),
281+
"X", "Unexpected conformance on accepted commands")
282+
asserts.assert_equal(str(clusters[0xFFFF].generated_commands[1].conformance),
283+
"X", "Unexpected conformance on generated commands")
284+
asserts.assert_equal(len(clusters[0xFFFF].unknown_commands), 0, "Unexpected number of unknown commands after merge")
285+
286+
def test_missing_command_direction(self):
287+
clusters: dict[int, XmlCluster] = {}
288+
pure_base_clusters: dict[str, XmlCluster] = {}
289+
ids_by_name: dict[str, int] = {}
290+
problems: list[ProblemNotice] = []
291+
cluster_xml = ElementTree.fromstring(CLUSTER_WITH_UNKNOWN_COMMAND)
292+
293+
add_cluster_data_from_xml(cluster_xml, clusters, pure_base_clusters, ids_by_name, problems)
294+
check_clusters_for_unknown_commands(clusters, problems)
295+
asserts.assert_equal(len(problems), 1, "Unexpected number of problems found")
296+
asserts.assert_equal(problems[0].location.cluster_id, 0xFFFE, "Unexpected problem location (cluster id)")
297+
asserts.assert_equal(problems[0].location.command_id, 0, "Unexpected problem location (command id)")
298+
109299

110300
if __name__ == "__main__":
111301
default_matter_test_main()

0 commit comments

Comments
 (0)