Skip to content

Commit dbe64c1

Browse files
committed
more things
1 parent 598fc94 commit dbe64c1

File tree

2 files changed

+110
-8
lines changed

2 files changed

+110
-8
lines changed

src/python_testing/matter_testing_support.py

+36-6
Original file line numberDiff line numberDiff line change
@@ -1892,22 +1892,52 @@ def has_feature(cluster: ClusterObjects.ClusterObjectDescriptor, feature: IntFla
18921892
return partial(_has_feature, cluster=cluster, feature=feature)
18931893

18941894

1895-
async def should_run_test_on_endpoint(self: MatterBaseTest, accept_function: EndpointCheckFunction) -> list[uint]:
1895+
async def _get_all_matching_endpoints(self: MatterBaseTest, accept_function: EndpointCheckFunction) -> list[uint]:
1896+
""" Returns a list of endpoints matching the accept condition. """
1897+
wildcard = await self.default_controller.Read(self.dut_node_id, [(Clusters.Descriptor), Attribute.AttributePath(None, None, GlobalAttributeIds.ATTRIBUTE_LIST_ID), Attribute.AttributePath(None, None, GlobalAttributeIds.FEATURE_MAP_ID), Attribute.AttributePath(None, None, GlobalAttributeIds.ACCEPTED_COMMAND_LIST_ID)])
1898+
matching = [e for e in wildcard.attributes.keys() if accept_function(wildcard, e)]
1899+
return matching
1900+
1901+
1902+
async def should_run_test_on_endpoint(self: MatterBaseTest, accept_function: EndpointCheckFunction) -> bool:
18961903
""" Helper function for the run_if_endpoint_matches decorator.
18971904
1898-
Returns a list of endpoints on which the test should be run given the accept_function for the test.
1905+
Returns True if self.matter_test_config.endpoint matches the accept function.
18991906
"""
1900-
19011907
if self.matter_test_config.endpoint is None:
19021908
msg = """
19031909
The --endpoint flag is required for this test.
19041910
"""
19051911
asserts.fail(msg)
1912+
matching = await (_get_all_matching_endpoints(self, accept_function))
1913+
return self.matter_test_config.endpoint in matching
19061914

1907-
wildcard = await self.default_controller.Read(self.dut_node_id, [(Clusters.Descriptor), Attribute.AttributePath(None, None, GlobalAttributeIds.ATTRIBUTE_LIST_ID), Attribute.AttributePath(None, None, GlobalAttributeIds.FEATURE_MAP_ID), Attribute.AttributePath(None, None, GlobalAttributeIds.ACCEPTED_COMMAND_LIST_ID)])
1908-
matching = [e for e in wildcard.attributes.keys() if accept_function(wildcard, e)]
19091915

1910-
return self.matter_test_config.endpoint in matching
1916+
def run_on_singleton_matching_endpoint(accept_function: EndpointCheckFunction):
1917+
""" Test decorator for a test that needs to be run on the endpoint that matches the given accept function.
1918+
1919+
This decorator should be used for tests where the endpoint is not known a-priori (dynamic endpoints).
1920+
Note that currently this test is limited to devices with a SINGLE matching endpoint.
1921+
"""
1922+
def run_on_singleton_matching_endpoint_internal(body):
1923+
def matching_runner(self: MatterBaseTest, *args, **kwargs):
1924+
runner_with_timeout = asyncio.wait_for(_get_all_matching_endpoints(self, accept_function), timeout=30)
1925+
matching = asyncio.run(runner_with_timeout)
1926+
asserts.assert_less_equal(len(matching), 1, "More than one matching endpoint found for singleton test.")
1927+
if not matching:
1928+
logging.info("Test is not applicable to any endpoint - skipping test")
1929+
asserts.skip('No endpoint matches test requirements')
1930+
return
1931+
# Exceptions should flow through, hence no except block
1932+
try:
1933+
old_endpoint = self.matter_test_config.endpoint
1934+
self.matter_test_config.endpoint = matching[0]
1935+
logging.info(f'Running test on endpoint {self.matter_test_config.endpoint}')
1936+
_async_runner(body, self, *args, **kwargs)
1937+
finally:
1938+
self.matter_test_config.endpoint = old_endpoint
1939+
return matching_runner
1940+
return run_on_singleton_matching_endpoint_internal
19111941

19121942

19131943
def run_if_endpoint_matches(accept_function: EndpointCheckFunction):

src/python_testing/test_testing/TestDecorators.py

+74-2
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,13 @@
3333

3434
try:
3535
from matter_testing_support import (MatterBaseTest, MatterTestConfig, async_test_body, has_attribute,
36-
has_cluster, has_feature, run_if_endpoint_matches, should_run_test_on_endpoint)
36+
has_cluster, has_feature, run_if_endpoint_matches, run_on_singleton_matching_endpoint,
37+
should_run_test_on_endpoint)
3738
except ImportError:
3839
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
3940
from matter_testing_support import (MatterBaseTest, MatterTestConfig, async_test_body, has_attribute,
40-
has_cluster, has_feature, run_if_endpoint_matches, should_run_test_on_endpoint)
41+
has_cluster, has_feature, run_if_endpoint_matches, run_on_singleton_matching_endpoint,
42+
should_run_test_on_endpoint)
4143

