Skip to content

Commit

Permalink
Python testing: Conformance support for device type conditions (#33622)
Browse files Browse the repository at this point in the history
* Python testing: Conformance support for device type conditions

* fix type

* strings

* simplify exception

* Restyled by isort

* address review comments

---------

Co-authored-by: Restyled.io <[email protected]>
  • Loading branch information
2 people authored and pull[bot] committed Dec 11, 2024
1 parent 55ac5d9 commit 1732528
Show file tree
Hide file tree
Showing 2 changed files with 206 additions and 73 deletions.
147 changes: 113 additions & 34 deletions src/python_testing/TestConformanceSupport.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,23 @@
#

import xml.etree.ElementTree as ElementTree
from typing import Callable

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 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 matter_testing_support import MatterBaseTest, default_matter_test_main
from mobly import asserts


def basic_test(xml: str, cls: Callable) -> None:
et = ElementTree.fromstring(xml)
xml_callable = parse_basic_callable_from_xml(et)
asserts.assert_true(isinstance(xml_callable, cls), "Unexpected class parsed from basic conformance")


class TestConformanceSupport(MatterBaseTest):
@async_test_body
async def setup_class(self):
def setup_class(self):
super().setup_class()
# a small feature map
self.feature_names_to_bits = {'AB': 0x01, 'CD': 0x02}
Expand All @@ -46,26 +54,23 @@ async def setup_class(self):
self.params = ConformanceParseParameters(
feature_map=self.feature_names_to_bits, attribute_map=self.attribute_names_to_values, command_map=self.command_names_to_values)

@async_test_body
async def test_conformance_mandatory(self):
def test_conformance_mandatory(self):
xml = '<mandatoryConform />'
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.MANDATORY)
asserts.assert_equal(str(xml_callable), 'M')

@async_test_body
async def test_conformance_optional(self):
def test_conformance_optional(self):
xml = '<optionalConform />'
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.OPTIONAL)
asserts.assert_equal(str(xml_callable), 'O')

@async_test_body
async def test_conformance_disallowed(self):
def test_conformance_disallowed(self):
xml = '<disallowConform />'
et = ElementTree.fromstring(xml)
xml_callable = parse_callable_from_xml(et, self.params)
Expand All @@ -80,26 +85,23 @@ async def test_conformance_disallowed(self):
asserts.assert_equal(xml_callable(f, [], []), ConformanceDecision.DISALLOWED)
asserts.assert_equal(str(xml_callable), 'D')

@async_test_body
async def test_conformance_provisional(self):
def test_conformance_provisional(self):
xml = '<provisionalConform />'
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.PROVISIONAL)
asserts.assert_equal(str(xml_callable), 'P')

@async_test_body
async def test_conformance_zigbee(self):
def test_conformance_zigbee(self):
xml = '<condition name="Zigbee"/>'
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.NOT_APPLICABLE)
asserts.assert_equal(str(xml_callable), 'Zigbee')

@async_test_body
async def test_conformance_mandatory_on_condition(self):
def test_conformance_mandatory_on_condition(self):
xml = ('<mandatoryConform>'
'<feature name="AB" />'
'</mandatoryConform>')
Expand Down Expand Up @@ -151,8 +153,7 @@ async def test_conformance_mandatory_on_condition(self):

# test command in optional and in boolean - this is the same as attribute essentially, so testing every permutation is overkill

