Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Choice conformance: Add choice conformance markers #33854

Merged
1 change: 1 addition & 0 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -563,6 +563,7 @@ jobs:
scripts/run_in_python_env.sh out/venv './src/python_testing/test_testing/test_TC_DA_1_2.py'
scripts/run_in_python_env.sh out/venv './src/python_testing/test_testing/test_TC_ICDM_2_1.py'
scripts/run_in_python_env.sh out/venv 'python3 ./src/python_testing/TestIdChecks.py'
scripts/run_in_python_env.sh out/venv 'python3 ./src/python_testing/TestConformanceSupport.py'

- name: Uploading core files
uses: actions/upload-artifact@v4
Expand Down
208 changes: 205 additions & 3 deletions src/python_testing/TestConformanceSupport.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@
import xml.etree.ElementTree as ElementTree
from typing import Callable

from conformance_support import (ConformanceDecision, ConformanceException, ConformanceParseParameters, deprecated, disallowed,
mandatory, optional, parse_basic_callable_from_xml, parse_callable_from_xml,
parse_device_type_callable_from_xml, provisional, zigbee)
from chip.tlv import uint
from conformance_support import (Choice, Conformance, ConformanceDecision, ConformanceException, ConformanceParseParameters,
deprecated, disallowed, mandatory, optional, parse_basic_callable_from_xml,
parse_callable_from_xml, parse_device_type_callable_from_xml, provisional, zigbee)
from matter_testing_support import MatterBaseTest, default_matter_test_main
from mobly import asserts

Expand Down Expand Up @@ -750,6 +751,207 @@ def test_device_type_conformance(self):
asserts.assert_equal(str(xml_callable), 'CD, testy', msg)
asserts.assert_equal(xml_callable(0, [], []), ConformanceDecision.OPTIONAL)

def check_good_choice(self, xml: str, conformance_str: str) -> Conformance:
et = ElementTree.fromstring(xml)
xml_callable = parse_callable_from_xml(et, self.params)
asserts.assert_equal(str(xml_callable), conformance_str, 'Bad choice conformance string')
return xml_callable

def check_decision(self, more_expected: bool, conformance: Conformance, feature_map: uint, attr_list: list[uint], cmd_list: list[uint]):
decision = conformance(feature_map, attr_list, cmd_list)
asserts.assert_true(decision.choice, 'Expected choice conformance on decision, but did not get one')
asserts.assert_equal(decision.choice.marker, 'a', 'Unexpected conformance string returned')
asserts.assert_equal(decision.choice.more, more_expected, "Unexpected more on choice")

def test_choice_conformance(self):
# Choice conformances can appear on:
# - base optional O.a
# - base optional feature [AB].a
# - base optional attribute [attr1].a
# - base optional command [cmd1].a
# - optional wrapper of complex feature [AB | CD].a, [!attr1].a
# - otherwise conformance attr1, [AB], O.a / attr1, [AB].a, O
# - multiple in otherwise [AB].a, [CD].b
#
# Choice conformances are disallowed on:
# - mandatory M.a
# - mandatory feature AB.a
# - mandatory attribute attr1.a
# - mandatory command cmd1.a
# - AND expressions (attr1 & O.a)
# - OR expressions (attr1 | O.a)
# - NOT expressions (!O.a)
# - internal expressions [AB.a], [attr1.a], [cmd1.a]
# - provisional P.a
# - disallowed X.a
# - deprecated D.a

choices = [('a+', 'choice="a" more="true"', True), ('a', 'choice="a"', False)]
for suffix, xml_attrs, more in choices:

AB = self.feature_names_to_bits['AB']
attr1 = [self.attribute_names_to_values['attr1']]
cmd1 = [self.command_names_to_values['cmd1']]

msg_not_applicable = "Expected NOT_APPLICABLE conformance"
xml = f'<optionalConform {xml_attrs} />'
conformance = self.check_good_choice(xml, f'O.{suffix}')
self.check_decision(more, conformance, 0, [], [])

xml = (f'<optionalConform {xml_attrs}>'
'<feature name="AB" />'
'</optionalConform>')
conformance = self.check_good_choice(xml, f'[AB].{suffix}')
asserts.assert_equal(conformance(0, [], []), ConformanceDecision.NOT_APPLICABLE, msg_not_applicable)
self.check_decision(more, conformance, AB, [], [])

xml = (f'<optionalConform {xml_attrs}>'
'<attribute name="attr1" />'
'</optionalConform>')
conformance = self.check_good_choice(xml, f'[attr1].{suffix}')
asserts.assert_equal(conformance(0, [], []), ConformanceDecision.NOT_APPLICABLE, msg_not_applicable)
self.check_decision(more, conformance, 0, attr1, [])

xml = (f'<optionalConform {xml_attrs}>'
'<command name="cmd1" />'
'</optionalConform>')
conformance = self.check_good_choice(xml, f'[cmd1].{suffix}')
asserts.assert_equal(conformance(0, [], []), ConformanceDecision.NOT_APPLICABLE, msg_not_applicable)
self.check_decision(more, conformance, 0, [], cmd1)

xml = (f'<optionalConform {xml_attrs}>'
'<orTerm>'
'<feature name="AB" />'
'<feature name="CD" />'
'</orTerm>'
'</optionalConform>')
conformance = self.check_good_choice(xml, f'[AB | CD].{suffix}')
asserts.assert_equal(conformance(0, [], []), ConformanceDecision.NOT_APPLICABLE, msg_not_applicable)
self.check_decision(more, conformance, AB, [], [])

