Skip to content

Commit a780008

Browse files
committed
chip-repl read event yamltests working with hacks
Need to clean this up
1 parent 062b770 commit a780008

File tree

2 files changed

+188
-17
lines changed

2 files changed

+188
-17
lines changed

scripts/tests/chiptest/__init__.py

-7
Original file line numberDiff line numberDiff line change
@@ -131,13 +131,6 @@ def _GetInDevelopmentTests() -> Set[str]:
131131
Goal is for this set to become empty.
132132
"""
133133
return {
134-
# TODO: Event not yet supported:
135-
"Test_TC_ACL_2_10.yaml",
136-
"Test_TC_ACL_2_7.yaml",
137-
"Test_TC_ACL_2_8.yaml",
138-
"Test_TC_ACL_2_9.yaml",
139-
"TestEvents.yaml",
140-
141134
"TestClusterMultiFabric.yaml", # Enum mismatch
142135
"TestGroupMessaging.yaml", # Needs group support in repl
143136
"TestMultiAdmin.yaml", # chip-repl hang on command expeted to fail

src/controller/python/chip/yaml/runner.py

+188-10
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
import chip.yaml.format_converter as Converter
2727
import stringcase
2828
from chip.ChipDeviceCtrl import ChipDeviceController, discovery
29-
from chip.clusters.Attribute import AttributeStatus, SubscriptionTransaction, TypedAttributePath, ValueDecodeFailure
29+
from chip.clusters.Attribute import AttributeStatus, EventReadResult, SubscriptionTransaction, TypedAttributePath, ValueDecodeFailure
3030
from chip.exceptions import ChipStackError
3131
from chip.yaml.errors import ParsingError, UnexpectedParsingError
3232
from matter_yamltests.pseudo_clusters.clusters.delay_commands import DelayCommands
@@ -56,6 +56,11 @@ class _GetCommissionerNodeIdResult:
5656
node_id: int
5757

5858

59+
@dataclass
60+
class EventResponse:
61+
event_result_list: list[EventReadResult]
62+
63+
5964
@dataclass
6065
class _ActionResult:
6166
status: _ActionStatus
@@ -69,6 +74,12 @@ class _AttributeSubscriptionCallbackResult:
6974
result: _ActionResult
7075

7176

77+
@dataclass
78+
class _EventSubscriptionCallbackResult:
79+
name: str
80+
result: _ActionResult
81+
82+
7283
@dataclass
7384
class _ExecutionContext:
7485
''' Objects that is commonly passed around this file that are vital to test execution.'''
@@ -78,7 +89,8 @@ class _ExecutionContext:
7889
subscriptions: list = field(default_factory=list)
7990
# The key is the attribute/event name, and the value is a queue of subscription callback results
8091
# that been sent by device under test. For attribute subscription the queue is of type
81-
# _AttributeSubscriptionCallbackResult.
92+
# _AttributeSubscriptionCallbackResult, for event the queue is of type
93+
# _EventSubscriptionCallbackResult.
8294
subscription_callback_result_queue: dict = field(default_factory=dict)
8395

8496

@@ -266,6 +278,51 @@ def parse_raw_response(self, raw_resp) -> _ActionResult:
266278
return _ActionResult(status=_ActionStatus.SUCCESS, response=return_val)
267279

268280

281+
class ReadEventAction(BaseAction):
282+
''' Read Event action to be executed.'''
283+
284+
def __init__(self, test_step, cluster: str, context: _ExecutionContext):
285+
'''Converts 'test_step' to read event action that can execute with ChipDeviceController.
286+
287+
Args:
288+
'test_step': Step containing information required to run read event action.
289+
'cluster': Name of cluster read event action is targeting.
290+
'context': Contains test-wide common objects such as DataModelLookup instance.
291+
Raises:
292+
UnexpectedParsingError: Raised if there is an unexpected parsing error.
293+
'''
294+
super().__init__(test_step)
295+
self._event_name = stringcase.pascalcase(test_step.event)
296+
self._cluster = cluster
297+
self._endpoint = test_step.endpoint
298+
self._node_id = test_step.node_id
299+
self._cluster_object = None
300+
self._request_object = None
301+
self._fabric_filtered = True
302+
self._event_number_filter = test_step.event_number
303+
304+
self._request_object = context.data_model_lookup.get_event(self._cluster,
305+
self._event_name)
306+
if self._request_object is None:
307+
raise UnexpectedParsingError(
308+
f'ReadEvent failed to find cluster:{self._cluster} Event:{self._event_name}')
309+
310+
if test_step.arguments:
311+
raise UnexpectedParsingError(
312+
f'ReadEvent should not contain arguments. {self.label}')
313+
314+
def run_action(self, dev_ctrl: ChipDeviceController) -> _ActionResult:
315+
try:
316+
urgent = 0
317+
request = [(self._endpoint, self._request_object, urgent)]
318+
resp = asyncio.run(dev_ctrl.ReadEvent(self._node_id, events=request, eventNumberFilter=self._event_number_filter))
319+
except chip.interaction_model.InteractionModelError as error:
320+
return _ActionResult(status=_ActionStatus.ERROR, response=error)
321+
322+
parsed_resp = EventResponse(event_result_list=resp)
323+
return _ActionResult(status=_ActionStatus.SUCCESS, response=parsed_resp)
324+
325+
269326
class WaitForCommissioneeAction(BaseAction):
270327
''' Wait for commissionee action to be executed.'''
271328

@@ -327,6 +384,27 @@ def name(self) -> str:
327384
return self._name
328385

329386

387+
class EventChangeAccumulator:
388+
def __init__(self, name: str, expected_event, output_queue: queue.SimpleQueue):
389+
self._name = name
390+
self._expected_event = expected_event
391+
self._output_queue = output_queue
392+
393+
def __call__(self, event_result: EventReadResult, transaction: SubscriptionTransaction):
394+
if (self._expected_event.cluster_id == event_result.Header.ClusterId and
395+
self._expected_event.event_id == event_result.Header.EventId):
396+
event_response = EventResponse(event_result_list=[event_result])
397+
result = _ActionResult(status=_ActionStatus.SUCCESS, response=event_response)
398+
399+
item = _EventSubscriptionCallbackResult(self._name, result)
400+
logging.debug(f'Got subscription report on client {self.name}')
401+
self._output_queue.put(item)
402+
403+
@property
404+
def name(self) -> str:
405+
return self._name
406+
407+
330408
class SubscribeAttributeAction(ReadAttributeAction):
331409
'''Single subscribe attribute action to be executed.'''
332410

@@ -382,6 +460,63 @@ def run_action(self, dev_ctrl: ChipDeviceController) -> _ActionResult:
382460
return self.parse_raw_response(raw_resp)
383461

384462

463+
class SubscribeEventAction(ReadEventAction):
464+
'''Single subscribe event action to be executed.'''
465+
466+
def __init__(self, test_step, cluster: str, context: _ExecutionContext):
467+
'''Converts 'test_step' to subscribe event action that can execute with ChipDeviceController.
468+
469+
Args:
470+
'test_step': Step containing information required to run subscribe event action.
471+
'cluster': Name of cluster subscribe event action is targeting.
472+
'context': Contains test-wide common objects such as DataModelLookup instance.
473+
Raises:
474+
ParsingError: Raised if there is a benign error, and there is currently no
475+
action to perform for this subscribe event.
476+
UnexpectedParsingError: Raised if there is an unexpected parsing error.
477+
'''
478+
super().__init__(test_step, cluster, context)
479+
self._context = context
480+
if test_step.min_interval is None:
481+
raise UnexpectedParsingError(
482+
f'SubscribeAttribute action does not have min_interval {self.label}')
483+
self._min_interval = test_step.min_interval
484+
485+
if test_step.max_interval is None:
486+
raise UnexpectedParsingError(
487+
f'SubscribeAttribute action does not have max_interval {self.label}')
488+
self._max_interval = test_step.max_interval
489+
490+
def run_action(self, dev_ctrl: ChipDeviceController) -> _ActionResult:
491+
try:
492+
urgent = 0
493+
request = [(self._endpoint, self._request_object, urgent)]
494+
subscription = asyncio.run(
495+
dev_ctrl.ReadEvent(self._node_id, events=request, eventNumberFilter=self._event_number_filter,
496+
reportInterval=(self._min_interval, self._max_interval),
497+
keepSubscriptions=False))
498+
except chip.interaction_model.InteractionModelError as error:
499+
return _ActionResult(status=_ActionStatus.ERROR, response=error)
500+
501+
self._context.subscriptions.append(subscription)
502+
output_queue = self._context.subscription_callback_result_queue.get(self._event_name,
503+
None)
504+
if output_queue is None:
505+
output_queue = queue.SimpleQueue()
506+
self._context.subscription_callback_result_queue[self._event_name] = output_queue
507+
508+
while not output_queue.empty():
509+
output_queue.get(block=False)
510+
511+
subscription_handler = EventChangeAccumulator(self.label, self._request_object, output_queue)
512+
513+
subscription.SetEventUpdateCallback(subscription_handler)
514+
515+
events = subscription.GetEvents()
516+
response = EventResponse(event_result_list=events)
517+
return _ActionResult(status=_ActionStatus.SUCCESS, response=response)
518+
519+
385520
class WriteAttributeAction(BaseAction):
386521
'''Single write attribute action to be executed.'''
387522

@@ -462,9 +597,15 @@ def __init__(self, test_step, context: _ExecutionContext):
462597
UnexpectedParsingError: Raised if the expected queue does not exist.
463598
'''
464599
super().__init__(test_step)
465-
self._attribute_name = stringcase.pascalcase(test_step.attribute)
466-
self._output_queue = context.subscription_callback_result_queue.get(self._attribute_name,
467-
None)
600+
if test_step.attribute is not None:
601+
queue_name = stringcase.pascalcase(test_step.attribute)
602+
elif test_step.event is not None:
603+
queue_name = stringcase.pascalcase(test_step.event)
604+
else:
605+
raise UnexpectedParsingError(
606+
f'WaitForReport needs to wait on either attribute or event, neither were provided')
607+
608+
self._output_queue = context.subscription_callback_result_queue.get(queue_name, None)
468609
if self._output_queue is None:
469610
raise UnexpectedParsingError(f'Could not find output queue')
470611

@@ -477,6 +618,8 @@ def run_action(self, dev_ctrl: ChipDeviceController) -> _ActionResult:
477618
except queue.Empty:
478619
return _ActionResult(status=_ActionStatus.ERROR, response=None)
479620

621+
if isinstance(item, _AttributeSubscriptionCallbackResult):
622+
return item.result
480623
return item.result
481624

482625

@@ -621,14 +764,15 @@ def _attribute_read_action_factory(self, test_step, cluster: str):
621764
'cluster': Name of cluster read attribute action is targeting.
622765
Returns:
623766
ReadAttributeAction if 'test_step' is a valid read attribute to be executed.
624-
None if we were unable to use the provided 'test_step' for a known reason that is not
625-
fatal to test execution.
626767
'''
627768
try:
628769
return ReadAttributeAction(test_step, cluster, self._context)
629770
except ParsingError:
630771
return None
631772

773+
def _event_read_action_factory(self, test_step, cluster: str):
774+
return ReadEventAction(test_step, cluster, self._context)
775+
632776
def _attribute_subscribe_action_factory(self, test_step, cluster: str):
633777
'''Creates subscribe attribute command from TestStep provided.
634778
@@ -648,6 +792,17 @@ def _attribute_subscribe_action_factory(self, test_step, cluster: str):
648792
# propogated.
649793
return None
650794

795+
def _attribute_subscribe_event_factory(self, test_step, cluster: str):
796+
'''Creates subscribe event command from TestStep provided.
797+
798+
Args:
799+
'test_step': Step containing information required to run subscribe attribute action.
800+
'cluster': Name of cluster write attribute action is targeting.
801+
Returns:
802+
SubscribeEventAction if 'test_step' is a valid subscribe attribute to be executed.
803+
'''
804+
return SubscribeEventAction(test_step, cluster, self._context)
805+
651806
def _attribute_write_action_factory(self, test_step, cluster: str):
652807
'''Creates write attribute command TestStep.
653808
@@ -712,11 +867,11 @@ def encode(self, request) -> BaseAction:
712867
elif command == 'readAttribute':
713868
action = self._attribute_read_action_factory(request, cluster)
714869
elif command == 'readEvent':
715-
# TODO need to implement _event_read_action_factory
716-
# action = self._event_read_action_factory(request, cluster)
717-
pass
870+
action = self._event_read_action_factory(request, cluster)
718871
elif command == 'subscribeAttribute':
719872
action = self._attribute_subscribe_action_factory(request, cluster)
873+
elif command == 'subscribeEvent':
874+
action = self._attribute_subscribe_event_factory(request, cluster)
720875
elif command == 'waitForReport':
721876
action = self._wait_for_report_action_factory(request)
722877
else:
@@ -779,6 +934,29 @@ def decode(self, result: _ActionResult):
779934
}
780935
return decoded_response
781936

937+
if isinstance(response, EventResponse):
938+
if not response.event_result_list:
939+
# This means that the event result we got back was empty, below is how we
940+
# represent this.
941+
decoded_response = [{}]
942+
return decoded_response
943+
decoded_response = []
944+
for event in response.event_result_list:
945+
if event.Status != chip.interaction_model.Status.Success:
946+
error_message = stringcase.snakecase(event.Status.name).upper()
947+
decoded_response.append({'error': error_message})
948+
continue
949+
cluster_id = event.Header.ClusterId
950+
cluster_name = self._test_spec_definition.get_cluster_name(cluster_id)
951+
event_id = event.Header.EventId
952+
event_name = self._test_spec_definition.get_event_name(cluster_id, event_id)
953+
event_definition = self._test_spec_definition.get_event_by_name(cluster_name, event_name)
954+
is_fabric_scoped = bool(event_definition.is_fabric_sensitive)
955+
decoded_event = Converter.from_data_model_to_test_definition(
956+
self._test_spec_definition, cluster_name, event_definition.fields, event.Data, is_fabric_scoped)
957+
decoded_response.append({'value': decoded_event})
958+
return decoded_response
959+
782960
if isinstance(response, ChipStackError):
783961
decoded_response['error'] = 'FAILURE'
784962
return decoded_response

0 commit comments

Comments
 (0)