Skip to content

Commit 3a65704

Browse files
committed
TC-TMP-2.1: Fix
The constraints here are actually relatively complex because the min and max can be null and fall back to default values. Updated the automation to python so we can do the appropriate tests, added tests for the various scenarios becuase there are a LOT and we don't cover any of them with our current examples. Please see CHIP-Specifications/chip-test-plans#4096 for test plan updates
1 parent 739923c commit 3a65704

File tree

4 files changed

+353
-9
lines changed

4 files changed

+353
-9
lines changed

src/python_testing/TC_TMP_2_1.py

+99
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
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+
import logging
18+
19+
import chip.clusters as Clusters
20+
from chip.clusters.Types import NullValue
21+
from matter_testing_support import MatterBaseTest, TestStep, async_test_body, default_matter_test_main
22+
from mobly import asserts
23+
24+
25+
class TC_TMP_2_1(MatterBaseTest):
26+
def desc_TC_TMP_2_1(self) -> str:
27+
return "[TC-TMP-2.1] Attributes with Server as DUT"
28+
29+
def pics_TC_TMP_2_1(self):
30+
return ["TMP.S"]
31+
32+
def steps_TC_TMP_2_1(self) -> list[TestStep]:
33+
return [
34+
TestStep(1, "Commissioning, already done", is_commissioning=True),
35+
TestStep(
36+
2, "Set default bounds `min_bound` = -27315, `max_bound` = 32767"),
37+
TestStep(3, "TH reads the MinMeasuredValue attribute from the DUT and saves as `min_measured_value`. If `min_measured_value` is not null, set `min_bound` to `min_measured_value`"),
38+
TestStep(4, "TH reads the MaxMeasuredValue attribute from the DUT and saves as `max_measured_value`. If `max_measured_value` is not null, set `max_bound` to `max_measured_value",
39+
"Verify that `max_measured_value` is either null or an int16 where min_bound < `max_measured_value` ≤ 32767."),
40+
TestStep(5, "If `min_measured_value` is not null, verify min measured value range",
41+
"Verify that -27315 ≤ `min_measured_value` < `max_bound`"),
42+
TestStep(6, "TH reads the MeasuredValue attribute from the DUT",
43+
"Verify that the DUT response contains either null or a int16 where `min_bound` ≤ MeasuredValue ≤ `max_bound`."),
44+
TestStep(7, "TH reads the Tolerance attribute from the DUT",
45+
"Verify that Tolerance is in the range of 0 to 2048"),
46+
]
47+
48+
@async_test_body
49+
async def test_TC_TMP_2_1(self):
50+
cluster = Clusters.TemperatureMeasurement
51+
attr = Clusters.TemperatureMeasurement.Attributes
52+
53+
# Commission DUT - already done
54+
self.step(1)
55+
56+
self.step(2)
57+
min_bound = -27315
58+
max_bound = 32767
59+
60+
self.step(3)
61+
min_measured_value = await self.read_single_attribute_check_success(cluster=cluster, attribute=attr.MinMeasuredValue)
62+
if min_measured_value != NullValue:
63+
min_bound = min_measured_value
64+
65+
self.step(4)
66+
max_measured_value = await self.read_single_attribute_check_success(cluster=cluster, attribute=attr.MaxMeasuredValue)
67+
if max_measured_value != NullValue:
68+
max_bound = max_measured_value
69+
asserts.assert_greater(max_measured_value, min_bound,
70+
"MaxMeasuredValue is not greater than the minimum bound")
71+
asserts.assert_less_equal(
72+
max_measured_value, 32767, "MaxMeasuredValue is not less than or equal to than 32767")
73+
74+
self.step(5)
75+
if min_measured_value != NullValue:
76+
asserts.assert_greater_equal(min_measured_value, -27315, "")
77+
asserts.assert_less(min_measured_value, max_bound)
78+
else:
79+
self.mark_current_step_skipped()
80+
81+
self.step(6)
82+
measured_value = await self.read_single_attribute_check_success(cluster=cluster, attribute=attr.MeasuredValue)
83+
if measured_value != NullValue:
84+
print(measured_value)
85+
print(min_bound)
86+
asserts.assert_greater_equal(
87+
measured_value, min_bound, "Measured value is less than min bound")
88+
asserts.assert_less_equal(
89+
measured_value, max_bound, "Measured value is greater than max bound")
90+
91+
self.step(7)
92+
tolerance = await self.read_single_attribute_check_success(cluster=cluster, attribute=attr.Tolerance)
93+
asserts.assert_greater_equal(tolerance, 0, "Tolerance is less than 0")
94+
asserts.assert_less_equal(
95+
tolerance, 2048, "Tolerance is greater than 2048")
96+
97+
98+
if __name__ == "__main__":
99+
default_matter_test_main()

