From e328965ae04a084a6f0e8698ea216f50ac860ddc Mon Sep 17 00:00:00 2001 From: Vivien Nicolas Date: Tue, 16 May 2023 17:49:58 +0200 Subject: [PATCH] [matter_yaml] Add a yaml test that exercise anyOf, EqualityCommands and runIf (#26567) * [matter_yamltests] Add anyOf constraint supports * [matter_yamltest] Add EqualityCommands pseudo cluster * [matter_yamltest] Add a runIf keyword to enable/disable a step based on a boolean variable * Add a yaml test that exercise anyOf, EqualityCommands and runIf --- .../matter_yamltests/constraints.py | 23 +++++ .../matter_yamltests/parser.py | 5 +- .../clusters/equality_commands.py | 71 ++++++++++++++ .../pseudo_clusters/pseudo_clusters.py | 2 + .../matter_yamltests/yaml_loader.py | 4 +- scripts/tests/chiptest/__init__.py | 1 + src/app/tests/suites/TestEqualities.yaml | 96 +++++++++++++++++++ 7 files changed, 200 insertions(+), 2 deletions(-) create mode 100644 scripts/py_matter_yamltests/matter_yamltests/pseudo_clusters/clusters/equality_commands.py create mode 100644 src/app/tests/suites/TestEqualities.yaml diff --git a/scripts/py_matter_yamltests/matter_yamltests/constraints.py b/scripts/py_matter_yamltests/matter_yamltests/constraints.py index 65e11b15db9cb8..fb0ecd8b99d432 100644 --- a/scripts/py_matter_yamltests/matter_yamltests/constraints.py +++ b/scripts/py_matter_yamltests/matter_yamltests/constraints.py @@ -114,6 +114,11 @@ def __init__(self, context, reason): super().__init__(context, 'notValue', reason) +class ConstraintAnyOfError(ConstraintCheckError): + def __init__(self, context, reason): + super().__init__(context, 'anyOf', reason) + + class BaseConstraint(ABC): '''Constraint Interface''' @@ -185,6 +190,8 @@ def _raise_error(self, reason): raise ConstraintMaxValueError(self._context, reason) elif isinstance(self, _ConstraintNotValue): raise ConstraintNotValueError(self._context, reason) + elif isinstance(self, _ConstraintAnyOf): + raise ConstraintAnyOfError(self._context, reason) else: # This should not happens. raise ConstraintParseError(f'Unknown constraint instance.') @@ -740,6 +747,18 @@ def get_reason(self, value, value_type_name) -> str: return f'The response value "{value}" should differs from the constraint.' +class _ConstraintAnyOf(BaseConstraint): + def __init__(self, context, any_of): + super().__init__(context, types=[], is_null_allowed=True) + self._any_of = any_of + + def check_response(self, value, value_type_name) -> bool: + return value in self._any_of + + def get_reason(self, value, value_type_name) -> str: + return f'The response value "{value}" is not a value from {self._any_of}.' + + def get_constraints(constraints: dict) -> list[BaseConstraint]: _constraints = [] context = constraints @@ -792,6 +811,9 @@ def get_constraints(constraints: dict) -> list[BaseConstraint]: elif 'notValue' == constraint: _constraints.append(_ConstraintNotValue( context, constraint_value)) + elif 'anyOf' == constraint: + _constraints.append(_ConstraintAnyOf( + context, constraint_value)) else: raise ConstraintParseError(f'Unknown constraint type:{constraint}') @@ -816,6 +838,7 @@ def is_typed_constraint(constraint: str): 'hasMasksSet': False, 'hasMasksClear': False, 'notValue': True, + 'anyOf': True, } is_typed = constraints.get(constraint) diff --git a/scripts/py_matter_yamltests/matter_yamltests/parser.py b/scripts/py_matter_yamltests/matter_yamltests/parser.py index 33ace02d5aca8c..c6c84740a9c868 100644 --- a/scripts/py_matter_yamltests/matter_yamltests/parser.py +++ b/scripts/py_matter_yamltests/matter_yamltests/parser.py @@ -192,6 +192,7 @@ def __init__(self, test: dict, config: dict, definitions: SpecDefinitions, pics_ self.busy_wait_ms = _value_or_none(test, 'busyWaitMs') self.wait_for = _value_or_none(test, 'wait') self.event_number = _value_or_none(test, 'eventNumber') + self.run_if = _value_or_none(test, 'runIf') self.is_attribute = self.__is_attribute_command() self.is_event = self.__is_event_command() @@ -477,6 +478,8 @@ def __init__(self, test: _TestStepWithPlaceholders, step_index: int, runtime_con self._update_placeholder_values(self.responses) self._test.node_id = self._config_variable_substitution( self._test.node_id) + self._test.run_if = self._config_variable_substitution( + self._test.run_if) self._test.event_number = self._config_variable_substitution( self._test.event_number) test.update_arguments(self.arguments) @@ -492,7 +495,7 @@ def is_enabled(self): @property def is_pics_enabled(self): - return self._test.is_pics_enabled + return self._test.is_pics_enabled and (self._test.run_if is None or self._test.run_if) @property def is_attribute(self): diff --git a/scripts/py_matter_yamltests/matter_yamltests/pseudo_clusters/clusters/equality_commands.py b/scripts/py_matter_yamltests/matter_yamltests/pseudo_clusters/clusters/equality_commands.py new file mode 100644 index 00000000000000..30f6c80b8dea0f --- /dev/null +++ b/scripts/py_matter_yamltests/matter_yamltests/pseudo_clusters/clusters/equality_commands.py @@ -0,0 +1,71 @@ +# +# Copyright (c) 2023 Project CHIP Authors +# +# Licensed under the Apache License, Version 2.0 (the 'License'); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an 'AS IS' BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ..pseudo_cluster import PseudoCluster + +_DEFINITION = ''' + + + EqualityCommands + 0xFFF1FD08 + + + + + + + + + + + + + + + + + + + + + +''' + + +def Compare(request): + value1 = None + value2 = None + + for argument in request.arguments['values']: + if argument['name'] == 'Value1': + value1 = argument['value'] + elif argument['name'] == 'Value2': + value2 = argument['value'] + + return value1 == value2 + + +class EqualityCommands(PseudoCluster): + name = 'EqualityCommands' + definition = _DEFINITION + + async def BooleanEquals(self, request): + return {'value': {'Equals': Compare(request)}} + + async def SignedNumberEquals(self, request): + return {'value': {'Equals': Compare(request)}} + + async def UnsignedNumberEquals(self, request): + return {'value': {'Equals': Compare(request)}} diff --git a/scripts/py_matter_yamltests/matter_yamltests/pseudo_clusters/pseudo_clusters.py b/scripts/py_matter_yamltests/matter_yamltests/pseudo_clusters/pseudo_clusters.py index 9255969fe9add0..4072d52bb8b62d 100644 --- a/scripts/py_matter_yamltests/matter_yamltests/pseudo_clusters/pseudo_clusters.py +++ b/scripts/py_matter_yamltests/matter_yamltests/pseudo_clusters/pseudo_clusters.py @@ -15,6 +15,7 @@ from .clusters.commissioner_commands import CommissionerCommands from .clusters.delay_commands import DelayCommands from .clusters.discovery_commands import DiscoveryCommands +from .clusters.equality_commands import EqualityCommands from .clusters.log_commands import LogCommands from .clusters.system_commands import SystemCommands from .pseudo_cluster import PseudoCluster @@ -51,6 +52,7 @@ def get_default_pseudo_clusters() -> PseudoClusters: CommissionerCommands(), DelayCommands(), DiscoveryCommands(), + EqualityCommands(), LogCommands(), SystemCommands() ] diff --git a/scripts/py_matter_yamltests/matter_yamltests/yaml_loader.py b/scripts/py_matter_yamltests/matter_yamltests/yaml_loader.py index 2332419c0d10ee..8664428197a41c 100644 --- a/scripts/py_matter_yamltests/matter_yamltests/yaml_loader.py +++ b/scripts/py_matter_yamltests/matter_yamltests/yaml_loader.py @@ -88,6 +88,7 @@ def __check_test_step(self, content): 'label': str, 'identity': str, 'nodeId': int, + 'runIf': str, # Should be a variable. 'groupId': int, 'endpoint': int, 'cluster': str, @@ -194,7 +195,8 @@ def __check_test_step_response_value_constraints(self, content): 'excludes': list, 'hasMasksSet': list, 'hasMasksClear': list, - 'notValue': (type(None), bool, str, int, float, list, dict) + 'notValue': (type(None), bool, str, int, float, list, dict), + 'anyOf': list } self.__check(content, schema) diff --git a/scripts/tests/chiptest/__init__.py b/scripts/tests/chiptest/__init__.py index b537f681091781..bb76e4254c77a5 100644 --- a/scripts/tests/chiptest/__init__.py +++ b/scripts/tests/chiptest/__init__.py @@ -135,6 +135,7 @@ def _GetInDevelopmentTests() -> Set[str]: """ return { "Test_AddNewFabricFromExistingFabric.yaml", # chip-repl does not support GetCommissionerRootCertificate and IssueNocChain command + "TestEqualities.yaml", # chip-repl does not support pseudo-cluster commands that return a value "TestClientMonitoringCluster.yaml" # Client Monitoring Tests need a rework after the XML update } diff --git a/src/app/tests/suites/TestEqualities.yaml b/src/app/tests/suites/TestEqualities.yaml new file mode 100644 index 00000000000000..0bbddb3cbfb0ef --- /dev/null +++ b/src/app/tests/suites/TestEqualities.yaml @@ -0,0 +1,96 @@ +# Copyright (c) 2023 Project CHIP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +name: Test for conditional steps using the runIf keyword + +config: + nodeId: 0x12344321 + cluster: "EqualityCommands" + endpoint: 1 + expectedValue: 20003 + unexpectedValue: 20004 + +tests: + - label: "Wait for the commissioned device to be retrieved" + cluster: "DelayCommands" + command: "WaitForCommissionee" + arguments: + values: + - name: "nodeId" + value: nodeId + + - label: "Send a command with a vendor_id and enum" + cluster: "Unit Testing" + command: "TestEnumsRequest" + arguments: + values: + - name: "arg1" + value: expectedValue + - name: "arg2" + value: 1 + response: + values: + - name: "arg1" + saveAs: Arg1Value + constraints: + anyOf: [expectedValue, unexpectedValue] + + - label: + "Compute the result of comparing Arg1Value to 20003 and save the + result as a variable for later use" + cluster: "EqualityCommands" + command: "UnsignedNumberEquals" + arguments: + values: + - name: "Value1" + value: Arg1Value + - name: "Value2" + value: expectedValue + response: + - values: + - name: "Equals" + value: true + saveAs: IsExpectedValue + + - label: + "Compute the result of comparing Arg1Value to 20004 and save the + result as a variable for later use" + cluster: "EqualityCommands" + command: "UnsignedNumberEquals" + arguments: + values: + - name: "Value1" + value: Arg1Value + - name: "Value2" + value: unexpectedValue + response: + - values: + - name: "Equals" + value: false + saveAs: IsUnexpectedValue + + - label: "Use runIf to skip this step" + runIf: IsUnexpectedValue + cluster: "EqualityCommands" + command: "BooleanEquals" + arguments: + values: + - name: "Value1" + value: true + - name: "Value2" + value: false + response: + - values: + - name: "Equals" + value: true # 'true' such that if the step is executed it will result in an error.