Skip to content

Commit 6ac62f1

Browse files
PeterC1965restyled-commitsjamesharrow
authored andcommitted
Add water heater mode cluster test scripts (project-chip#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>
1 parent 276036f commit 6ac62f1

File tree

3 files changed

+319
-0
lines changed

3 files changed

+319
-0
lines changed

.github/workflows/tests.yaml

+2
Original file line numberDiff line numberDiff line change
@@ -598,6 +598,8 @@ jobs:
598598
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'
599599
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'
600600
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'
601+
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'
602+
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'
601603
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'
602604
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'
603605
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"'

src/python_testing/TC_WHM_1_2.py

+153
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
#
2+
# Copyright (c) 2024 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+
# === BEGIN CI TEST ARGUMENTS ===
19+
# test-runner-runs: run1
20+
# test-runner-run/run1/app: ${ALL_CLUSTERS_APP}
21+
# test-runner-run/run1/factoryreset: True
22+
# test-runner-run/run1/quiet: True
23+
# test-runner-run/run1/app-args: --discriminator 1234 --KVS kvs1 --trace-to json:${TRACE_APP}.json --enable-key 000102030405060708090a0b0c0d0e0f
24+
# 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
25+
# === END CI TEST ARGUMENTS ===
26+
27+
import logging
28+
29+
import chip.clusters as Clusters
30+
from matter_testing_support import MatterBaseTest, TestStep, async_test_body, default_matter_test_main
31+
from mobly import asserts
32+
33+
34+
class TC_WHM_1_2(MatterBaseTest):
35+
36+
async def read_mode_attribute_expect_success(self, endpoint, attribute):
37+
cluster = Clusters.Objects.WaterHeaterMode
38+
return await self.read_single_attribute_check_success(endpoint=endpoint, cluster=cluster, attribute=attribute)
39+
40+
def desc_TC_WHM_1_2(self) -> str:
41+
return "[TC-WHM-1.2] Cluster attributes with DUT as Server"
42+
43+
def steps_TC_WHM_1_2(self) -> list[TestStep]:
44+
steps = [
45+
TestStep(1, "Commissioning, already done", is_commissioning=True),
46+
TestStep(2, "Read the SupportedModes attribute"),
47+
TestStep(3, "Read the CurrentMode attribute"),
48+
]
49+
return steps
50+
51+
def pics_TC_WHM_1_2(self) -> list[str]:
52+
pics = [
53+
"WHM.S",
54+
]
55+
return pics
56+
57+
@async_test_body
58+
async def test_TC_WHM_1_2(self):
59+
60+
endpoint = self.user_params.get("endpoint", 1)
61+
62+
attributes = Clusters.WaterHeaterMode.Attributes
63+
64+
self.step(1)
65+
66+
self.step(2)
67+
supported_modes = await self.read_mode_attribute_expect_success(endpoint=endpoint, attribute=attributes.SupportedModes)
68+
asserts.assert_greater_equal(len(supported_modes), 2,
69+
"SupportedModes must have at least 2 entries!")
70+
asserts.assert_less_equal(len(supported_modes), 255,
71+
"SupportedModes must have at most 255 entries!")
72+
modes = set([m.mode for m in supported_modes])
73+
asserts.assert_equal(len(modes), len(supported_modes),
74+
"SupportedModes must have unique mode values")
75+
76+
labels = set([m.label for m in supported_modes])
77+
asserts.assert_equal(len(labels), len(supported_modes),
78+
"SupportedModes must have unique mode label values")
79+
80+
# common mode tags
81+
commonTags = {0x0: 'Auto',
82+
0x1: 'Quick',
83+
0x2: 'Quiet',
84+
0x3: 'LowNoise',
85+
0x4: 'LowEnergy',
86+
0x5: 'Vacation',
87+
0x6: 'Min',
88+
0x7: 'Max',
89+
0x8: 'Night',
90+
0x9: 'Day'}
91+
92+
# derived cluster defined tags
93+
derivedTags = [tag.value for tag in Clusters.WaterHeaterMode.Enums.ModeTag]
94+
95+
logging.info("Derived tags: %s" % derivedTags)
96+
97+
# According to the Mode spec:
98+
# At least one entry in the SupportedModes attribute SHALL include the Manual mode tag in the ModeTags field list.
99+
# At least one entry in the SupportedModes attribute SHALL include the Off mode tag in the ModeTags field list.
100+
# An entry in the SupportedModes attribute that includes one of an Off, Manual, or Timed tag
101+
# SHALL NOT also include an additional instance of any one of these tag types.
102+
off_present = 0
103+
manual_present = 0
104+
timed_present = 0
105+
106+
for m in supported_modes:
107+
off_manual_timed_present_in_this_mode = 0
108+
for t in m.modeTags:
109+
is_mfg = (0x8000 <= t.value and t.value <= 0xBFFF)
110+
asserts.assert_true(t.value in commonTags.keys() or t.value in derivedTags or is_mfg,
111+
"Found a SupportedModes entry with invalid mode tag value!")
112+
if t.value == Clusters.WaterHeaterMode.Enums.ModeTag.kOff:
113+
off_present += 1
114+
off_manual_timed_present_in_this_mode += 1
115+
logging.info(
116+
"Found Off mode tag %s with tag value %s", m.mode, t.value)
117+
118+
if t.value == Clusters.WaterHeaterMode.Enums.ModeTag.kManual:
119+
manual_present += 1
120+
off_manual_timed_present_in_this_mode += 1
121+
logging.info(
122+
"Found Manual mode tag %s with tag value %s", m.mode, t.value)
123+
124+
if t.value == Clusters.WaterHeaterMode.Enums.ModeTag.kTimed:
125+
timed_present += 1
126+
off_manual_timed_present_in_this_mode += 1
127+
logging.info(
128+
"Found Timed mode tag %s with tag value %s", m.mode, t.value)
129+
130+
asserts.assert_less_equal(off_manual_timed_present_in_this_mode, 1,
131+
f"The supported mode ({m.mode}) should only include one of OFF, MANUAL or TIMED, but includes more than one.")
132+
133+
asserts.assert_greater(off_present, 0,
134+
"SupportedModes does not have an entry of Off(0x4000)")
135+
asserts.assert_greater(manual_present, 0,
136+
"SupportedModes does not have an entry of Manual(0x4001)")
137+
138+
asserts.assert_less_equal(off_present, 1,
139+
"SupportedModes cannot have more than one instance of Off(0x4000)")
140+
asserts.assert_less_equal(manual_present, 1,
141+
"SupportedModes cannot have more than one instance of Manual(0x4001)")
142+
asserts.assert_less_equal(timed_present, 1,
143+
"SupportedModes cannot have more than one instance of Timed(0x4002)")
144+
145+
self.step(3)
146+
current_mode = await self.read_mode_attribute_expect_success(endpoint=endpoint, attribute=attributes.CurrentMode)
147+
logging.info("CurrentMode: %s" % current_mode)
148+
asserts.assert_true(current_mode in modes,
149+
"CurrentMode is not a supported mode!")
150+
151+
152+
if __name__ == "__main__":
153+
default_matter_test_main()

src/python_testing/TC_WHM_2_1.py

+164
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
#
2+
# Copyright (c) 2024 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+
# See https://github.com/project-chip/connectedhomeip/blob/master/docs/testing/python.md#defining-the-ci-test-arguments
19+
# for details about the block below.
20+
#
21+
# === BEGIN CI TEST ARGUMENTS ===
22+
# test-runner-runs: run1
23+
# test-runner-run/run1/app: ${ALL_CLUSTERS_APP}
24+
# test-runner-run/run1/factoryreset: True
25+
# test-runner-run/run1/quiet: True
26+
# test-runner-run/run1/app-args: --discriminator 1234 --KVS kvs1 --trace-to json:${TRACE_APP}.json
27+
# 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
28+
# === END CI TEST ARGUMENTS ===
29+
30+
import logging
31+
32+
import chip.clusters as Clusters
33+
from chip.interaction_model import Status
34+
from matter_testing_support import MatterBaseTest, TestStep, async_test_body, default_matter_test_main, type_matches
35+
from mobly import asserts
36+
37+
38+
class TC_WHM_2_1(MatterBaseTest):
39+
40+
def __init__(self, *args):
41+
super().__init__(*args)
42+
self.endpoint = 0
43+
44+
def steps_TC_WHM_2_1(self) -> list[TestStep]:
45+
steps = [
46+
TestStep(1, "Commissioning, already done", is_commissioning=True),
47+
TestStep(2, "Read the SupportedModes attribute"),
48+
TestStep(3, "Read the CurrentMode attribute"),
49+
TestStep(4, "Send ChangeToMode command with NewMode"),
50+
TestStep(5, "Manually put the device in a state from which it will FAIL to transition"),
51+
TestStep(6, "Read CurrentMode attribute"),
52+
TestStep(7, "Send ChangeToMode command with NewMode"),
53+
TestStep(8, "Read CurrentMode attribute"),
54+
TestStep(9, "Manually put the device in a state from which it will SUCCESSFULLY transition"),
55+
TestStep(10, "Read CurrentMode attribute"),
56+
TestStep(11, "Send ChangeToMode command with NewMode"),
57+
TestStep(12, "Read CurrentMode attribute"),
58+
TestStep(13, "Send ChangeToMode command with NewMode set to an invalid mode"),
59+
TestStep(14, "Read CurrentMode attribute"),
60+
]
61+
return steps
62+
63+
async def read_mode_attribute_expect_success(self, endpoint, attribute):
64+
cluster = Clusters.Objects.WaterHeaterMode
65+
return await self.read_single_attribute_check_success(endpoint=endpoint, cluster=cluster, attribute=attribute)
66+
67+
async def send_change_to_mode_cmd(self, newMode) -> Clusters.Objects.WaterHeaterMode.Commands.ChangeToModeResponse:
68+
ret = await self.send_single_cmd(cmd=Clusters.Objects.WaterHeaterMode.Commands.ChangeToMode(newMode=newMode), endpoint=self.endpoint)
69+
asserts.assert_true(type_matches(ret, Clusters.Objects.WaterHeaterMode.Commands.ChangeToModeResponse),
70+
"Unexpected return type for Water Heater Mode ChangeToMode")
71+
return ret
72+
73+
def pics_TC_WHM_2_1(self) -> list[str]:
74+
return ["WHM.S"]
75+
76+
@async_test_body
77+
async def test_TC_WHM_2_1(self):
78+
79+
# Valid modes. Only ModeManual referred to in this test
80+
# ModeOff = 0
81+
ModeManual = 1
82+
# ModeTimed = 2
83+
84+
self.endpoint = self.matter_test_config.endpoint
85+
86+
attributes = Clusters.WaterHeaterMode.Attributes
87+
88+
self.step(1)
89+
# Commission DUT - already done
90+
91+
self.step(2)
92+
93+
supported_modes = await self.read_mode_attribute_expect_success(endpoint=self.endpoint, attribute=attributes.SupportedModes)
94+
95+
logging.info(f"SupportedModes: {supported_modes}")
96+
97+
asserts.assert_greater_equal(len(supported_modes), 2,
98+
"SupportedModes must have at least two entries!")
99+
100+
self.step(3)
101+
102+
old_current_mode = await self.read_mode_attribute_expect_success(endpoint=self.endpoint, attribute=attributes.CurrentMode)
103+
104+
logging.info(f"CurrentMode: {old_current_mode}")
105+
106+
# pick a value that's not on the list of supported modes
107+
modes = [m.mode for m in supported_modes]
108+
invalid_mode = max(modes) + 1
109+
110+
self.step(4)
111+
112+
ret = await self.send_change_to_mode_cmd(newMode=old_current_mode)
113+
logging.info(f"ret.status {ret.status}")
114+
asserts.assert_equal(ret.status, Status.Success,
115+
"Changing the mode to the current mode should be a no-op")
116+
117+
# Steps 5-9 are not performed as WHM.S.M.CAN_TEST_MODE_FAILURE is false
118+
# TODO - see issue 34565
119+
self.step(5)
120+
self.step(6)
121+
self.step(7)
122+
self.step(8)
123+
self.step(9)
124+
125+
self.step(10)
126+
127+
old_current_mode = await self.read_mode_attribute_expect_success(endpoint=self.endpoint, attribute=attributes.CurrentMode)
128+
129+
logging.info(f"CurrentMode: {old_current_mode}")
130+
131+
self.step(11)
132+
133+
ret = await self.send_change_to_mode_cmd(newMode=ModeManual)
134+
asserts.assert_true(ret.status == Status.Success,
135+
f"Changing to mode {ModeManual}must succeed due to the current state of the device")
136+
137+
self.step(12)
138+
139+
current_mode = await self.read_mode_attribute_expect_success(endpoint=self.endpoint, attribute=attributes.CurrentMode)
140+
141+
logging.info(f"CurrentMode: {current_mode}")
142+
143+
asserts.assert_true(current_mode == ModeManual,
144+
"CurrentMode doesn't match the argument of the successful ChangeToMode command!")
145+
146+
self.step(13)
147+
148+
ret = await self.send_change_to_mode_cmd(newMode=invalid_mode)
149+
logging.info(f"ret {ret}")
150+
asserts.assert_true(ret.status == Status.Failure,
151+
f"Attempt to change to invalid mode {invalid_mode} didn't fail as expected")
152+
153+
self.step(14)
154+
155+
current_mode = await self.read_mode_attribute_expect_success(endpoint=self.endpoint, attribute=attributes.CurrentMode)
156+
157+
logging.info(f"CurrentMode: {current_mode}")
158+
159+
asserts.assert_true(current_mode == ModeManual,
160+
"CurrentMode changed after failed ChangeToMode command!")
161+
162+
163+
if __name__ == "__main__":
164+
default_matter_test_main()

0 commit comments

Comments
 (0)