From 5511621f4a643c722a0c7e518e5fdf0be22c7ec3 Mon Sep 17 00:00:00 2001 From: cecille Date: Mon, 13 May 2024 10:25:01 -0400 Subject: [PATCH] Conformance: add start of arithmetic conformance https://github.com/CHIP-Specifications/connectedhomeip-spec/pull/7808 represents the first arithmetic conformance operation. Adding a new conformance class to match this. Currently this just returns optional to match the current behaviour, but this will be changed in an upcoming PR (see https://github.com/project-chip/connectedhomeip/issues/33422) However, thet change requires that we assess the conformance based on the attribute values, which we do pass in right now. This therefore requires a bit of an API change on the conformance classes and I've opted to address this in a separate PR to keep the changes compact. --- src/python_testing/TestConformanceSupport.py | 57 +++++++++++++++++++- src/python_testing/conformance_support.py | 41 +++++++++++++- 2 files changed, 95 insertions(+), 3 deletions(-) diff --git a/src/python_testing/TestConformanceSupport.py b/src/python_testing/TestConformanceSupport.py index b383f1acb63cc7..f71eb3ed1d531f 100644 --- a/src/python_testing/TestConformanceSupport.py +++ b/src/python_testing/TestConformanceSupport.py @@ -17,7 +17,7 @@ import xml.etree.ElementTree as ElementTree -from conformance_support import ConformanceDecision, ConformanceParseParameters, parse_callable_from_xml +from conformance_support import ConformanceDecision, ConformanceException, ConformanceParseParameters, parse_callable_from_xml from matter_testing_support import MatterBaseTest, async_test_body, default_matter_test_main from mobly import asserts @@ -616,6 +616,61 @@ def test_conformance_otherwise(self): asserts.assert_equal(xml_callable(f, [], []), ConformanceDecision.PROVISIONAL) asserts.assert_equal(str(xml_callable), 'AB & !CD, P') + def test_conformance_greater(self): + # AB, [CD] + xml = ('' + '' + '' + '' + '' + '') + et = ElementTree.fromstring(xml) + xml_callable = parse_callable_from_xml(et, self.params) + # TODO: switch this to check greater than once the update to the base is done (#33422) + asserts.assert_equal(xml_callable(0x00, [], []), ConformanceDecision.OPTIONAL) + asserts.assert_equal(str(xml_callable), 'attr1 > 1') + + # Ensure that we can only have greater terms with exactly 2 value + xml = ('' + '' + '' + '' + '' + '' + '') + et = ElementTree.fromstring(xml) + try: + xml_callable = parse_callable_from_xml(et, self.params) + asserts.fail("Incorrectly parsed bad greaterTerm XML with > 2 values") + except ConformanceException: + pass + + xml = ('' + '' + '' + '' + '') + et = ElementTree.fromstring(xml) + try: + xml_callable = parse_callable_from_xml(et, self.params) + asserts.fail("Incorrectly parsed bad greaterTerm XML with < 2 values") + except ConformanceException: + pass + + # Only attributes and literals allowed because arithmetic operations require values + xml = ('' + '' + '' + '' + '' + '') + et = ElementTree.fromstring(xml) + try: + xml_callable = parse_callable_from_xml(et, self.params) + asserts.fail("Incorrectly parsed greater term with feature value") + except ConformanceException: + pass + if __name__ == "__main__": default_matter_test_main() diff --git a/src/python_testing/conformance_support.py b/src/python_testing/conformance_support.py index 89157c226351b1..29f81947a094a6 100644 --- a/src/python_testing/conformance_support.py +++ b/src/python_testing/conformance_support.py @@ -33,10 +33,12 @@ AND_TERM = 'andTerm' OR_TERM = 'orTerm' NOT_TERM = 'notTerm' +GREATER_TERM = 'greaterTerm' FEATURE_TAG = 'feature' ATTRIBUTE_TAG = 'attribute' COMMAND_TAG = 'command' CONDITION_TAG = 'condition' +LITERAL_TAG = 'literal' class ConformanceException(Exception): @@ -123,6 +125,18 @@ def __str__(self): return 'P' +class literal: + def __init__(self, value: str): + self.value = int(value) + + def __call__(self): + # This should never be called + raise ConformanceException(f'Literal conformance function should not be called - this is simply a value holder') + + def __str__(self): + return str(self.value) + + class feature: def __init__(self, requiredFeature: uint, code: str): self.requiredFeature = requiredFeature @@ -267,8 +281,25 @@ def __str__(self): op_strs = [str(op) for op in self.op_list] return f'({" | ".join(op_strs)})' -# TODO: add xor operation once it's required -# TODO: how would equal and unequal operations work here? + +class greater_operation: + def _type_ok(self, op: Callable): + return type(op) == attribute or type(op) == literal + + def __init__(self, op1: Callable, op2: Callable): + if not self._type_ok(op1) or not self._type_ok(op2): + raise ConformanceException('Arithmetic operations can only have attribute or literal value children') + self.op1 = op1 + self.op2 = op2 + + def __call__(self, feature_map: uint, attribute_list: list[uint], all_command_list: list[uint]) -> ConformanceDecision: + # For now, this is fully optional, need to implement this properly later, but it requires access to the actual attribute values + # We need to reach into the attribute, but can't use it directly because the attribute callable is an EXISTENCE check and + # the arithmetic functions require a value. + return ConformanceDecision.OPTIONAL + + def __str__(self): + return f'{str(self.op1)} > {str(self.op2)}' class otherwise: @@ -325,6 +356,8 @@ def parse_callable_from_xml(element: ElementTree.Element, params: ConformancePar return command(params.command_map[element.get('name')], element.get('name')) elif element.tag == CONDITION_TAG and element.get('name').lower() == 'zigbee': return zigbee() + elif element.tag == LITERAL_TAG: + return literal(element.get('value')) else: raise ConformanceException( f'Unexpected xml conformance element with no children {str(element.tag)} {str(element.attrib)}') @@ -354,5 +387,9 @@ def parse_callable_from_xml(element: ElementTree.Element, params: ConformancePar return not_operation(ops[0]) elif element.tag == OTHERWISE_CONFORM: return otherwise(ops) + elif element.tag == GREATER_TERM: + if len(ops) != 2: + raise ConformanceException(f'Greater than term found with more than two subelements {list(element)}') + return greater_operation(ops[0], ops[1]) else: raise ConformanceException(f'Unexpected conformance tag with children {element}')