| # | |
| # Copyright (c) 2023 Project CHIP Authors | |
| # All rights reserved. | |
| # | |
| # Licensed under the Apache License, Version 2.0 (the "License"); | |
| # you may not use this file except in compliance with the License. | |
| # You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, software | |
| # distributed under the License is distributed on an "AS IS" BASIS, | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| # See the License for the specific language governing permissions and | |
| # limitations under the License. | |
| # | |
| from mobly import asserts | |
| from matter.clusters.Types import NullValue | |
| # Maximum value for ModeTags according to specs is 16bits. | |
| MAX_MODE_TAG = 0xFFFF | |
| # According to specs, the specific MfgTags should be defined in the range 0x8000 - 0xBFFF | |
| START_MFGTAGS_RANGE = 0x8000 | |
| END_MFGTAGS_RANGE = 0xBFFF | |
| class ModeBaseClusterChecks: | |
| """ Class that holds the common Mode checks between TCs | |
| Several TCs have similar checks in place for functionality that is common among them. | |
| This class holds most of this common functionality to avoid duplicating code with the same validations. | |
| Link to spec: | |
| https://github.com/CHIP-Specifications/chip-test-plans/blob/master/src/cluster/modebase_common.adoc | |
| Attributes: | |
| modebase_derived_cluster: A reference to the cluster to be tested, it should be a derived from the Mode Base cluster. | |
| """ | |
| def __init__(self, modebase_derived_cluster): | |
| self.mode_tags = [tag.value for tag in modebase_derived_cluster.Enums.ModeTag] | |
| self.cluster = modebase_derived_cluster | |
| self.attributes = modebase_derived_cluster.Attributes | |
| async def check_supported_modes_and_labels(self, endpoint): | |
| """ Verifies the device supported modes and labels. | |
| Checks that the SupportedModes attribute has the expected structure and values like: | |
| - Between 2 and 255 entries. | |
| - The Mode values of all entries are unique. | |
| - The Label values of all entries are unique. | |
| Args: | |
| endpoint: The endpoint used for the requests to the cluster. | |
| Returns: | |
| A list of ModeOptionStruct supported by the cluster. | |
| """ | |
| # Get the supported modes | |
| supported_modes = await self.read_single_attribute_check_success(endpoint=endpoint, | |
| cluster=self.cluster, | |
| attribute=self.attributes.SupportedModes) | |
| # Check if the list of supported modes is larger than 2 | |
| asserts.assert_greater_equal(len(supported_modes), 2, "SupportedModes must have at least 2 entries!") | |
| # Check that supported modes are less than 255 | |
| asserts.assert_less_equal(len(supported_modes), 255, "SupportedModes must have at most 255 entries!") | |
| # Check for repeated labels or modes | |
| labels = set() | |
| modes = set() | |
| for mode_option_struct in supported_modes: | |
| # Verify that the modes in all ModeOptionStruct in SupportedModes are unique. | |
| if mode_option_struct.mode in modes: | |
| asserts.fail("SupportedModes can't have repeated Mode values") | |
| else: | |
| modes.add(mode_option_struct.mode) | |
| # Verify that the labels in all ModeOptionStruct in SupportedModes are unique. | |
| if mode_option_struct.label in labels: | |
| asserts.fail("SupportedModes can't have repeated Label values") | |
| else: | |
| labels.add(mode_option_struct.label) | |
| return supported_modes | |
| def check_tags_in_lists(self, supported_modes, required_tags=None): | |
| """ Validates the ModeTags values. | |
| This function evaluates the ModeTags of each ModeOptionStruct: | |
| - Should have at least one tag. | |
| - There are no duplicates in ModeTag list. | |
| - Should be maximum 16bits in size. | |
| - Should be a Mfg tag or one of the supported ones (either common or specific). | |
| - Should have at least one common or specific tag. | |
| - If defined, verify that at least one of the "required_tags" exists. | |
| Args: | |
| supported_modes: A list of ModeOptionStruct. | |
| required_tags: List of tags that are required according to the cluster spec. | |
| """ | |
| # Verify the ModeTags on each ModeOptionStruct | |
| for mode_option_struct in supported_modes: | |
| # Shuld have at least one entry | |
| if len(mode_option_struct.modeTags) == 0: | |
| asserts.fail("The ModeTags field should have at least one entry.") | |
| # There are no duplicates | |
| tag_values = [tag.value for tag in mode_option_struct.modeTags] | |
| if len(tag_values) != len(set(tag_values)): | |
| asserts.fail(f"ModeTags contain duplicates in mode {mode_option_struct.mode}: {tag_values}.") | |
| # Check each ModeTag | |
| at_least_one_common_or_derived = False | |
| for tag in mode_option_struct.modeTags: | |
| # Value should not larger than 16bits | |
| if not (0 <= tag.value <= MAX_MODE_TAG): | |
| asserts.fail("Tag should not be larger than 16bits.") | |
| # Check if is tag is common, derived or mfg. | |
| is_mfg = (START_MFGTAGS_RANGE <= tag.value <= END_MFGTAGS_RANGE) | |
| value = hex(tag.value) | |
| if not (is_mfg or tag.value in self.mode_tags): | |
| asserts.fail(f"Mode tag value: {value} is not a common tag, derived tag or vendor tag") | |
| # Confirm if tag is common or derived. | |
| if not is_mfg: | |
| at_least_one_common_or_derived = True | |
| if not at_least_one_common_or_derived: | |
| asserts.fail("There should be at least one common or derived tag on each ModeOptionsStruct") | |
| if required_tags: | |
| has_required_tags = False | |
| for mode_options_struct in supported_modes: | |
| has_required_tags = any(tag.value in required_tags for tag in mode_options_struct.modeTags) | |
| if has_required_tags: | |
| break | |
| asserts.assert_true(has_required_tags, "No ModeOptionsStruct has the required tags.") | |
| async def read_and_check_mode(self, endpoint, mode, supported_modes, is_nullable=False): | |
| """Evaluates the current mode | |
| This functions checks if the requested mode attribute has a valid value from the SupportedModes, | |
| supports optional nullable values. | |
| Args: | |
| endpoint: The endpoint used for the requests to the cluster. | |
| mode: Mode that will be verified. | |
| supported_modes: A list of ModeOptionStruct. | |
| is_nullable: Optional argument to indicate if the tested mode allows NullValue | |
| """ | |
| mode_value = await self.read_single_attribute_check_success(endpoint=endpoint, | |
| cluster=self.cluster, | |
| attribute=mode) | |
| supported_modes_dut = {mode_option_struct.mode for mode_option_struct in supported_modes} | |
| is_valid = mode_value in supported_modes_dut | |
| if is_nullable and mode_value == NullValue: | |
| is_valid = True | |
| asserts.assert_true(is_valid, f"{mode} not supported") |