Skip to content

Commit acb3eb8

Browse files
committed
Reapply "TC-IDM-10.2: Add check for MACL (project-chip#35086)" (project-chip#35111)
This reverts commit 796394f.
1 parent 44f8837 commit acb3eb8

File tree

4 files changed

+121
-0
lines changed

4 files changed

+121
-0
lines changed

.github/workflows/tests.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,7 @@ jobs:
522522
scripts/run_in_python_env.sh out/venv 'python3 ./src/python_testing/TestSpecParsingSupport.py'
523523
scripts/run_in_python_env.sh out/venv 'python3 ./src/python_testing/TestConformanceTest.py'
524524
scripts/run_in_python_env.sh out/venv 'python3 ./src/python_testing/TestConformanceSupport.py'
525+
scripts/run_in_python_env.sh out/venv 'python3 ./src/python_testing/TestConformanceTest.py'
525526
scripts/run_in_python_env.sh out/venv 'python3 ./src/python_testing/TestChoiceConformanceSupport.py'
526527
scripts/run_in_python_env.sh out/venv 'python3 ./src/python_testing/test_testing/test_IDM_10_4.py'
527528
scripts/run_in_python_env.sh out/venv 'python3 ./src/python_testing/test_testing/test_TC_SC_7_1.py'

src/python_testing/TC_DeviceConformance.py

+23
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,22 @@ async def setup_class_helper(self):
5050
self.xml_device_types, problems = build_xml_device_types()
5151
self.problems.extend(problems)
5252

53+
def _get_device_type_id(self, device_type_name: str) -> int:
54+
id = [id for id, dt in self.xml_device_types.items() if dt.name.lower() == device_type_name.lower()]
55+
if len(id) != 1:
56+
self.fail_current_test(f"Unable to find {device_type_name} device type")
57+
return id[0]
58+
59+
def _has_nim(self):
60+
nim_id = self._get_device_type_id('network infrastructure manager')
61+
for endpoint in self.endpoints_tlv.values():
62+
desc = Clusters.Descriptor
63+
device_types = [dt.deviceType for dt in endpoint[desc.id][desc.Attributes.DeviceTypeList.attribute_id]]
64+
if nim_id in device_types:
65+
# TODO: it's unclear if this needs to be present on every endpoint. Right now, this assumes one is sufficient.
66+
return True
67+
return False
68+
5369
def check_conformance(self, ignore_in_progress: bool, is_ci: bool, allow_provisional: bool):
5470
problems = []
5571
success = True
@@ -120,6 +136,13 @@ def record_warning(location, problem):
120136
for f in feature_masks:
121137
location = AttributePathLocation(endpoint_id=endpoint_id, cluster_id=cluster_id,
122138
attribute_id=GlobalAttributeIds.FEATURE_MAP_ID)
139+
if cluster_id == Clusters.AccessControl.id and f == Clusters.AccessControl.Bitmaps.Feature.kManagedDevice:
140+
# Managed ACL is treated as a special case because it is only allowed if other endpoints support NIM and disallowed otherwise.
141+
if not self._has_nim():
142+
record_error(
143+
location=location, problem="MACL feature is disallowed if the Network Infrastructure Manager device type is not present")
144+
continue
145+
123146
if f not in self.xml_clusters[cluster_id].features.keys():
124147
record_error(location=location, problem=f'Unknown feature with mask 0x{f:02x}')
125148
continue

src/python_testing/TestConformanceTest.py

+96
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
from typing import Any
1919

2020
import chip.clusters as Clusters
21+
from conformance_support import ConformanceDecision
22+
from global_attribute_ids import GlobalAttributeIds
2123
from matter_testing_support import MatterBaseTest, async_test_body, default_matter_test_main
2224
from mobly import asserts
2325
from spec_parsing_support import build_xml_clusters, build_xml_device_types
@@ -109,6 +111,10 @@ def create_onoff_endpoint(endpoint: int) -> dict[int, dict[int, dict[int, Any]]]
109111
return endpoint_tlv
110112

111113

114+
def is_mandatory(conformance):
115+
return conformance(0, [], []).decision == ConformanceDecision.MANDATORY
116+
117+
112118
class TestConformanceSupport(MatterBaseTest, DeviceConformanceTests):
113119
def setup_class(self):
114120
self.xml_clusters, self.problems = build_xml_clusters()
@@ -135,6 +141,96 @@ async def test_provisional_cluster(self):
135141
success, problems = self.check_conformance(ignore_in_progress=False, is_ci=False, allow_provisional=False)
136142
asserts.assert_true(success, "Unexpected failure parsing endpoint with no clusters marked as provisional")
137143

144+
def _create_minimal_cluster(self, cluster_id: int) -> dict[int, Any]:
145+
attrs = {}
146+
attrs[GlobalAttributeIds.FEATURE_MAP_ID] = 0
147+
148+
mandatory_attributes = [id for id, a in self.xml_clusters[cluster_id].attributes.items() if is_mandatory(a.conformance)]
149+
for m in mandatory_attributes:
150+
# dummy versions - we're not using the values in this test
151+
attrs[m] = 0
152+
attrs[GlobalAttributeIds.ATTRIBUTE_LIST_ID] = mandatory_attributes
153+
mandatory_accepted_commands = [id for id, a in self.xml_clusters[cluster_id].accepted_commands.items()
154+
if is_mandatory(a.conformance)]
155+
attrs[GlobalAttributeIds.ACCEPTED_COMMAND_LIST_ID] = mandatory_accepted_commands
156+
mandatory_generated_commands = [id for id, a in self.xml_clusters[cluster_id].generated_commands.items()
157+
if is_mandatory(a.conformance)]
158+
attrs[GlobalAttributeIds.GENERATED_COMMAND_LIST_ID] = mandatory_generated_commands
159+
attrs[GlobalAttributeIds.CLUSTER_REVISION_ID] = self.xml_clusters[cluster_id].revision
160+
return attrs
161+
162+
def _create_minimal_dt(self, device_type_id: int) -> dict[int, dict[int, Any]]:
163+
''' Creates the internals of an endpoint_tlv with the minimal set of clusters, with the minimal set of attributes and commands. Global attributes only.
164+
Does NOT take into account overrides yet.
165+
'''
166+
endpoint_tlv = {}
167+
required_servers = [id for id, c in self.xml_device_types[device_type_id].server_clusters.items()
168+
if is_mandatory(c.conformance)]
169+
required_clients = [id for id, c in self.xml_device_types[device_type_id].client_clusters.items()
170+
if is_mandatory(c.conformance)]
171+
device_type_revision = self.xml_device_types[device_type_id].revision
172+
173+
for s in required_servers:
174+
endpoint_tlv[s] = self._create_minimal_cluster(s)
175+
176+
# Descriptor
177+
attr = Clusters.Descriptor.Attributes
178+
attrs = {}
179+
attrs[attr.FeatureMap.attribute_id] = 0
180+
attrs[attr.AcceptedCommandList.attribute_id] = []
181+
attrs[attr.GeneratedCommandList.attribute_id] = []
182+
attrs[attr.ClusterRevision.attribute_id] = self.xml_clusters[Clusters.Descriptor.id].revision
183+
attrs[attr.DeviceTypeList.attribute_id] = [
184+
Clusters.Descriptor.Structs.DeviceTypeStruct(deviceType=device_type_id, revision=device_type_revision)]
185+
attrs[attr.ServerList.attribute_id] = required_servers
186+
attrs[attr.ClientList.attribute_id] = required_clients
187+
attrs[attr.PartsList.attribute_id] = []
188+
attrs[attr.AttributeList.attribute_id] = []
189+
attrs[attr.AttributeList.attribute_id] = list(attrs.keys())
190+
191+
endpoint_tlv[Clusters.Descriptor.id] = attrs
192+
return endpoint_tlv
193+
194+
def add_macl(self, root_endpoint: dict[int, dict[int, Any]]):
195+
ac = Clusters.AccessControl
196+
root_endpoint[ac.id][ac.Attributes.FeatureMap.attribute_id] = ac.Bitmaps.Feature.kManagedDevice
197+
root_endpoint[ac.id][ac.Attributes.Arl.attribute_id] = []
198+
root_endpoint[ac.id][ac.Attributes.CommissioningARL.attribute_id] = []
199+
root_endpoint[ac.id][ac.Attributes.AttributeList.attribute_id].extend([
200+
ac.Attributes.Arl.attribute_id, ac.Attributes.CommissioningARL.attribute_id])
201+
root_endpoint[ac.id][ac.Attributes.AcceptedCommandList.attribute_id].append(ac.Commands.ReviewFabricRestrictions.command_id)
202+
root_endpoint[ac.id][ac.Attributes.GeneratedCommandList.attribute_id].append(
203+
ac.Commands.ReviewFabricRestrictionsResponse.command_id)
204+
205+
@async_test_body
206+
async def test_macl_handling(self):
207+
nim_id = self._get_device_type_id('network infrastructure manager')
208+
root_node_id = self._get_device_type_id('root node')
209+
on_off_id = self._get_device_type_id('On/Off Light')
210+
211+
root = self._create_minimal_dt(device_type_id=root_node_id)
212+
nim = self._create_minimal_dt(device_type_id=nim_id)
213+
self.endpoints_tlv = {0: root, 1: nim}
214+
asserts.assert_true(self._has_nim(), "Did not find NIM in generated device")
215+
216+
success, problems = self.check_conformance(ignore_in_progress=False, is_ci=False, allow_provisional=True)
217+
self.problems.extend(problems)
218+
asserts.assert_true(success, "Unexpected failure parsing minimal dt")
219+
220+
self.add_macl(root)
221+
# A MACL is allowed when there is a NIM, so this should succeed as well
222+
success, problems = self.check_conformance(ignore_in_progress=False, is_ci=False, allow_provisional=True)
223+
self.problems.extend(problems)
224+
asserts.assert_true(success, "Unexpected failure with NIM and MACL")
225+
226+
# A MACL is not allowed when there is no NIM
227+
self.endpoints_tlv[1] = self._create_minimal_dt(device_type_id=on_off_id)
228+
success, problems = self.check_conformance(ignore_in_progress=False, is_ci=False, allow_provisional=True)
229+
self.problems.extend(problems)
230+
asserts.assert_false(success, "Unexpected success with On/Off and MACL")
231+
232+
# TODO: what happens if there is a NIM and a non-NIM endpoint?
233+
138234

139235
if __name__ == "__main__":
140236
default_matter_test_main()

src/python_testing/execute_python_tests.py

+1
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ def main(search_directory, env_file):
8585
"TestChoiceConformanceSupport.py",
8686
"TC_DEMTestBase.py",
8787
"choice_conformance_support.py",
88+
"TestConformanceTest.py", # Unit test of the conformance test (TC_DeviceConformance) - does not run against an app.
8889
"TestIdChecks.py",
8990
"TestSpecParsingDeviceType.py",
9091
"TestConformanceTest.py",

0 commit comments

Comments
 (0)