xml = (f'<optionalConform {xml_attrs}>'
'<notTerm>'
'<attribute name="attr1" />'
'</notTerm>'
'</optionalConform>')
conformance = self.check_good_choice(xml, f'[!attr1].{suffix}')
asserts.assert_equal(conformance(0, attr1, []), ConformanceDecision.NOT_APPLICABLE, msg_not_applicable)
self.check_decision(more, conformance, 0, [], [])

xml = ('<otherwiseConform>'
'<attribute name="attr1" />'
f'<optionalConform>'
'<feature name="AB" />'
'</optionalConform>'
f'<optionalConform {xml_attrs} />'
'</otherwiseConform>')
conformance = self.check_good_choice(xml, f'attr1, [AB], O.{suffix}')
# with no features or attributes, this should end up as O.a, so there should be a choice
self.check_decision(more, conformance, 0, [], [])
# when we have this attribute, we should not have a choice
asserts.assert_equal(conformance(0, attr1, []), ConformanceDecision.MANDATORY, 'Unexpected conformance')
asserts.assert_equal(conformance(0, attr1, []).choice, None, 'Unexpected choice in conformance')
# when we have only this feature, we should not have a choice
asserts.assert_equal(conformance(AB, [], []), ConformanceDecision.OPTIONAL, 'Unexpected conformance')
asserts.assert_equal(conformance(AB, [], []).choice, None, 'Unexpected choice in conformance')

# - multiple in otherwise [AB].a, [CD].b
xml = ('<otherwiseConform>'
'<attribute name="attr1" />'
f'<optionalConform {xml_attrs}>'
'<feature name="AB" />'
'</optionalConform>'
f'<optionalConform choice="b">'
'<feature name="CD" />'
'</optionalConform>'
'</otherwiseConform>')
conformance = self.check_good_choice(xml, f'attr1, [AB].{suffix}, [CD].b')
asserts.assert_equal(conformance(0, [], []), ConformanceDecision.NOT_APPLICABLE, msg_not_applicable)
# when we have this attribute, we should not have a choice
asserts.assert_equal(conformance(0, attr1, []), ConformanceDecision.MANDATORY, 'Unexpected conformance')
asserts.assert_equal(conformance(0, attr1, []).choice, None, 'Unexpected choice in conformance')
# When it's just AB, we should have a choice
self.check_decision(more, conformance, AB, [], [])
# When we have both the attribute and AB, we should not have a choice
asserts.assert_equal(conformance(0, attr1, []), ConformanceDecision.MANDATORY, 'Unexpected conformance')
asserts.assert_equal(conformance(0, attr1, []).choice, None, 'Unexpected choice in conformance')
# When we have AB and CD, we should be using the AB choice
CD = self.feature_names_to_bits['CD']
ABCD = AB | CD
self.check_decision(more, conformance, ABCD, [], [])
# When we just have CD, we still have a choice, but the string should be b
asserts.assert_equal(conformance(CD, [], []), ConformanceDecision.OPTIONAL, 'Unexpected conformance')
asserts.assert_equal(conformance(CD, [], []).choice, Choice('b', False), 'Unexpected choice in conformance')

# Ones that should throw exceptions

def check_bad_choice(xml: str):
msg = f'Choice conformance string should cause exception, but did not: {xml}'
et = ElementTree.fromstring(xml)
try:
parse_callable_from_xml(et, self.params)
asserts.fail(msg)
except ConformanceException:
pass
xml = f'<mandatoryConform {xml_attrs} />'
check_bad_choice(xml)

xml = f'<feature name="AB" {xml_attrs} />'
check_bad_choice(xml)

xml = f'<attribute name="attr1" {xml_attrs} />'
check_bad_choice(xml)

xml = f'<command name="cmd1" {xml_attrs} />'
check_bad_choice(xml)

xml = ('<mandatoryConform>'
'<andTerm>'
'<attribute name="attr1"/>'
f'<optionalConform {xml_attrs} />'
'</andTerm>'
'</mandatoryConform>')
check_bad_choice(xml)

xml = ('<mandatoryConform>'
'<orTerm>'
'<attribute name="attr1"/>'
f'<optionalConform {xml_attrs} />'
'</orTerm>'
'</mandatoryConform>')
check_bad_choice(xml)

xml = ('<mandatoryConform>'
'<notTerm>'
f'<optionalConform {xml_attrs} />'
'</notTerm>'
'</mandatoryConform>')
check_bad_choice(xml)

xml = ('<optionalConform>'
f'<feature name="AB" {xml_attrs}/>'
'</optionalConform>')
check_bad_choice(xml)

xml = ('<optionalConform>'
f'<attribute name="attr1" {xml_attrs}/>'
'</optionalConform>')
check_bad_choice(xml)

xml = ('<optionalConform>'
f'<command name="cmd1" {xml_attrs}/>'
'</optionalConform>')
check_bad_choice(xml)

xml = (f'<provisionalConform {xml_attrs}/>')
check_bad_choice(xml)

xml = (f'<disallowConform {xml_attrs}/>')
check_bad_choice(xml)

xml = (f'<deprecateConform {xml_attrs}/>')
check_bad_choice(xml)


if __name__ == "__main__":
default_matter_test_main()
Loading
Loading