Skip to content

Commit 2998228

Browse files
authored
Merge branch 'master' into feature/fix-cdc-passcode-cancel
2 parents 645c625 + 1b1340f commit 2998228

File tree

4 files changed

+316
-1
lines changed

4 files changed

+316
-1
lines changed
+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#
2+
# Copyright (c) 2024 Project CHIP Authors
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
#
16+
17+
SB_CONFIG_MATTER=y
18+
SB_CONFIG_MATTER_OTA=n

src/python_testing/TC_DeviceConformance.py

+13-1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
import chip.clusters as Clusters
3333
from basic_composition_support import BasicCompositionTests
3434
from chip.tlv import uint
35+
from choice_conformance_support import (evaluate_attribute_choice_conformance, evaluate_command_choice_conformance,
36+
evaluate_feature_choice_conformance)
3537
from conformance_support import ConformanceDecision, conformance_allowed
3638
from global_attribute_ids import GlobalAttributeIds
3739
from matter_testing_support import (AttributePathLocation, ClusterPathLocation, CommandPathLocation, MatterBaseTest, ProblemNotice,
@@ -188,7 +190,17 @@ def check_spec_conformance_for_commands(command_type: CommandType):
188190
check_spec_conformance_for_commands(CommandType.ACCEPTED)
189191
check_spec_conformance_for_commands(CommandType.GENERATED)
190192

191-
# TODO: Add choice checkers
193+
feature_choice_problems = evaluate_feature_choice_conformance(
194+
endpoint_id, cluster_id, self.xml_clusters, feature_map, attribute_list, all_command_list)
195+
attribute_choice_problems = evaluate_attribute_choice_conformance(
196+
endpoint_id, cluster_id, self.xml_clusters, feature_map, attribute_list, all_command_list)
197+
command_choice_problem = evaluate_command_choice_conformance(
198+
endpoint_id, cluster_id, self.xml_clusters, feature_map, attribute_list, all_command_list)
199+
200+
if feature_choice_problems or attribute_choice_problems or command_choice_problem:
201+
success = False
202+
problems.extend(feature_choice_problems + attribute_choice_problems + command_choice_problem)
203+
192204
print(f'success = {success}')
193205
return success, problems
194206

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
#
2+
# Copyright (c) 2023 Project CHIP Authors
3+
# All rights reserved.
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
#
17+
18+
import itertools
19+
import xml.etree.ElementTree as ElementTree
20+
21+
import jinja2
22+
from choice_conformance_support import (evaluate_attribute_choice_conformance, evaluate_command_choice_conformance,
23+
evaluate_feature_choice_conformance)
24+
from matter_testing_support import MatterBaseTest, ProblemNotice, default_matter_test_main
25+
from mobly import asserts
26+
from spec_parsing_support import XmlCluster, add_cluster_data_from_xml
27+
28+
FEATURE_TEMPLATE = '''\
29+
<feature bit="{{ id }}" code="{{ name }}" name="{{ name }}" summary="summary">
30+
<optionalConform choice="{{ choice }}" more="{{ more }}">
31+
{%- if XXX %}'
32+
<feature name="XXX" />
33+
{% endif %}
34+
</optionalConform>
35+
</feature>
36+
'''
37+
38+
ATTRIBUTE_TEMPLATE = (
39+
' <attribute id="{{ id }}" name="{{ name }}" type="uint16">\n'
40+
' <optionalConform choice="{{ choice }}" more="{{ more }}">\n'
41+
' {% if XXX %}'
42+
' <attribute name="XXX" />\n'
43+
' {% endif %}'
44+
' </optionalConform>\n'
45+
' </attribute>\n'
46+
)
47+
48+
COMMAND_TEMPLATE = (
49+
' <command id="{{ id }}" name="{{ name }}" direction="commandToServer" response="Y">\n'
50+
' <optionalConform choice="{{ choice }}" more="{{ more }}">\n'
51+
' {% if XXX %}'
52+
' <command name="XXX" />\n'
53+
' {% endif %}'
54+
' </optionalConform>\n'
55+
' </command>\n'
56+
)
57+
58+
CLUSTER_TEMPLATE = (
59+
'<cluster xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="types types.xsd cluster cluster.xsd" id="0x0001" name="Test Base" revision="1">\n'
60+
' <revisionHistory>\n'
61+
' <revision revision="1" summary="Initial version"/>\n'
62+
' </revisionHistory>\n'
63+
' <clusterIds>\n'
64+
' <clusterId id="0x0001" name="Test Base"/>\n'
65+
' </clusterIds>\n'
66+
' <classification hierarchy="base" role="application" picsCode="BASE" scope="Endpoint"/>\n'
67+
' <features>\n'
68+
' {{ feature_string }}\n'
69+
' </features>\n'
70+
' <attributes>\n'
71+
' {{ attribute_string}}\n'
72+
' </attributes>\n'
73+
' <commands>\n'
74+
' {{ command_string }}\n'
75+
' </commands>\n'
76+
'</cluster>\n')
77+
78+
79+
def _create_elements(template_str: str, base_name: str) -> list[str]:
80+
xml_str = []
81+
82+
def add_elements(curr_choice: str, starting_id: int, more: str, XXX: bool):
83+
for i in range(3):
84+
element_name = f'{base_name}{curr_choice.upper()*(i+1)}'
85+
environment = jinja2.Environment()
86+
template = environment.from_string(template_str)
87+
xml_str.append(template.render(id=(i + starting_id), name=element_name, choice=curr_choice, more=more, XXX=XXX))
88+
add_elements('a', 1, 'false', False)
89+
add_elements('b', 4, 'true', False)
90+
add_elements('c', 7, 'false', True)
91+
add_elements('d', 10, 'true', True)
92+
93+
return xml_str
94+
95+
# TODO: this setup makes my life easy because it assumes that choice conformances apply only within one table
96+
# if this is not true (ex, you can have choose 1 of a feature or an attribute), then this gets more complex
97+
# in this case we need to have this test evaluate the same choice conformance value between multiple tables, and all
98+
# the conformances need to be assessed for the entire element set.
99+
# I've done it this way specifically so I can hardcode the choice values and test the features, attributes and commands
100+
# separately, even though I load them all at the start.
101+
102+
# Cluster with choices on all elements
103+
# 3 of each element with O.a
104+
# 3 of each element with O.b+
105+
# 3 of each element with [XXX].c
106+
# 3 of each element with [XXX].d+
107+
# 1 element named XXX
108+
109+
110+
def _create_features():
111+
xml = _create_elements(FEATURE_TEMPLATE, 'F')
112+
xxx = (' <feature bit="13" code="XXX" name="XXX" summary="summary">\n'
113+
' <optionalConform />\n'
114+
' </feature>\n')
115+
xml.append(xxx)
116+
return '\n'.join(xml)
117+
118+
119+
def _create_attributes():
120+
xml = _create_elements(ATTRIBUTE_TEMPLATE, "attr")
121+
xxx = (' <attribute id="13" name="XXX" summary="summary">\n'
122+
' <optionalConform />\n'
123+
' </attribute>\n')
124+
xml.append(xxx)
125+
return '\n'.join(xml)
126+
127+
128+
def _create_commands():
129+
xml = _create_elements(COMMAND_TEMPLATE, 'cmd')
130+
xxx = (' <command id="13" name="XXX" summary="summary" direction="commandToServer" response="Y">\n'
131+
' <optionalConform />\n'
132+
' </command>\n')
133+
xml.append(xxx)
134+
return '\n'.join(xml)
135+
136+
137+
def _create_cluster():
138+
environment = jinja2.Environment()
139+
template = environment.from_string(CLUSTER_TEMPLATE)
140+
return template.render(feature_string=_create_features(), attribute_string=_create_attributes(), command_string=_create_commands())
141+
142+
143+
class TestConformanceSupport(MatterBaseTest):
144+
def setup_class(self):
145+
super().setup_class()
146+
147+
clusters: dict[int, XmlCluster] = {}
148+
pure_base_clusters: dict[str, XmlCluster] = {}
149+
ids_by_name: dict[str, int] = {}
150+
problems: list[ProblemNotice] = []
151+
cluster_xml = ElementTree.fromstring(_create_cluster())
152+
add_cluster_data_from_xml(cluster_xml, clusters, pure_base_clusters, ids_by_name, problems)
153+
self.clusters = clusters
154+
# each element type uses 13 IDs from 1-13 (or bits for the features) and we want to test all the combinations
155+
num_elements = 13
156+
ids = range(1, num_elements + 1)
157+
self.all_id_combos = []
158+
combos = []
159+
for r in range(1, num_elements + 1):
160+
combos.extend(list(itertools.combinations(ids, r)))
161+
for combo in combos:
162+
# The first three IDs are all O.a, so we need exactly one for the conformance to be valid
163+
expected_failures = set()
164+
if len(set([1, 2, 3]) & set(combo)) != 1:
165+
expected_failures.add('a')
166+
if len(set([4, 5, 6]) & set(combo)) < 1:
167+
expected_failures.add('b')
168+
# For these, we are checking that choice conformance checkers
169+
# - Correctly report errors and correct cases when the gating feature is ON
170+
# - Do not report any errors when the gating features is off.
171+
# Errors where we incorrectly set disallowed features based on the gating feature are checked
172+
# elsewhere in the cert test in a comprehensive way. We just want to ensure that we are not
173+
# incorrectly reporting choice conformance error as well
174+
if 13 in combo and ((len(set([7, 8, 9]) & set(combo)) != 1)):
175+
expected_failures.add('c')
176+
if 13 in combo and (len(set([10, 11, 12]) & set(combo)) < 1):
177+
expected_failures.add('d')
178+
179+
self.all_id_combos.append((combo, expected_failures))
180+
181+
def _evaluate_problems(self, problems, expected_failures=list[str]):
182+
if len(expected_failures) != len(problems):
183+
print(problems)
184+
asserts.assert_equal(len(expected_failures), len(problems), 'Unexpected number of choice conformance problems')
185+
actual_failures = set([p.choice.marker for p in problems])
186+
asserts.assert_equal(actual_failures, expected_failures, "Mismatch between failures")
187+
188+
def test_features(self):
189+
def make_feature_map(combo: tuple[int]) -> int:
190+
feature_map = 0
191+
for bit in combo:
192+
feature_map += pow(2, bit)
193+
return feature_map
194+
195+
for combo, expected_failures in self.all_id_combos:
196+
problems = evaluate_feature_choice_conformance(0, 1, self.clusters, make_feature_map(combo), [], [])
197+
self._evaluate_problems(problems, expected_failures)
198+
199+
def test_attributes(self):
200+
for combo, expected_failures in self.all_id_combos:
201+
problems = evaluate_attribute_choice_conformance(0, 1, self.clusters, 0, list(combo), [])
202+
self._evaluate_problems(problems, expected_failures)
203+
204+
def test_commands(self):
205+
for combo, expected_failures in self.all_id_combos:
206+
problems = evaluate_command_choice_conformance(0, 1, self.clusters, 0, [], list(combo))
207+
self._evaluate_problems(problems, expected_failures)
208+
209+
210+
if __name__ == "__main__":
211+
default_matter_test_main()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
from chip.tlv import uint
2+
from conformance_support import Choice, ConformanceDecisionWithChoice
3+
from global_attribute_ids import GlobalAttributeIds
4+
from matter_testing_support import AttributePathLocation, ProblemNotice, ProblemSeverity
5+
from spec_parsing_support import XmlCluster
6+
7+
8+
class ChoiceConformanceProblemNotice(ProblemNotice):
9+
def __init__(self, location: AttributePathLocation, choice: Choice, count: int):
10+
problem = f'Problem with choice conformance {choice} - {count} selected'
11+
super().__init__(test_name='Choice conformance', location=location, severity=ProblemSeverity.ERROR, problem=problem, spec_location='')
12+
self.choice = choice
13+
self.count = count
14+
15+
16+
def _add_to_counts_if_required(conformance_decision_with_choice: ConformanceDecisionWithChoice, element_present: bool, counts: dict[Choice, int]):
17+
choice = conformance_decision_with_choice.choice
18+
if not choice:
19+
return
20+
counts[choice] = counts.get(choice, 0)
21+
if element_present:
22+
counts[choice] += 1
23+
24+
25+
def _evaluate_choices(location: AttributePathLocation, counts: dict[Choice, int]) -> list[ChoiceConformanceProblemNotice]:
26+
problems: list[ChoiceConformanceProblemNotice] = []
27+
for choice, count in counts.items():
28+
if count == 0 or (not choice.more and count > 1):
29+
problems.append(ChoiceConformanceProblemNotice(location, choice, count))
30+
return problems
31+
32+
33+
def evaluate_feature_choice_conformance(endpoint_id: int, cluster_id: int, xml_clusters: dict[int, XmlCluster], feature_map: uint, attribute_list: list[uint], all_command_list: list[uint]) -> list[ChoiceConformanceProblemNotice]:
34+
all_features = [1 << i for i in range(32)]
35+
all_features = [f for f in all_features if f in xml_clusters[cluster_id].features.keys()]
36+
37+
# Other pieces of the 10.2 test check for unknown features, so just remove them here to check choice conformance
38+
counts: dict[Choice, int] = {}
39+
for f in all_features:
40+
xml_feature = xml_clusters[cluster_id].features[f]
41+
conformance_decision_with_choice = xml_feature.conformance(feature_map, attribute_list, all_command_list)
42+
_add_to_counts_if_required(conformance_decision_with_choice, (feature_map & f), counts)
43+
44+
location = AttributePathLocation(endpoint_id=endpoint_id, cluster_id=cluster_id,
45+
attribute_id=GlobalAttributeIds.FEATURE_MAP_ID)
46+
return _evaluate_choices(location, counts)
47+
48+
49+
def evaluate_attribute_choice_conformance(endpoint_id: int, cluster_id: int, xml_clusters: dict[int, XmlCluster], feature_map: uint, attribute_list: list[uint], all_command_list: list[uint]) -> list[ChoiceConformanceProblemNotice]:
50+
all_attributes = xml_clusters[cluster_id].attributes.keys()
51+
52+
counts: dict[Choice, int] = {}
53+
for attribute_id in all_attributes:
54+
conformance_decision_with_choice = xml_clusters[cluster_id].attributes[attribute_id].conformance(
55+
feature_map, attribute_list, all_command_list)
56+
_add_to_counts_if_required(conformance_decision_with_choice, attribute_id in attribute_list, counts)
57+
58+
location = AttributePathLocation(endpoint_id=endpoint_id, cluster_id=cluster_id,
59+
attribute_id=GlobalAttributeIds.ATTRIBUTE_LIST_ID)
60+
return _evaluate_choices(location, counts)
61+
62+
63+
def evaluate_command_choice_conformance(endpoint_id: int, cluster_id: int, xml_clusters: dict[int, XmlCluster], feature_map: uint, attribute_list: list[uint], all_command_list: list[uint]) -> list[ChoiceConformanceProblemNotice]:
64+
all_commands = xml_clusters[cluster_id].accepted_commands.keys()
65+
66+
counts: dict[Choice, int] = {}
67+
for command_id in all_commands:
68+
conformance_decision_with_choice = xml_clusters[cluster_id].accepted_commands[command_id].conformance(
69+
feature_map, attribute_list, all_command_list)
70+
_add_to_counts_if_required(conformance_decision_with_choice, command_id in all_command_list, counts)
71+
72+
location = AttributePathLocation(endpoint_id=endpoint_id, cluster_id=cluster_id,
73+
attribute_id=GlobalAttributeIds.ACCEPTED_COMMAND_LIST_ID)
74+
return _evaluate_choices(location, counts)

0 commit comments

Comments
 (0)