From bd17b9f52f41eeac5aaa74c1ecf3a76e06dd6958 Mon Sep 17 00:00:00 2001 From: Boris Zbarsky Date: Tue, 28 Mar 2023 20:56:10 -0400 Subject: [PATCH] Add some basic XCTest tests for OTA in the Darwin framework. (#25869) The change to MTRPairingTests.m is to stop using deprecated APIs. --- .github/workflows/darwin.yaml | 3 + .../Framework/CHIPTests/MTROTAProviderTests.m | 286 ++++++++++++++++++ .../Framework/CHIPTests/MTRPairingTests.m | 34 +-- .../Matter.xcodeproj/project.pbxproj | 4 + 4 files changed, 310 insertions(+), 17 deletions(-) create mode 100644 src/darwin/Framework/CHIPTests/MTROTAProviderTests.m diff --git a/.github/workflows/darwin.yaml b/.github/workflows/darwin.yaml index 5f054d177001bb..bd8af5cd75fd1f 100644 --- a/.github/workflows/darwin.yaml +++ b/.github/workflows/darwin.yaml @@ -139,6 +139,9 @@ jobs: run: | mkdir -p /tmp/darwin/framework-tests ../../../out/debug/chip-all-clusters-app --interface-id -1 > >(tee /tmp/darwin/framework-tests/all-cluster-app.log) 2> >(tee /tmp/darwin/framework-tests/all-cluster-app-err.log >&2) & + # Make sure ota-requestor is using a different port, discriminator, and KVS from all-clusters-app. + # And a different one from the test harness too; the test harness uses port 5541. + ../../../out/debug/chip-ota-requestor-app --interface-id -1 --secured-device-port 5542 --discriminator 1111 --KVS /tmp/chip-ota-requestor-kvs > >(tee /tmp/darwin/framework-tests/ota-requestor-app.log) 2> >(tee /tmp/darwin/framework-tests/ota-requestor-app-err.log >&2) & xcodebuild test -target "Matter" -scheme "Matter Framework Tests" -sdk macosx OTHER_CFLAGS='${inherited} -Werror -Wconversion -Wno-incomplete-umbrella -Wno-unguarded-availability-new' > >(tee /tmp/darwin/framework-tests/darwin-tests.log) 2> >(tee /tmp/darwin/framework-tests/darwin-tests-err.log >&2) working-directory: src/darwin/Framework - name: Uploading log files diff --git a/src/darwin/Framework/CHIPTests/MTROTAProviderTests.m b/src/darwin/Framework/CHIPTests/MTROTAProviderTests.m new file mode 100644 index 00000000000000..83c1fb677bc72a --- /dev/null +++ b/src/darwin/Framework/CHIPTests/MTROTAProviderTests.m @@ -0,0 +1,286 @@ +/* + * + * Copyright (c) 2022 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. + */ + +// module headers +#import + +#import "MTRErrorTestUtils.h" +#import "MTRTestKeys.h" +#import "MTRTestResetCommissioneeHelper.h" +#import "MTRTestStorage.h" + +// system dependencies +#import + +// Set the following to 1 in order to run individual test case manually. +#define MANUAL_INDIVIDUAL_TEST 0 + +static const uint16_t kPairingTimeoutInSeconds = 10; +static const uint16_t kTimeoutInSeconds = 3; +static const uint64_t kDeviceId = 0x12341234; +// NOTE: This onboarding payload is for the chip-ota-requestor-app, not chip-all-clusters-app +static NSString * kOnboardingPayload = @"MT:-24J0SO527K10648G00"; + +static const uint16_t kLocalPort = 5541; +static const uint16_t kTestVendorId = 0xFFF1u; +static const uint16_t kOTAProviderEndpointId = 0; + +static MTRDevice * sConnectedDevice; + +// Singleton controller we use. +static MTRDeviceController * sController = nil; + +// Keys we can use to restart the controller. +static MTRTestKeys * sTestKeys = nil; + +@interface MTROTAProviderTestControllerDelegate : NSObject +@property (nonatomic, strong) XCTestExpectation * expectation; +@end + +@implementation MTROTAProviderTestControllerDelegate +- (id)initWithExpectation:(XCTestExpectation *)expectation +{ + self = [super init]; + if (self) { + _expectation = expectation; + } + return self; +} + +- (void)controller:(MTRDeviceController *)controller commissioningSessionEstablishmentDone:(NSError * _Nullable)error +{ + XCTAssertEqual(error.code, 0); + + NSError * commissionError = nil; + [sController commissionNodeWithID:@(kDeviceId) + commissioningParams:[[MTRCommissioningParameters alloc] init] + error:&commissionError]; + XCTAssertNil(commissionError); + + // Keep waiting for onCommissioningComplete +} + +- (void)controller:(MTRDeviceController *)controller commissioningComplete:(NSError *)error +{ + XCTAssertEqual(error.code, 0); + [_expectation fulfill]; + _expectation = nil; +} + +@end + +@interface MTROTAProviderDelegateImpl : NSObject +@property (nonatomic) XCTestExpectation * handleQueryImageExpectation; +@end + +@implementation MTROTAProviderDelegateImpl +- (void)handleQueryImageForNodeID:(NSNumber *)nodeID + controller:(MTRDeviceController *)controller + params:(MTROTASoftwareUpdateProviderClusterQueryImageParams *)params + completion:(void (^)(MTROTASoftwareUpdateProviderClusterQueryImageResponseParams * _Nullable data, + NSError * _Nullable error))completion +{ + XCTAssertEqualObjects(nodeID, @(kDeviceId)); + XCTAssertEqual(controller, sController); + // TODO: Anything we can test here about the params? + + // TODO: Make it possible to configure our responses. + __auto_type * responseParams = [[MTROTASoftwareUpdateProviderClusterQueryImageResponseParams alloc] init]; + responseParams.status = @(MTROTASoftwareUpdateProviderOTAQueryStatusNotAvailable); + completion(responseParams, nil); + + if (self.handleQueryImageExpectation != nil) { + [self.handleQueryImageExpectation fulfill]; + } +} + +- (void)handleApplyUpdateRequestForNodeID:(NSNumber *)nodeID + controller:(MTRDeviceController *)controller + params:(MTROTASoftwareUpdateProviderClusterApplyUpdateRequestParams *)params + completion:(void (^)(MTROTASoftwareUpdateProviderClusterApplyUpdateResponseParams * _Nullable data, + NSError * _Nullable error))completion +{ + completion(nil, [NSError errorWithDomain:MTRErrorDomain code:MTRErrorCodeGeneralError userInfo:nil]); +} + +- (void)handleNotifyUpdateAppliedForNodeID:(NSNumber *)nodeID + controller:(MTRDeviceController *)controller + params:(MTROTASoftwareUpdateProviderClusterNotifyUpdateAppliedParams *)params + completion:(MTRStatusCompletion)completion +{ + completion([NSError errorWithDomain:MTRErrorDomain code:MTRErrorCodeGeneralError userInfo:nil]); +} + +- (void)handleBDXTransferSessionBeginForNodeID:(NSNumber *)nodeID + controller:(MTRDeviceController *)controller + fileDesignator:(NSString *)fileDesignator + offset:(NSNumber *)offset + completion:(MTRStatusCompletion)completion +{ + completion([NSError errorWithDomain:MTRErrorDomain code:MTRErrorCodeGeneralError userInfo:nil]); +} + +- (void)handleBDXQueryForNodeID:(NSNumber *)nodeID + controller:(MTRDeviceController *)controller + blockSize:(NSNumber *)blockSize + blockIndex:(NSNumber *)blockIndex + bytesToSkip:(NSNumber *)bytesToSkip + completion:(void (^)(NSData * _Nullable data, BOOL isEOF))completion +{ + completion(nil, YES); +} + +- (void)handleBDXTransferSessionEndForNodeID:(NSNumber *)nodeID + controller:(MTRDeviceController *)controller + error:(NSError * _Nullable)error +{ +} + +@end + +static MTROTAProviderDelegateImpl * sOTAProviderDelegate; + +@interface MTROTAProviderTests : XCTestCase +@end + +@implementation MTROTAProviderTests + +- (void)setUp +{ + [super setUp]; + [self setContinueAfterFailure:NO]; +} + +- (void)tearDown +{ +#if MANUAL_INDIVIDUAL_TEST + [self shutdownStack]; +#endif + [super tearDown]; +} + +- (void)initStack +{ + __auto_type * factory = [MTRDeviceControllerFactory sharedInstance]; + XCTAssertNotNil(factory); + + __auto_type * storage = [[MTRTestStorage alloc] init]; + sOTAProviderDelegate = [[MTROTAProviderDelegateImpl alloc] init]; + + __auto_type * factoryParams = [[MTRDeviceControllerFactoryParams alloc] initWithStorage:storage]; + factoryParams.port = @(kLocalPort); + factoryParams.otaProviderDelegate = sOTAProviderDelegate; + factoryParams.shouldStartServer = YES; + + BOOL ok = [factory startControllerFactory:factoryParams error:nil]; + XCTAssertTrue(ok); + + __auto_type * testKeys = [[MTRTestKeys alloc] init]; + XCTAssertNotNil(testKeys); + + sTestKeys = testKeys; + + // Needs to match what startControllerOnExistingFabric calls elsewhere in + // this file do. + __auto_type * params = [[MTRDeviceControllerStartupParams alloc] initWithIPK:testKeys.ipk fabricID:@(1) nocSigner:testKeys]; + params.vendorID = @(kTestVendorId); + + MTRDeviceController * controller = [factory createControllerOnNewFabric:params error:nil]; + XCTAssertNotNil(controller); + + sController = controller; + + XCTestExpectation * expectation = [self expectationWithDescription:@"Commissioning Complete"]; + __auto_type * deviceControllerDelegate = [[MTROTAProviderTestControllerDelegate alloc] initWithExpectation:expectation]; + dispatch_queue_t callbackQueue = dispatch_queue_create("com.chip.device_controller_delegate", DISPATCH_QUEUE_SERIAL); + + [controller setDeviceControllerDelegate:deviceControllerDelegate queue:callbackQueue]; + + NSError * error; + __auto_type * payload = [MTRSetupPayload setupPayloadWithOnboardingPayload:kOnboardingPayload error:&error]; + XCTAssertNotNil(payload); + XCTAssertNil(error); + + [controller setupCommissioningSessionWithPayload:payload newNodeID:@(kDeviceId) error:&error]; + XCTAssertNil(error); + + [self waitForExpectations:@[ expectation ] timeout:kPairingTimeoutInSeconds]; + + sConnectedDevice = [MTRDevice deviceWithNodeID:@(kDeviceId) controller:controller]; +} + +- (void)shutdownStack +{ + MTRDeviceController * controller = sController; + XCTAssertNotNil(controller); + + [controller shutdown]; + XCTAssertFalse([controller isRunning]); + + [[MTRDeviceControllerFactory sharedInstance] stopControllerFactory]; +} + +#if !MANUAL_INDIVIDUAL_TEST +- (void)test000_SetUp +{ + [self initStack]; +} +#endif + +- (void)test001_ReceiveOTAQuery +{ +#if MANUAL_INDIVIDUAL_TEST + [self initStack]; +#endif + + __auto_type * device = sConnectedDevice; + dispatch_queue_t queue = dispatch_get_main_queue(); + + XCTestExpectation * queryExpectation = [self expectationWithDescription:@"handleQueryImageForNodeID called"]; + XCTestExpectation * responseExpectation = [self expectationWithDescription:@"AnnounceOTAProvider succeeded"]; + sOTAProviderDelegate.handleQueryImageExpectation = queryExpectation; + + // Advertise ourselves as an OTA provider. + __auto_type * params = [[MTROTASoftwareUpdateRequestorClusterAnnounceOTAProviderParams alloc] init]; + params.providerNodeID = [sController controllerNodeID]; + params.vendorID = @(kTestVendorId); + params.announcementReason = @(MTROTASoftwareUpdateRequestorOTAAnnouncementReasonSimpleAnnouncement); + params.endpoint = @(kOTAProviderEndpointId); + + __auto_type * cluster = [[MTRClusterOTASoftwareUpdateRequestor alloc] initWithDevice:device endpointID:@(0) queue:queue]; + [cluster announceOTAProviderWithParams:params + expectedValues:nil + expectedValueInterval:nil + completion:^(NSError * _Nullable error) { + XCTAssertNil(error); + [responseExpectation fulfill]; + }]; + + [self waitForExpectations:@[ queryExpectation, responseExpectation ] timeout:kTimeoutInSeconds]; + sOTAProviderDelegate.handleQueryImageExpectation = nil; +} + +#if !MANUAL_INDIVIDUAL_TEST +- (void)test999_TearDown +{ + __auto_type * device = [MTRBaseDevice deviceWithNodeID:@(kDeviceId) controller:sController]; + ResetCommissionee(device, dispatch_get_main_queue(), self, kTimeoutInSeconds); + [self shutdownStack]; +} +#endif + +@end diff --git a/src/darwin/Framework/CHIPTests/MTRPairingTests.m b/src/darwin/Framework/CHIPTests/MTRPairingTests.m index f2d59c67d58319..ba164102055f13 100644 --- a/src/darwin/Framework/CHIPTests/MTRPairingTests.m +++ b/src/darwin/Framework/CHIPTests/MTRPairingTests.m @@ -72,13 +72,13 @@ - (void)deviceAttestationCompletedForController:(MTRDeviceController *)controlle @end -@interface MTRPairingTestPairingDelegate : NSObject +@interface MTRPairingTestControllerDelegate : NSObject @property (nonatomic, strong) XCTestExpectation * expectation; @property (nonatomic, nullable) id attestationDelegate; @property (nonatomic, nullable) NSNumber * failSafeExtension; @end -@implementation MTRPairingTestPairingDelegate +@implementation MTRPairingTestControllerDelegate - (id)initWithExpectation:(XCTestExpectation *)expectation attestationDelegate:(id)attestationDelegate failSafeExtension:(NSNumber *)failSafeExtension @@ -92,7 +92,7 @@ - (id)initWithExpectation:(XCTestExpectation *)expectation return self; } -- (void)onPairingComplete:(NSError *)error +- (void)controller:(MTRDeviceController *)controller commissioningSessionEstablishmentDone:(NSError * _Nullable)error { XCTAssertEqual(error.code, 0); @@ -101,13 +101,13 @@ - (void)onPairingComplete:(NSError *)error params.failSafeTimeout = self.failSafeExtension; NSError * commissionError = nil; - [sController commissionDevice:sDeviceId commissioningParams:params error:&commissionError]; + [controller commissionNodeWithID:@(sDeviceId) commissioningParams:params error:&commissionError]; XCTAssertNil(commissionError); // Keep waiting for onCommissioningComplete } -- (void)onCommissioningComplete:(NSError *)error +- (void)controller:(MTRDeviceController *)controller commissioningComplete:(NSError * _Nullable)error { XCTAssertEqual(error.code, 0); [_expectation fulfill]; @@ -121,15 +121,15 @@ static void DoPairingTest(XCTestCase * testcase, id