-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add water heater mode cluster test scripts (#34347)
* Add water heater mode cluster test scripts * Add TC_EWATERHTRM_2_1.py * Fix lint errors * Restyled by isort * Renamed read_mod_attribute_expect_success to read_mode_attribute_expect_success * Changed test runner to run All-clusters-app where waterheater mode is implemented. * Added Test scripts to CI test.yaml (TC_EWATERHTRM_1_2.py & TC_EWATERHTRM_2_2.py) * Corrected checking of modes for WaterHeaterModes which must be included, and corrected the common modes. * Alignment (autowrapping) * Update tests.yaml - fixed test script name * Address review comments from Andrei * Address comments from Cecille * Restyled by autopep8 * Rename tests as PICS=WHM * Adding a TODO * Remove unnecessary blank line --------- Co-authored-by: Restyled.io <commits@restyled.io> Co-authored-by: James Harrow <james.harrow@gmail.com> Co-authored-by: jamesharrow <93921463+jamesharrow@users.noreply.github.com>
- Loading branch information
Showing
3 changed files
with
319 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
# | ||
# 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 | ||
# test-runner-run/run1/app: ${ALL_CLUSTERS_APP} | ||
# test-runner-run/run1/factoryreset: True | ||
# test-runner-run/run1/quiet: True | ||
# test-runner-run/run1/app-args: --discriminator 1234 --KVS kvs1 --trace-to json:${TRACE_APP}.json --enable-key 000102030405060708090a0b0c0d0e0f | ||
# test-runner-run/run1/script-args: --storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --hex-arg enableKey:000102030405060708090a0b0c0d0e0f --endpoint 1 --trace-to json:${TRACE_TEST_JSON}.json --trace-to perfetto:${TRACE_TEST_PERFETTO}.perfetto | ||
# === END CI TEST ARGUMENTS === | ||
|
||
import logging | ||
|
||
import chip.clusters as Clusters | ||
from matter_testing_support import MatterBaseTest, TestStep, async_test_body, default_matter_test_main | ||
from mobly import asserts | ||
|
||
|
||
class TC_WHM_1_2(MatterBaseTest): | ||
|
||
async def read_mode_attribute_expect_success(self, endpoint, attribute): | ||
cluster = Clusters.Objects.WaterHeaterMode | ||
return await self.read_single_attribute_check_success(endpoint=endpoint, cluster=cluster, attribute=attribute) | ||
|
||
def desc_TC_WHM_1_2(self) -> str: | ||
return "[TC-WHM-1.2] Cluster attributes with DUT as Server" | ||
|
||
def steps_TC_WHM_1_2(self) -> list[TestStep]: | ||
steps = [ | ||
TestStep(1, "Commissioning, already done", is_commissioning=True), | ||
TestStep(2, "Read the SupportedModes attribute"), | ||
TestStep(3, "Read the CurrentMode attribute"), | ||
] | ||
return steps | ||
|
||
def pics_TC_WHM_1_2(self) -> list[str]: | ||
pics = [ | ||
"WHM.S", | ||
] | ||
return pics | ||
|
||
@async_test_body | ||
async def test_TC_WHM_1_2(self): | ||
|
||
endpoint = self.user_params.get("endpoint", 1) | ||
|
||
attributes = Clusters.WaterHeaterMode.Attributes | ||
|
||
self.step(1) | ||
|
||
self.step(2) | ||
supported_modes = await self.read_mode_attribute_expect_success(endpoint=endpoint, attribute=attributes.SupportedModes) | ||
asserts.assert_greater_equal(len(supported_modes), 2, | ||
"SupportedModes must have at least 2 entries!") | ||
asserts.assert_less_equal(len(supported_modes), 255, | ||
"SupportedModes must have at most 255 entries!") | ||
modes = set([m.mode for m in supported_modes]) | ||
asserts.assert_equal(len(modes), len(supported_modes), | ||
"SupportedModes must have unique mode values") | ||
|
||
labels = set([m.label for m in supported_modes]) | ||
asserts.assert_equal(len(labels), len(supported_modes), | ||
"SupportedModes must have unique mode label values") | ||
|
||
# common mode tags | ||
commonTags = {0x0: 'Auto', | ||
0x1: 'Quick', | ||
0x2: 'Quiet', | ||
0x3: 'LowNoise', | ||
0x4: 'LowEnergy', | ||
0x5: 'Vacation', | ||
0x6: 'Min', | ||
0x7: 'Max', | ||
0x8: 'Night', | ||
0x9: 'Day'} | ||
|
||
# derived cluster defined tags | ||
derivedTags = [tag.value for tag in Clusters.WaterHeaterMode.Enums.ModeTag] | ||
|
||
logging.info("Derived tags: %s" % derivedTags) | ||
|
||
# According to the Mode spec: | ||
# At least one entry in the SupportedModes attribute SHALL include the Manual mode tag in the ModeTags field list. | ||
# At least one entry in the SupportedModes attribute SHALL include the Off mode tag in the ModeTags field list. | ||
# An entry in the SupportedModes attribute that includes one of an Off, Manual, or Timed tag | ||
# SHALL NOT also include an additional instance of any one of these tag types. | ||
off_present = 0 | ||
manual_present = 0 | ||
timed_present = 0 | ||
|
||
for m in supported_modes: | ||
off_manual_timed_present_in_this_mode = 0 | ||
for t in m.modeTags: | ||
is_mfg = (0x8000 <= t.value and t.value <= 0xBFFF) | ||
asserts.assert_true(t.value in commonTags.keys() or t.value in derivedTags or is_mfg, | ||
"Found a SupportedModes entry with invalid mode tag value!") | ||
if t.value == Clusters.WaterHeaterMode.Enums.ModeTag.kOff: | ||
off_present += 1 | ||
off_manual_timed_present_in_this_mode += 1 | ||
logging.info( | ||
"Found Off mode tag %s with tag value %s", m.mode, t.value) | ||
|
||
if t.value == Clusters.WaterHeaterMode.Enums.ModeTag.kManual: | ||
manual_present += 1 | ||
off_manual_timed_present_in_this_mode += 1 | ||
logging.info( | ||
"Found Manual mode tag %s with tag value %s", m.mode, t.value) | ||
|
||
if t.value == Clusters.WaterHeaterMode.Enums.ModeTag.kTimed: | ||
timed_present += 1 | ||
off_manual_timed_present_in_this_mode += 1 | ||
logging.info( | ||
"Found Timed mode tag %s with tag value %s", m.mode, t.value) | ||
|
||
asserts.assert_less_equal(off_manual_timed_present_in_this_mode, 1, | ||
f"The supported mode ({m.mode}) should only include one of OFF, MANUAL or TIMED, but includes more than one.") | ||
|
||
asserts.assert_greater(off_present, 0, | ||
"SupportedModes does not have an entry of Off(0x4000)") | ||
asserts.assert_greater(manual_present, 0, | ||
"SupportedModes does not have an entry of Manual(0x4001)") | ||
|
||
asserts.assert_less_equal(off_present, 1, | ||
"SupportedModes cannot have more than one instance of Off(0x4000)") | ||
asserts.assert_less_equal(manual_present, 1, | ||
"SupportedModes cannot have more than one instance of Manual(0x4001)") | ||
asserts.assert_less_equal(timed_present, 1, | ||
"SupportedModes cannot have more than one instance of Timed(0x4002)") | ||
|
||
self.step(3) | ||
current_mode = await self.read_mode_attribute_expect_success(endpoint=endpoint, attribute=attributes.CurrentMode) | ||
logging.info("CurrentMode: %s" % current_mode) | ||
asserts.assert_true(current_mode in modes, | ||
"CurrentMode is not a supported mode!") | ||
|
||
|
||
if __name__ == "__main__": | ||
default_matter_test_main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
# | ||
# 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. | ||
# | ||
|
||
# See https://github.com/project-chip/connectedhomeip/blob/master/docs/testing/python.md#defining-the-ci-test-arguments | ||
# for details about the block below. | ||
# | ||
# === BEGIN CI TEST ARGUMENTS === | ||
# test-runner-runs: run1 | ||
# test-runner-run/run1/app: ${ALL_CLUSTERS_APP} | ||
# test-runner-run/run1/factoryreset: True | ||
# test-runner-run/run1/quiet: True | ||
# test-runner-run/run1/app-args: --discriminator 1234 --KVS kvs1 --trace-to json:${TRACE_APP}.json | ||
# test-runner-run/run1/script-args: --storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --endpoint 1 --trace-to json:${TRACE_TEST_JSON}.json --trace-to perfetto:${TRACE_TEST_PERFETTO}.perfetto | ||
# === END CI TEST ARGUMENTS === | ||
|
||
import logging | ||
|
||
import chip.clusters as Clusters | ||
from chip.interaction_model import Status | ||
from matter_testing_support import MatterBaseTest, TestStep, async_test_body, default_matter_test_main, type_matches | ||
from mobly import asserts | ||
|
||
|
||
class TC_WHM_2_1(MatterBaseTest): | ||
|
||
def __init__(self, *args): | ||
super().__init__(*args) | ||
self.endpoint = 0 | ||
|
||
def steps_TC_WHM_2_1(self) -> list[TestStep]: | ||
steps = [ | ||
TestStep(1, "Commissioning, already done", is_commissioning=True), | ||
TestStep(2, "Read the SupportedModes attribute"), | ||
TestStep(3, "Read the CurrentMode attribute"), | ||
TestStep(4, "Send ChangeToMode command with NewMode"), | ||
TestStep(5, "Manually put the device in a state from which it will FAIL to transition"), | ||
TestStep(6, "Read CurrentMode attribute"), | ||
TestStep(7, "Send ChangeToMode command with NewMode"), | ||
TestStep(8, "Read CurrentMode attribute"), | ||
TestStep(9, "Manually put the device in a state from which it will SUCCESSFULLY transition"), | ||
TestStep(10, "Read CurrentMode attribute"), | ||
TestStep(11, "Send ChangeToMode command with NewMode"), | ||
TestStep(12, "Read CurrentMode attribute"), | ||
TestStep(13, "Send ChangeToMode command with NewMode set to an invalid mode"), | ||
TestStep(14, "Read CurrentMode attribute"), | ||
] | ||
return steps | ||
|
||
async def read_mode_attribute_expect_success(self, endpoint, attribute): | ||
cluster = Clusters.Objects.WaterHeaterMode | ||
return await self.read_single_attribute_check_success(endpoint=endpoint, cluster=cluster, attribute=attribute) | ||
|
||
async def send_change_to_mode_cmd(self, newMode) -> Clusters.Objects.WaterHeaterMode.Commands.ChangeToModeResponse: | ||
ret = await self.send_single_cmd(cmd=Clusters.Objects.WaterHeaterMode.Commands.ChangeToMode(newMode=newMode), endpoint=self.endpoint) | ||
asserts.assert_true(type_matches(ret, Clusters.Objects.WaterHeaterMode.Commands.ChangeToModeResponse), | ||
"Unexpected return type for Water Heater Mode ChangeToMode") | ||
return ret | ||
|
||
def pics_TC_WHM_2_1(self) -> list[str]: | ||
return ["WHM.S"] | ||
|
||
@async_test_body | ||
async def test_TC_WHM_2_1(self): | ||
|
||
# Valid modes. Only ModeManual referred to in this test | ||
# ModeOff = 0 | ||
ModeManual = 1 | ||
# ModeTimed = 2 | ||
|
||
self.endpoint = self.matter_test_config.endpoint | ||
|
||
attributes = Clusters.WaterHeaterMode.Attributes | ||
|
||
self.step(1) | ||
# Commission DUT - already done | ||
|
||
self.step(2) | ||
|
||
supported_modes = await self.read_mode_attribute_expect_success(endpoint=self.endpoint, attribute=attributes.SupportedModes) | ||
|
||
logging.info(f"SupportedModes: {supported_modes}") | ||
|
||
asserts.assert_greater_equal(len(supported_modes), 2, | ||
"SupportedModes must have at least two entries!") | ||
|
||
self.step(3) | ||
|
||
old_current_mode = await self.read_mode_attribute_expect_success(endpoint=self.endpoint, attribute=attributes.CurrentMode) | ||
|
||
logging.info(f"CurrentMode: {old_current_mode}") | ||
|
||
# pick a value that's not on the list of supported modes | ||
modes = [m.mode for m in supported_modes] | ||
invalid_mode = max(modes) + 1 | ||
|
||
self.step(4) | ||
|
||
ret = await self.send_change_to_mode_cmd(newMode=old_current_mode) | ||
logging.info(f"ret.status {ret.status}") | ||
asserts.assert_equal(ret.status, Status.Success, | ||
"Changing the mode to the current mode should be a no-op") | ||
|
||
# Steps 5-9 are not performed as WHM.S.M.CAN_TEST_MODE_FAILURE is false | ||
# TODO - see issue 34565 | ||
self.step(5) | ||
self.step(6) | ||
self.step(7) | ||
self.step(8) | ||
self.step(9) | ||
|
||
self.step(10) | ||
|
||
old_current_mode = await self.read_mode_attribute_expect_success(endpoint=self.endpoint, attribute=attributes.CurrentMode) | ||
|
||
logging.info(f"CurrentMode: {old_current_mode}") | ||
|
||
self.step(11) | ||
|
||
ret = await self.send_change_to_mode_cmd(newMode=ModeManual) | ||
asserts.assert_true(ret.status == Status.Success, | ||
f"Changing to mode {ModeManual}must succeed due to the current state of the device") | ||
|
||
self.step(12) | ||
|
||
current_mode = await self.read_mode_attribute_expect_success(endpoint=self.endpoint, attribute=attributes.CurrentMode) | ||
|
||
logging.info(f"CurrentMode: {current_mode}") | ||
|
||
asserts.assert_true(current_mode == ModeManual, | ||
"CurrentMode doesn't match the argument of the successful ChangeToMode command!") | ||
|
||
self.step(13) | ||
|
||
ret = await self.send_change_to_mode_cmd(newMode=invalid_mode) | ||
logging.info(f"ret {ret}") | ||
asserts.assert_true(ret.status == Status.Failure, | ||
f"Attempt to change to invalid mode {invalid_mode} didn't fail as expected") | ||
|
||
self.step(14) | ||
|
||
current_mode = await self.read_mode_attribute_expect_success(endpoint=self.endpoint, attribute=attributes.CurrentMode) | ||
|
||
logging.info(f"CurrentMode: {current_mode}") | ||
|
||
asserts.assert_true(current_mode == ModeManual, | ||
"CurrentMode changed after failed ChangeToMode command!") | ||
|
||
|
||
if __name__ == "__main__": | ||
default_matter_test_main() |