From e8f516a1e2b9528c97227d06c63a7b9034a0b701 Mon Sep 17 00:00:00 2001 From: Boris Zbarsky Date: Wed, 11 Oct 2023 10:24:41 -0400 Subject: [PATCH] Add support for contains/excludes for struct types. (#29643) Also makes it possible to use a variable for nodeId. --- .../matter_yamltests/constraints.py | 56 +++++++++-- .../matter_yamltests/parser.py | 2 + .../matter_yamltests/yaml_loader.py | 2 +- .../py_matter_yamltests/test_yaml_loader.py | 18 +++- .../Test_AddNewFabricFromExistingFabric.yaml | 95 ++++++++++++++++++- .../certification/Test_TC_WAKEONLAN_1_5.yaml | 4 +- .../zap-generated/test/Commands.h | 4 +- 7 files changed, 162 insertions(+), 19 deletions(-) diff --git a/scripts/py_matter_yamltests/matter_yamltests/constraints.py b/scripts/py_matter_yamltests/matter_yamltests/constraints.py index 88b0d518393e51..69a04b190f6fe5 100644 --- a/scripts/py_matter_yamltests/matter_yamltests/constraints.py +++ b/scripts/py_matter_yamltests/matter_yamltests/constraints.py @@ -689,20 +689,53 @@ def get_reason(self, value, value_type_name) -> str: return f'The response value ({value}) should be lower or equal to the constraint but {value} > {self._max_value}.' +def _values_match(expected_value, received_value): + # TODO: This is a copy of _response_value_validation over in parser.py, + # but with the recursive calls renamed. + if isinstance(expected_value, list): + if len(expected_value) != len(received_value): + return False + + for index, expected_item in enumerate(expected_value): + received_item = received_value[index] + if not _values_match(expected_item, received_item): + return False + return True + elif isinstance(expected_value, dict): + for key, expected_item in expected_value.items(): + received_item = received_value.get(key) + if not _values_match(expected_item, received_item): + return False + return True + else: + return expected_value == received_value + + class _ConstraintContains(BaseConstraint): def __init__(self, context, contains): super().__init__(context, types=[list]) self._contains = contains + def _find_missing_values(self, expected_values, received_values): + # Make a copy of received_values, so that we can remove things from the + # list as they match up against our expected values. + received_values = list(received_values) + missing_values = [] + for expected_value in expected_values: + for index, received_value in enumerate(received_values): + if _values_match(expected_value, received_value): + # We've used up this received value + del received_values[index] + break + else: + missing_values.append(expected_value) + return missing_values + def check_response(self, value, value_type_name) -> bool: - return set(self._contains).issubset(value) + return len(self._find_missing_values(self._contains, value)) == 0 def get_reason(self, value, value_type_name) -> str: - expected_values = [] - - for expected_value in self._contains: - if expected_value not in value: - expected_values.append(expected_value) + expected_values = self._find_missing_values(self._contains, value) return f'The response ({value}) is missing {expected_values}.' @@ -713,14 +746,19 @@ def __init__(self, context, excludes): self._excludes = excludes def check_response(self, value, value_type_name) -> bool: - return set(self._excludes).isdisjoint(value) + for expected_value in self._excludes: + for received_value in value: + if _values_match(expected_value, received_value): + return False + return True def get_reason(self, value, value_type_name) -> str: unexpected_values = [] for unexpected_value in self._excludes: - if unexpected_value in value: - unexpected_values.append(unexpected_value) + for received_value in value: + if _values_match(unexpected_value, received_value): + unexpected_values.append(unexpected_value) return f'The response ({value}) contains {unexpected_values}.' diff --git a/scripts/py_matter_yamltests/matter_yamltests/parser.py b/scripts/py_matter_yamltests/matter_yamltests/parser.py index ec4462b4359dd3..01898641a1591f 100644 --- a/scripts/py_matter_yamltests/matter_yamltests/parser.py +++ b/scripts/py_matter_yamltests/matter_yamltests/parser.py @@ -595,6 +595,8 @@ def __init__(self, test: _TestStepWithPlaceholders, step_index: int, runtime_con self._test.endpoint) self._test.group_id = self._config_variable_substitution( self._test.group_id) + self._test.node_id = self._config_variable_substitution( + self._test.node_id) test.update_arguments(self.arguments) test.update_responses(self.responses) diff --git a/scripts/py_matter_yamltests/matter_yamltests/yaml_loader.py b/scripts/py_matter_yamltests/matter_yamltests/yaml_loader.py index aba73157e1aa9f..01b9e040f3efd8 100644 --- a/scripts/py_matter_yamltests/matter_yamltests/yaml_loader.py +++ b/scripts/py_matter_yamltests/matter_yamltests/yaml_loader.py @@ -89,7 +89,7 @@ def __check_test_step(self, config: dict, content): schema = { 'label': str, 'identity': str, - 'nodeId': int, + 'nodeId': (int, str), # Can be a variable. 'runIf': str, # Should be a variable. 'groupId': (int, str), # Can be a variable. 'endpoint': (int, str), # Can be a variable diff --git a/scripts/py_matter_yamltests/test_yaml_loader.py b/scripts/py_matter_yamltests/test_yaml_loader.py index 41929d95747306..a1739faf49cb58 100644 --- a/scripts/py_matter_yamltests/test_yaml_loader.py +++ b/scripts/py_matter_yamltests/test_yaml_loader.py @@ -246,7 +246,6 @@ def test_key_tests_step_int_keys(self): content = ('tests:\n' ' - {key}: {value}') keys = [ - 'nodeId', 'minInterval', 'maxInterval', 'timedInteractionTimeoutMs', @@ -340,6 +339,23 @@ def test_key_tests_step_group_id_key(self): x = content.format(value=value) self.assertRaises(TestStepInvalidTypeError, load, x) + def test_key_tests_step_node_id_key(self): + load = YamlLoader().load + + content = ('tests:\n' + ' - nodeId: {value}') + + _, _, _, _, tests = load(content.format(value=1)) + self.assertEqual(tests, [{'nodeId': 1}]) + + _, _, _, _, tests = load(content.format(value='TestKey')) + self.assertEqual(tests, [{'nodeId': 'TestKey'}]) + + wrong_values = self._get_wrong_values([str, int], spaces=6) + for value in wrong_values: + x = content.format(value=value) + self.assertRaises(TestStepInvalidTypeError, load, x) + def test_key_tests_step_event_number_key(self): load = YamlLoader().load diff --git a/src/app/tests/suites/Test_AddNewFabricFromExistingFabric.yaml b/src/app/tests/suites/Test_AddNewFabricFromExistingFabric.yaml index 9890afb851586c..c92eea094e3a79 100644 --- a/src/app/tests/suites/Test_AddNewFabricFromExistingFabric.yaml +++ b/src/app/tests/suites/Test_AddNewFabricFromExistingFabric.yaml @@ -17,6 +17,9 @@ name: Test config: nodeId: 0x12344321 endpoint: 0 + nodeId2: + type: node_id + defaultValue: 0x43211234 tests: - label: "Wait for the alpha device to be retrieved " @@ -91,7 +94,7 @@ tests: - name: "Elements" value: NOCSRElements - name: "nodeId" - value: 0x43211234 + value: nodeId2 response: values: - name: "NOC" @@ -130,11 +133,27 @@ tests: value: 0 - label: "Send Commissioning Complete command from beta" - nodeId: 0x43211234 + nodeId: nodeId2 identity: "beta" cluster: "General Commissioning" command: "CommissioningComplete" + - label: "Read alpha fabric index" + command: "readAttribute" + cluster: "Operational Credentials" + attribute: "CurrentFabricIndex" + response: + saveAs: AlphaFabricIndex + + - label: "Read beta fabric index" + identity: "beta" + nodeId: nodeId2 + command: "readAttribute" + cluster: "Operational Credentials" + attribute: "CurrentFabricIndex" + response: + saveAs: BetaFabricIndex + - label: "Read the fabrics list again from alpha" command: "readAttribute" cluster: "Operational Credentials" @@ -144,13 +163,81 @@ tests: constraints: type: list + - label: "Read the fabrics list again from alpha without fabric-filtering" + command: "readAttribute" + cluster: "Operational Credentials" + attribute: "Fabrics" + fabricFiltered: false + response: + value: + [ + { Label: "", NodeID: nodeId, FabricIndex: AlphaFabricIndex }, + { Label: "", NodeID: nodeId2, FabricIndex: BetaFabricIndex }, + ] + constraints: + type: list + + - label: + "Read the fabrics list again from alpha without fabric-filtering, + checking contains constraint" + command: "readAttribute" + cluster: "Operational Credentials" + attribute: "Fabrics" + fabricFiltered: false + response: + constraints: + type: list + # Flip the order to test the constraint better. + contains: + [ + { + Label: "", + NodeID: nodeId2, + FabricIndex: BetaFabricIndex, + }, + { + Label: "", + NodeID: nodeId, + FabricIndex: AlphaFabricIndex, + }, + ] + excludes: + [ + { + Label: "", + NodeID: nodeId, + FabricIndex: BetaFabricIndex, + }, + { + Label: "", + NodeID: nodeId2, + FabricIndex: AlphaFabricIndex, + }, + ] + - label: "Read the fabrics list from beta this time" identity: "beta" - nodeId: 0x43211234 + nodeId: nodeId2 + command: "readAttribute" + cluster: "Operational Credentials" + attribute: "Fabrics" + response: + value: [{ Label: "", NodeID: nodeId2 }] + constraints: + type: list + + - label: "Read the fabrics list from beta without fabric-filtering" + identity: "beta" + nodeId: nodeId2 command: "readAttribute" cluster: "Operational Credentials" attribute: "Fabrics" + fabricFiltered: false response: - value: [{ Label: "", NodeID: 0x43211234 }] + value: + [ + { Label: "", NodeID: nodeId, FabricIndex: AlphaFabricIndex }, + { Label: "", NodeID: nodeId2, FabricIndex: BetaFabricIndex }, + ] constraints: type: list diff --git a/src/app/tests/suites/certification/Test_TC_WAKEONLAN_1_5.yaml b/src/app/tests/suites/certification/Test_TC_WAKEONLAN_1_5.yaml index 6f7a235bf0922b..49dda66b14df29 100644 --- a/src/app/tests/suites/certification/Test_TC_WAKEONLAN_1_5.yaml +++ b/src/app/tests/suites/certification/Test_TC_WAKEONLAN_1_5.yaml @@ -56,7 +56,7 @@ tests: response: constraints: type: list - contains: [65528, 65529, 65530, 65531, 65531, 65533] + contains: [65528, 65529, 65530, 65531, 65532, 65533] - label: "Step 3a: Read the global attribute: AttributeList" PICS: "!PICS_EVENT_LIST_ENABLED" @@ -65,7 +65,7 @@ tests: response: constraints: type: list - contains: [65528, 65529, 65531, 65531, 65533] + contains: [65528, 65529, 65531, 65532, 65533] - label: "Step 3b: Read the optional attribute(MACAddress) in AttributeList" PICS: WAKEONLAN.S.A0000 diff --git a/zzz_generated/darwin-framework-tool/zap-generated/test/Commands.h b/zzz_generated/darwin-framework-tool/zap-generated/test/Commands.h index 7548717b142ae3..88da0ea3ab8aaa 100644 --- a/zzz_generated/darwin-framework-tool/zap-generated/test/Commands.h +++ b/zzz_generated/darwin-framework-tool/zap-generated/test/Commands.h @@ -66735,7 +66735,7 @@ class Test_TC_WAKEONLAN_1_5 : public TestCommandBridge { VerifyOrReturn(CheckConstraintContains("attributeList", value, 65529UL)); VerifyOrReturn(CheckConstraintContains("attributeList", value, 65530UL)); VerifyOrReturn(CheckConstraintContains("attributeList", value, 65531UL)); - VerifyOrReturn(CheckConstraintContains("attributeList", value, 65531UL)); + VerifyOrReturn(CheckConstraintContains("attributeList", value, 65532UL)); VerifyOrReturn(CheckConstraintContains("attributeList", value, 65533UL)); NextTest(); @@ -66760,7 +66760,7 @@ class Test_TC_WAKEONLAN_1_5 : public TestCommandBridge { VerifyOrReturn(CheckConstraintContains("attributeList", value, 65528UL)); VerifyOrReturn(CheckConstraintContains("attributeList", value, 65529UL)); VerifyOrReturn(CheckConstraintContains("attributeList", value, 65531UL)); - VerifyOrReturn(CheckConstraintContains("attributeList", value, 65531UL)); + VerifyOrReturn(CheckConstraintContains("attributeList", value, 65532UL)); VerifyOrReturn(CheckConstraintContains("attributeList", value, 65533UL)); NextTest();