-
Notifications
You must be signed in to change notification settings - Fork 2.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
TC-TCCM Python Migration #36767
base: master
Are you sure you want to change the base?
TC-TCCM Python Migration #36767
Changes from 4 commits
f2b7922
52b4ad6
06e5730
ff5e2eb
bfa96d4
4a745ba
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
# | ||
# Copyright (c) 2024 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. | ||
# | ||
|
||
# === BEGIN CI TEST ARGUMENTS === | ||
# test-runner-runs: | ||
# run1: | ||
# app: ${ENERGY_MANAGEMENT_APP} | ||
# app-args: > | ||
# --discriminator 1234 | ||
# --KVS kvs1 | ||
# --trace-to json:${TRACE_APP}.json | ||
# --enable-key 000102030405060708090a0b0c0d0e0f | ||
# --application water-heater | ||
# script-args: > | ||
# --storage-path admin_storage.json | ||
# --commissioning-method on-network | ||
# --discriminator 1234 | ||
# --passcode 20202021 | ||
# --hex-arg enableKey:000102030405060708090a0b0c0d0e0f | ||
# --endpoint 2 | ||
# --trace-to json:${TRACE_TEST_JSON}.json | ||
# --trace-to perfetto:${TRACE_TEST_PERFETTO}.perfetto | ||
# factory-reset: true | ||
# quiet: true | ||
# === END CI TEST ARGUMENTS === | ||
|
||
|
||
from tc_mode_base import ClusterModeCheck | ||
|
||
import chip.clusters as Clusters | ||
from chip.testing.matter_testing import MatterBaseTest, TestStep, async_test_body, default_matter_test_main | ||
|
||
|
||
class TC_TCCM_1_2(MatterBaseTest, ClusterModeCheck): | ||
|
||
def __init__(self, *args): | ||
super().__init__(*args) | ||
ClusterModeCheck.__init__(self, | ||
requested_cluster=Clusters.RefrigeratorAndTemperatureControlledCabinetMode) | ||
|
||
def desc_TC_TCCM_1_2(self) -> str: | ||
return "[TC-TCCM-1.2] Cluster attributes with DUT as Server" | ||
|
||
def steps_TC_TCCM_1_2(self) -> list[TestStep]: | ||
steps = [ | ||
TestStep(1, "Commissioning, already done", is_commissioning=True), | ||
TestStep(2, "TH reads from the DUT the SupportedModes attribute."), | ||
TestStep(3, "TH reads from the DUT the CurrentMode attribute."), | ||
TestStep(4, "TH reads from the DUT the OnMode attribute."), | ||
TestStep(5, "TH reads from the DUT the StartUpMode attribute.") | ||
] | ||
return steps | ||
|
||
def pics_TC_TCCM_1_2(self) -> list[str]: | ||
pics = [ | ||
"TCCM.S" | ||
] | ||
return pics | ||
|
||
@async_test_body | ||
async def test_TC_TCCM_1_2(self): | ||
|
||
# Setup common mode check | ||
endpoint = self.get_endpoint(default=1) | ||
|
||
self.step(1) | ||
|
||
self.step(2) | ||
# Verify common checks for Mode Base as described in the TC-TCCM-1.2 | ||
await self.check_supported_modes_and_labels(endpoint=endpoint) | ||
# According to the spec, there should be at least one RapidCool or RapidFreeze tag in | ||
# the ones supported. | ||
additional_tags = [Clusters.RefrigeratorAndTemperatureControlledCabinetMode.Enums.ModeTag.kRapidCool, | ||
Clusters.RefrigeratorAndTemperatureControlledCabinetMode.Enums.ModeTag.kRapidFreeze] | ||
self.check_tags_in_lists(requiredtags=additional_tags) | ||
|
||
self.step(3) | ||
# Verify that the CurrentMode attribute has a valid value. | ||
mode = Clusters.RefrigeratorAndTemperatureControlledCabinetMode.Attributes.CurrentMode | ||
await self.read_and_check_mode(endpoint=endpoint, mode=mode) | ||
|
||
self.step(4) | ||
# Verify that the OnMode attribute has a valid value or null. | ||
mode = Clusters.RefrigeratorAndTemperatureControlledCabinetMode.Attributes.OnMode | ||
await self.read_and_check_mode(endpoint=endpoint, mode=mode, is_nullable=True) | ||
|
||
self.step(5) | ||
# Verify that the StartUpMode has a valid value or null | ||
mode = Clusters.RefrigeratorAndTemperatureControlledCabinetMode.Attributes.StartUpMode | ||
await self.read_and_check_mode(endpoint=endpoint, mode=mode, is_nullable=True) | ||
|
||
|
||
if __name__ == "__main__": | ||
default_matter_test_main() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
# | ||
# 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. | ||
# | ||
|
||
import logging | ||
|
||
from mobly import asserts | ||
from chip.clusters.Types import NullValue | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
# 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 ClusterModeCheck: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Based on the description:
How about naming this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Changed the name for the class and also the name of the file. I'm not sure if I can completely remove the word "base" since the name of the "parent" cluster is ModeBase, but hopefully it is less general now. |
||
""" 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: | ||
requested_cluster: A reference to the cluster to be tested, it should be a derived from the Mode Base cluster. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: consider There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That makes sense, changed the name of the parameter. |
||
""" | ||
|
||
def __init__(self, requested_cluster): | ||
self.modeTags = [tag.value for tag in requested_cluster.Enums.ModeTag] | ||
self.requested_cluster = requested_cluster | ||
self.attributes = requested_cluster.Attributes | ||
self.supported_modes_dut = set() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. having these as members seems to introduce a call dependency: you need to call check_supported_modes_and_labels and only then read_and_check_mode Could we instead make things return values and not store internal state in the class? that would allow decoupling call order and make it more obvious to callers what data is used there (i.e. that the data from check is used in read_and_check) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, that makes sense. I overlook this dependency. Removed those members and used them as parameters in the other calls. |
||
self.supported_modes = None | ||
|
||
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. | ||
""" | ||
# Get the supported modes | ||
self.supported_modes = await self.read_single_attribute_check_success(endpoint=endpoint, | ||
cluster=self.requested_cluster, | ||
attribute=self.attributes.SupportedModes) | ||
|
||
# Check if the list of supported modes is larger than 2 | ||
asserts.assert_greater_equal(len(self.supported_modes), 2, "SupportedModes must have at least 2 entries!") | ||
# Check that supported modes are less than 255 | ||
asserts.assert_less_equal(len(self.supported_modes), 255, "SupportedModes must have at most 255 entries!") | ||
|
||
# Check for repeated labels or modes | ||
labels = set() | ||
for mode_options_struct in self.supported_modes: | ||
# Verify that the modes in all ModeOptionsStruct in SupportedModes are unique. | ||
if mode_options_struct.mode in self.supported_modes_dut: | ||
asserts.fail("SupportedModes can't have repeated Mode values") | ||
else: | ||
self.supported_modes_dut.add(mode_options_struct.mode) | ||
# Verify that the labels in all ModeOptionsStruct in SupportedModes are unique. | ||
if mode_options_struct.label in labels: | ||
asserts.fail("SupportedModes can't have repeated Label values") | ||
else: | ||
labels.add(mode_options_struct.label) | ||
|
||
def check_tags_in_lists(self, requiredtags=None): | ||
""" Validates the ModeTags values. | ||
|
||
This function evaluates the ModeTags of each ModeOptionsStruct: | ||
- Should have at least one tag. | ||
- 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 "requiredTags" exists. | ||
""" | ||
# Verify the ModeTags on each ModeOptionsStruct | ||
for mode_options_struct in self.supported_modes: | ||
# Shuld have at least one entry | ||
if len(mode_options_struct.modeTags) == 0: | ||
asserts.fail("The ModeTags field should have at least one entry.") | ||
|
||
# Check each ModelTag | ||
at_least_one_common_or_derived = False | ||
for tag in mode_options_struct.modeTags: | ||
# Value should not larger than 16bits | ||
if tag.value > MAX_MODE_TAG or tag.value < 0: | ||
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) | ||
if (tag.value not in self.modeTags and | ||
not is_mfg): | ||
asserts.fail("Mode tag value is not a common, derived 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 requiredtags: | ||
has_required_tags = False | ||
for mode_options_struct in self.supported_modes: | ||
has_required_tags = any(tag.value in requiredtags 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, 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. | ||
""" | ||
mode_value = await self.read_single_attribute_check_success(endpoint=endpoint, cluster=self.requested_cluster, attribute=mode) | ||
is_valid = mode_value in self.supported_modes_dut | ||
if is_nullable and mode_value == NullValue: | ||
is_valid = True | ||
asserts.assert_true(is_valid, f"{mode} not supported") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
minor question / possible nit - is this the entire enum there, or a subset? If it's the fully list, can you pull the full list by walking the enum? If it's a subset, can you add a comment noting that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From what I see in the different TCs, these tags are a subset of the whole enum and seem to be different for each cluster. Added a comment for this specific cluster.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if we could somehow shorten things by giving a name to
Clusters.RefrigeratorAndTemperatureControlledCabinetMode.Enums
as well asClusters.RefrigeratorAndTemperatureControlledCabinetMode.Attributes
(probably like enums and attributes) as they make the code lines very long.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we already named
attributes
in the checks class.