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

Python testing: Conformance support for device type conditions #33622

Merged
merged 7 commits into from
Jun 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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>'
andy31415 marked this conversation as resolved.
Show resolved Hide resolved
'<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
Loading