From 63959caf97e78b999ccbe81fa6152ee1ad9e001f Mon Sep 17 00:00:00 2001 From: Song GUO Date: Tue, 23 Jul 2024 22:06:41 +0800 Subject: [PATCH] [icd] Add WaitForActive testing in Cirque (#34200) * [icd] Add WaitForActive testing in Cirque * Update * Update * Fix test * fix test * fix * Fix * Restyled by autopep8 --------- Co-authored-by: Restyled.io --- examples/lit-icd-app/linux/main.cpp | 10 ++ scripts/tests/cirque_tests.sh | 1 + .../ChipDeviceController-ScriptBinding.cpp | 2 + src/controller/python/chip/ChipDeviceCtrl.py | 11 +- .../python/test/test_scripts/base.py | 20 ++- .../test_scripts/icd_wait_for_device_test.py | 148 ++++++++++++++++++ .../linux-cirque/IcdWaitForActiveTest.py | 117 ++++++++++++++ 7 files changed, 303 insertions(+), 6 deletions(-) create mode 100755 src/controller/python/test/test_scripts/icd_wait_for_device_test.py create mode 100755 src/test_driver/linux-cirque/IcdWaitForActiveTest.py diff --git a/examples/lit-icd-app/linux/main.cpp b/examples/lit-icd-app/linux/main.cpp index 1f4031af407d42..601cc22d3d2af9 100644 --- a/examples/lit-icd-app/linux/main.cpp +++ b/examples/lit-icd-app/linux/main.cpp @@ -19,16 +19,26 @@ #include "AppMain.h" #include +#include "system/SystemClock.h" + using namespace chip; using namespace chip::app; +using namespace chip::System::Clock::Literals; void ApplicationInit() {} void ApplicationShutdown() {} +void notifyIcdActive(System::Layer * layer, void *) +{ + ICDNotifier::GetInstance().NotifyNetworkActivityNotification(); + DeviceLayer::SystemLayer().StartTimer(10000_ms32, notifyIcdActive, nullptr); +} + int main(int argc, char * argv[]) { VerifyOrDie(ChipLinuxAppInit(argc, argv) == 0); + DeviceLayer::SystemLayer().StartTimer(10000_ms32, notifyIcdActive, nullptr); ChipLinuxAppMainLoop(); return 0; } diff --git a/scripts/tests/cirque_tests.sh b/scripts/tests/cirque_tests.sh index b6fa0eb7f64e56..a8be290f6c7e2c 100755 --- a/scripts/tests/cirque_tests.sh +++ b/scripts/tests/cirque_tests.sh @@ -44,6 +44,7 @@ CIRQUE_TESTS=( "MobileDeviceTest" "CommissioningTest" "InteractionModelTest" + "IcdWaitForActiveTest" "SplitCommissioningTest" "CommissioningFailureTest" "CommissioningFailureOnReportTest" diff --git a/src/controller/python/ChipDeviceController-ScriptBinding.cpp b/src/controller/python/ChipDeviceController-ScriptBinding.cpp index 556ae2c6943325..d4603efe05e469 100644 --- a/src/controller/python/ChipDeviceController-ScriptBinding.cpp +++ b/src/controller/python/ChipDeviceController-ScriptBinding.cpp @@ -584,6 +584,7 @@ struct IcdRegistrationParameters uint64_t checkInNodeId; uint64_t monitoredSubject; uint32_t stayActiveMsec; + uint8_t clientType; }; PyChipError pychip_DeviceController_SetIcdRegistrationParameters(bool enabled, const IcdRegistrationParameters * params) @@ -622,6 +623,7 @@ PyChipError pychip_DeviceController_SetIcdRegistrationParameters(bool enabled, c sCommissioningParameters.SetICDCheckInNodeId(params->checkInNodeId); sCommissioningParameters.SetICDMonitoredSubject(params->monitoredSubject); sCommissioningParameters.SetICDRegistrationStrategy(ICDRegistrationStrategy::kBeforeComplete); + sCommissioningParameters.SetICDClientType(static_cast(params->clientType)); return ToPyChipError(CHIP_NO_ERROR); } diff --git a/src/controller/python/chip/ChipDeviceCtrl.py b/src/controller/python/chip/ChipDeviceCtrl.py index 4e1c5d6678ed3e..0e568803b1e523 100644 --- a/src/controller/python/chip/ChipDeviceCtrl.py +++ b/src/controller/python/chip/ChipDeviceCtrl.py @@ -107,13 +107,14 @@ class ICDRegistrationParameters: checkInNodeId: typing.Optional[int] monitoredSubject: typing.Optional[int] stayActiveMs: typing.Optional[int] + clientType: typing.Optional[Clusters.IcdManagement.Enums.ClientTypeEnum] class CStruct(Structure): _fields_ = [('symmetricKey', c_char_p), ('symmetricKeyLength', c_size_t), ('checkInNodeId', - c_uint64), ('monitoredSubject', c_uint64), ('stayActiveMsec', c_uint32)] + c_uint64), ('monitoredSubject', c_uint64), ('stayActiveMsec', c_uint32), ('clientType', c_uint8)] def to_c(self): - return ICDRegistrationParameters.CStruct(self.symmetricKey, len(self.symmetricKey), self.checkInNodeId, self.monitoredSubject, self.stayActiveMs) + return ICDRegistrationParameters.CStruct(self.symmetricKey, len(self.symmetricKey), self.checkInNodeId, self.monitoredSubject, self.stayActiveMs, self.clientType.value) @_DeviceAvailableCallbackFunct @@ -207,8 +208,7 @@ def OnCheckInCallback(nodeid): RegisterOnActiveCallback(scopedNodeId, OnCheckInCallback) try: - async with asyncio.timeout(timeoutSeconds): - await future + asyncio.wait_for(future, timeout=timeoutSeconds) finally: UnregisterOnActiveCallback(scopedNodeId, OnCheckInCallback) @@ -2018,7 +2018,8 @@ def GenerateICDRegistrationParameters(self): secrets.token_bytes(16), self._nodeId, self._nodeId, - 30) + 30, + Clusters.IcdManagement.Enums.ClientTypeEnum.kPermanent) def EnableICDRegistration(self, parameters: ICDRegistrationParameters): ''' Enables ICD registration for the following commissioning session. diff --git a/src/controller/python/test/test_scripts/base.py b/src/controller/python/test/test_scripts/base.py index ca1e37a279b417..dc07e15bc2f709 100644 --- a/src/controller/python/test/test_scripts/base.py +++ b/src/controller/python/test/test_scripts/base.py @@ -184,7 +184,7 @@ def __init__(self, nodeid: int, paaTrustStorePath: str, testCommissioner: bool = keypair: p256keypair.P256Keypair = None): chip.native.Init() - self.chipStack = ChipStack('/tmp/repl_storage.json') + self.chipStack = ChipStack('/tmp/repl_storage.json', enableServerInteractions=True) self.certificateAuthorityManager = chip.CertificateAuthority.CertificateAuthorityManager(chipStack=self.chipStack) self.certificateAuthority = self.certificateAuthorityManager.NewCertificateAuthority() self.fabricAdmin = self.certificateAuthority.NewFabricAdmin(vendorId=0xFFF1, fabricId=1) @@ -1158,6 +1158,24 @@ def TestResolve(self, nodeid): self.logger.exception("Failed to resolve. {}".format(ex)) return False + async def TestTriggerTestEventHandler(self, nodeid, enable_key, event_trigger): + self.logger.info("Test trigger test event handler for device = %08x", nodeid) + try: + await self.devCtrl.SendCommand(nodeid, 0, Clusters.GeneralDiagnostics.Commands.TestEventTrigger(enableKey=enable_key, eventTrigger=event_trigger)) + return True + except Exception as ex: + self.logger.exception("Failed to trigger test event handler {}".format(ex)) + return False + + async def TestWaitForActive(self, nodeid): + self.logger.info("Test wait for device = %08x", nodeid) + try: + await self.devCtrl.WaitForActive(nodeid) + return True + except Exception as ex: + self.logger.exception("Failed to wait for active. {}".format(ex)) + return False + async def TestReadBasicAttributes(self, nodeid: int, endpoint: int): attrs = Clusters.BasicInformation.Attributes basic_cluster_attrs = { diff --git a/src/controller/python/test/test_scripts/icd_wait_for_device_test.py b/src/controller/python/test/test_scripts/icd_wait_for_device_test.py new file mode 100755 index 00000000000000..e28a76e17ac41e --- /dev/null +++ b/src/controller/python/test/test_scripts/icd_wait_for_device_test.py @@ -0,0 +1,148 @@ +#!/usr/bin/env python3 + +# +# Copyright (c) 2021 Project CHIP Authors +# All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# Commissioning test. + +import asyncio +import os +import sys +from optparse import OptionParser + +from base import BaseTestHelper, FailIfNot, TestFail, TestTimeout, logger + +TEST_DISCRIMINATOR = 3840 +TEST_DISCOVERY_TYPE = 2 + +ENDPOINT_ID = 0 +LIGHTING_ENDPOINT_ID = 1 +GROUP_ID = 0 + + +async def waitForActiveAndTriggerCheckIn(test, nodeid): + coro = test.TestWaitForActive(nodeid=nodeid) + await test.TestTriggerTestEventHandler(nodeid, bytes.fromhex("00112233445566778899aabbccddeeff"), 0x0046 << 48) + return await coro + + +async def main(): + optParser = OptionParser() + optParser.add_option( + "-t", + "--timeout", + action="store", + dest="testTimeout", + default=75, + type='int', + help="The program will return with timeout after specified seconds.", + metavar="", + ) + optParser.add_option( + "-a", + "--address", + action="store", + dest="deviceAddress", + default='', + type='str', + help="Address of the device", + metavar="", + ) + optParser.add_option( + "--setup-payload", + action="store", + dest="setupPayload", + default='', + type='str', + help="Setup Payload (manual pairing code or QR code content)", + metavar="" + ) + optParser.add_option( + "--nodeid", + action="store", + dest="nodeid", + default=1, + type=int, + help="The Node ID issued to the device", + metavar="" + ) + optParser.add_option( + "--discriminator", + action="store", + dest="discriminator", + default=TEST_DISCRIMINATOR, + type=int, + help="Discriminator of the device", + metavar="" + ) + optParser.add_option( + "-p", + "--paa-trust-store-path", + action="store", + dest="paaTrustStorePath", + default='', + type='str', + help="Path that contains valid and trusted PAA Root Certificates.", + metavar="" + ) + optParser.add_option( + "--discovery-type", + action="store", + dest="discoveryType", + default=TEST_DISCOVERY_TYPE, + type=int, + help="Discovery type of commissioning. (0: networkOnly 1: networkOnlyWithoutPASEAutoRetry 2: All)", + metavar="" + ) + + (options, remainingArgs) = optParser.parse_args(sys.argv[1:]) + + timeoutTicker = TestTimeout(options.testTimeout) + timeoutTicker.start() + + test = BaseTestHelper( + nodeid=112233, paaTrustStorePath=options.paaTrustStorePath, testCommissioner=True) + + devCtrl = test.devCtrl + devCtrl.EnableICDRegistration(devCtrl.GenerateICDRegistrationParameters()) + logger.info("Testing commissioning") + FailIfNot(await test.TestCommissioning(ip=options.deviceAddress, + setuppin=20202021, + nodeid=options.nodeid), + "Failed to finish key exchange") + logger.info("Commissioning completed") + logger.info("Testing wait for active") + FailIfNot(await waitForActiveAndTriggerCheckIn(test, nodeid=options.nodeid), "Failed to test wait for active") + logger.info('Successfully handled wait-for-active') + + timeoutTicker.stop() + + logger.info("Test finished") + + # TODO: Python device controller cannot be shutdown clean sometimes and will block on AsyncDNSResolverSockets shutdown. + # Call os._exit(0) to force close it. + os._exit(0) + + +if __name__ == "__main__": + try: + loop = asyncio.get_event_loop() + loop.run_until_complete(main()) + loop.close() + except Exception as ex: + logger.exception(ex) + TestFail("Exception occurred when running tests.") diff --git a/src/test_driver/linux-cirque/IcdWaitForActiveTest.py b/src/test_driver/linux-cirque/IcdWaitForActiveTest.py new file mode 100755 index 00000000000000..bb2c1cb8384a3c --- /dev/null +++ b/src/test_driver/linux-cirque/IcdWaitForActiveTest.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python3 +""" +Copyright (c) 2021 Project CHIP Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import logging +import os +import sys + +from helper.CHIPTestBase import CHIPVirtualHome + +logger = logging.getLogger('MobileDeviceTest') +logger.setLevel(logging.INFO) + +sh = logging.StreamHandler() +sh.setFormatter( + logging.Formatter( + '%(asctime)s [%(name)s] %(levelname)s %(message)s')) +logger.addHandler(sh) + +CHIP_PORT = 5540 + +CIRQUE_URL = "http://localhost:5000" +CHIP_REPO = os.path.join(os.path.abspath( + os.path.dirname(__file__)), "..", "..", "..") +TEST_EXTPANID = "fedcba9876543210" +TEST_DISCRIMINATOR = 3840 +TEST_DISCRIMINATOR2 = 3584 +TEST_DISCRIMINATOR3 = 1203 +TEST_DISCRIMINATOR4 = 2145 +TEST_DISCOVERY_TYPE = [0, 1, 2] +MATTER_DEVELOPMENT_PAA_ROOT_CERTS = "credentials/development/paa-root-certs" + +DEVICE_CONFIG = { + 'device0': { + 'type': 'MobileDevice', + 'base_image': '@default', + 'capability': ['TrafficControl', 'Mount'], + 'rcp_mode': True, + 'docker_network': 'Ipv6', + 'traffic_control': {'latencyMs': 25}, + "mount_pairs": [[CHIP_REPO, CHIP_REPO]], + }, + 'device1': { + 'type': 'CHIPEndDevice', + 'base_image': '@default', + 'capability': ['Thread', 'TrafficControl', 'Mount'], + 'rcp_mode': True, + 'docker_network': 'Ipv6', + 'traffic_control': {'latencyMs': 25}, + "mount_pairs": [[CHIP_REPO, CHIP_REPO]], + } +} + + +class TestCommissioner(CHIPVirtualHome): + def __init__(self, device_config): + super().__init__(CIRQUE_URL, device_config) + self.logger = logger + + def setup(self): + self.initialize_home() + + def test_routine(self): + self.run_controller_test() + + def run_controller_test(self): + ethernet_ip = [device['description']['ipv6_addr'] for device in self.non_ap_devices + if device['type'] == 'CHIPEndDevice'][0] + server_ids = [device['id'] for device in self.non_ap_devices + if device['type'] == 'CHIPEndDevice'] + req_ids = [device['id'] for device in self.non_ap_devices + if device['type'] == 'MobileDevice'] + + for server in server_ids: + self.execute_device_cmd( + server, + ("CHIPCirqueDaemon.py -- run gdb -batch -return-child-result -q -ex \"set pagination off\" " + "-ex run -ex \"thread apply all bt\" --args {} --thread --discriminator {}").format( + os.path.join(CHIP_REPO, "out/debug/lit_icd/lit-icd-app"), TEST_DISCRIMINATOR)) + + self.reset_thread_devices(server_ids) + + req_device_id = req_ids[0] + + self.execute_device_cmd(req_device_id, "pip3 install --break-system-packages {}".format(os.path.join( + CHIP_REPO, "out/debug/linux_x64_gcc/controller/python/chip_clusters-0.0-py3-none-any.whl"))) + self.execute_device_cmd(req_device_id, "pip3 install --break-system-packages {}".format(os.path.join( + CHIP_REPO, "out/debug/linux_x64_gcc/controller/python/chip_core-0.0-cp37-abi3-linux_x86_64.whl"))) + self.execute_device_cmd(req_device_id, "pip3 install --break-system-packages {}".format(os.path.join( + CHIP_REPO, "out/debug/linux_x64_gcc/controller/python/chip_repl-0.0-py3-none-any.whl"))) + + command = ("gdb -batch -return-child-result -q -ex run -ex \"thread apply all bt\" " + "--args python3 {} -t 300 -a {} --paa-trust-store-path {}").format( + os.path.join( + CHIP_REPO, "src/controller/python/test/test_scripts/icd_wait_for_device_test.py"), ethernet_ip, + os.path.join(CHIP_REPO, MATTER_DEVELOPMENT_PAA_ROOT_CERTS)) + ret = self.execute_device_cmd(req_device_id, command) + + self.assertEqual(ret['return_code'], '0', + "Test failed: non-zero return code") + + +if __name__ == "__main__": + sys.exit(TestCommissioner(DEVICE_CONFIG).run_test())