@async_test_body
async def test_conformance_optional_on_condition(self):
def test_conformance_optional_on_condition(self):
# single feature optional
xml = ('<optionalConform>'
'<feature name="AB" />'
Expand Down Expand Up @@ -228,8 +229,7 @@ async def test_conformance_optional_on_condition(self):
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):
def test_conformance_not_term_mandatory(self):
# single feature not mandatory
xml = ('<mandatoryConform>'
'<notTerm>'
Expand Down Expand Up @@ -288,8 +288,7 @@ async def test_conformance_not_term_mandatory(self):
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):
def test_conformance_not_term_optional(self):
# single feature not optional
xml = ('<optionalConform>'
'<notTerm>'
Expand Down Expand Up @@ -319,8 +318,7 @@ async def test_conformance_not_term_optional(self):
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):
def test_conformance_and_term(self):
# and term for features only
xml = ('<mandatoryConform>'
'<andTerm>'
Expand Down Expand Up @@ -370,8 +368,7 @@ async def test_conformance_and_term(self):
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):
def test_conformance_or_term(self):
# or term feature only
xml = ('<mandatoryConform>'
'<orTerm>'
Expand Down Expand Up @@ -421,8 +418,7 @@ async def test_conformance_or_term(self):
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):
def test_conformance_and_term_with_not(self):
# and term with not
xml = ('<optionalConform>'
'<andTerm>'
Expand All @@ -441,8 +437,7 @@ async def test_conformance_and_term_with_not(self):
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):
def test_conformance_or_term_with_not(self):
# or term with not on second feature
xml = ('<mandatoryConform>'
'<orTerm>'
Expand Down Expand Up @@ -479,8 +474,7 @@ async def test_conformance_or_term_with_not(self):
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):
def test_conformance_and_term_with_three_terms(self):
# and term with three features
xml = ('<optionalConform>'
'<andTerm>'
Expand Down Expand Up @@ -519,8 +513,7 @@ async def test_conformance_and_term_with_three_terms(self):
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):
def test_conformance_or_term_with_three_terms(self):
# or term with three features
xml = ('<optionalConform>'
'<orTerm>'
Expand Down Expand Up @@ -671,6 +664,92 @@ def test_conformance_greater(self):
except ConformanceException:
pass

def test_basic_conformance(self):
basic_test('<mandatoryConform />', mandatory)
basic_test('<optionalConform />', optional)
basic_test('<disallowConform />', disallowed)
basic_test('<deprecateConform />', deprecated)
basic_test('<provisionalConform />', provisional)
basic_test('<condition name="zigbee" />', zigbee)

# feature is not basic so we should get an exception
xml = '<feature name="CD" />'
et = ElementTree.fromstring(xml)
try:
parse_basic_callable_from_xml(et)
asserts.fail("Unexpected success parsing non-basic conformance")
except ConformanceException:
pass

# mandatory tag is basic, but this one is a wrapper, so we should get a TypeError
xml = ('<mandatoryConform>'
'<andTerm>'
'<feature name="AB" />'
'<notTerm>'
'<feature name="CD" />'
'</notTerm>'
'</andTerm>'
'</mandatoryConform>')
et = ElementTree.fromstring(xml)
try:
parse_basic_callable_from_xml(et)
asserts.fail("Unexpected success parsing mandatory wrapper")
except ConformanceException:
pass

def test_device_type_conformance(self):
msg = "Unexpected conformance returned for device type"
xml = ('<mandatoryConform>'
'<condition name="zigbee" />'
'</mandatoryConform>')
et = ElementTree.fromstring(xml)
xml_callable = parse_device_type_callable_from_xml(et)
asserts.assert_equal(str(xml_callable), 'Zigbee', msg)
asserts.assert_equal(xml_callable(0, [], []), ConformanceDecision.NOT_APPLICABLE, msg)

xml = ('<optionalConform>'
'<condition name="zigbee" />'
'</optionalConform>')
et = ElementTree.fromstring(xml)
xml_callable = parse_device_type_callable_from_xml(et)
# expect no exception here
asserts.assert_equal(str(xml_callable), '[Zigbee]', msg)
asserts.assert_equal(xml_callable(0, [], []), ConformanceDecision.NOT_APPLICABLE, msg)

# otherwise conforms are allowed
xml = ('<otherwiseConform>'
'<condition name="zigbee" />'
'<provisionalConform />'
'</otherwiseConform>')
et = ElementTree.fromstring(xml)
xml_callable = parse_device_type_callable_from_xml(et)
# expect no exception here
asserts.assert_equal(str(xml_callable), 'Zigbee, P', msg)
asserts.assert_equal(xml_callable(0, [], []), ConformanceDecision.PROVISIONAL, msg)

# Device type conditions or features don't correspond to anything in the spec, so the XML takes a best
# guess as to what they are. We should be able to parse features, conditions, attributes as the same
# thing.
# TODO: allow querying conformance for conditional device features
# TODO: adjust conformance call function to accept a list of features and evaluate based on that
xml = ('<mandatoryConform>'
'<feature name="CD" />'
'</mandatoryConform>')
et = ElementTree.fromstring(xml)
xml_callable = parse_device_type_callable_from_xml(et)
asserts.assert_equal(str(xml_callable), 'CD', msg)
# Device features are always optional (at least for now), even though we didn't pass this feature in
asserts.assert_equal(xml_callable(0, [], []), ConformanceDecision.OPTIONAL)

xml = ('<otherwiseConform>'
'<feature name="CD" />'
'<condition name="testy" />'
'</otherwiseConform>')
et = ElementTree.fromstring(xml)
xml_callable = parse_device_type_callable_from_xml(et)
asserts.assert_equal(str(xml_callable), 'CD, testy', msg)
asserts.assert_equal(xml_callable(0, [], []), ConformanceDecision.OPTIONAL)


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

0 comments on commit 1732528

Please sign in to comment.