4244
from typing import Optional
4345

@@ -222,6 +224,18 @@ async def test_fail_on_ep1(self):
222224
if self.matter_test_config.endpoint == 1:
223225
asserts.fail("Expected failure")
224226

227+
@run_on_singleton_matching_endpoint(has_cluster(Clusters.OnOff))
228+
async def test_run_on_singleton_matching_endpoint(self):
229+
pass
230+
231+
@run_on_singleton_matching_endpoint(has_cluster(Clusters.OnOff))
232+
async def test_run_on_singleton_matching_endpoint_failure(self):
233+
asserts.fail("Expected failure")
234+
235+
@run_on_singleton_matching_endpoint(has_attribute(Clusters.OnOff.Attributes.OffWaitTime))
236+
async def test_no_run_on_singleton_matching_endpoint(self):
237+
pass
238+
225239

226240
def main():
227241
failures = []
@@ -308,6 +322,64 @@ def check_all_skipped(test_name: str):
308322
if ok:
309323
failures.append(f"Did not get expected test assertion on {test_name}")
310324

325+
def run_singleton_dynamic(test_name: str, cluster_list: list[int]) -> tuple[bool, DecoratorTestRunnerHooks]:
326+
nonlocal failures
327+
read_resp = get_clusters(cluster_list)
328+
test_runner.set_test('TestDecorators.py', 'TestDecorators', test_name)
329+
test_runner.set_test_config(MatterTestConfig(endpoint=2))
330+
hooks = DecoratorTestRunnerHooks()
331+
ok = test_runner.run_test_with_mock_read(read_resp, hooks)
332+
# for all tests, we need to ensure the endpoint was set back to the prior values
333+
if test_runner.config.endpoint != 2:
334+
failures.append(f"Dynamic tests {test_name} with clusters {cluster_list} did not set endpoint back to prior")
335+
# All tests should have a start and a stop
336+
started_ok = len(hooks.started) == 1
337+
stopped_ok = hooks.stopped == 1
338+
if not started_ok or not stopped_ok:
339+
failures.append(
340+
f'Hooks failure on {test_name}, Runs: {hooks.started}, skips: {hooks.skipped} stops: {hooks.stopped}')
341+
return ok, hooks
342+
343+
def expect_success_dynamic(test_name: str, cluster_list: list[int]):
344+
ok, hooks = run_singleton_dynamic(test_name, [0])
345+
if not ok:
346+
failures.append(f"Unexpected failure on {test_name} with cluster list {cluster_list}")
347+
if hooks.skipped:
348+
failures.append(f'Unexpected skip call on {test_name} with cluster list {cluster_list}')
349+
350+
def expect_failure_dynamic(test_name: str, cluster_list: list[int]):
351+
ok = run_singleton_dynamic(test_name, [0])
352+
if ok:
353+
failures.append(f"Unexpected success on {test_name} with cluster list {cluster_list}")
354+
if hooks.skipped:
355+
# We don't expect a skip call because the test actually failed.
356+
failures.append(f'Skip called for {test_name} with cluster list {cluster_list}')
357+
358+
def expect_skip_dynamic(test_name: str, cluster_list: list[int]):
359+
ok = run_singleton_dynamic(test_name, [0])
360+
if not ok:
361+
failures.append(f"Unexpected failure on {test_name} with cluster list {cluster_list}")
362+
if not hooks.skipped:
363+
# We don't expect a skip call because the test actually failed.
364+
failures.append(f'Skip not called for {test_name} with cluster list {cluster_list}')
365+
366+
test_name = 'test_run_on_singleton_matching_endpoint'
367+
expect_success_dynamic(test_name, [0])
368+
expect_success_dynamic(test_name, [1])
369+
# expect failure because there is more than 1 endpoint
370+
expect_failure_dynamic(test_name, [0, 1])
371+
372+
test_name = 'test_run_on_singleton_matching_endpoint_failure'
373+
expect_failure_dynamic(test_name, [0])
374+
expect_failure_dynamic(test_name, [1])
375+
expect_failure_dynamic(test_name, [0, 1])
376+
377+
test_name = 'test_no_run_on_singleton_matching_endpoint'
378+
# no failure, expect skips on single endpoints, expect asserts on multiple matching
379+
expect_skip_dynamic(test_name, [0])
380+
expect_skip_dynamic(test_name, [1])
381+
expect_failure_dynamic(test_name, [0, 1])
382+
311383
test_runner.Shutdown()
312384
print(
313385
f"Test of Decorators: test response incorrect: {len(failures)}")

0 commit comments

Comments
 (0)