From 11636022f7c5c6e7736b8abdeb83a340e174a694 Mon Sep 17 00:00:00 2001 From: Boris Zbarsky Date: Fri, 25 Aug 2023 14:42:28 -0400 Subject: [PATCH] Improve management of OTA requestor apps in darwin tests. (#28674) * Improve management of OTA requestor apps in darwin tests. Instead of relying on the apps to be started externally, just start them directly from the test itself. This removes the proliferation of test app instances at runtime, except during this test, and makes it easier to run other tests without paying the cost of all the compilation/setup for this one. The change in MTROTAProviderDelegateBridge.mm is fixing a bug that was uncovered by the changed tests: if we shut down our last controller, that incorrectly cleared out mDelegateNotificationQueue on the BdxOTASender, and that state is not restored until factory restart. Since the queue is tied to the factory, not controllers, nulling it out does not make sense here. * Address review comment. --- .github/workflows/darwin.yaml | 15 +- .../CHIP/MTROTAProviderDelegateBridge.mm | 1 - .../CHIPTests/MTRCommissionableBrowserTests.m | 13 +- .../Framework/CHIPTests/MTROTAProviderTests.m | 408 ++++++++++++------ 4 files changed, 281 insertions(+), 156 deletions(-) diff --git a/.github/workflows/darwin.yaml b/.github/workflows/darwin.yaml index c30cc044f35b33..1eddaec04a8667 100644 --- a/.github/workflows/darwin.yaml +++ b/.github/workflows/darwin.yaml @@ -95,15 +95,9 @@ jobs: - name: Build example OTA Provider run: | scripts/examples/gn_build_example.sh examples/ota-provider-app/linux out/debug chip_config_network_layer_ble=false - - name: Build OTA image files with software version number 5 and 10 - run: | - scripts/examples/gn_build_example.sh examples/ota-requestor-app/linux out/debug chip_config_network_layer_ble=false non_spec_compliant_ota_action_delay_floor=0 chip_device_config_device_software_version=5 chip_device_config_device_software_version_string='"5.0"' - cp out/debug/chip-ota-requestor-app /tmp/ota-raw-image-v5 - scripts/examples/gn_build_example.sh examples/ota-requestor-app/linux out/debug chip_config_network_layer_ble=false non_spec_compliant_ota_action_delay_floor=0 chip_device_config_device_software_version=10 chip_device_config_device_software_version_string='"10.0"' - cp out/debug/chip-ota-requestor-app /tmp/ota-raw-image-v10 - name: Build example OTA Requestor run: | - scripts/examples/gn_build_example.sh examples/ota-requestor-app/linux out/debug chip_config_network_layer_ble=false non_spec_compliant_ota_action_delay_floor=0 + scripts/examples/gn_build_example.sh examples/ota-requestor-app/linux out/debug/ota-requestor-app chip_config_network_layer_ble=false non_spec_compliant_ota_action_delay_floor=0 - name: Delete Defaults run: defaults delete com.apple.dt.xctest.tool continue-on-error: true @@ -116,13 +110,6 @@ jobs: 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) & ../../../out/debug/chip-all-clusters-app --interface-id -1 --dac_provider ../../../credentials/development/commissioner_dut/struct_cd_origin_pid_vid_correct/test_case_vector.json --product-id 32768 --discriminator 3839 --secured-device-port 5539 --KVS /tmp/chip-all-clusters-app-kvs2 > >(tee /tmp/darwin/framework-tests/all-cluster-app-origin-vid.log) 2> >(tee /tmp/darwin/framework-tests/all-cluster-app-origin-vid-err.log >&2) & - # Make sure each ota-requestor is using a different port, discriminator, and KVS from - # all-clusters-app and from other requestors. - # - # And a different port 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-kvs1 --otaDownloadPath /tmp/chip-ota-requestor-downloaded-image1 --autoApplyImage > >(tee /tmp/darwin/framework-tests/ota-requestor-app-1.log) 2> >(tee /tmp/darwin/framework-tests/ota-requestor-app-err-1.log >&2) & - ../../../out/debug/chip-ota-requestor-app --interface-id -1 --secured-device-port 5543 --discriminator 1112 --KVS /tmp/chip-ota-requestor-kvs2 --otaDownloadPath /tmp/chip-ota-requestor-downloaded-image2 --autoApplyImage > >(tee /tmp/darwin/framework-tests/ota-requestor-app-2.log) 2> >(tee /tmp/darwin/framework-tests/ota-requestor-app-err-2.log >&2) & - ../../../out/debug/chip-ota-requestor-app --interface-id -1 --secured-device-port 5544 --discriminator 1113 --KVS /tmp/chip-ota-requestor-kvs3 --otaDownloadPath /tmp/chip-ota-requestor-downloaded-image3 --autoApplyImage > >(tee /tmp/darwin/framework-tests/ota-requestor-app-3.log) 2> >(tee /tmp/darwin/framework-tests/ota-requestor-app-err-3.log >&2) & # Disable BLE because the app does not have the permission to use # it and that may crash the CI. # diff --git a/src/darwin/Framework/CHIP/MTROTAProviderDelegateBridge.mm b/src/darwin/Framework/CHIP/MTROTAProviderDelegateBridge.mm index d64522f4ceded7..4ebef0535253ee 100644 --- a/src/darwin/Framework/CHIP/MTROTAProviderDelegateBridge.mm +++ b/src/darwin/Framework/CHIP/MTROTAProviderDelegateBridge.mm @@ -108,7 +108,6 @@ CHIP_ERROR Shutdown() mExchangeMgr = nullptr; mSystemLayer = nullptr; - mDelegateNotificationQueue = nil; return CHIP_NO_ERROR; } diff --git a/src/darwin/Framework/CHIPTests/MTRCommissionableBrowserTests.m b/src/darwin/Framework/CHIPTests/MTRCommissionableBrowserTests.m index c984ac670a8500..8c9eb603d08518 100644 --- a/src/darwin/Framework/CHIPTests/MTRCommissionableBrowserTests.m +++ b/src/darwin/Framework/CHIPTests/MTRCommissionableBrowserTests.m @@ -27,13 +27,10 @@ static const uint16_t kTestVendorId = 0xFFF1u; static const uint16_t kTestProductId1 = 0x8000u; static const uint16_t kTestProductId2 = 0x8001u; -static const uint16_t kTestDiscriminator1 = 1111u; -static const uint16_t kTestDiscriminator2 = 1112u; -static const uint16_t kTestDiscriminator3 = 1113u; -static const uint16_t kTestDiscriminator4 = 3840u; -static const uint16_t kTestDiscriminator5 = 3839u; +static const uint16_t kTestDiscriminator1 = 3840u; +static const uint16_t kTestDiscriminator2 = 3839u; static const uint16_t kDiscoverDeviceTimeoutInSeconds = 10; -static const uint16_t kExpectedDiscoveredDevicesCount = 5; +static const uint16_t kExpectedDiscoveredDevicesCount = 2; // Singleton controller we use. static MTRDeviceController * sController = nil; @@ -77,9 +74,7 @@ - (void)controller:(MTRDeviceController *)controller didFindCommissionableDevice XCTAssertEqual(instanceName.length, 16); // The instance name is random, so just ensure the len is right. XCTAssertEqualObjects(vendorId, @(kTestVendorId)); XCTAssertTrue([productId isEqual:@(kTestProductId1)] || [productId isEqual:@(kTestProductId2)]); - XCTAssertTrue([discriminator isEqual:@(kTestDiscriminator1)] || [discriminator isEqual:@(kTestDiscriminator2)] || - [discriminator isEqual:@(kTestDiscriminator3)] || [discriminator isEqual:@(kTestDiscriminator4)] || - [discriminator isEqual:@(kTestDiscriminator5)]); + XCTAssertTrue([discriminator isEqual:@(kTestDiscriminator1)] || [discriminator isEqual:@(kTestDiscriminator2)]); XCTAssertEqual(commissioningMode, YES); NSLog(@"Found Device (%@) with discriminator: %@ (vendor: %@, product: %@)", instanceName, discriminator, vendorId, productId); diff --git a/src/darwin/Framework/CHIPTests/MTROTAProviderTests.m b/src/darwin/Framework/CHIPTests/MTROTAProviderTests.m index 472bd22d9919c7..c464e325ca436d 100644 --- a/src/darwin/Framework/CHIPTests/MTROTAProviderTests.m +++ b/src/darwin/Framework/CHIPTests/MTROTAProviderTests.m @@ -46,32 +46,22 @@ static const uint16_t kTimeoutWithUpdateInSeconds = 60; static const uint64_t kDeviceId1 = 0x12341234; static const uint64_t kDeviceId2 = 0x12341235; +#ifdef ENABLE_REAL_OTA_UPDATE_TESTS static const uint64_t kDeviceId3 = 0x12341236; +#endif // ENABLE_REAL_OTA_UPDATE_TESTS // NOTE: These onboarding payloads are for the chip-ota-requestor-app, not chip-all-clusters-app static NSString * kOnboardingPayload1 = @"MT:-24J0SO527K10648G00"; // Discriminator: 1111 static NSString * kOnboardingPayload2 = @"MT:-24J0AFN00L10648G00"; // Discriminator: 1112 +#ifdef ENABLE_REAL_OTA_UPDATE_TESTS static NSString * kOnboardingPayload3 = @"MT:-24J0IRV01L10648G00"; // Discriminator: 1113 +#endif // ENABLE_REAL_OTA_UPDATE_TESTS static const uint16_t kLocalPort = 5541; static const uint16_t kTestVendorId = 0xFFF1u; static const uint16_t kOTAProviderEndpointId = 0; -static MTRDevice * sConnectedDevice1; -static MTRDevice * sConnectedDevice2; -static MTRDevice * sConnectedDevice3; - -// Singleton controller we use. static MTRDeviceController * sController = nil; -// Keys we can use to restart the controller. -static MTRTestKeys * sTestKeys = nil; - -static NSString * kOtaDownloadedFilePath1 = @"/tmp/chip-ota-requestor-downloaded-image1"; - -static NSString * kOtaDownloadedFilePath2 = @"/tmp/chip-ota-requestor-downloaded-image2"; - -static NSString * kOtaDownloadedFilePath3 = @"/tmp/chip-ota-requestor-downloaded-image3"; - static NSNumber * kUpdatedSoftwareVersion_5 = @5; static NSString * kUpdatedSoftwareVersionString_5 = @"5.0"; @@ -80,6 +70,111 @@ static NSString * kUpdatedSoftwareVersionString_10 = @"10.0"; +// kOtaRequestorBasePort gets the discriminator added to it to figure out the +// port the ota-requestor app should be using. This ensures that apps with +// distinct discriminators use distinct ports. +static const uint16_t kOtaRequestorBasePort = 5542 - 1111; + +@class MTROTARequestorAppRunner; + +@interface MTROTAProviderTests : XCTestCase +#if ENABLE_OTA_TESTS +- (NSTask *)createTaskForPath:(NSString *)path; +- (NSString *)createImageFromRawImage:(NSString *)rawImage withVersion:(NSNumber *)version; +- (MTRDevice *)commissionDeviceWithPayload:(NSString *)payloadString nodeID:(NSNumber *)nodeID; +- (void)registerRunningRequestor:(MTROTARequestorAppRunner *)requestor; +#endif // ENABLE_OTA_TESTS +@end + +#if ENABLE_OTA_TESTS +static unsigned sAppRunnerIndex = 1; + +@interface MTROTARequestorAppRunner : NSObject +@property (nonatomic, copy) NSString * downloadFilePath; + +- (instancetype)initWithPayload:(NSString *)payload testcase:(MTROTAProviderTests *)testcase; +- (MTRDevice *)commissionWithNodeID:(NSNumber *)nodeID; +@end + +@implementation MTROTARequestorAppRunner { + unsigned _uniqueIndex; + NSTask * _appTask; + MTROTAProviderTests * _testcase; + NSString * _payload; + MTRDevice * commissionedDevice; +} + +- (MTRDevice *)commissionWithNodeID:(NSNumber *)nodeID +{ + return [_testcase commissionDeviceWithPayload:_payload nodeID:nodeID]; +} + +- (instancetype)initWithPayload:(NSString *)payload testcase:(MTROTAProviderTests *)testcase +{ + if (!(self = [super init])) { + return nil; + } + + _uniqueIndex = sAppRunnerIndex++; + _testcase = testcase; + _payload = payload; + _downloadFilePath = [NSString stringWithFormat:@"/tmp/chip-ota-requestor-downloaded-image%u", _uniqueIndex]; + + NSError * error; + __auto_type * parsedPayload = [MTRSetupPayload setupPayloadWithOnboardingPayload:payload error:&error]; + XCTAssertNotNil(parsedPayload); + XCTAssertNil(error); + + XCTAssertFalse(parsedPayload.hasShortDiscriminator); + + __auto_type * discriminator = parsedPayload.discriminator; + + _appTask = [testcase createTaskForPath:@"out/debug/ota-requestor-app/chip-ota-requestor-app"]; + + __auto_type * arguments = @[ + @"--interface-id", + @"-1", + @"--secured-device-port", + [NSString stringWithFormat:@"%u", kOtaRequestorBasePort + discriminator.unsignedShortValue], + @"--discriminator", + [NSString stringWithFormat:@"%u", discriminator.unsignedShortValue], + @"--KVS", + [NSString stringWithFormat:@"/tmp/chip-ota-requestor-kvs%u", _uniqueIndex], + @"--otaDownloadPath", + _downloadFilePath, + @"--autoApplyImage", + ]; + + [_appTask setArguments:arguments]; + + NSString * outFile = [NSString stringWithFormat:@"/tmp/darwin/framework-tests/ota-requestor-app-%u.log", _uniqueIndex]; + NSString * errorFile = [NSString stringWithFormat:@"/tmp/darwin/framework-tests/ota-requestor-app-err-%u.log", _uniqueIndex]; + + // Make sure the files exist. + [[NSFileManager defaultManager] createFileAtPath:outFile contents:nil attributes:nil]; + [[NSFileManager defaultManager] createFileAtPath:errorFile contents:nil attributes:nil]; + + _appTask.standardOutput = [NSFileHandle fileHandleForWritingAtPath:outFile]; + _appTask.standardError = [NSFileHandle fileHandleForWritingAtPath:errorFile]; + + [_appTask launchAndReturnError:&error]; + XCTAssertNil(error); + + NSLog(@"Started requestor with arguments %@ stdout=%@ and stderr=%@", arguments, outFile, errorFile); + + [_testcase registerRunningRequestor:self]; + + return self; +} + +- (void)terminate +{ + [_appTask terminate]; +} + +@end +#endif // ENABLE_OTA_TESTS + @interface MTROTAProviderTestControllerDelegate : NSObject @property (nonatomic, readonly) XCTestExpectation * expectation; @property (nonatomic, readonly) NSNumber * commissioneeNodeID; @@ -352,7 +447,7 @@ - (instancetype)initWithRawImagePath:(NSString *)rawImagePath softwareVersion:(NSNumber *)softwareVersion softwareVersionString:(NSString *)softwareVersionString applyUpdateAction:(MTROTASoftwareUpdateProviderOTAApplyUpdateAction)applyUpdateAction - testcase:(XCTestCase *)testcase + testcase:(MTROTAProviderTests *)testcase { if (!(self = [super init])) { return nil; @@ -365,28 +460,7 @@ - (instancetype)initWithRawImagePath:(NSString *)rawImagePath _applyUpdateRequestExpectation = [testcase expectationWithDescription:@"handleApplyUpdateRequestForNodeID called"]; _notifyUpdateAppliedExpectation = [testcase expectationWithDescription:@"handleNotifyUpdateAppliedForNodeID called"]; - NSString * imagePath = [rawImagePath stringByReplacingOccurrencesOfString:@"raw-image" withString:@"image"]; - - // Find the right absolute path to our ota_image_tool.py script. PWD should - // point to our src/darwin/Framework, while the script is in - // src/app/ota_image_tool.py. - NSString * pwd = [[NSProcessInfo processInfo] environment][@"PWD"]; - NSString * imageToolPath = [NSString - pathWithComponents:@[ [pwd substringToIndex:(pwd.length - @"darwin/Framework".length)], @"app", @"ota_image_tool.py" ]]; - -#if ENABLE_OTA_TESTS - NSTask * task = [[NSTask alloc] init]; - [task setLaunchPath:imageToolPath]; - [task setArguments:@[ - @"create", @"-v", @"0xFFF1", @"-p", @"0x8001", @"-vn", [softwareVersion stringValue], @"-vs", softwareVersionString, @"-da", - @"sha256", rawImagePath, imagePath - ]]; - NSError * launchError = nil; - [task launchAndReturnError:&launchError]; - XCTAssertNil(launchError); - [task waitUntilExit]; - XCTAssertEqual([task terminationStatus], 0); -#endif + NSString * imagePath = [testcase createImageFromRawImage:rawImagePath withVersion:softwareVersion]; NSData * updateToken = [sOTAProviderDelegate generateUpdateToken]; @@ -501,13 +575,15 @@ - (instancetype)initWithRawImagePath:(NSString *)rawImagePath } @end -@interface MTROTAProviderTests : XCTestCase -@end - static BOOL sStackInitRan = NO; static BOOL sNeedsStackShutdown = YES; -@implementation MTROTAProviderTests +@implementation MTROTAProviderTests { + NSMutableSet * _commissionedNodeIDs; + NSMutableSet * _runningRequestors; +} + +#if ENABLE_OTA_TESTS + (void)tearDown { @@ -531,16 +607,51 @@ - (void)setUp [self initStack]; } + _commissionedNodeIDs = [[NSMutableSet alloc] init]; + _runningRequestors = [[NSMutableSet alloc] init]; + XCTAssertNil(sOTAProviderDelegate.queryImageHandler); XCTAssertNil(sOTAProviderDelegate.applyUpdateRequestHandler); XCTAssertNil(sOTAProviderDelegate.notifyUpdateAppliedHandler); XCTAssertNil(sOTAProviderDelegate.transferBeginHandler); XCTAssertNil(sOTAProviderDelegate.blockQueryHandler); XCTAssertNil(sOTAProviderDelegate.transferEndHandler); + + // Start a new controller for each test, with a new fabric. Otherwise + // reusing the same node id for our commissionee devices will cause us to + // try to reuse sessions in ways that fail. + __auto_type * testKeys = [[MTRTestKeys alloc] init]; + XCTAssertNotNil(testKeys); + + __auto_type * params = [[MTRDeviceControllerStartupParams alloc] initWithIPK:testKeys.ipk fabricID:@(1) nocSigner:testKeys]; + params.vendorID = @(kTestVendorId); + + MTRDeviceController * controller = [[MTRDeviceControllerFactory sharedInstance] createControllerOnNewFabric:params error:nil]; + XCTAssertNotNil(controller); + XCTAssertTrue([controller isRunning]); + + sController = controller; } - (void)tearDown { + for (NSNumber * nodeID in _commissionedNodeIDs) { + __auto_type * device = [MTRBaseDevice deviceWithNodeID:nodeID controller:sController]; + ResetCommissionee(device, dispatch_get_main_queue(), self, kTimeoutInSeconds); + } + + for (MTROTARequestorAppRunner * runner in _runningRequestors) { + [runner terminate]; + } + // Break cycle. + _runningRequestors = nil; + + if (sController != nil) { + [sController shutdown]; + XCTAssertFalse([sController isRunning]); + sController = nil; + } + // Per-test teardown, runs after each test. [super tearDown]; @@ -573,9 +684,16 @@ - (MTRDevice *)commissionDeviceWithPayload:(NSString *)payloadString nodeID:(NSN [self waitForExpectations:@[ expectation ] timeout:kPairingTimeoutInSeconds]; + [_commissionedNodeIDs addObject:nodeID]; + return [MTRDevice deviceWithNodeID:nodeID controller:sController]; } +- (void)registerRunningRequestor:(MTROTARequestorAppRunner *)requestor +{ + [_runningRequestors addObject:requestor]; +} + - (void)initStack { sStackInitRan = YES; @@ -593,41 +711,103 @@ - (void)initStack BOOL ok = [factory startControllerFactory:factoryParams error:nil]; XCTAssertTrue(ok); +} - __auto_type * testKeys = [[MTRTestKeys alloc] init]; - XCTAssertNotNil(testKeys); ++ (void)shutdownStack +{ + sNeedsStackShutdown = NO; - sTestKeys = testKeys; + [[MTRDeviceControllerFactory sharedInstance] stopControllerFactory]; +} - // 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); +/** + * Given a path relative to the Matter root, create an absolute path to the file. + */ +- (NSString *)absolutePathFor:(NSString *)matterRootRelativePath +{ + // Find the right absolute path to our file. PWD should + // point to our src/darwin/Framework. + NSString * pwd = [[NSProcessInfo processInfo] environment][@"PWD"]; + NSMutableArray * pathComponents = [[NSMutableArray alloc] init]; + [pathComponents addObject:[pwd substringToIndex:(pwd.length - @"src/darwin/Framework".length)]]; + [pathComponents addObjectsFromArray:[matterRootRelativePath pathComponents]]; + return [NSString pathWithComponents:pathComponents]; +} - MTRDeviceController * controller = [factory createControllerOnNewFabric:params error:nil]; - XCTAssertNotNil(controller); +/** + * Create a task given a path relative to the Matter root. + */ +- (NSTask *)createTaskForPath:(NSString *)path +{ + NSTask * task = [[NSTask alloc] init]; + [task setLaunchPath:[self absolutePathFor:path]]; + return task; +} - sController = controller; +/** + * Runs a task to completion and makes sure it succeeds. + */ +- (void)runTask:(NSTask *)task +{ + NSError * launchError; + [task launchAndReturnError:&launchError]; + XCTAssertNil(launchError); - sConnectedDevice1 = [self commissionDeviceWithPayload:kOnboardingPayload1 nodeID:@(kDeviceId1)]; - sConnectedDevice2 = [self commissionDeviceWithPayload:kOnboardingPayload2 nodeID:@(kDeviceId2)]; - sConnectedDevice3 = [self commissionDeviceWithPayload:kOnboardingPayload3 nodeID:@(kDeviceId3)]; + [task waitUntilExit]; + XCTAssertEqual([task terminationStatus], 0); } -+ (void)shutdownStack +/** + * Returns path to the raw image. + */ +- (NSString *)createRawImageWithVersion:(NSNumber *)version { - sNeedsStackShutdown = NO; + NSTask * buildTask = [self createTaskForPath:@"scripts/examples/gn_build_example.sh"]; + NSString * objdir = + [self absolutePathFor:[NSString stringWithFormat:@"out/debug/ota-requestor-app-v%u", version.unsignedIntValue]]; + [buildTask setArguments:@[ + [self absolutePathFor:@"examples/ota-requestor-app/linux"], + objdir, + @"chip_config_network_layer_ble=false", + @"non_spec_compliant_ota_action_delay_floor=0", + [NSString stringWithFormat:@"chip_device_config_device_software_version=%u", version.unsignedIntValue], + [NSString stringWithFormat:@"chip_device_config_device_software_version_string=\"%u.0\"", version.unsignedIntValue], + ]]; - MTRDeviceController * controller = sController; - XCTAssertNotNil(controller); + [self runTask:buildTask]; - [controller shutdown]; - XCTAssertFalse([controller isRunning]); + NSString * sourcePath = [NSString pathWithComponents:@[ objdir, @"chip-ota-requestor-app" ]]; + NSString * destPath = [NSString stringWithFormat:@"/tmp/ota-raw-image-v%u", version.unsignedIntValue]; - [[MTRDeviceControllerFactory sharedInstance] stopControllerFactory]; + // We don't care about error on remove; the file might not be there. But if + // it _is_ there, we have to remove, or the copy will fail. + [[NSFileManager defaultManager] removeItemAtPath:destPath error:nil]; + + NSError * copyError; + BOOL ok = [[NSFileManager defaultManager] copyItemAtPath:sourcePath toPath:destPath error:©Error]; + XCTAssertNil(copyError); + XCTAssertTrue(ok); + + return destPath; } -#if ENABLE_OTA_TESTS +/** + * Returns path to the created image. + */ +- (NSString *)createImageFromRawImage:(NSString *)rawImage withVersion:(NSNumber *)version +{ + NSString * image = [rawImage stringByReplacingOccurrencesOfString:@"raw-image" withString:@"image"]; + + NSTask * task = [self createTaskForPath:@"src/app/ota_image_tool.py"]; + [task setArguments:@[ + @"create", @"-v", @"0xFFF1", @"-p", @"0x8001", @"-vn", version.stringValue, @"-vs", + [NSString stringWithFormat:@"%.1f", version.floatValue], @"-da", @"sha256", rawImage, image + ]]; + + [self runTask:task]; + + return image; +} - (void)test000_SetUp { @@ -665,7 +845,8 @@ - (void)test001_ReceiveQueryImageRequest_RespondUpdateNotAvailable { // Test that if we advertise ourselves as a provider we end up getting a // QueryImage callbacks that we can respond to. - __auto_type * device = sConnectedDevice1; + __auto_type * runner = [[MTROTARequestorAppRunner alloc] initWithPayload:kOnboardingPayload1 testcase:self]; + __auto_type * device = [runner commissionWithNodeID:@(kDeviceId1)]; XCTestExpectation * queryExpectation = [self expectationWithDescription:@"handleQueryImageForNodeID called"]; sOTAProviderDelegate.queryImageHandler = ^(NSNumber * nodeID, MTRDeviceController * controller, @@ -689,7 +870,8 @@ - (void)test002_ReceiveTwoQueryImageRequests_RespondExplicitBusy // Test that if we advertise ourselves as a provider and respond BUSY to // QueryImage callback, then we get a second QueryImage callback later on // that we can then respond to however we wish. - __auto_type * device = sConnectedDevice1; + __auto_type * runner = [[MTROTARequestorAppRunner alloc] initWithPayload:kOnboardingPayload1 testcase:self]; + __auto_type * device = [runner commissionWithNodeID:@(kDeviceId1)]; XCTestExpectation * queryExpectation1 = [self expectationWithDescription:@"handleQueryImageForNodeID called first time"]; XCTestExpectation * queryExpectation2 = [self expectationWithDescription:@"handleQueryImageForNodeID called second time"]; @@ -734,8 +916,11 @@ - (void)test003_ReceiveQueryImageRequestWhileHandlingBDX_RespondImplicitBusy // in the middle of doing BDX with device1, this actually responds with Busy. // 5) Error out of the device1 transfer. // 6) Wait for device2 to query us again. - __auto_type * device1 = sConnectedDevice1; - __auto_type * device2 = sConnectedDevice2; + __auto_type * runner1 = [[MTROTARequestorAppRunner alloc] initWithPayload:kOnboardingPayload1 testcase:self]; + __auto_type * device1 = [runner1 commissionWithNodeID:@(kDeviceId1)]; + + __auto_type * runner2 = [[MTROTARequestorAppRunner alloc] initWithPayload:kOnboardingPayload2 testcase:self]; + __auto_type * device2 = [runner2 commissionWithNodeID:@(kDeviceId2)]; __block XCTestExpectation * announceResponseExpectation2; XCTestExpectation * queryExpectation1 = [self expectationWithDescription:@"handleQueryImageForNodeID called first time"]; @@ -823,7 +1008,8 @@ - (void)test004_DoBDXTransferDenyUpdateRequest // 5) Send the data as the BDX transfer proceeds. // 6) When device invokes ApplyUpdateRequest, respond with Discontinue so // that the update does not actually proceed. - __auto_type * device = sConnectedDevice1; + __auto_type * runner = [[MTROTARequestorAppRunner alloc] initWithPayload:kOnboardingPayload1 testcase:self]; + __auto_type * device = [runner commissionWithNodeID:@(kDeviceId1)]; // First, create an image. Make it at least 4096 bytes long, so we get // multiple BDX blocks going. @@ -840,7 +1026,7 @@ - (void)test004_DoBDXTransferDenyUpdateRequest __auto_type * checker = [[MTROTAProviderTransferChecker alloc] initWithRawImagePath:rawImagePath - otaImageDownloadFilePath:kOtaDownloadedFilePath1 + otaImageDownloadFilePath:runner.downloadFilePath nodeID:@(kDeviceId1) softwareVersion:kUpdatedSoftwareVersion_5 softwareVersionString:kUpdatedSoftwareVersionString_5 @@ -888,17 +1074,17 @@ - (void)test005_DoBDXTransferAllowUpdateRequest // 7) When device invokes ApplyUpdateRequest, respond with Proceed so that the update proceeds // 8) Wait for the app to restart and wait for the NotifyUpdateApplied message to confirm the app has updated to the new version - // This test expects a pre-generated raw image at otaRawImagePath. - NSString * otaRawImagePath = @"/tmp/ota-raw-image-v5"; + NSString * otaRawImagePath = [self createRawImageWithVersion:kUpdatedSoftwareVersion_5]; // Check whether the ota raw image exists at otaRawImagePath XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:otaRawImagePath]); - __auto_type * device = sConnectedDevice1; + __auto_type * runner = [[MTROTARequestorAppRunner alloc] initWithPayload:kOnboardingPayload1 testcase:self]; + __auto_type * device = [runner commissionWithNodeID:@(kDeviceId1)]; __auto_type * checker = [[MTROTAProviderTransferChecker alloc] initWithRawImagePath:otaRawImagePath - otaImageDownloadFilePath:kOtaDownloadedFilePath1 + otaImageDownloadFilePath:runner.downloadFilePath nodeID:@(kDeviceId1) softwareVersion:kUpdatedSoftwareVersion_5 softwareVersionString:kUpdatedSoftwareVersionString_5 @@ -928,10 +1114,6 @@ - (void)test005_DoBDXTransferAllowUpdateRequest - (void)test006_DoBDXTransferWithTwoOTARequesters { - // Note: This test has a dependency on test005_DoBDXTransferAllowUpdateRequest since we update device1 to version - // number 5 in the above test. We reuse device1 for this test and we need to use an OTA image with a higher version number (10) - // for device1 to update itself again. We need to fix this when we want to run tests out of order. - // In this test, we test BDX transfers between one provider and two OTA requestors device1 and device2. // // 1) We announce ourselves to device1 first. @@ -946,9 +1128,8 @@ - (void)test006_DoBDXTransferWithTwoOTARequesters // 10) At this point, we set the apply update handlers for device2. // 11) Device2 applies the update and reboots with its new image. - // This test expects a pre-generated raw image at otaRawImagePath1 for device1 and at otaRawImagePath2 for device2. - NSString * otaRawImagePath1 = @"/tmp/ota-raw-image-v10"; - NSString * otaRawImagePath2 = @"/tmp/ota-raw-image-v5"; + NSString * otaRawImagePath1 = [self createRawImageWithVersion:kUpdatedSoftwareVersion_10]; + NSString * otaRawImagePath2 = [self createRawImageWithVersion:kUpdatedSoftwareVersion_5]; // Check whether the ota raw image exists at otaRawImagePath1 and otaRawImagePath2 XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:otaRawImagePath1]); @@ -985,40 +1166,8 @@ - (void)test006_DoBDXTransferWithTwoOTARequesters __block XCTestExpectation * announceResponseExpectation2; - NSString * imagePath1 = [otaRawImagePath1 stringByReplacingOccurrencesOfString:@"raw-image" withString:@"image"]; - - NSString * imagePath2 = [otaRawImagePath2 stringByReplacingOccurrencesOfString:@"raw-image" withString:@"image"]; - - // Find the right absolute path to our ota_image_tool.py script. PWD should - // point to our src/darwin/Framework, while the script is in - // src/app/ota_image_tool.py. - NSString * pwd = [[NSProcessInfo processInfo] environment][@"PWD"]; - NSString * imageToolPath = [NSString - pathWithComponents:@[ [pwd substringToIndex:(pwd.length - @"darwin/Framework".length)], @"app", @"ota_image_tool.py" ]]; - - NSTask * task1 = [[NSTask alloc] init]; - [task1 setLaunchPath:imageToolPath]; - [task1 setArguments:@[ - @"create", @"-v", @"0xFFF1", @"-p", @"0x8001", @"-vn", [kUpdatedSoftwareVersion_10 stringValue], @"-vs", - kUpdatedSoftwareVersionString_10, @"-da", @"sha256", otaRawImagePath1, imagePath1 - ]]; - NSError * launchError = nil; - [task1 launchAndReturnError:&launchError]; - XCTAssertNil(launchError); - [task1 waitUntilExit]; - XCTAssertEqual([task1 terminationStatus], 0); - - NSTask * task2 = [[NSTask alloc] init]; - [task2 setLaunchPath:imageToolPath]; - [task2 setArguments:@[ - @"create", @"-v", @"0xFFF1", @"-p", @"0x8001", @"-vn", [kUpdatedSoftwareVersion_5 stringValue], @"-vs", - kUpdatedSoftwareVersionString_5, @"-da", @"sha256", otaRawImagePath2, imagePath2 - ]]; - launchError = nil; - [task2 launchAndReturnError:&launchError]; - XCTAssertNil(launchError); - [task2 waitUntilExit]; - XCTAssertEqual([task2 terminationStatus], 0); + NSString * imagePath1 = [self createImageFromRawImage:otaRawImagePath1 withVersion:kUpdatedSoftwareVersion_10]; + NSString * imagePath2 = [self createImageFromRawImage:otaRawImagePath2 withVersion:kUpdatedSoftwareVersion_5]; NSData * updateToken1 = [sOTAProviderDelegate generateUpdateToken]; NSData * updateToken2 = [sOTAProviderDelegate generateUpdateToken]; @@ -1027,8 +1176,12 @@ - (void)test006_DoBDXTransferWithTwoOTARequesters __block uint64_t imageSize; __block uint32_t lastBlockIndex = UINT32_MAX; const uint16_t busyDelay = 30; // 30 second - __auto_type * device1 = sConnectedDevice1; - __auto_type * device2 = sConnectedDevice2; + + __auto_type * runner1 = [[MTROTARequestorAppRunner alloc] initWithPayload:kOnboardingPayload1 testcase:self]; + __auto_type * device1 = [runner1 commissionWithNodeID:@(kDeviceId1)]; + + __auto_type * runner2 = [[MTROTARequestorAppRunner alloc] initWithPayload:kOnboardingPayload2 testcase:self]; + __auto_type * device2 = [runner2 commissionWithNodeID:@(kDeviceId2)]; // This to keep track of whether queryImageHandler for device 2 was called or not. The first time it's called we will // fulfill queryExpectation2 and proceed with BDX for device 1. @@ -1238,7 +1391,7 @@ - (void)test006_DoBDXTransferWithTwoOTARequesters // Device1 is updated to version 10 and device2 to version 5. NSNumber * kSoftwareVersion = (isDeviceID1) ? kUpdatedSoftwareVersion_10 : kUpdatedSoftwareVersion_5; NSString * otaImageFilePath = (isDeviceID1) ? otaRawImagePath1 : otaRawImagePath2; - NSString * otaDownloadedFilePath = (isDeviceID1) ? kOtaDownloadedFilePath1 : kOtaDownloadedFilePath2; + NSString * otaDownloadedFilePath = (isDeviceID1) ? runner1.downloadFilePath : runner2.downloadFilePath; XCTAssertEqual(controller, sController); XCTAssertEqualObjects(params.updateToken, updateToken); @@ -1343,19 +1496,19 @@ - (void)test007_DoBDXTransferIncrementalOtaUpdate // 4) Device3 completes the BDX transfer // 5) Device3 applies the update and reboots with the new image with version number 10 - // This test expects a pre-generated raw image at otaRawImagePath1 and a raw image at otaRawImagePath2 - NSString * otaRawImagePath1 = @"/tmp/ota-raw-image-v5"; - NSString * otaRawImagePath2 = @"/tmp/ota-raw-image-v10"; + NSString * otaRawImagePath1 = [self createRawImageWithVersion:kUpdatedSoftwareVersion_5]; + NSString * otaRawImagePath2 = [self createRawImageWithVersion:kUpdatedSoftwareVersion_10]; // Check whether the ota raw image exists at otaRawImagePath1 and otaRawImagePath2 XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:otaRawImagePath1]); XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:otaRawImagePath2]); - __auto_type * device = sConnectedDevice3; + __auto_type * runner = [[MTROTARequestorAppRunner alloc] initWithPayload:kOnboardingPayload3 testcase:self]; + __auto_type * device = [runner commissionWithNodeID:@(kDeviceId3)]; __auto_type * checker = [[MTROTAProviderTransferChecker alloc] initWithRawImagePath:otaRawImagePath1 - otaImageDownloadFilePath:kOtaDownloadedFilePath3 + otaImageDownloadFilePath:runner.downloadFilePath nodeID:@(kDeviceId3) softwareVersion:kUpdatedSoftwareVersion_5 softwareVersionString:kUpdatedSoftwareVersionString_5 @@ -1386,7 +1539,7 @@ - (void)test007_DoBDXTransferIncrementalOtaUpdate __auto_type * checker1 = [[MTROTAProviderTransferChecker alloc] initWithRawImagePath:otaRawImagePath2 - otaImageDownloadFilePath:kOtaDownloadedFilePath3 + otaImageDownloadFilePath:runner.downloadFilePath nodeID:@(kDeviceId3) softwareVersion:kUpdatedSoftwareVersion_10 softwareVersionString:kUpdatedSoftwareVersionString_10 @@ -1417,18 +1570,9 @@ - (void)test007_DoBDXTransferIncrementalOtaUpdate - (void)test999_TearDown { - __auto_type * device = [MTRBaseDevice deviceWithNodeID:@(kDeviceId1) controller:sController]; - ResetCommissionee(device, dispatch_get_main_queue(), self, kTimeoutInSeconds); - - device = [MTRBaseDevice deviceWithNodeID:@(kDeviceId2) controller:sController]; - ResetCommissionee(device, dispatch_get_main_queue(), self, kTimeoutInSeconds); - - device = [MTRBaseDevice deviceWithNodeID:@(kDeviceId3) controller:sController]; - ResetCommissionee(device, dispatch_get_main_queue(), self, kTimeoutInSeconds); - [[self class] shutdownStack]; } -#endif +#endif // ENABLE_OTA_TESTS @end