|
| 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 | + |
| 18 | +import logging |
| 19 | + |
| 20 | +from chip.clusters.Types import NullValue |
| 21 | +from mobly import asserts |
| 22 | + |
| 23 | +logger = logging.getLogger(__name__) |
| 24 | + |
| 25 | +# Maximum value for ModeTags according to specs is 16bits. |
| 26 | +MAX_MODE_TAG = 0xFFFF |
| 27 | +# According to specs, the specific MfgTags should be defined in the range 0x8000 - 0xBFFF |
| 28 | +START_MFGTAGS_RANGE = 0x8000 |
| 29 | +END_MFGTAGS_RANGE = 0xBFFF |
| 30 | + |
| 31 | + |
| 32 | +class ModeBaseClusterChecks: |
| 33 | + """ Class that holds the common Mode checks between TCs |
| 34 | +
|
| 35 | + Several TCs have similar checks in place for functionality that is common among them. |
| 36 | + This class holds most of this common functionality to avoid duplicating code with the same validations. |
| 37 | +
|
| 38 | + Link to spec: |
| 39 | + https://github.com/CHIP-Specifications/chip-test-plans/blob/master/src/cluster/modebase_common.adoc |
| 40 | +
|
| 41 | +
|
| 42 | + Attributes: |
| 43 | + modebase_derived_cluster: A reference to the cluster to be tested, it should be a derived from the Mode Base cluster. |
| 44 | + """ |
| 45 | + |
| 46 | + def __init__(self, modebase_derived_cluster): |
| 47 | + self.mode_tags = [tag.value for tag in modebase_derived_cluster.Enums.ModeTag] |
| 48 | + self.cluster = modebase_derived_cluster |
| 49 | + self.attributes = modebase_derived_cluster.Attributes |
| 50 | + |
| 51 | + async def check_supported_modes_and_labels(self, endpoint): |
| 52 | + """ Verifies the device supported modes and labels. |
| 53 | +
|
| 54 | + Checks that the SupportedModes attribute has the expected structure and values like: |
| 55 | + - Between 2 and 255 entries. |
| 56 | + - The Mode values of all entries are unique. |
| 57 | + - The Label values of all entries are unique. |
| 58 | +
|
| 59 | + Args: |
| 60 | + endpoint: The endpoint used for the requests to the cluster. |
| 61 | +
|
| 62 | + Returns: |
| 63 | + A list of ModeOptionStruct supported by the cluster. |
| 64 | + """ |
| 65 | + # Get the supported modes |
| 66 | + supported_modes = await self.read_single_attribute_check_success(endpoint=endpoint, |
| 67 | + cluster=self.cluster, |
| 68 | + attribute=self.attributes.SupportedModes) |
| 69 | + |
| 70 | + # Check if the list of supported modes is larger than 2 |
| 71 | + asserts.assert_greater_equal(len(supported_modes), 2, "SupportedModes must have at least 2 entries!") |
| 72 | + # Check that supported modes are less than 255 |
| 73 | + asserts.assert_less_equal(len(supported_modes), 255, "SupportedModes must have at most 255 entries!") |
| 74 | + |
| 75 | + # Check for repeated labels or modes |
| 76 | + labels = set() |
| 77 | + modes = set() |
| 78 | + for mode_option_struct in supported_modes: |
| 79 | + # Verify that the modes in all ModeOptionStruct in SupportedModes are unique. |
| 80 | + if mode_option_struct.mode in modes: |
| 81 | + asserts.fail("SupportedModes can't have repeated Mode values") |
| 82 | + else: |
| 83 | + modes.add(mode_option_struct.mode) |
| 84 | + # Verify that the labels in all ModeOptionStruct in SupportedModes are unique. |
| 85 | + if mode_option_struct.label in labels: |
| 86 | + asserts.fail("SupportedModes can't have repeated Label values") |
| 87 | + else: |
| 88 | + labels.add(mode_option_struct.label) |
| 89 | + |
| 90 | + return supported_modes |
| 91 | + |
| 92 | + def check_tags_in_lists(self, supported_modes, required_tags=None): |
| 93 | + """ Validates the ModeTags values. |
| 94 | +
|
| 95 | + This function evaluates the ModeTags of each ModeOptionStruct: |
| 96 | + - Should have at least one tag. |
| 97 | + - Should be maximum 16bits in size. |
| 98 | + - Should be a Mfg tag or one of the supported ones (either common or specific). |
| 99 | + - Should have at least one common or specific tag. |
| 100 | + - If defined, verify that at least one of the "required_tags" exists. |
| 101 | +
|
| 102 | + Args: |
| 103 | + supported_modes: A list of ModeOptionStruct. |
| 104 | + required_tags: List of tags that are required according to the cluster spec. |
| 105 | + """ |
| 106 | + # Verify the ModeTags on each ModeOptionStruct |
| 107 | + for mode_option_struct in supported_modes: |
| 108 | + # Shuld have at least one entry |
| 109 | + if len(mode_option_struct.modeTags) == 0: |
| 110 | + asserts.fail("The ModeTags field should have at least one entry.") |
| 111 | + |
| 112 | + # Check each ModelTag |
| 113 | + at_least_one_common_or_derived = False |
| 114 | + for tag in mode_option_struct.modeTags: |
| 115 | + # Value should not larger than 16bits |
| 116 | + if not (0 <= tag.value <= MAX_MODE_TAG): |
| 117 | + asserts.fail("Tag should not be larger than 16bits.") |
| 118 | + |
| 119 | + # Check if is tag is common, derived or mfg. |
| 120 | + is_mfg = (START_MFGTAGS_RANGE <= tag.value <= END_MFGTAGS_RANGE) |
| 121 | + if not (is_mfg or tag.value in self.mode_tags): |
| 122 | + asserts.fail("Mode tag value is not a common, derived or vendor tag.") |
| 123 | + |
| 124 | + # Confirm if tag is common or derived. |
| 125 | + if not is_mfg: |
| 126 | + at_least_one_common_or_derived = True |
| 127 | + |
| 128 | + if not at_least_one_common_or_derived: |
| 129 | + asserts.fail("There should be at least one common or derived tag on each ModeOptionsStruct") |
| 130 | + |
| 131 | + if required_tags: |
| 132 | + has_required_tags = False |
| 133 | + for mode_options_struct in supported_modes: |
| 134 | + has_required_tags = any(tag.value in required_tags for tag in mode_options_struct.modeTags) |
| 135 | + if has_required_tags: |
| 136 | + break |
| 137 | + asserts.assert_true(has_required_tags, "No ModeOptionsStruct has the required tags.") |
| 138 | + |
| 139 | + async def read_and_check_mode(self, endpoint, mode, supported_modes, is_nullable=False): |
| 140 | + """Evaluates the current mode |
| 141 | +
|
| 142 | + This functions checks if the requested mode attribute has a valid value from the SupportedModes, |
| 143 | + supports optional nullable values. |
| 144 | +
|
| 145 | + Args: |
| 146 | + endpoint: The endpoint used for the requests to the cluster. |
| 147 | + mode: Mode that will be verified. |
| 148 | + supported_modes: A list of ModeOptionStruct. |
| 149 | + is_nullable: Optional argument to indicate if the tested mode allows NullValue |
| 150 | + """ |
| 151 | + mode_value = await self.read_single_attribute_check_success(endpoint=endpoint, |
| 152 | + cluster=self.cluster, |
| 153 | + attribute=mode) |
| 154 | + supported_modes_dut = {mode_option_struct.mode for mode_option_struct in supported_modes} |
| 155 | + is_valid = mode_value in supported_modes_dut |
| 156 | + if is_nullable and mode_value == NullValue: |
| 157 | + is_valid = True |
| 158 | + asserts.assert_true(is_valid, f"{mode} not supported") |
0 commit comments