|
| 1 | +# |
| 2 | +# Copyright (c) 2023 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 | +import math |
| 18 | + |
| 19 | +import chip.clusters as Clusters |
| 20 | +from basic_composition_support import BasicCompositionTests |
| 21 | +from global_attribute_ids import GlobalAttributeIds |
| 22 | +from matter_testing_support import (AttributePathLocation, ClusterPathLocation, CommandPathLocation, FeaturePathLocation, |
| 23 | + MatterBaseTest, async_test_body, default_matter_test_main) |
| 24 | +from mobly import asserts |
| 25 | +from spec_parsing_support import build_xml_clusters |
| 26 | + |
| 27 | + |
| 28 | +def attribute_pics(pics_base: str, id: int) -> str: |
| 29 | + return f'{pics_base}.S.A{id:04x}' |
| 30 | + |
| 31 | + |
| 32 | +def accepted_cmd_pics(pics_base: str, id: int) -> str: |
| 33 | + return f'{pics_base}.S.C{id:02x}.Rsp' |
| 34 | + |
| 35 | + |
| 36 | +def generated_cmd_pics(pics_base: str, id: int) -> str: |
| 37 | + return f'{pics_base}.S.C{id:02x}.Tx' |
| 38 | + |
| 39 | + |
| 40 | +def feature_pics(pics_base: str, bit: int) -> str: |
| 41 | + return f'{pics_base}.S.F{bit:02x}' |
| 42 | + |
| 43 | + |
| 44 | +class TC_PICS_Checker(MatterBaseTest, BasicCompositionTests): |
| 45 | + @async_test_body |
| 46 | + async def setup_class(self): |
| 47 | + super().setup_class() |
| 48 | + await self.setup_class_helper(False) |
| 49 | + # build_xml_cluster returns a list of issues found when paring the XML |
| 50 | + # Problems in the XML shouldn't cause test failure, but we want them recorded |
| 51 | + # so they are added to the list of problems that get output when the test set completes. |
| 52 | + self.xml_clusters, self.problems = build_xml_clusters() |
| 53 | + |
| 54 | + def _check_and_record_errors(self, location, required, pics): |
| 55 | + if required and not self.check_pics(pics): |
| 56 | + self.record_error("PICS check", location=location, |
| 57 | + problem=f"An element found on the device, but the corresponding PICS {pics} was not found in pics list") |
| 58 | + self.success = False |
| 59 | + elif not required and self.check_pics(pics): |
| 60 | + self.record_error("PICS check", location=location, problem=f"PICS {pics} found in PICS list, but not on device") |
| 61 | + self.success = False |
| 62 | + |
| 63 | + def _add_pics_for_lists(self, cluster_id: int, attribute_id_of_element_list: GlobalAttributeIds) -> None: |
| 64 | + try: |
| 65 | + if attribute_id_of_element_list == GlobalAttributeIds.ATTRIBUTE_LIST_ID: |
| 66 | + all_spec_elements_to_check = Clusters.ClusterObjects.ALL_ATTRIBUTES[cluster_id] |
| 67 | + pics_mapper = attribute_pics |
| 68 | + elif attribute_id_of_element_list == GlobalAttributeIds.ACCEPTED_COMMAND_LIST_ID: |
| 69 | + all_spec_elements_to_check = Clusters.ClusterObjects.ALL_ACCEPTED_COMMANDS[cluster_id] |
| 70 | + pics_mapper = accepted_cmd_pics |
| 71 | + |
| 72 | + elif attribute_id_of_element_list == GlobalAttributeIds.GENERATED_COMMAND_LIST_ID: |
| 73 | + all_spec_elements_to_check = Clusters.ClusterObjects.ALL_GENERATED_COMMANDS[cluster_id] |
| 74 | + pics_mapper = generated_cmd_pics |
| 75 | + else: |
| 76 | + asserts.fail("add_pics_for_list function called for non-list attribute") |
| 77 | + except KeyError: |
| 78 | + # This cluster does not have any of this element type |
| 79 | + return |
| 80 | + |
| 81 | + for element_id in all_spec_elements_to_check: |
| 82 | + if element_id > 0xF000: |
| 83 | + # No pics for global elements |
| 84 | + continue |
| 85 | + pics = pics_mapper(self.xml_clusters[cluster_id].pics, element_id) |
| 86 | + |
| 87 | + if cluster_id not in self.endpoint.keys(): |
| 88 | + # This cluster is not on this endpoint |
| 89 | + required = False |
| 90 | + elif element_id in self.endpoint[cluster_id][attribute_id_of_element_list]: |
| 91 | + # Cluster and element are on the endpoint |
| 92 | + required = True |
| 93 | + else: |
| 94 | + # Cluster is on the endpoint but the element is not in the list |
| 95 | + required = False |
| 96 | + |
| 97 | + if attribute_id_of_element_list == GlobalAttributeIds.ATTRIBUTE_LIST_ID: |
| 98 | + location = AttributePathLocation(endpoint_id=self.endpoint_id, cluster_id=cluster_id, attribute_id=element_id) |
| 99 | + else: |
| 100 | + location = CommandPathLocation(endpoint_id=self.endpoint_id, cluster_id=cluster_id, command_id=element_id) |
| 101 | + |
| 102 | + self._check_and_record_errors(location, required, pics) |
| 103 | + |
| 104 | + def test_TC_pics_checker(self): |
| 105 | + self.endpoint_id = self.matter_test_config.endpoint |
| 106 | + self.endpoint = self.endpoints_tlv[self.endpoint_id] |
| 107 | + self.success = True |
| 108 | + |
| 109 | + for cluster_id, cluster in Clusters.ClusterObjects.ALL_CLUSTERS.items(): |
| 110 | + # Data model XML is used to get the PICS code for this cluster. If we don't know the PICS |
| 111 | + # code, we can't evaluate the PICS list. Clusters that are present on the device but are |
| 112 | + # not present in the spec are checked in the IDM tests. |
| 113 | + if cluster_id not in self.xml_clusters or self.xml_clusters[cluster_id].pics is None: |
| 114 | + continue |
| 115 | + |
| 116 | + # Ensure the PICS.S code is correctly marked |
| 117 | + pics_cluster = f'{self.xml_clusters[cluster_id].pics}.S' |
| 118 | + location = ClusterPathLocation(endpoint_id=self.endpoint_id, cluster_id=cluster_id) |
| 119 | + self._check_and_record_errors(location, cluster_id in self.endpoint, pics_cluster) |
| 120 | + |
| 121 | + self._add_pics_for_lists(cluster_id, GlobalAttributeIds.ATTRIBUTE_LIST_ID) |
| 122 | + self._add_pics_for_lists(cluster_id, GlobalAttributeIds.ACCEPTED_COMMAND_LIST_ID) |
| 123 | + self._add_pics_for_lists(cluster_id, GlobalAttributeIds.GENERATED_COMMAND_LIST_ID) |
| 124 | + |
| 125 | + try: |
| 126 | + cluster_features = cluster.Bitmaps.Feature |
| 127 | + except AttributeError: |
| 128 | + # cluster has no features |
| 129 | + continue |
| 130 | + |
| 131 | + pics_base = self.xml_clusters[cluster_id].pics |
| 132 | + try: |
| 133 | + feature_map = self.endpoint[cluster_id][GlobalAttributeIds.FEATURE_MAP_ID] |
| 134 | + except KeyError: |
| 135 | + feature_map = 0 |
| 136 | + |
| 137 | + for feature_mask in cluster_features: |
| 138 | + # Codegen in python uses feature masks (0x01, 0x02, 0x04 etc.) |
| 139 | + # PICS uses the mask bit number (1, 2, 3) |
| 140 | + # Convert the mask to a bit number so we can check the PICS. |
| 141 | + feature_bit = int(math.log2(feature_mask)) |
| 142 | + pics = feature_pics(pics_base, feature_bit) |
| 143 | + if feature_mask & feature_map: |
| 144 | + required = True |
| 145 | + else: |
| 146 | + required = False |
| 147 | + |
| 148 | + try: |
| 149 | + location = FeaturePathLocation(endpoint_id=self.endpoint_id, cluster_id=cluster_id, |
| 150 | + feature_code=self.xml_clusters[cluster_id].features[feature_mask].code) |
| 151 | + except KeyError: |
| 152 | + location = ClusterPathLocation(endpoint_id=self.endpoint_id, cluster_id=cluster_id) |
| 153 | + self._check_and_record_errors(location, required, pics) |
| 154 | + |
| 155 | + if not self.success: |
| 156 | + self.fail_current_test("At least one PICS error was found for this endpoint") |
| 157 | + |
| 158 | + |
| 159 | +if __name__ == "__main__": |
| 160 | + default_matter_test_main() |
0 commit comments