Skip to content

Commit 906d6dc

Browse files
Move CommissionDeviceTest class to commissioning.py (project-chip#39478)
* Moved class CommissionDeviceTest from matter_testing to commission.py. Added class global_stash to have stashed attirbutes outside matter_testing. Updated runner imports. * fix: removed unused lines * Added docstring. * fix: gemini code suggestions. * Removed clusterobjects from commissioning.py. Renamed get_setup_payload_info for get_setup_payload_info_config at commissioning.py * Added # chipstack-ok at get_setup_payload_info_config
1 parent 66ed034 commit 906d6dc

File tree

5 files changed

+165
-87
lines changed

5 files changed

+165
-87
lines changed

src/python_testing/matter_testing_infrastructure/BUILD.gn

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ pw_python_package("chip-testing-module") {
4343
"chip/testing/conversions.py",
4444
"chip/testing/decorators.py",
4545
"chip/testing/global_attribute_ids.py",
46+
"chip/testing/global_stash.py",
4647
"chip/testing/matchers.py",
4748
"chip/testing/matter_asserts.py",
4849
"chip/testing/matter_testing.py",

src/python_testing/matter_testing_infrastructure/chip/testing/commissioning.py

Lines changed: 101 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,14 @@
2121

2222
import logging
2323
from dataclasses import dataclass
24-
from typing import List, Optional
24+
from typing import Any, List, Optional
2525

26+
import chip.testing.global_stash as global_stash
2627
from chip import ChipDeviceCtrl, discovery
2728
from chip.ChipDeviceCtrl import CommissioningParameters
28-
from chip.clusters import ClusterObjects as ClusterObjects
2929
from chip.exceptions import ChipStackError
30+
from chip.setup_payload import SetupPayload
31+
from mobly import asserts, base_test, signals
3032

3133
logger = logging.getLogger("matter.python_testing")
3234
logger.setLevel(logging.INFO)
@@ -211,3 +213,100 @@ async def commission_devices(
211213
commissioned.append(await commission_device(dev_ctrl, node_id, setup_payload, commissioning_info))
212214

213215
return all(commissioned)
216+
217+
218+
def get_setup_payload_info_config(matter_test_config: Any) -> List[SetupPayloadInfo]:
219+
"""
220+
Get and builds the payload info provided in the execution.
221+
222+
Args:
223+
matter_test_config: Matter test configuration object
224+
225+
Returns:
226+
List[SetupPayloadInfo]: List of Payload used by the test case
227+
"""
228+
setup_payloads = []
229+
for qr_code in matter_test_config.qr_code_content:
230+
try:
231+
setup_payloads.append(SetupPayload().ParseQrCode(qr_code))
232+
except ChipStackError: # chipstack-ok: This disables ChipStackError linter check. Can not use 'with' because it is not expected to fail
233+
asserts.fail(f"QR code '{qr_code} failed to parse properly as a Matter setup code.")
234+
235+
for manual_code in matter_test_config.manual_code:
236+
try:
237+
setup_payloads.append(SetupPayload().ParseManualPairingCode(manual_code))
238+
except ChipStackError: # chipstack-ok: This disables ChipStackError linter check. Can not use 'with' because it is not expected to fail
239+
asserts.fail(
240+
f"Manual code code '{manual_code}' failed to parse properly as a Matter setup code. Check that all digits are correct and length is 11 or 21 characters.")
241+
242+
infos = []
243+
for setup_payload in setup_payloads:
244+
info = SetupPayloadInfo()
245+
info.passcode = setup_payload.setup_passcode
246+
if setup_payload.short_discriminator is not None:
247+
info.filter_type = discovery.FilterType.SHORT_DISCRIMINATOR
248+
info.filter_value = setup_payload.short_discriminator
249+
else:
250+
info.filter_type = discovery.FilterType.LONG_DISCRIMINATOR
251+
info.filter_value = setup_payload.long_discriminator
252+
infos.append(info)
253+
254+
num_passcodes = 0 if matter_test_config.setup_passcodes is None else len(matter_test_config.setup_passcodes)
255+
num_discriminators = 0 if matter_test_config.discriminators is None else len(matter_test_config.discriminators)
256+
asserts.assert_equal(num_passcodes, num_discriminators, "Must have same number of discriminators as passcodes")
257+
if matter_test_config.discriminators:
258+
for idx, discriminator in enumerate(matter_test_config.discriminators):
259+
info = SetupPayloadInfo()
260+
info.passcode = matter_test_config.setup_passcodes[idx]
261+
info.filter_type = DiscoveryFilterType.LONG_DISCRIMINATOR
262+
info.filter_value = discriminator
263+
infos.append(info)
264+
265+
return infos
266+
267+
268+
class CommissionDeviceTest(base_test.BaseTestClass):
269+
"""Test class auto-injected at the start of test list to commission a device when requested"""
270+
271+
def __init__(self, *args):
272+
super().__init__(*args)
273+
# This class is used to commission the device so is set to True
274+
self.is_commissioning = True
275+
# Save the stashed values into attributes to avoid mobly conflic with ctypes when mobly performs copy().
276+
test_config = args[0]
277+
self.default_controller = test_config.user_params['default_controller']
278+
meta_config = test_config.user_params['meta_config']
279+
self.dut_node_ids: List[int] = meta_config['dut_node_ids']
280+
self.commissioning_info: CommissioningInfo = CommissioningInfo(
281+
commissionee_ip_address_just_for_testing=meta_config['commissionee_ip_address_just_for_testing'],
282+
commissioning_method=meta_config['commissioning_method'],
283+
thread_operational_dataset=meta_config['thread_operational_dataset'],
284+
wifi_passphrase=meta_config['wifi_passphrase'],
285+
wifi_ssid=meta_config['wifi_ssid'],
286+
tc_version_to_simulate=meta_config['tc_version_to_simulate'],
287+
tc_user_response_to_simulate=meta_config['tc_user_response_to_simulate'],
288+
)
289+
self.setup_payloads: List[SetupPayloadInfo] = get_setup_payload_info_config(
290+
global_stash.unstash_globally(test_config.user_params['matter_test_config']))
291+
292+
def test_run_commissioning(self):
293+
"""This method is the test called by mobly, which try to commission the device until is complete or raises an error.
294+
Raises:
295+
signals.TestAbortAll: Failed to commission node(s)
296+
"""
297+
if not self.event_loop.run_until_complete(commission_devices(
298+
dev_ctrl=self.default_controller,
299+
dut_node_ids=self.dut_node_ids,
300+
setup_payloads=self.setup_payloads,
301+
commissioning_info=self.commissioning_info
302+
)):
303+
raise signals.TestAbortAll("Failed to commission node(s)")
304+
305+
# Default controller is used by commission_devices
306+
@property
307+
def default_controller(self) -> ChipDeviceCtrl.ChipDeviceController:
308+
return global_stash.unstash_globally(self._default_controller)
309+
310+
@default_controller.setter
311+
def default_controller(self, tmp_default_controller):
312+
self._default_controller = tmp_default_controller
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#
2+
# Copyright (c) 2025 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+
"""
19+
This module contains functions designed to handle config values of ctypes objects as mobly cannot deal with those.
20+
The methods just use a global dict of uuid -> object to recover items stashed by reference.
21+
"""
22+
23+
import uuid
24+
from typing import Any
25+
26+
# Mobly cannot deal with user config passing of ctypes objects,
27+
# so we use this dict of uuid -> object to recover items stashed
28+
# by reference.
29+
_GLOBAL_DATA = {}
30+
31+
32+
def stash_globally(o: object) -> str:
33+
unique_id = str(uuid.uuid1())
34+
_GLOBAL_DATA[unique_id] = o
35+
return unique_id
36+
37+
38+
def unstash_globally(id: str) -> Any:
39+
return _GLOBAL_DATA.get(id)

src/python_testing/matter_testing_infrastructure/chip/testing/matter_testing.py

Lines changed: 15 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
import threading
3333
import time
3434
import typing
35-
import uuid
3635
from binascii import unhexlify
3736
from dataclasses import asdict as dataclass_asdict
3837
from dataclasses import dataclass, field
@@ -59,16 +58,15 @@
5958
import chip.clusters as Clusters
6059
import chip.logging
6160
import chip.native
62-
from chip import discovery
61+
import chip.testing.global_stash as global_stash
6362
from chip.ChipStack import ChipStack
64-
from chip.clusters import Attribute
65-
from chip.clusters import ClusterObjects as ClusterObjects
63+
from chip.clusters import Attribute, ClusterObjects
6664
from chip.clusters.Attribute import EventReadResult, SubscriptionTransaction, TypedAttributePath
67-
from chip.exceptions import ChipStackError
6865
from chip.interaction_model import InteractionModelError, Status
6966
from chip.setup_payload import SetupPayload
7067
from chip.storage import PersistentStorage
71-
from chip.testing.commissioning import CommissioningInfo, CustomCommissioningParameters, SetupPayloadInfo, commission_devices
68+
from chip.testing.commissioning import (CommissioningInfo, CustomCommissioningParameters, SetupPayloadInfo, commission_devices,
69+
get_setup_payload_info_config)
7270
from chip.testing.global_attribute_ids import GlobalAttributeIds
7371
from chip.testing.pics import read_pics_from_file
7472
from chip.testing.runner import TestRunnerHooks, TestStep
@@ -90,21 +88,6 @@
9088
_DEFAULT_DUT_NODE_ID = 0x12344321
9189
_DEFAULT_TRUST_ROOT_INDEX = 1
9290

93-
# Mobly cannot deal with user config passing of ctypes objects,
94-
# so we use this dict of uuid -> object to recover items stashed
95-
# by reference.
96-
_GLOBAL_DATA = {}
97-
98-
99-
def stash_globally(o: object) -> str:
100-
id = str(uuid.uuid1())
101-
_GLOBAL_DATA[id] = o
102-
return id
103-
104-
105-
def unstash_globally(id: str) -> Any:
106-
return _GLOBAL_DATA.get(id)
107-
10891

10992
def default_paa_rootstore_from_root(root_path: pathlib.Path) -> Optional[pathlib.Path]:
11093
"""Attempt to find a PAA trust store following SDK convention at `root_path`
@@ -1040,23 +1023,23 @@ def default_timeout(self) -> int:
10401023

10411024
@property
10421025
def runner_hook(self) -> TestRunnerHooks:
1043-
return unstash_globally(self.user_params.get("hooks"))
1026+
return global_stash.unstash_globally(self.user_params.get("hooks"))
10441027

10451028
@property
10461029
def matter_test_config(self) -> MatterTestConfig:
1047-
return unstash_globally(self.user_params.get("matter_test_config"))
1030+
return global_stash.unstash_globally(self.user_params.get("matter_test_config"))
10481031

10491032
@property
10501033
def default_controller(self) -> ChipDeviceCtrl.ChipDeviceController:
1051-
return unstash_globally(self.user_params.get("default_controller"))
1034+
return global_stash.unstash_globally(self.user_params.get("default_controller"))
10521035

10531036
@property
10541037
def matter_stack(self) -> MatterStackState:
1055-
return unstash_globally(self.user_params.get("matter_stack"))
1038+
return global_stash.unstash_globally(self.user_params.get("matter_stack"))
10561039

10571040
@property
10581041
def certificate_authority_manager(self) -> chip.CertificateAuthority.CertificateAuthorityManager:
1059-
return unstash_globally(self.user_params.get("certificate_authority_manager"))
1042+
return global_stash.unstash_globally(self.user_params.get("certificate_authority_manager"))
10601043

10611044
@property
10621045
def dut_node_id(self) -> int:
@@ -1612,44 +1595,12 @@ def step(self, step: typing.Union[int, str]):
16121595
self.step_skipped = False
16131596

16141597
def get_setup_payload_info(self) -> List[SetupPayloadInfo]:
1615-
setup_payloads = []
1616-
for qr_code in self.matter_test_config.qr_code_content:
1617-
try:
1618-
setup_payloads.append(SetupPayload().ParseQrCode(qr_code))
1619-
except ChipStackError: # chipstack-ok: This disables ChipStackError linter check. Can not use 'with' because it is not expected to fail
1620-
asserts.fail(f"QR code '{qr_code} failed to parse properly as a Matter setup code.")
1621-
1622-
for manual_code in self.matter_test_config.manual_code:
1623-
try:
1624-
setup_payloads.append(SetupPayload().ParseManualPairingCode(manual_code))
1625-
except ChipStackError: # chipstack-ok: This disables ChipStackError linter check. Can not use 'with' because it is not expected to fail
1626-
asserts.fail(
1627-
f"Manual code code '{manual_code}' failed to parse properly as a Matter setup code. Check that all digits are correct and length is 11 or 21 characters.")
1628-
1629-
infos = []
1630-
for setup_payload in setup_payloads:
1631-
info = SetupPayloadInfo()
1632-
info.passcode = setup_payload.setup_passcode
1633-
if setup_payload.short_discriminator is not None:
1634-
info.filter_type = discovery.FilterType.SHORT_DISCRIMINATOR
1635-
info.filter_value = setup_payload.short_discriminator
1636-
else:
1637-
info.filter_type = discovery.FilterType.LONG_DISCRIMINATOR
1638-
info.filter_value = setup_payload.long_discriminator
1639-
infos.append(info)
1640-
1641-
num_passcodes = 0 if self.matter_test_config.setup_passcodes is None else len(self.matter_test_config.setup_passcodes)
1642-
num_discriminators = 0 if self.matter_test_config.discriminators is None else len(self.matter_test_config.discriminators)
1643-
asserts.assert_equal(num_passcodes, num_discriminators, "Must have same number of discriminators as passcodes")
1644-
if self.matter_test_config.discriminators:
1645-
for idx, discriminator in enumerate(self.matter_test_config.discriminators):
1646-
info = SetupPayloadInfo()
1647-
info.passcode = self.matter_test_config.setup_passcodes[idx]
1648-
info.filter_type = DiscoveryFilterType.LONG_DISCRIMINATOR
1649-
info.filter_value = discriminator
1650-
infos.append(info)
1651-
1652-
return infos
1598+
"""
1599+
Get and builds the payload info provided in the execution.
1600+
Returns:
1601+
List[SetupPayloadInfo]: List of Payload used by the test case
1602+
"""
1603+
return get_setup_payload_info_config(self.matter_test_config)
16531604

16541605
def wait_for_user_input(self,
16551606
prompt_msg: str,
@@ -2196,18 +2147,6 @@ async def _get_all_matching_endpoints(self: MatterBaseTest, accept_function: End
21962147
return matching
21972148

21982149

2199-
class CommissionDeviceTest(MatterBaseTest):
2200-
"""Test class auto-injected at the start of test list to commission a device when requested"""
2201-
2202-
def __init__(self, *args):
2203-
super().__init__(*args)
2204-
self.is_commissioning = True
2205-
2206-
def test_run_commissioning(self):
2207-
if not self.event_loop.run_until_complete(self.commission_devices()):
2208-
raise signals.TestAbortAll("Failed to commission node(s)")
2209-
2210-
22112150
# TODO(#37537): Remove these temporary aliases after transition period
22122151
type_matches = matchers.is_type
22132152
utc_time_in_matter_epoch = timeoperations.utc_time_in_matter_epoch

src/python_testing/matter_testing_infrastructure/chip/testing/runner.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
from typing import Optional
2828
from unittest.mock import MagicMock
2929

30+
import chip.testing.global_stash as global_stash
3031
from chip.clusters import Attribute
3132
from mobly import signals
3233
from mobly.config_parser import ENV_MOBLY_LOGPATH, TestRunConfig
@@ -311,15 +312,15 @@ def run_tests_no_exit(
311312
# Lazy import to avoid circular dependency
312313
from typing import TYPE_CHECKING
313314

314-
from chip.testing.matter_testing import MatterStackState, stash_globally
315+
from chip.testing.matter_testing import MatterStackState
315316
if TYPE_CHECKING:
316-
from chip.testing.matter_testing import CommissionDeviceTest
317+
from chip.testing.commissioning import CommissionDeviceTest
317318
else:
318319
CommissionDeviceTest = None # Initial placeholder
319320

320321
# Actual runtime import
321322
if CommissionDeviceTest is None:
322-
from chip.testing.matter_testing import CommissionDeviceTest
323+
from chip.testing.commissioning import CommissionDeviceTest
323324

324325
# NOTE: It's not possible to pass event loop via Mobly TestRunConfig user params, because the
325326
# Mobly deep copies the user params before passing them to the test class and the event
@@ -347,7 +348,7 @@ def run_tests_no_exit(
347348
for destination in matter_test_config.trace_to:
348349
tracing_ctx.StartFromString(destination)
349350

350-
test_config.user_params["matter_stack"] = stash_globally(stack)
351+
test_config.user_params["matter_stack"] = global_stash.stash_globally(stack)
351352

352353
# TODO: Steer to right FabricAdmin!
353354
# TODO: If CASE Admin Subject is a CAT tag range, then make sure to
@@ -360,17 +361,16 @@ def run_tests_no_exit(
360361
catTags=matter_test_config.controller_cat_tags,
361362
dacRevocationSetPath=matter_test_config.dac_revocation_set_path if matter_test_config.dac_revocation_set_path else ""
362363
)
363-
test_config.user_params["default_controller"] = stash_globally(
364+
test_config.user_params["default_controller"] = global_stash.stash_globally(
364365
default_controller)
365-
366-
test_config.user_params["matter_test_config"] = stash_globally(
366+
test_config.user_params["matter_test_config"] = global_stash.stash_globally(
367367
matter_test_config)
368-
test_config.user_params["hooks"] = stash_globally(hooks)
368+
test_config.user_params["hooks"] = global_stash.stash_globally(hooks)
369369

370370
# Execute the test class with the config
371371
ok = True
372372

373-
test_config.user_params["certificate_authority_manager"] = stash_globally(
373+
test_config.user_params["certificate_authority_manager"] = global_stash.stash_globally(
374374
stack.certificate_authority_manager)
375375

376376
# Execute the test class with the config

0 commit comments

Comments
 (0)