src/python_testing/matter_testing_support.py

+18-9
Original file line numberDiff line numberDiff line change
@@ -1664,7 +1664,7 @@ def get_test_info(test_class: MatterBaseTest, matter_test_config: MatterTestConf
16641664
return info
16651665

16661666

1667-
def run_tests(test_class: MatterBaseTest, matter_test_config: MatterTestConfig, hooks: TestRunnerHooks) -> None:
1667+
def run_tests_no_exit(test_class: MatterBaseTest, matter_test_config: MatterTestConfig, hooks: TestRunnerHooks, default_controller=None, external_stack=None) -> bool:
16681668

16691669
get_test_info(test_class, matter_test_config)
16701670

@@ -1676,7 +1676,10 @@ def run_tests(test_class: MatterBaseTest, matter_test_config: MatterTestConfig,
16761676
if len(matter_test_config.tests) > 0:
16771677
tests = matter_test_config.tests
16781678

1679-
stack = MatterStackState(matter_test_config)
1679+
if external_stack:
1680+
stack = external_stack
1681+
else:
1682+
stack = MatterStackState(matter_test_config)
16801683

16811684
with TracingContext() as tracing_ctx:
16821685
for destination in matter_test_config.trace_to:
@@ -1686,12 +1689,12 @@ def run_tests(test_class: MatterBaseTest, matter_test_config: MatterTestConfig,
16861689

16871690
# TODO: Steer to right FabricAdmin!
16881691
# TODO: If CASE Admin Subject is a CAT tag range, then make sure to issue NOC with that CAT tag
1689-
1690-
default_controller = stack.certificate_authorities[0].adminList[0].NewController(
1691-
nodeId=matter_test_config.controller_node_id,
1692-
paaTrustStorePath=str(matter_test_config.paa_trust_store_path),
1693-
catTags=matter_test_config.controller_cat_tags
1694-
)
1692+
if not default_controller:
1693+
default_controller = stack.certificate_authorities[0].adminList[0].NewController(
1694+
nodeId=matter_test_config.controller_node_id,
1695+
paaTrustStorePath=str(matter_test_config.paa_trust_store_path),
1696+
catTags=matter_test_config.controller_cat_tags
1697+
)
16951698
test_config.user_params["default_controller"] = stash_globally(default_controller)
16961699

16971700
test_config.user_params["matter_test_config"] = stash_globally(matter_test_config)
@@ -1740,10 +1743,16 @@ def run_tests(test_class: MatterBaseTest, matter_test_config: MatterTestConfig,
17401743
hooks.stop(duration=duration)
17411744

17421745
# Shutdown the stack when all done
1743-
stack.Shutdown()
1746+
if not external_stack:
1747+
stack.Shutdown()
17441748

17451749
if ok:
17461750
logging.info("Final result: PASS !")
17471751
else:
17481752
logging.error("Final result: FAIL !")
1753+
return ok
1754+
1755+
1756+
def run_tests(test_class: MatterBaseTest, matter_test_config: MatterTestConfig, hooks: TestRunnerHooks, default_controller=None, external_stack=None) -> None:
1757+
if not run_tests_internal(test_class, matter_test_config, hooks, default_controller, external_stack):
17491758
sys.exit(1)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
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+
import importlib
19+
import os
20+
import sys
21+
from pathlib import Path
22+
from unittest.mock import MagicMock
23+
24+
from chip.clusters import Attribute
25+
26+
try:
27+
from matter_testing_support import MatterStackState, MatterTestConfig, run_tests_no_exit
28+
except ImportError:
29+
sys.path.append(os.path.abspath(
30+
os.path.join(os.path.dirname(__file__), '..')))
31+
from matter_testing_support import MatterStackState, MatterTestConfig, run_tests_no_exit
32+
33+
34+
class AsyncMock(MagicMock):
35+
async def __call__(self, *args, **kwargs):
36+
return super(AsyncMock, self).__call__(*args, **kwargs)
37+
38+
39+
class MockTestRunner():
40+
def __init__(self, filename: str, classname: str, test: str):
41+
self.config = MatterTestConfig(
42+
tests=[test], endpoint=1, dut_node_ids=[1])
43+
self.stack = MatterStackState(self.config)
44+
self.default_controller = self.stack.certificate_authorities[0].adminList[0].NewController(
45+
nodeId=self.config.controller_node_id,
46+
paaTrustStorePath=str(self.config.paa_trust_store_path),
47+
catTags=self.config.controller_cat_tags
48+
)
49+
module = importlib.import_module(Path(os.path.basename(filename)).stem)
50+
self.test_class = getattr(module, classname)
51+
52+
def Shutdown(self):
53+
self.stack.Shutdown()
54+
55+
def run_test_with_mock_read(self, read_cache: Attribute.AsyncReadTransaction.ReadResponse):
56+
self.default_controller.Read = AsyncMock(return_value=read_cache)
57+
return run_tests_no_exit(self.test_class, self.config, None, self.default_controller, self.stack)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
#!/usr/bin/env -S python3 -B
2+
#
3+
# Copyright (c) 2024 Project CHIP Authors
4+
# All rights reserved.
5+
#
6+
# Licensed under the Apache License, Version 2.0 (the "License");
7+
# you may not use this file except in compliance with the License.
8+
# You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
#
18+
19+
import sys
20+
import typing
21+
from dataclasses import dataclass
22+
23+
import chip.clusters as Clusters
24+
from chip.clusters import Attribute
25+
from chip.clusters.Types import NullValue
26+
from MockTestRunner import MockTestRunner
27+
28+
29+
@dataclass
30+
class TestSpec():
31+
min: typing.Optional[int]
32+
max: typing.Optional[int]
33+
measured: int
34+
tolerance: int
35+
expect_pass: bool
36+
37+
38+
TEST_CASES = [
39+
# ==============================
40+
# Measured Test cases
41+
# ==============================
42+
# --------
43+
# Neither min nor max specified
44+
# --------
45+
# Measured lowest
46+
TestSpec(NullValue, NullValue, -27315, 10, True),
47+
# Measured highest
48+
TestSpec(NullValue, NullValue, 32767, 10, True),
49+
# Measured below
50+
TestSpec(NullValue, NullValue, -27316, 10, False),
51+
# Measured above
52+
TestSpec(NullValue, NullValue, 32768, 10, False),
53+
# Measured null
54+
TestSpec(NullValue, NullValue, NullValue, 10, True),
55+
56+
# --------
57+
# min only
58+
# --------
59+
# Measured lowest
60+
TestSpec(5, NullValue, 5, 10, True),
61+
# Measured highest
62+
TestSpec(5, NullValue, 32767, 10, True),
63+
# Measured below
64+
TestSpec(5, NullValue, 4, 10, False),
65+
# Measured above
66+
TestSpec(5, NullValue, 32768, 10, False),
67+
# Measured null
68+
TestSpec(5, NullValue, NullValue, 10, True),
69+
70+
# --------
71+
# max only
72+
# --------
73+
# Measured lowest
74+
TestSpec(NullValue, 5, -27315, 10, True),
75+
# Measured highest
76+
TestSpec(NullValue, 5, 5, 10, True),
77+
# Measured below
78+
TestSpec(NullValue, 5, -27316, 10, False),
79+
# Measured above
80+
TestSpec(NullValue, 5, 6, 10, False),
81+
# Measured null
82+
TestSpec(NullValue, 5, NullValue, 10, True),
83+
84+
# --------
85+
# both
86+
# --------
87+
# Measured lowest
88+
TestSpec(-5, 5, -5, 10, True),
89+
# Measured highest
90+
TestSpec(-5, 5, 5, 10, True),
91+
# Measured below
92+
TestSpec(-5, 5, -6, 10, False),
93+
# Measured above
94+
TestSpec(-5, 5, 6, 10, False),
95+
# Measured null
96+
TestSpec(-5, 5, NullValue, 10, True),
97+
98+
# ==============================
99+
# Min Test cases
100+
# ==============================
101+
# Max not specified, min OK bottom
102+
TestSpec(-27315, NullValue, 0, 10, True),
103+
# Max not specified, min OK top
104+
TestSpec(32766, NullValue, 32767, 10, True),
105+
# Max not specified, min out of range below
106+
TestSpec(-27316, NullValue, 0, 10, False),
107+
# Max not specified, min out of range above
108+
TestSpec(32767, NullValue, 32767, 10, False),
109+
# Max specified, min OK bottom
110+
TestSpec(-27315, 5, 0, 10, True),
111+
# Max specified, min OK top
112+
TestSpec(4, 5, 4, 10, True),
113+
# Max specified, min out of range below
114+
TestSpec(-27316, 5, 0, 10, False),
115+
# Max specified, min out of range above
116+
TestSpec(5, 5, 5, 10, False),
117+
118+
# ==============================
119+
# Min Test cases
120+
# ==============================
121+
# min not specified, max OK bottom
122+
TestSpec(NullValue, -27314, -27315, 10, True),
123+
# min not specified, max OK top
124+
TestSpec(NullValue, 32767, 0, 10, True),
125+
# min not specified, max out of range bottom
126+
TestSpec(NullValue, -27315, -27315, 10, False),
127+
# min not specified, max out of range top
128+
TestSpec(NullValue, 32768, 0, 10, False),
129+
# min specified, max OK bottom
130+
TestSpec(0, 1, 0, 10, True),
131+
# min specified, max OK top
132+
TestSpec(0, 32767, 0, 10, True),
133+
# min specified, max out of range bottom
134+
TestSpec(0, 0, 0, 10, False),
135+
# min specified, max out of range top
136+
TestSpec(0, 32768, 0, 10, False),
137+
138+
# ==============================
139+
# Tolerance test cases
140+
# ==============================
141+
# Tolerance OK bottom
142+
TestSpec(NullValue, NullValue, 0, 0, True),
143+
# Tolerance OK top
144+
TestSpec(NullValue, NullValue, 0, 2048, True),
145+
# Tolerance out of range bottom
146+
TestSpec(NullValue, NullValue, 0, -1, False),
147+
# Tolerance out of range top
148+
TestSpec(NullValue, NullValue, 0, 2049, False),
149+
150+
]
151+
152+
153+
def test_spec_to_attribute_cache(test_spec: TestSpec) -> Attribute.AsyncReadTransaction.ReadResponse:
154+
c = Clusters.TemperatureMeasurement
155+
attr = Clusters.TemperatureMeasurement.Attributes
156+
resp = Attribute.AsyncReadTransaction.ReadResponse({}, [], {})
157+
resp.attributes = {1: {c: {attr.MaxMeasuredValue: test_spec.max,
158+
attr.MinMeasuredValue: test_spec.min, attr.MeasuredValue: test_spec.measured, attr.Tolerance: test_spec.tolerance}}}
159+
return resp
160+
161+
162+
def main():
163+
test_runner = MockTestRunner('TC_TMP_2_1', 'TC_TMP_2_1', 'test_TC_TMP_2_1')
164+
failures = []
165+
for idx, t in enumerate(TEST_CASES):
166+
ok = test_runner.run_test_with_mock_read(test_spec_to_attribute_cache(t)) == t.expect_pass
167+
if not ok:
168+
failures.append(f"Measured test case failure: {idx} {t}")
169+
170+
test_runner.Shutdown()
171+
print(f"Test of tests: run {len(TEST_CASES)}, test response correct: {len(TEST_CASES) - len(failures)} test response incorrect: {len(failures)}")
172+
for f in failures:
173+
print(f)
174+
175+
return 1 if failures else 0
176+
177+
178+
if __name__ == "__main__":
179+
sys.exit(main())

0 commit comments

Comments
 (0)