Skip to content

Commit

Permalink
Add water heater mode cluster test scripts (project-chip#34347)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
Co-authored-by: James Harrow <[email protected]>
Co-authored-by: jamesharrow <[email protected]>
  • Loading branch information
4 people authored Jul 30, 2024
1 parent 9625b3c commit b426fde
Show file tree
Hide file tree
Showing 3 changed files with 319 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,8 @@ jobs:
scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_RVCOPSTATE_2_4.py'
scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_SC_7_1.py'
scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_SWTCH.py'
scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_WHM_1_2.py'
scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_WHM_2_1.py'
scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_LVL_2_3.py'
scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TCP_Tests.py'
scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --script "src/python_testing/TestConformanceSupport.py" --script-args "--trace-to json:out/trace_data/test-{SCRIPT_BASE_NAME}.json --trace-to perfetto:out/trace_data/test-{SCRIPT_BASE_NAME}.perfetto"'
Expand Down
153 changes: 153 additions & 0 deletions src/python_testing/TC_WHM_1_2.py
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()
164 changes: 164 additions & 0 deletions src/python_testing/TC_WHM_2_1.py
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()

0 comments on commit b426fde

Please sign in to comment.