diff --git a/src/controller/python/chip/yaml/runner.py b/src/controller/python/chip/yaml/runner.py index 71ff37531943f9..895fb79d10256c 100644 --- a/src/controller/python/chip/yaml/runner.py +++ b/src/controller/python/chip/yaml/runner.py @@ -27,10 +27,16 @@ import stringcase from chip import ChipDeviceCtrl from chip.clusters.Attribute import AttributeStatus, SubscriptionTransaction, TypedAttributePath, ValueDecodeFailure +from chip.exceptions import ChipStackError from chip.yaml.errors import ParsingError, UnexpectedParsingError +from matter_yamltests.pseudo_clusters.clusters.delay_commands import DelayCommands +from matter_yamltests.pseudo_clusters.clusters.log_commands import LogCommands +from matter_yamltests.pseudo_clusters.clusters.system_commands import SystemCommands +from matter_yamltests.pseudo_clusters.pseudo_clusters import PseudoClusters from .data_model_lookup import * +_PSEUDO_CLUSTERS = PseudoClusters([DelayCommands(), LogCommands(), SystemCommands()]) logger = logging.getLogger('YamlParser') @@ -96,6 +102,18 @@ def run_action(self, dev_ctrl: ChipDeviceCtrl) -> _ActionResult: pass +class DefaultPseudoCluster(BaseAction): + def __init__(self, test_step): + super().__init__(test_step) + self._test_step = test_step + if not _PSEUDO_CLUSTERS.supports(test_step): + raise ParsingError(f'Default cluster {test_step.cluster} {test_step.command}, not supported') + + def run_action(self, dev_ctrl: ChipDeviceCtrl) -> _ActionResult: + resp = asyncio.run(_PSEUDO_CLUSTERS.execute(self._test_step)) + return _ActionResult(status=_ActionStatus.SUCCESS, response=None) + + class InvokeAction(BaseAction): '''Single invoke action to be executed.''' @@ -213,6 +231,13 @@ def run_action(self, dev_ctrl: ChipDeviceCtrl) -> _ActionResult: fabricFiltered=self._fabric_filtered)) except chip.interaction_model.InteractionModelError as error: return _ActionResult(status=_ActionStatus.ERROR, response=error) + except ChipStackError as error: + _CHIP_TIMEOUT_ERROR = 50 + if error.err == _CHIP_TIMEOUT_ERROR: + return _ActionResult(status=_ActionStatus.ERROR, response=error) + # For now it is unsure if all ChipStackError are supposed to be intentional. + # As a result we simply re-raise the error. + raise error return self.parse_raw_response(raw_resp) @@ -253,8 +278,13 @@ def __init__(self, test_step): args = test_step.arguments['values'] request_data_as_dict = Converter.convert_list_of_name_value_pair_to_dict(args) + # There's a chance the commissionee may have rebooted before this call here as part of a + # test flow or is just starting out fresh outright. Unless expireExistingSession is + # explicitly set, the default behaviour it to make sure we're not re-using any cached CASE + # sessions that will now be stale and mismatched with the peer, causing subsequent + # interactions to fail. + self._expire_existing_session = request_data_as_dict.get('expireExistingSession', True) self._node_id = request_data_as_dict['nodeId'] - self._expire_existing_session = request_data_as_dict.get('expireExistingSession', False) if 'timeout' in request_data_as_dict: # Timeout is provided in seconds we need to conver to milliseconds. self._timeout_ms = request_data_as_dict['timeout'] * 1000 @@ -579,6 +609,12 @@ def _commissioner_command_action_factory(self, test_step): except ParsingError: return None + def _default_pseudo_cluster(self, test_step): + try: + return DefaultPseudoCluster(test_step) + except ParsingError: + return None + def encode(self, request) -> BaseAction: action = None cluster = request.cluster.replace(' ', '').replace('/', '') @@ -605,6 +641,10 @@ def encode(self, request) -> BaseAction: else: action = self._invoke_action_factory(request, cluster) + if action is None: + # Now we try to create a default pseudo cluster. + action = self._default_pseudo_cluster(request) + if action is None: logger.warn(f"Failed to parse {request.label}") return action @@ -628,6 +668,10 @@ def decode(self, result: _ActionResult): decoded_response['error'] = stringcase.snakecase(response.name).upper() return decoded_response + if isinstance(response, ChipStackError): + decoded_response['error'] = 'FAILURE' + return decoded_response + cluster_name = self._test_spec_definition.get_cluster_name(response.cluster_id) if cluster_name is None: raise Exception("Cannot find cluster name for id 0x%0X / %d" % (response.cluster_id, response.cluster_id))