Skip to content

Commit 927c818

Browse files
TC-IDM-10.2: Add check for MACL (project-chip#35086)
* TC-IDM-10.2: Add check for MACL Test: unit tests. Note this has not been tested against the example app as it is still under development. * Update src/python_testing/TC_DeviceConformance.py Co-authored-by: Tennessee Carmel-Veilleux <tennessee.carmelveilleux@gmail.com> * Update src/python_testing/TestConformanceTest.py --------- Co-authored-by: Tennessee Carmel-Veilleux <tennessee.carmelveilleux@gmail.com>
1 parent ec029c1 commit 927c818

File tree

4 files changed

+156
-0
lines changed

4 files changed

+156
-0
lines changed

.github/workflows/tests.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -514,6 +514,7 @@ jobs:
514514
scripts/run_in_python_env.sh out/venv 'python3 ./src/python_testing/TestIdChecks.py'
515515
scripts/run_in_python_env.sh out/venv 'python3 ./src/python_testing/TestSpecParsingDeviceType.py'
516516
scripts/run_in_python_env.sh out/venv 'python3 ./src/python_testing/TestConformanceSupport.py'
517+
scripts/run_in_python_env.sh out/venv 'python3 ./src/python_testing/TestConformanceTest.py'
517518
scripts/run_in_python_env.sh out/venv 'python3 ./src/python_testing/TestChoiceConformanceSupport.py'
518519
scripts/run_in_python_env.sh out/venv 'python3 ./src/python_testing/test_testing/test_IDM_10_4.py'
519520
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):
5470
problems = []
5571
success = True
@@ -125,6 +141,13 @@ def record_warning(location, problem):
125141
for f in feature_masks:
126142
location = AttributePathLocation(endpoint_id=endpoint_id, cluster_id=cluster_id,
127143
attribute_id=GlobalAttributeIds.FEATURE_MAP_ID)
144+
if cluster_id == Clusters.AccessControl.id and f == Clusters.AccessControl.Bitmaps.Feature.kManagedDevice:
145+
# Managed ACL is treated as a special case because it is only allowed if other endpoints support NIM and disallowed otherwise.
146+
if not self._has_nim():
147+
record_error(
148+
location=location, problem="MACL feature is disallowed if the Network Infrastructure Manager device type is not present")
149+
continue
150+
128151
if f not in self.xml_clusters[cluster_id].features.keys():
129152
record_error(location=location, problem=f'Unknown feature with mask 0x{f:02x}')
130153
continue
+131
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
#
2+
# Copyright (c) 2024 Project CHIP Authors
3+
# All rights reserved.
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
#
17+
18+
from typing import Any
19+
20+
import chip.clusters as Clusters
21+
from conformance_support import ConformanceDecision
22+
from global_attribute_ids import GlobalAttributeIds
23+
from matter_testing_support import MatterBaseTest, async_test_body, default_matter_test_main
24+
from mobly import asserts
25+
from spec_parsing_support import build_xml_clusters, build_xml_device_types
26+
from TC_DeviceConformance import DeviceConformanceTests
27+
28+
29+
def is_mandatory(conformance):
30+
return conformance(0, [], []).decision == ConformanceDecision.MANDATORY
31+
32+
33+
class TestConformanceSupport(MatterBaseTest, DeviceConformanceTests):
34+
def setup_class(self):
35+
self.xml_clusters, self.problems = build_xml_clusters()
36+
self.xml_device_types, problems = build_xml_device_types()
37+
self.problems.extend(problems)
38+
39+
def _create_minimal_cluster(self, cluster_id: int) -> dict[int, Any]:
40+
attrs = {}
41+
attrs[GlobalAttributeIds.FEATURE_MAP_ID] = 0
42+
43+
mandatory_attributes = [id for id, a in self.xml_clusters[cluster_id].attributes.items() if is_mandatory(a.conformance)]
44+
for m in mandatory_attributes:
45+
# dummy versions - we're not using the values in this test
46+
attrs[m] = 0
47+
attrs[GlobalAttributeIds.ATTRIBUTE_LIST_ID] = mandatory_attributes
48+
mandatory_accepted_commands = [id for id, a in self.xml_clusters[cluster_id].accepted_commands.items()
49+
if is_mandatory(a.conformance)]
50+
attrs[GlobalAttributeIds.ACCEPTED_COMMAND_LIST_ID] = mandatory_accepted_commands
51+
mandatory_generated_commands = [id for id, a in self.xml_clusters[cluster_id].generated_commands.items()
52+
if is_mandatory(a.conformance)]
53+
attrs[GlobalAttributeIds.GENERATED_COMMAND_LIST_ID] = mandatory_generated_commands
54+
attrs[GlobalAttributeIds.CLUSTER_REVISION_ID] = self.xml_clusters[cluster_id].revision
55+
return attrs
56+
57+
def _create_minimal_dt(self, device_type_id: int) -> dict[int, dict[int, Any]]:
58+
''' Creates the internals of an endpoint_tlv with the minimal set of clusters, with the minimal set of attributes and commands. Global attributes only.
59+
Does NOT take into account overrides yet.
60+
'''
61+
endpoint_tlv = {}
62+
required_servers = [id for id, c in self.xml_device_types[device_type_id].server_clusters.items()
63+
if is_mandatory(c.conformance)]
64+
required_clients = [id for id, c in self.xml_device_types[device_type_id].client_clusters.items()
65+
if is_mandatory(c.conformance)]
66+
device_type_revision = self.xml_device_types[device_type_id].revision
67+
68+
for s in required_servers:
69+
endpoint_tlv[s] = self._create_minimal_cluster(s)
70+
71+
# Descriptor
72+
attr = Clusters.Descriptor.Attributes
73+
attrs = {}
74+
attrs[attr.FeatureMap.attribute_id] = 0
75+
attrs[attr.AcceptedCommandList.attribute_id] = []
76+
attrs[attr.GeneratedCommandList.attribute_id] = []
77+
attrs[attr.ClusterRevision.attribute_id] = self.xml_clusters[Clusters.Descriptor.id].revision
78+
attrs[attr.DeviceTypeList.attribute_id] = [
79+
Clusters.Descriptor.Structs.DeviceTypeStruct(deviceType=device_type_id, revision=device_type_revision)]
80+
attrs[attr.ServerList.attribute_id] = required_servers
81+
attrs[attr.ClientList.attribute_id] = required_clients
82+
attrs[attr.PartsList.attribute_id] = []
83+
attrs[attr.AttributeList.attribute_id] = []
84+
attrs[attr.AttributeList.attribute_id] = list(attrs.keys())
85+
86+
endpoint_tlv[Clusters.Descriptor.id] = attrs
87+
return endpoint_tlv
88+
89+
def add_macl(self, root_endpoint: dict[int, dict[int, Any]]):
90+
ac = Clusters.AccessControl
91+
root_endpoint[ac.id][ac.Attributes.FeatureMap.attribute_id] = ac.Bitmaps.Feature.kManagedDevice
92+
root_endpoint[ac.id][ac.Attributes.Arl.attribute_id] = []
93+
root_endpoint[ac.id][ac.Attributes.CommissioningARL.attribute_id] = []
94+
root_endpoint[ac.id][ac.Attributes.AttributeList.attribute_id].extend([
95+
ac.Attributes.Arl.attribute_id, ac.Attributes.CommissioningARL.attribute_id])
96+
root_endpoint[ac.id][ac.Attributes.AcceptedCommandList.attribute_id].append(ac.Commands.ReviewFabricRestrictions.command_id)
97+
root_endpoint[ac.id][ac.Attributes.GeneratedCommandList.attribute_id].append(
98+
ac.Commands.ReviewFabricRestrictionsResponse.command_id)
99+
100+
@async_test_body
101+
async def test_macl_handling(self):
102+
nim_id = self._get_device_type_id('network infrastructure manager')
103+
root_node_id = self._get_device_type_id('root node')
104+
on_off_id = self._get_device_type_id('On/Off Light')
105+
106+
root = self._create_minimal_dt(device_type_id=root_node_id)
107+
nim = self._create_minimal_dt(device_type_id=nim_id)
108+
self.endpoints_tlv = {0: root, 1: nim}
109+
asserts.assert_true(self._has_nim(), "Did not find NIM in generated device")
110+
111+
success, problems = self.check_conformance(ignore_in_progress=False, is_ci=False)
112+
self.problems.extend(problems)
113+
asserts.assert_true(success, "Unexpected failure parsing minimal dt")
114+
115+
self.add_macl(root)
116+
# A MACL is allowed when there is a NIM, so this should succeed as well
117+
success, problems = self.check_conformance(ignore_in_progress=False, is_ci=False)
118+
self.problems.extend(problems)
119+
asserts.assert_true(success, "Unexpected failure with NIM and MACL")
120+
121+
# A MACL is not allowed when there is no NIM
122+
self.endpoints_tlv[1] = self._create_minimal_dt(device_type_id=on_off_id)
123+
success, problems = self.check_conformance(ignore_in_progress=False, is_ci=False)
124+
self.problems.extend(problems)
125+
asserts.assert_false(success, "Unexpected success with On/Off and MACL")
126+
127+
# TODO: what happens if there is a NIM and a non-NIM endpoint?
128+
129+
130+
if __name__ == "__main__":
131+
default_matter_test_main()

src/python_testing/execute_python_tests.py

+1
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ def main(search_directory, env_file):
8787
"TestChoiceConformanceSupport.py",
8888
"TC_DEMTestBase.py",
8989
"choice_conformance_support.py",
90+
"TestConformanceTest.py", # Unit test of the conformance test (TC_DeviceConformance) - does not run against an app.
9091
"TestIdChecks.py",
9192
"TestSpecParsingDeviceType.py",
9293
"TestMatterTestingSupport.py",

0 commit comments

Comments
 (0)