diff --git a/src/python_testing/TC_DeviceBasicComposition.py b/src/python_testing/TC_DeviceBasicComposition.py
index 1f5dc38813a975..51ab97b7fa1dcc 100644
--- a/src/python_testing/TC_DeviceBasicComposition.py
+++ b/src/python_testing/TC_DeviceBasicComposition.py
@@ -31,6 +31,7 @@
import chip.clusters.ClusterObjects
import chip.tlv
from chip.clusters.Attribute import ValueDecodeFailure
+from chip.tlv import uint
from conformance_support import ConformanceDecision, conformance_allowed
from matter_testing_support import (AttributePathLocation, ClusterPathLocation, CommandPathLocation, MatterBaseTest,
async_test_body, default_matter_test_main)
@@ -874,6 +875,14 @@ def test_DESC_2_2(self):
self.fail_current_test("Problems with tags lists")
def test_spec_conformance(self):
+ def conformance_str(conformance: Callable, feature_map: uint, feature_dict: dict[str, uint]) -> str:
+ codes = []
+ for mask, details in feature_dict.items():
+ if mask & feature_map:
+ codes.append(details.code)
+
+ return f'Conformance: {str(conformance)}, implemented features: {",".join(codes)}'
+
success = True
# TODO: provisional needs to be an input parameter
allow_provisional = True
@@ -921,7 +930,7 @@ def test_spec_conformance(self):
conformance_decision = xml_feature.conformance(feature_map, attribute_list, all_command_list)
if conformance_decision == ConformanceDecision.MANDATORY and feature_mask not in feature_masks:
self.record_error(self.get_test_name(), location=location,
- problem=f'Required feature with mask 0x{f:02x} is not present in feature map')
+ problem=f'Required feature with mask 0x{f:02x} is not present in feature map. {conformance_str(xml_feature.conformance, feature_map, clusters[cluster_id].features)}')
success = False
# Attribute conformance checking
@@ -940,14 +949,14 @@ def test_spec_conformance(self):
if not conformance_allowed(conformance_decision, allow_provisional):
location = AttributePathLocation(endpoint_id=endpoint_id, cluster_id=cluster_id, attribute_id=attribute_id)
self.record_error(self.get_test_name(), location=location,
- problem=f'Attribute 0x{attribute_id:02x} is included, but is disallowed by conformance')
+ problem=f'Attribute 0x{attribute_id:02x} is included, but is disallowed by conformance. {conformance_str(xml_attribute.conformance, feature_map, clusters[cluster_id].features)}')
success = False
for attribute_id, xml_attribute in clusters[cluster_id].attributes.items():
conformance_decision = xml_attribute.conformance(feature_map, attribute_list, all_command_list)
if conformance_decision == ConformanceDecision.MANDATORY and attribute_id not in cluster.keys():
location = AttributePathLocation(endpoint_id=endpoint_id, cluster_id=cluster_id, attribute_id=attribute_id)
self.record_error(self.get_test_name(), location=location,
- problem=f'Attribute 0x{attribute_id:02x} is required, but is not present on the DUT')
+ problem=f'Attribute 0x{attribute_id:02x} is required, but is not present on the DUT. {conformance_str(xml_attribute.conformance, feature_map, clusters[cluster_id].features)}')
success = False
def check_spec_conformance_for_commands(command_type: CommandType) -> bool:
@@ -971,14 +980,14 @@ def check_spec_conformance_for_commands(command_type: CommandType) -> bool:
conformance_decision = xml_command.conformance(feature_map, attribute_list, all_command_list)
if not conformance_allowed(conformance_decision, allow_provisional):
self.record_error(self.get_test_name(), location=location,
- problem=f'Command 0x{command_id:02x} is included, but disallowed by conformance')
+ problem=f'Command 0x{command_id:02x} is included, but disallowed by conformance. {conformance_str(xml_command.conformance, feature_map, clusters[cluster_id].features)}')
success = False
for command_id, xml_command in xml_commands_dict.items():
conformance_decision = xml_command.conformance(feature_map, attribute_list, all_command_list)
if conformance_decision == ConformanceDecision.MANDATORY and command_id not in command_list:
location = CommandPathLocation(endpoint_id=endpoint_id, cluster_id=cluster_id, command_id=command_id)
self.record_error(self.get_test_name(), location=location,
- problem=f'Command 0x{command_id:02x} is required, but is not present on the DUT')
+ problem=f'Command 0x{command_id:02x} is required, but is not present on the DUT. {conformance_str(xml_command.conformance, feature_map, clusters[cluster_id].features)}')
success = False
return success
diff --git a/src/python_testing/TestConformanceSupport.py b/src/python_testing/TestConformanceSupport.py
index 53f9e885ff9449..788f1025a52fa3 100644
--- a/src/python_testing/TestConformanceSupport.py
+++ b/src/python_testing/TestConformanceSupport.py
@@ -53,6 +53,7 @@ async def test_conformance_mandatory(self):
xml_callable = parse_callable_from_xml(et, self.params)
for f in self.feature_maps:
asserts.assert_equal(xml_callable(f, [], []), ConformanceDecision.MANDATORY)
+ asserts.assert_equal(str(xml_callable), 'M')
@async_test_body
async def test_conformance_optional(self):
@@ -61,6 +62,7 @@ async def test_conformance_optional(self):
xml_callable = parse_callable_from_xml(et, self.params)
for f in self.feature_maps:
asserts.assert_equal(xml_callable(f, [], []), ConformanceDecision.OPTIONAL)
+ asserts.assert_equal(str(xml_callable), 'O')
@async_test_body
async def test_conformance_disallowed(self):
@@ -69,12 +71,14 @@ async def test_conformance_disallowed(self):
xml_callable = parse_callable_from_xml(et, self.params)
for f in self.feature_maps:
asserts.assert_equal(xml_callable(f, [], []), ConformanceDecision.DISALLOWED)
+ asserts.assert_equal(str(xml_callable), 'X')
xml = ''
et = ElementTree.fromstring(xml)
xml_callable = parse_callable_from_xml(et, self.params)
for f in self.feature_maps:
asserts.assert_equal(xml_callable(f, [], []), ConformanceDecision.DISALLOWED)
+ asserts.assert_equal(str(xml_callable), 'D')
@async_test_body
async def test_conformance_provisional(self):
@@ -83,6 +87,7 @@ async def test_conformance_provisional(self):
xml_callable = parse_callable_from_xml(et, self.params)
for f in self.feature_maps:
asserts.assert_equal(xml_callable(f, [], []), ConformanceDecision.PROVISIONAL)
+ asserts.assert_equal(str(xml_callable), 'P')
@async_test_body
async def test_conformance_mandatory_on_condition(self):
@@ -96,6 +101,7 @@ async def test_conformance_mandatory_on_condition(self):
asserts.assert_equal(xml_callable(f, [], []), ConformanceDecision.MANDATORY)
else:
asserts.assert_equal(xml_callable(f, [], []), ConformanceDecision.NOT_APPLICABLE)
+ asserts.assert_equal(str(xml_callable), 'AB')
xml = (''
''
@@ -107,6 +113,7 @@ async def test_conformance_mandatory_on_condition(self):
asserts.assert_equal(xml_callable(f, [], []), ConformanceDecision.MANDATORY)
else:
asserts.assert_equal(xml_callable(f, [], []), ConformanceDecision.NOT_APPLICABLE)
+ asserts.assert_equal(str(xml_callable), 'CD')
# single attribute mandatory
xml = (''
@@ -119,6 +126,7 @@ async def test_conformance_mandatory_on_condition(self):
asserts.assert_equal(xml_callable(0x00, a, []), ConformanceDecision.MANDATORY)
else:
asserts.assert_equal(xml_callable(0x00, a, []), ConformanceDecision.NOT_APPLICABLE)
+ asserts.assert_equal(str(xml_callable), 'attr1')
xml = (''
''
@@ -130,6 +138,7 @@ async def test_conformance_mandatory_on_condition(self):
asserts.assert_equal(xml_callable(0x00, a, []), ConformanceDecision.MANDATORY)
else:
asserts.assert_equal(xml_callable(0x00, a, []), ConformanceDecision.NOT_APPLICABLE)
+ asserts.assert_equal(str(xml_callable), 'attr2')
# test command in optional and in boolean - this is the same as attribute essentially, so testing every permutation is overkill
@@ -146,6 +155,7 @@ async def test_conformance_optional_on_condition(self):
asserts.assert_equal(xml_callable(f, [], []), ConformanceDecision.OPTIONAL)
else:
asserts.assert_equal(xml_callable(f, [], []), ConformanceDecision.NOT_APPLICABLE)
+ asserts.assert_equal(str(xml_callable), '[AB]')
xml = (''
''
@@ -157,6 +167,7 @@ async def test_conformance_optional_on_condition(self):
asserts.assert_equal(xml_callable(f, [], []), ConformanceDecision.OPTIONAL)
else:
asserts.assert_equal(xml_callable(f, [], []), ConformanceDecision.NOT_APPLICABLE)
+ asserts.assert_equal(str(xml_callable), '[CD]')
# single attribute optional
xml = (''
@@ -169,6 +180,7 @@ async def test_conformance_optional_on_condition(self):
asserts.assert_equal(xml_callable(0x00, a, []), ConformanceDecision.OPTIONAL)
else:
asserts.assert_equal(xml_callable(0x00, a, []), ConformanceDecision.NOT_APPLICABLE)
+ asserts.assert_equal(str(xml_callable), '[attr1]')
xml = (''
''
@@ -180,6 +192,7 @@ async def test_conformance_optional_on_condition(self):
asserts.assert_equal(xml_callable(0x00, a, []), ConformanceDecision.OPTIONAL)
else:
asserts.assert_equal(xml_callable(0x00, a, []), ConformanceDecision.NOT_APPLICABLE)
+ asserts.assert_equal(str(xml_callable), '[attr2]')
# single command optional
xml = (''
@@ -192,6 +205,7 @@ async def test_conformance_optional_on_condition(self):
asserts.assert_equal(xml_callable(0x00, [], c), ConformanceDecision.OPTIONAL)
else:
asserts.assert_equal(xml_callable(0x00, [], c), ConformanceDecision.NOT_APPLICABLE)
+ asserts.assert_equal(str(xml_callable), '[cmd1]')
xml = (''
''
@@ -203,6 +217,7 @@ async def test_conformance_optional_on_condition(self):
asserts.assert_equal(xml_callable(0x00, [], c), ConformanceDecision.OPTIONAL)
else:
asserts.assert_equal(xml_callable(0x00, [], c), ConformanceDecision.NOT_APPLICABLE)
+ asserts.assert_equal(str(xml_callable), '[cmd2]')
@async_test_body
async def test_conformance_not_term_mandatory(self):
@@ -219,6 +234,7 @@ async def test_conformance_not_term_mandatory(self):
asserts.assert_equal(xml_callable(f, [], []), ConformanceDecision.MANDATORY)
else:
asserts.assert_equal(xml_callable(f, [], []), ConformanceDecision.NOT_APPLICABLE)
+ asserts.assert_equal(str(xml_callable), '!AB')
xml = (''
''
@@ -232,6 +248,7 @@ async def test_conformance_not_term_mandatory(self):
asserts.assert_equal(xml_callable(f, [], []), ConformanceDecision.MANDATORY)
else:
asserts.assert_equal(xml_callable(f, [], []), ConformanceDecision.NOT_APPLICABLE)
+ asserts.assert_equal(str(xml_callable), '!CD')
# single attribute not mandatory
xml = (''
@@ -246,6 +263,7 @@ async def test_conformance_not_term_mandatory(self):
asserts.assert_equal(xml_callable(0x00, a, []), ConformanceDecision.MANDATORY)
else:
asserts.assert_equal(xml_callable(0x00, a, []), ConformanceDecision.NOT_APPLICABLE)
+ asserts.assert_equal(str(xml_callable), '!attr1')
xml = (''
''
@@ -259,6 +277,7 @@ async def test_conformance_not_term_mandatory(self):
asserts.assert_equal(xml_callable(0x00, a, []), ConformanceDecision.MANDATORY)
else:
asserts.assert_equal(xml_callable(0x00, a, []), ConformanceDecision.NOT_APPLICABLE)
+ asserts.assert_equal(str(xml_callable), '!attr2')
@async_test_body
async def test_conformance_not_term_optional(self):
@@ -275,6 +294,7 @@ async def test_conformance_not_term_optional(self):
asserts.assert_equal(xml_callable(f, [], []), ConformanceDecision.OPTIONAL)
else:
asserts.assert_equal(xml_callable(f, [], []), ConformanceDecision.NOT_APPLICABLE)
+ asserts.assert_equal(str(xml_callable), '[!AB]')
xml = (''
''
@@ -288,6 +308,7 @@ async def test_conformance_not_term_optional(self):
asserts.assert_equal(xml_callable(f, [], []), ConformanceDecision.OPTIONAL)
else:
asserts.assert_equal(xml_callable(f, [], []), ConformanceDecision.NOT_APPLICABLE)
+ asserts.assert_equal(str(xml_callable), '[!CD]')
@async_test_body
async def test_conformance_and_term(self):
@@ -305,6 +326,7 @@ async def test_conformance_and_term(self):
asserts.assert_equal(xml_callable(f, [], []), ConformanceDecision.MANDATORY)
else:
asserts.assert_equal(xml_callable(f, [], []), ConformanceDecision.NOT_APPLICABLE)
+ asserts.assert_equal(str(xml_callable), 'AB & CD')
# and term for attributes only
xml = (''
@@ -320,6 +342,7 @@ async def test_conformance_and_term(self):
asserts.assert_equal(xml_callable(0x00, a, []), ConformanceDecision.MANDATORY)
else:
asserts.assert_equal(xml_callable(0x00, a, []), ConformanceDecision.NOT_APPLICABLE)
+ asserts.assert_equal(str(xml_callable), 'attr1 & attr2')
# and term for feature and attribute
xml = (''
@@ -336,6 +359,7 @@ async def test_conformance_and_term(self):
asserts.assert_equal(xml_callable(f, a, []), ConformanceDecision.MANDATORY)
else:
asserts.assert_equal(xml_callable(f, a, []), ConformanceDecision.NOT_APPLICABLE)
+ asserts.assert_equal(str(xml_callable), 'AB & attr2')
@async_test_body
async def test_conformance_or_term(self):
@@ -353,6 +377,7 @@ async def test_conformance_or_term(self):
asserts.assert_equal(xml_callable(f, [], []), ConformanceDecision.MANDATORY)
else:
asserts.assert_equal(xml_callable(f, [], []), ConformanceDecision.NOT_APPLICABLE)
+ asserts.assert_equal(str(xml_callable), 'AB | CD')
# or term attribute only
xml = (''
@@ -368,6 +393,7 @@ async def test_conformance_or_term(self):
asserts.assert_equal(xml_callable(0x00, a, []), ConformanceDecision.MANDATORY)
else:
asserts.assert_equal(xml_callable(0x00, a, []), ConformanceDecision.NOT_APPLICABLE)
+ asserts.assert_equal(str(xml_callable), 'attr1 | attr2')
# or term feature and attribute
xml = (''
@@ -384,6 +410,7 @@ async def test_conformance_or_term(self):
asserts.assert_equal(xml_callable(f, a, []), ConformanceDecision.MANDATORY)
else:
asserts.assert_equal(xml_callable(f, a, []), ConformanceDecision.NOT_APPLICABLE)
+ asserts.assert_equal(str(xml_callable), 'AB | attr2')
@async_test_body
async def test_conformance_and_term_with_not(self):
@@ -403,6 +430,7 @@ async def test_conformance_and_term_with_not(self):
asserts.assert_equal(xml_callable(f, [], []), ConformanceDecision.OPTIONAL)
else:
asserts.assert_equal(xml_callable(f, [], []), ConformanceDecision.NOT_APPLICABLE)
+ asserts.assert_equal(str(xml_callable), '[!AB & CD]')
@async_test_body
async def test_conformance_or_term_with_not(self):
@@ -422,6 +450,7 @@ async def test_conformance_or_term_with_not(self):
asserts.assert_equal(xml_callable(f, [], []), ConformanceDecision.MANDATORY)
else:
asserts.assert_equal(xml_callable(f, [], []), ConformanceDecision.NOT_APPLICABLE)
+ asserts.assert_equal(str(xml_callable), 'AB | !CD')
# not around or term with
xml = (''
@@ -439,6 +468,7 @@ async def test_conformance_or_term_with_not(self):
asserts.assert_equal(xml_callable(f, [], []), ConformanceDecision.OPTIONAL)
else:
asserts.assert_equal(xml_callable(f, [], []), ConformanceDecision.NOT_APPLICABLE)
+ asserts.assert_equal(str(xml_callable), '[!(AB | CD)]')
@async_test_body
async def test_conformance_and_term_with_three_terms(self):
@@ -459,6 +489,7 @@ async def test_conformance_and_term_with_three_terms(self):
asserts.assert_equal(xml_callable(0x01, [], []), ConformanceDecision.NOT_APPLICABLE)
# all features
asserts.assert_equal(xml_callable(0x07, [], []), ConformanceDecision.OPTIONAL)
+ asserts.assert_equal(str(xml_callable), '[AB & CD & EF]')
# and term with one of each
xml = (''
@@ -477,6 +508,7 @@ async def test_conformance_and_term_with_three_terms(self):
asserts.assert_equal(xml_callable(f, a, c), ConformanceDecision.OPTIONAL)
else:
asserts.assert_equal(xml_callable(f, a, c), ConformanceDecision.NOT_APPLICABLE)
+ asserts.assert_equal(str(xml_callable), '[AB & attr1 & cmd1]')
@async_test_body
async def test_conformance_or_term_with_three_terms(self):
@@ -496,6 +528,7 @@ async def test_conformance_or_term_with_three_terms(self):
asserts.assert_equal(xml_callable(0x01, [], []), ConformanceDecision.OPTIONAL)
# all features
asserts.assert_equal(xml_callable(0x07, [], []), ConformanceDecision.OPTIONAL)
+ asserts.assert_equal(str(xml_callable), '[AB | CD | EF]')
# or term with one of each
xml = (''
@@ -514,6 +547,7 @@ async def test_conformance_or_term_with_three_terms(self):
asserts.assert_equal(xml_callable(f, a, c), ConformanceDecision.OPTIONAL)
else:
asserts.assert_equal(xml_callable(f, a, c), ConformanceDecision.NOT_APPLICABLE)
+ asserts.assert_equal(str(xml_callable), '[AB | attr1 | cmd1]')
def test_conformance_otherwise(self):
# AB, O
@@ -530,6 +564,7 @@ def test_conformance_otherwise(self):
asserts.assert_equal(xml_callable(f, [], []), ConformanceDecision.MANDATORY)
else:
asserts.assert_equal(xml_callable(f, [], []), ConformanceDecision.OPTIONAL)
+ asserts.assert_equal(str(xml_callable), 'AB, O')
# AB, [CD]
xml = (''
@@ -549,6 +584,7 @@ def test_conformance_otherwise(self):
asserts.assert_equal(xml_callable(f, [], []), ConformanceDecision.OPTIONAL)
else:
asserts.assert_equal(xml_callable(f, [], []), ConformanceDecision.NOT_APPLICABLE)
+ asserts.assert_equal(str(xml_callable), 'AB, [CD]')
# AB & !CD, P
xml = (''
@@ -569,6 +605,7 @@ def test_conformance_otherwise(self):
asserts.assert_equal(xml_callable(f, [], []), ConformanceDecision.MANDATORY)
else:
asserts.assert_equal(xml_callable(f, [], []), ConformanceDecision.PROVISIONAL)
+ asserts.assert_equal(str(xml_callable), 'AB & !CD, P')
if __name__ == "__main__":
diff --git a/src/python_testing/conformance_support.py b/src/python_testing/conformance_support.py
index 2dabb584c9d0f7..1d3f3128ef37cd 100644
--- a/src/python_testing/conformance_support.py
+++ b/src/python_testing/conformance_support.py
@@ -67,74 +67,131 @@ def conformance_allowed(conformance_decision: ConformanceDecision, allow_provisi
return True
-def mandatory(feature_map: uint, attribute_list: list[uint], all_command_list: list[uint]) -> ConformanceDecision:
- return ConformanceDecision.MANDATORY
+class mandatory:
+ def __call__(self, feature_map: uint, attribute_list: list[uint], all_command_list: list[uint]) -> ConformanceDecision:
+ return ConformanceDecision.MANDATORY
+
+ def __str__(self):
+ return 'M'
+
+
+class optional:
+ def __call__(self, feature_map: uint, attribute_list: list[uint], all_command_list: list[uint]) -> ConformanceDecision:
+ return ConformanceDecision.OPTIONAL
+ def __str__(self):
+ return 'O'
+
+
+class deprecated:
+ def __call__(self, feature_map: uint, attribute_list: list[uint], all_command_list: list[uint]) -> ConformanceDecision:
+ return ConformanceDecision.DISALLOWED
+
+ def __str__(self):
+ return 'D'
-def optional(feature_map: uint, attribute_list: list[uint], all_command_list: list[uint]) -> ConformanceDecision:
- return ConformanceDecision.OPTIONAL
+class disallowed:
+ def __call__(self, feature_map: uint, attribute_list: list[uint], all_command_list: list[uint]) -> ConformanceDecision:
+ return ConformanceDecision.DISALLOWED
-def deprecated(feature_map: uint, attribute_list: list[uint], all_command_list: list[uint]) -> ConformanceDecision:
- return ConformanceDecision.DISALLOWED
+ def __str__(self):
+ return 'X'
-def disallowed(feature_map: uint, attribute_list: list[uint], all_command_list: list[uint]) -> ConformanceDecision:
- return ConformanceDecision.DISALLOWED
+class provisional:
+ def __call__(self, feature_map: uint, attribute_list: list[uint], all_command_list: list[uint]) -> ConformanceDecision:
+ return ConformanceDecision.PROVISIONAL
+ def __str__(self):
+ return 'P'
-def provisional(feature_map: uint, attribute_list: list[uint], all_command_list: list[uint]) -> ConformanceDecision:
- return ConformanceDecision.PROVISIONAL
+class feature:
+ def __init__(self, requiredFeature: uint, code: str):
+ self.requiredFeature = requiredFeature
+ self.code = code
-def feature(requiredFeature: uint) -> Callable:
- def feature_inner(feature_map: uint, attribute_list: list[uint], all_command_list: list[uint]) -> ConformanceDecision:
- if requiredFeature & feature_map != 0:
+ def __call__(self, feature_map: uint, attribute_list: list[uint], all_command_list: list[uint]) -> ConformanceDecision:
+ if self.requiredFeature & feature_map != 0:
return ConformanceDecision.MANDATORY
return ConformanceDecision.NOT_APPLICABLE
- return feature_inner
+ def __str__(self):
+ return f'{self.code}'
+
+
+class attribute:
+ def __init__(self, requiredAttribute: uint, name: str):
+ self.requiredAttribute = requiredAttribute
+ self.name = name
-def attribute(requiredAttribute: uint) -> Callable:
- def attribute_inner(feature_map: uint, attribute_list: list[uint], all_command_list: list[uint]) -> ConformanceDecision:
- if requiredAttribute in attribute_list:
+ def __call__(self, feature_map: uint, attribute_list: list[uint], all_command_list: list[uint]) -> ConformanceDecision:
+ if self.requiredAttribute in attribute_list:
return ConformanceDecision.MANDATORY
return ConformanceDecision.NOT_APPLICABLE
- return attribute_inner
+ def __str__(self):
+ return f'{self.name}'
+
+
+class command:
+ def __init__(self, requiredCommand: uint, name: str):
+ self.requiredCommand = requiredCommand
+ self.name = name
-def command(requiredCommand: uint) -> Callable:
- def command_inner(feature_map: uint, attribute_list: list[uint], all_command_list: list[uint]) -> ConformanceDecision:
- if requiredCommand in all_command_list:
+ def __call__(self, feature_map: uint, attribute_list: list[uint], all_command_list: list[uint]) -> ConformanceDecision:
+ if self.requiredCommand in all_command_list:
return ConformanceDecision.MANDATORY
return ConformanceDecision.NOT_APPLICABLE
- return command_inner
+
+ def __str__(self):
+ return f'{self.name}'
-def optional_wrapper(op: Callable) -> Callable:
- def optional_wrapper_inner(feature_map: uint, attribute_list: list[uint], all_command_list: list[uint]) -> ConformanceDecision:
- decision = op(feature_map, attribute_list, all_command_list)
+def strip_outer_parentheses(inner: str) -> str:
+ if inner[0] == '(' and inner[-1] == ')':
+ return inner[1:-1]
+ return inner
+
+
+class optional_wrapper:
+ def __init__(self, op: Callable):
+ self.op = op
+
+ def __call__(self, feature_map: uint, attribute_list: list[uint], all_command_list: list[uint]) -> ConformanceDecision:
+ decision = self.op(feature_map, attribute_list, all_command_list)
if decision == ConformanceDecision.MANDATORY or decision == ConformanceDecision.OPTIONAL:
return ConformanceDecision.OPTIONAL
elif decision == ConformanceDecision.NOT_APPLICABLE:
return ConformanceDecision.NOT_APPLICABLE
else:
raise ConformanceException(f'Optional wrapping invalid op {decision}')
- return optional_wrapper_inner
+
+ def __str__(self):
+ return f'[{strip_outer_parentheses(str(self.op))}]'
-def mandatory_wrapper(op: Callable) -> Callable:
- def mandatory_wrapper_inner(feature_map: uint, attribute_list: list[uint], all_command_list: list[uint]) -> ConformanceDecision:
- return op(feature_map, attribute_list, all_command_list)
- return mandatory_wrapper_inner
+class mandatory_wrapper:
+ def __init__(self, op: Callable):
+ self.op = op
+ def __call__(self, feature_map: uint, attribute_list: list[uint], all_command_list: list[uint]) -> ConformanceDecision:
+ return self.op(feature_map, attribute_list, all_command_list)
-def not_operation(op: Callable):
- def not_operation_inner(feature_map: uint, attribute_list: list[uint], all_command_list: list[uint]) -> ConformanceDecision:
+ def __str__(self):
+ return strip_outer_parentheses(str(self.op))
+
+
+class not_operation:
+ def __init__(self, op: Callable):
+ self.op = op
+
+ def __call__(self, feature_map: uint, attribute_list: list[uint], all_command_list: list[uint]) -> ConformanceDecision:
# not operations can't be used with anything that returns DISALLOWED
# not operations also can't be used with things that are optional
# ie, ![AB] doesn't make sense, nor does !O
- decision = op(feature_map, attribute_list, all_command_list)
+ decision = self.op(feature_map, attribute_list, all_command_list)
if decision == ConformanceDecision.OPTIONAL or decision == ConformanceDecision.DISALLOWED or decision == ConformanceDecision.PROVISIONAL:
raise ConformanceException('NOT operation on optional or disallowed item')
elif decision == ConformanceDecision.NOT_APPLICABLE:
@@ -143,12 +200,17 @@ def not_operation_inner(feature_map: uint, attribute_list: list[uint], all_comma
return ConformanceDecision.NOT_APPLICABLE
else:
raise ConformanceException('NOT called on item with non-conformance value')
- return not_operation_inner
+ def __str__(self):
+ return f'!{str(self.op)}'
+
+
+class and_operation:
+ def __init__(self, op_list: list[Callable]):
+ self.op_list = op_list
-def and_operation(op_list: list[Callable]) -> Callable:
- def and_operation_inner(feature_map: uint, attribute_list: list[uint], all_command_list: list[uint]) -> ConformanceDecision:
- for op in op_list:
+ def __call__(self, feature_map: uint, attribute_list: list[uint], all_command_list: list[uint]) -> ConformanceDecision:
+ for op in self.op_list:
decision = op(feature_map, attribute_list, all_command_list)
# and operations can't happen on optional or disallowed
if decision == ConformanceDecision.OPTIONAL or decision == ConformanceDecision.DISALLOWED or decision == ConformanceDecision.PROVISIONAL:
@@ -160,12 +222,18 @@ def and_operation_inner(feature_map: uint, attribute_list: list[uint], all_comma
else:
raise ConformanceException('Oplist item returned non-conformance value')
return ConformanceDecision.MANDATORY
- return and_operation_inner
+ def __str__(self):
+ op_strs = [str(op) for op in self.op_list]
+ return f'({" & ".join(op_strs)})'
+
+
+class or_operation:
+ def __init__(self, op_list: list[Callable]):
+ self.op_list = op_list
-def or_operation(op_list: list[Callable]) -> Callable:
- def or_operation_inner(feature_map: uint, attribute_list: list[uint], all_command_list: list[uint]) -> ConformanceDecision:
- for op in op_list:
+ def __call__(self, feature_map: uint, attribute_list: list[uint], all_command_list: list[uint]) -> ConformanceDecision:
+ for op in self.op_list:
decision = op(feature_map, attribute_list, all_command_list)
if decision == ConformanceDecision.DISALLOWED or decision == ConformanceDecision.PROVISIONAL:
raise ConformanceException('OR operation on optional or disallowed item')
@@ -178,58 +246,67 @@ def or_operation_inner(feature_map: uint, attribute_list: list[uint], all_comman
else:
raise ConformanceException('Oplist item returned non-conformance value')
return ConformanceDecision.NOT_APPLICABLE
- return or_operation_inner
+
+ 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?
-def otherwise(op_list: list[Callable]) -> Callable:
- def otherwise_inner(feature_map: uint, attribute_list: list[uint], all_command_list: list[uint]) -> ConformanceDecision:
+class otherwise:
+ def __init__(self, op_list: list[Callable]):
+ self.op_list = op_list
+
+ def __call__(self, feature_map: uint, attribute_list: list[uint], all_command_list: list[uint]) -> ConformanceDecision:
# Otherwise operations apply from left to right. If any of them
# has a definite decision (optional, mandatory or disallowed), that is the one that applies
# Provisional items are meant to be marked as the first item in the list
# Deprecated items are either on their own, or follow an O as O,D.
# For O,D, optional applies (leftmost), but we should consider some way to warn here as well,
# possibly in another function
- for op in op_list:
+ for op in self.op_list:
decision = op(feature_map, attribute_list, all_command_list)
if decision == ConformanceDecision.NOT_APPLICABLE:
continue
return decision
return ConformanceDecision.NOT_APPLICABLE
- return otherwise_inner
+
+ def __str__(self):
+ op_strs = [strip_outer_parentheses(str(op)) for op in self.op_list]
+ return ', '.join(op_strs)
def parse_callable_from_xml(element: ElementTree.Element, params: ConformanceParseParameters) -> Callable:
if len(list(element)) == 0:
# no subchildren here, so this can only be mandatory, optional, provisional, deprecated, disallowed, feature or attribute
if element.tag == MANDATORY_CONFORM:
- return mandatory
+ return mandatory()
elif element.tag == OPTIONAL_CONFORM:
- return optional
+ return optional()
elif element.tag == PROVISIONAL_CONFORM:
- return provisional
+ return provisional()
elif element.tag == DEPRECATE_CONFORM:
- return deprecated
+ return deprecated()
elif element.tag == DISALLOW_CONFORM:
- return disallowed
+ return disallowed()
elif element.tag == FEATURE_TAG:
try:
- return feature(params.feature_map[element.get('name')])
+ return feature(params.feature_map[element.get('name')], element.get('name'))
except KeyError:
raise ConformanceException(f'Conformance specifies feature not in feature table: {element.get("name")}')
elif element.tag == ATTRIBUTE_TAG:
# Some command conformance tags are marked as attribute, so if this key isn't in attribute, try command
name = element.get('name')
if name in params.attribute_map:
- return attribute(params.attribute_map[name])
+ return attribute(params.attribute_map[name], name)
elif name in params.command_map:
- return command(params.command_map[name])
+ return command(params.command_map[name], name)
else:
raise ConformanceException(f'Conformance specifies attribute or command not in table: {name}')
elif element.tag == COMMAND_TAG:
- return command(params.command_map[element.get('name')])
+ return command(params.command_map[element.get('name')], element.get('name'))
else:
raise ConformanceException(
f'Unexpected xml conformance element with no children {str(element.tag)} {str(element.attrib)}')