Skip to content

Commit 51cb716

Browse files
Fix remaining post-TE2 TC-SWTCH issues (#35291)
* Fix remaining post-TE2 TC-SWTCH issues - Fixes #34556 (-2.2, -2.3 and -2.4 to better match test plan) - Fixes #28620 (Better bound checks) - Fixes #35241 * Restyled by clang-format * Restyled by autopep8 * Restyled by isort * Fix step 2 of TC-SWTCH--2.4 --------- Co-authored-by: Restyled.io <commits@restyled.io>
1 parent 8da30ca commit 51cb716

File tree

3 files changed

+133
-40
lines changed

3 files changed

+133
-40
lines changed

examples/all-clusters-app/linux/AllClustersCommandDelegate.cpp

+37
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,17 @@ bool HasNumericField(Json::Value & jsonValue, const std::string & field)
5757
return jsonValue.isMember(field) && jsonValue[field].isNumeric();
5858
}
5959

60+
uint8_t GetNumberOfSwitchPositions(EndpointId endpointId)
61+
{
62+
// TODO: Move to using public API of cluster.
63+
uint8_t numPositions = 0;
64+
65+
// On failure, the numPositions won't be changed, so 0 returned.
66+
(void) Switch::Attributes::NumberOfPositions::Get(endpointId, &numPositions);
67+
68+
return numPositions;
69+
}
70+
6071
/**
6172
* Named pipe handler for simulated long press
6273
*
@@ -99,6 +110,15 @@ void HandleSimulateLongPress(Json::Value & jsonValue)
99110

100111
EndpointId endpointId = static_cast<EndpointId>(jsonValue["EndpointId"].asUInt());
101112
uint8_t buttonId = static_cast<uint8_t>(jsonValue["ButtonId"].asUInt());
113+
114+
uint8_t numPositions = GetNumberOfSwitchPositions(endpointId);
115+
if (buttonId >= numPositions)
116+
{
117+
std::string inputJson = jsonValue.toStyledString();
118+
ChipLogError(NotSpecified, "Invalid ButtonId (out of range) in %s", inputJson.c_str());
119+
return;
120+
}
121+
102122
System::Clock::Milliseconds32 longPressDelayMillis{ static_cast<unsigned>(jsonValue["LongPressDelayMillis"].asUInt()) };
103123
System::Clock::Milliseconds32 longPressDurationMillis{ static_cast<unsigned>(jsonValue["LongPressDurationMillis"].asUInt()) };
104124
uint32_t featureMap = static_cast<uint32_t>(jsonValue["FeatureMap"].asUInt());
@@ -169,6 +189,15 @@ void HandleSimulateMultiPress(Json::Value & jsonValue)
169189

170190
EndpointId endpointId = static_cast<EndpointId>(jsonValue["EndpointId"].asUInt());
171191
uint8_t buttonId = static_cast<uint8_t>(jsonValue["ButtonId"].asUInt());
192+
193+
uint8_t numPositions = GetNumberOfSwitchPositions(endpointId);
194+
if (buttonId >= numPositions)
195+
{
196+
std::string inputJson = jsonValue.toStyledString();
197+
ChipLogError(NotSpecified, "Invalid ButtonId (out of range) in %s", inputJson.c_str());
198+
return;
199+
}
200+
172201
System::Clock::Milliseconds32 multiPressPressedTimeMillis{ static_cast<unsigned>(
173202
jsonValue["MultiPressPressedTimeMillis"].asUInt()) };
174203
System::Clock::Milliseconds32 multiPressReleasedTimeMillis{ static_cast<unsigned>(
@@ -227,6 +256,14 @@ void HandleSimulateLatchPosition(Json::Value & jsonValue)
227256
EndpointId endpointId = static_cast<EndpointId>(jsonValue["EndpointId"].asUInt());
228257
uint8_t positionId = static_cast<uint8_t>(jsonValue["PositionId"].asUInt());
229258

259+
uint8_t numPositions = GetNumberOfSwitchPositions(endpointId);
260+
if (positionId >= numPositions)
261+
{
262+
std::string inputJson = jsonValue.toStyledString();
263+
ChipLogError(NotSpecified, "Invalid PositionId (out of range) in %s", inputJson.c_str());
264+
return;
265+
}
266+
230267
uint8_t previousPositionId = 0;
231268
Protocols::InteractionModel::Status status = Switch::Attributes::CurrentPosition::Get(endpointId, &previousPositionId);
232269
VerifyOrReturn(Protocols::InteractionModel::Status::Success == status,

src/python_testing/TC_SWTCH.py

+71-39
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@
3838
from chip.clusters import ClusterObjects as ClusterObjects
3939
from chip.clusters.Attribute import EventReadResult
4040
from chip.tlv import uint
41-
from matter_testing_support import (ClusterAttributeChangeAccumulator, EventChangeCallback, MatterBaseTest, TestStep,
42-
await_sequence_of_reports, default_matter_test_main, has_feature, per_endpoint_test)
41+
from matter_testing_support import (AttributeValue, ClusterAttributeChangeAccumulator, EventChangeCallback, MatterBaseTest,
42+
TestStep, await_sequence_of_reports, default_matter_test_main, has_feature, per_endpoint_test)
4343
from mobly import asserts
4444

4545
logger = logging.getLogger(__name__)
@@ -70,6 +70,10 @@ def __init__(self, *args, **kwargs):
7070
super().__init__(*args, **kwargs)
7171
self._default_pressed_position = self.user_params.get("default_pressed_position", 1)
7272

73+
def setup_test(self):
74+
super().setup_test()
75+
self.is_ci = self._use_button_simulator()
76+
7377
def _send_named_pipe_command(self, command_dict: dict[str, Any]):
7478
app_pid = self.matter_test_config.app_pid
7579
if app_pid == 0:
@@ -259,32 +263,39 @@ def _received_event(self, event_listener: EventChangeCallback, target_event: Clu
259263

260264
def steps_TC_SWTCH_2_2(self):
261265
return [TestStep(1, test_plan_support.commission_if_required(), "", is_commissioning=True),
262-
TestStep(2, "Set up subscription to all events of Switch cluster on the endpoint"),
266+
TestStep(2, "Set up subscription to all events and attributes of Switch cluster on the endpoint"),
263267
TestStep(3, "Operator sets switch to first position on the DUT"),
264-
TestStep(4, "TH reads the CurrentPosition attribute from the DUT", "Verify that the value is 0"),
268+
TestStep(4, "TH reads the CurrentPosition attribute from the DUT.", "Verify that the value is 0."),
265269
TestStep(5, "Operator sets switch to second position (one) on the DUT",
266-
"Verify that the TH receives SwitchLatched event with NewPosition set to 1 from the DUT"),
267-
TestStep(6, "TH reads the CurrentPosition attribute from the DUT", "Verify that the value is 1"),
270+
"Verify that the TH receives SwitchLatched event with NewPosition set to 1 from the DUT."),
271+
TestStep(6, "TH reads the CurrentPosition attribute from the DUT",
272+
"Verify that the value is 1, and that a subscription report was received for that change."),
268273
TestStep(7, "If there are more than 2 positions, test subsequent positions of the DUT"),
269274
TestStep(8, "Operator sets switch to first position on the DUT."),
270275
TestStep(9, "Wait 10 seconds for event reports stable." "Verify that last SwitchLatched event received is for NewPosition 0."),
271-
TestStep(10, "TH reads the CurrentPosition attribute from the DUT", "Verify that the value is 0"),
276+
TestStep(10, "TH reads the CurrentPosition attribute from the DUT",
277+
"Verify that the value is 0, and that a subscription report was received for that change."),
272278
]
273279

274280
@per_endpoint_test(has_feature(Clusters.Switch, Clusters.Switch.Bitmaps.Feature.kLatchingSwitch))
275281
async def test_TC_SWTCH_2_2(self):
276282
post_prompt_settle_delay_seconds = 10.0
283+
cluster = Clusters.Switch
284+
endpoint_id = self.matter_test_config.endpoint
277285

278286
# Step 1: Commissioning - already done
279287
self.step(1)
280288

281-
cluster = Clusters.Switch
282-
endpoint_id = self.matter_test_config.endpoint
283-
284289
# Step 2: Set up subscription to all events of Switch cluster on the endpoint.
285290
self.step(2)
286291
event_listener = EventChangeCallback(cluster)
287292
await event_listener.start(self.default_controller, self.dut_node_id, endpoint=endpoint_id)
293+
attrib_listener = ClusterAttributeChangeAccumulator(cluster)
294+
await attrib_listener.start(self.default_controller, self.dut_node_id, endpoint=endpoint_id)
295+
296+
# Pre-get number of positions for step 7 later.
297+
num_positions = await self.read_single_attribute_check_success(cluster=cluster, attribute=cluster.Attributes.NumberOfPositions)
298+
asserts.assert_greater(num_positions, 1, "Latching switch has only 1 position, this is impossible.")
288299

289300
# Step 3: Operator sets switch to first position on the DUT.
290301
self.step(3)
@@ -296,25 +307,39 @@ async def test_TC_SWTCH_2_2(self):
296307
button_val = await self.read_single_attribute_check_success(cluster=cluster, attribute=cluster.Attributes.CurrentPosition)
297308
asserts.assert_equal(button_val, 0, "Switch position value is not 0")
298309

299-
# Step 5: Operator sets switch to second position (one) on the DUT",
300-
# Verify that the TH receives SwitchLatched event with NewPosition set to 1 from the DUT
301-
self.step(5)
302-
expected_switch_position = 1
303-
self._ask_for_switch_position(endpoint_id, expected_switch_position)
304-
305-
data = event_listener.wait_for_event_report(cluster.Events.SwitchLatched, timeout_sec=post_prompt_settle_delay_seconds)
306-
logging.info(f"-> SwitchLatched event last received: {data}")
307-
asserts.assert_equal(data, cluster.Events.SwitchLatched(
308-
newPosition=expected_switch_position), "Did not get expected switch position")
309-
310-
# Step 6: TH reads the CurrentPosition attribute from the DUT", "Verify that the value is 1
311-
self.step(6)
312-
button_val = await self.read_single_attribute_check_success(cluster=cluster, attribute=cluster.Attributes.CurrentPosition)
313-
asserts.assert_equal(button_val, expected_switch_position, f"Switch position is not {expected_switch_position}")
314-
315-
# Step 7: If there are more than 2 positions, test subsequent positions of the DUT
316-
# # TODO(#34656): Implement loop for > 2 total positions
317-
self.skip_step(7)
310+
attrib_listener.reset()
311+
event_listener.reset()
312+
313+
for expected_switch_position in range(1, num_positions):
314+
# Step 5: Operator sets switch to second position (one) on the DUT",
315+
# Verify that the TH receives SwitchLatched event with NewPosition set to 1 from the DUT
316+
if expected_switch_position == 1:
317+
self.step(5)
318+
self._ask_for_switch_position(endpoint_id, expected_switch_position)
319+
320+
data = event_listener.wait_for_event_report(cluster.Events.SwitchLatched, timeout_sec=post_prompt_settle_delay_seconds)
321+
logging.info(f"-> SwitchLatched event last received: {data}")
322+
asserts.assert_equal(data, cluster.Events.SwitchLatched(
323+
newPosition=expected_switch_position), "Did not get expected switch position")
324+
325+
# Step 6: TH reads the CurrentPosition attribute from the DUT", "Verify that the value is 1
326+
if expected_switch_position == 1: # Indicate step 7 only once
327+
self.step(6)
328+
button_val = await self.read_single_attribute_check_success(cluster=cluster, attribute=cluster.Attributes.CurrentPosition)
329+
asserts.assert_equal(button_val, expected_switch_position, f"Switch position is not {expected_switch_position}")
330+
logging.info(f"Checking to see if a report for {expected_switch_position} is received")
331+
attrib_listener.await_sequence_of_reports(attribute=cluster.Attributes.CurrentPosition, sequence=[
332+
expected_switch_position], timeout_sec=post_prompt_settle_delay_seconds)
333+
334+
# Step 7: If there are more than 2 positions, test subsequent positions of the DUT
335+
if expected_switch_position == 1:
336+
if num_positions > 2: # Indicate step 7 only once
337+
self.step(7)
338+
else:
339+
self.skip_step(7)
340+
341+
if num_positions > 2:
342+
logging.info("Looping for the other positions")
318343

319344
# Step 8: Operator sets switch to first position on the DUT.
320345
self.step(8)
@@ -324,7 +349,7 @@ async def test_TC_SWTCH_2_2(self):
324349
# Step 9: Wait 10 seconds for event reports stable.
325350
# Verify that last SwitchLatched event received is for NewPosition 0.
326351
self.step(9)
327-
time.sleep(10.0)
352+
time.sleep(10.0 if not self.is_ci else 1.0)
328353

329354
expected_switch_position = 0
330355
last_event = event_listener.get_last_event()
@@ -337,15 +362,21 @@ async def test_TC_SWTCH_2_2(self):
337362
# Step 10: TH reads the CurrentPosition attribute from the DUT.
338363
# Verify that the value is 0
339364
self.step(10)
365+
340366
button_val = await self.read_single_attribute_check_success(cluster=cluster, attribute=cluster.Attributes.CurrentPosition)
341367
asserts.assert_equal(button_val, 0, "Button value is not 0")
342368

369+
logging.info(f"Checking to see if a report for {expected_switch_position} is received")
370+
expected_final_value = [AttributeValue(
371+
endpoint_id, attribute=cluster.Attributes.CurrentPosition, value=expected_switch_position)]
372+
attrib_listener.await_all_final_values_reported(expected_final_value, timeout_sec=post_prompt_settle_delay_seconds)
373+
343374
def steps_TC_SWTCH_2_3(self):
344375
return [TestStep(1, test_plan_support.commission_if_required(), "", is_commissioning=True),
345376
TestStep(2, "Set up subscription to all events of Switch cluster on the endpoint"),
346377
TestStep(3, "Operator does not operate switch on the DUT"),
347378
TestStep(4, "TH reads the CurrentPosition attribute from the DUT", "Verify that the value is 0"),
348-
TestStep(5, "Operator operates switch (keep it pressed)",
379+
TestStep(5, "Operator operates switch (keep it pressed, and wait at least 5 seconds)",
349380
"Verify that the TH receives InitialPress event with NewPosition set to 1 on the DUT"),
350381
TestStep(6, "TH reads the CurrentPosition attribute from the DUT", "Verify that the value is 1"),
351382
TestStep(7, "Operator releases switch on the DUT"),
@@ -421,15 +452,15 @@ def desc_TC_SWTCH_2_4(self) -> str:
421452

422453
def steps_TC_SWTCH_2_4(self):
423454
return [TestStep(1, test_plan_support.commission_if_required(), "", is_commissioning=True),
424-
TestStep(2, "Set up subscription to all events of Switch cluster on the endpoint"),
455+
TestStep(2, "Set up subscription to all events and attributes of Switch cluster on the endpoint"),
425456
TestStep(3, "Operator does not operate switch on the DUT"),
426457
TestStep(4, "TH reads the CurrentPosition attribute from the DUT", "Verify that the value is 0"),
427-
TestStep(5, "Operator operates switch (keep pressed for long time, e.g. 5 seconds) on the DUT, the release it",
458+
TestStep(5, "Operator operates switch (keep pressed for long time, e.g. 5 seconds) on the DUT, then release it",
428459
"""
429460
* TH expects receiving a subscription report of CurrentPosition 1, followed by a report of Current Position 0.
430461
* TH expects receiving at InitialPress event with NewPosition = 1.
431-
* if MSL or AS feature is supported, TH expect receiving LongPress/LongRelease in that order.
432-
* if MS & (!MSL & !AS & !MSR) features present, TH expects receiving no further events for 10 seconds after release.
462+
* if MSL feature is supported, TH expect receiving LongPress/LongRelease in that order.
463+
* if MS & (!MSL & !AS & !MSR & !MSM) features present, TH expects receiving no further events for 10 seconds after release.
433464
* if (MSR & !MSL) features present, TH expects receiving ShortRelease event.
434465
""")
435466
]
@@ -451,17 +482,18 @@ async def test_TC_SWTCH_2_4(self):
451482
has_ms_feature = (feature_map & cluster.Bitmaps.Feature.kMomentarySwitch) != 0
452483
has_msr_feature = (feature_map & cluster.Bitmaps.Feature.kMomentarySwitchRelease) != 0
453484
has_msl_feature = (feature_map & cluster.Bitmaps.Feature.kMomentarySwitchLongPress) != 0
485+
has_msm_feature = (feature_map & cluster.Bitmaps.Feature.kMomentarySwitchMultiPress) != 0
454486
has_as_feature = (feature_map & cluster.Bitmaps.Feature.kActionSwitch) != 0
455487

456488
if not has_ms_feature:
457489
logging.info("Skipping rest of test: SWTCH.S.F01(MS) feature not present")
458490
self.skip_all_remaining_steps("2")
459491

460-
# Step 2: Set up subscription to all events of Switch cluster on the endpoint
492+
# Step 2: Set up subscription to all events and attributes of Switch cluster on the endpoint
461493
self.step(2)
462494
event_listener = EventChangeCallback(cluster)
463-
attrib_listener = ClusterAttributeChangeAccumulator(cluster)
464495
await event_listener.start(self.default_controller, self.dut_node_id, endpoint=endpoint_id)
496+
attrib_listener = ClusterAttributeChangeAccumulator(cluster)
465497
await attrib_listener.start(self.default_controller, self.dut_node_id, endpoint=endpoint_id)
466498

467499
# Step 3: Operator does not operate switch on the DUT
@@ -503,8 +535,8 @@ async def test_TC_SWTCH_2_4(self):
503535
self._await_sequence_of_events(event_queue=event_listener.event_queue, endpoint_id=endpoint_id,
504536
sequence=expected_events, timeout_sec=post_prompt_settle_delay_seconds)
505537

506-
# - if MS & (!MSL & !AS & !MSR) features present, expect no further events for 10 seconds after release.
507-
if not has_msl_feature and not has_as_feature and not has_msr_feature:
538+
# - if MS & (!MSL & !AS & !MSR & !MSM) features present, expect no further events for 10 seconds after release.
539+
if not has_msl_feature and not has_as_feature and not has_msr_feature and not has_msm_feature:
508540
self._expect_no_events_for_cluster(event_queue=event_listener.event_queue,
509541
endpoint_id=endpoint_id, expected_cluster=cluster, timeout_sec=10.0)
510542

src/python_testing/matter_testing_support.py

+25-1
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,10 @@ def flush_events(self) -> None:
301301
_ = self.get_last_event()
302302
return
303303

304+
def reset(self) -> None:
305+
"""Resets state as if no events had ever been received."""
306+
self.flush_events()
307+
304308
@property
305309
def event_queue(self) -> queue.Queue:
306310
return self._q
@@ -356,7 +360,7 @@ class AttributeValue:
356360
timestamp_utc: Optional[datetime] = None
357361

358362

359-
def await_sequence_of_reports(report_queue: queue.Queue, endpoint_id: int, attribute: TypedAttributePath, sequence: list[Any], timeout_sec: float):
363+
def await_sequence_of_reports(report_queue: queue.Queue, endpoint_id: int, attribute: TypedAttributePath, sequence: list[Any], timeout_sec: float) -> None:
360364
"""Given a queue.Queue hooked-up to an attribute change accumulator, await a given expected sequence of attribute reports.
361365
362366
Args:
@@ -418,6 +422,7 @@ def __init__(self, expected_cluster: ClusterObjects.Cluster):
418422
self._subscription = None
419423
self._lock = threading.Lock()
420424
self._q = queue.Queue()
425+
self._endpoint_id = 0
421426
self.reset()
422427

423428
def reset(self):
@@ -441,6 +446,7 @@ async def start(self, dev_ctrl, node_id: int, endpoint: int, fabric_filtered: bo
441446
fabricFiltered=fabric_filtered,
442447
keepSubscriptions=keepSubscriptions
443448
)
449+
self._endpoint_id = endpoint
444450
self._subscription.SetAttributeUpdateCallback(self.__call__)
445451
return self._subscription
446452

@@ -513,6 +519,24 @@ def await_all_final_values_reported(self, expected_final_values: Iterable[Attrib
513519
logging.info(f" -> {expected_element} found: {last_report_matches.get(expected_idx)}")
514520
asserts.fail("Did not find all expected last report values before time-out")
515521

522+
def await_sequence_of_reports(self, attribute: TypedAttributePath, sequence: list[Any], timeout_sec: float) -> None:
523+
"""Await a given expected sequence of attribute reports in the accumulator for the endpoint associated.
524+
525+
Args:
526+
- attribute: attribute to match for reports to check.
527+
- sequence: list of attribute values in order that are expected.
528+
- timeout_sec: number of seconds to wait for.
529+
530+
*** WARNING: The queue contains every report since the sub was established. Use
531+
self.reset() to make it empty. ***
532+
533+
This will fail current Mobly test with assertion failure if the data is not as expected in order.
534+
535+
Returns nothing on success so the test can go on.
536+
"""
537+
await_sequence_of_reports(report_queue=self.attribute_queue, endpoint_id=self._endpoint_id,
538+
attribute=attribute, sequence=sequence, timeout_sec=timeout_sec)
539+
516540
@property
517541
def attribute_queue(self) -> queue.Queue:
518542
return self._q

0 commit comments

Comments
 (0)