diff --git a/src/darwin/Framework/CHIPTests/MTROTAProviderTests.m b/src/darwin/Framework/CHIPTests/MTROTAProviderTests.m index 39f6a499312088..3998a84a140863 100644 --- a/src/darwin/Framework/CHIPTests/MTROTAProviderTests.m +++ b/src/darwin/Framework/CHIPTests/MTROTAProviderTests.m @@ -20,8 +20,10 @@ #import "MTRDeviceTestDelegate.h" #import "MTRErrorTestUtils.h" +#import "MTRTestCase.h" #import "MTRTestKeys.h" #import "MTRTestResetCommissioneeHelper.h" +#import "MTRTestServerAppRunner.h" #import "MTRTestStorage.h" // system dependencies @@ -73,23 +75,12 @@ 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 -- (NSTask *)createTaskForPath:(NSString *)path; +@interface MTROTAProviderTests : MTRTestCase - (NSString *)createImageFromRawImage:(NSString *)rawImage withVersion:(NSNumber *)version; - (MTRDevice *)commissionDeviceWithPayload:(NSString *)payloadString nodeID:(NSNumber *)nodeID; -- (void)registerRunningRequestor:(MTROTARequestorAppRunner *)requestor; @end -static unsigned sAppRunnerIndex = 1; - -@interface MTROTARequestorAppRunner : NSObject +@interface MTROTARequestorAppRunner : MTRTestServerAppRunner @property (nonatomic, copy) NSString * downloadFilePath; - (instancetype)initWithPayload:(NSString *)payload testcase:(MTROTAProviderTests *)testcase; @@ -97,11 +88,8 @@ - (MTRDevice *)commissionWithNodeID:(NSNumber *)nodeID; @end @implementation MTROTARequestorAppRunner { - unsigned _uniqueIndex; - NSTask * _appTask; MTROTAProviderTests * _testcase; NSString * _payload; - MTRDevice * commissionedDevice; } - (MTRDevice *)commissionWithNodeID:(NSNumber *)nodeID @@ -111,67 +99,24 @@ - (MTRDevice *)commissionWithNodeID:(NSNumber *)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], + __auto_type * downloadFilePath = [NSString stringWithFormat:@"/tmp/chip-ota-requestor-downloaded-image%u", [MTRTestServerAppRunner nextUniqueIndex]]; + __auto_type * extraArguments = @[ @"--otaDownloadPath", - _downloadFilePath, + 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); + if (!(self = [super initWithAppName:@"ota-requestor" arguments:extraArguments payload:payload testcase:testcase])) { + return nil; + } - [_testcase registerRunningRequestor:self]; + _testcase = testcase; + _payload = payload; + _downloadFilePath = downloadFilePath; return self; } -- (void)terminate -{ - [_appTask terminate]; -} - @end @interface MTROTAProviderTestControllerDelegate : NSObject @@ -579,7 +524,6 @@ - (instancetype)initWithRawImagePath:(NSString *)rawImagePath @implementation MTROTAProviderTests { NSMutableSet * _commissionedNodeIDs; - NSMutableSet * _runningRequestors; } + (void)tearDown @@ -605,7 +549,6 @@ - (void)setUp } _commissionedNodeIDs = [[NSMutableSet alloc] init]; - _runningRequestors = [[NSMutableSet alloc] init]; XCTAssertNil(sOTAProviderDelegate.queryImageHandler); XCTAssertNil(sOTAProviderDelegate.applyUpdateRequestHandler); @@ -637,12 +580,6 @@ - (void)tearDown 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]); @@ -686,11 +623,6 @@ - (MTRDevice *)commissionDeviceWithPayload:(NSString *)payloadString nodeID:(NSN return [MTRDevice deviceWithNodeID:nodeID controller:sController]; } -- (void)registerRunningRequestor:(MTROTARequestorAppRunner *)requestor -{ - [_runningRequestors addObject:requestor]; -} - - (void)initStack { sStackInitRan = YES; @@ -717,43 +649,6 @@ + (void)shutdownStack [[MTRDeviceControllerFactory sharedInstance] stopControllerFactory]; } -/** - * Given a path relative to the Matter root, create an absolute path to the file. - */ -- (NSString *)absolutePathFor:(NSString *)matterRootRelativePath -{ - // Start with the absolute path to our file, then remove the suffix that - // comes after the path to the Matter SDK root. - NSString * pathToTest = [NSString stringWithUTF8String:__FILE__]; - NSMutableArray * pathComponents = [[NSMutableArray alloc] init]; - [pathComponents addObject:[pathToTest substringToIndex:(pathToTest.length - @"src/darwin/Framework/CHIPTests/MTROTAProviderTests.m".length)]]; - [pathComponents addObjectsFromArray:[matterRootRelativePath pathComponents]]; - return [NSString pathWithComponents:pathComponents]; -} - -/** - * 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; -} - -/** - * Runs a task to completion and makes sure it succeeds. - */ -- (void)runTask:(NSTask *)task -{ - NSError * launchError; - [task launchAndReturnError:&launchError]; - XCTAssertNil(launchError); - - [task waitUntilExit]; - XCTAssertEqual([task terminationStatus], 0); -} - /** * Returns path to the raw image. */ diff --git a/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestCase.h b/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestCase.h index d12ea0a1f69f84..c9f20e174d5816 100644 --- a/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestCase.h +++ b/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestCase.h @@ -16,6 +16,12 @@ #import +#if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR +#define HAVE_NSTASK 0 +#else +#define HAVE_NSTASK 1 +#endif + NS_ASSUME_NONNULL_BEGIN @interface MTRTestCase : XCTestCase @@ -23,6 +29,31 @@ NS_ASSUME_NONNULL_BEGIN // on every single sub-test is slow, and some of our tests seem to have leaks // outside Matter.framework. So have it be opt-in for now, and improve later. @property (nonatomic) BOOL detectLeaks; + +#if HAVE_NSTASK +/** + * Create an NSTask for the given path. Path should be relative to the Matter + * SDK root. + */ +- (NSTask *)createTaskForPath:(NSString *)path; + +/** + * Run a task to completion and make sure it succeeds. + */ +- (void)runTask:(NSTask *)task; + +/** + * Launch a task. The task will be automatically terminated when the testcase + * tearDown happens. + */ +- (void)launchTask:(NSTask *)task; +#endif // HAVE_NSTASK + +/** + * Get an absolute path from a path relative to the Matter SDK root. + */ +- (NSString *)absolutePathFor:(NSString *)matterRootRelativePath; + @end NS_ASSUME_NONNULL_END diff --git a/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestCase.mm b/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestCase.mm index 1dd1fea675b46c..1d055cdcdf5089 100644 --- a/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestCase.mm +++ b/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestCase.mm @@ -19,7 +19,18 @@ #import "MTRTestCase.h" -@implementation MTRTestCase +@implementation MTRTestCase { +#if HAVE_NSTASK + NSMutableSet * _runningTasks; +#endif // NSTask +} + +- (void)setUp +{ +#if HAVE_NSTASK + _runningTasks = [[NSMutableSet alloc] init]; +#endif // HAVE_NSTASK +} /** * Unfortunately, doing this in "+ (void)tearDown" (the global suite teardown) @@ -36,7 +47,53 @@ - (void)tearDown } #endif +#if HAVE_NSTASK + for (NSTask * task in _runningTasks) { + [task terminate]; + } + _runningTasks = nil; +#endif // HAVE_NSTASK + [super tearDown]; } +#if HAVE_NSTASK +- (NSTask *)createTaskForPath:(NSString *)path +{ + NSTask * task = [[NSTask alloc] init]; + [task setLaunchPath:[self absolutePathFor:path]]; + return task; +} + +- (void)runTask:(NSTask *)task +{ + NSError * launchError; + [task launchAndReturnError:&launchError]; + XCTAssertNil(launchError); + + [task waitUntilExit]; + XCTAssertEqual([task terminationStatus], 0); +} + +- (void)launchTask:(NSTask *)task +{ + NSError * launchError; + [task launchAndReturnError:&launchError]; + XCTAssertNil(launchError); + + [_runningTasks addObject:task]; +} +#endif // HAVE_NSTASK + +- (NSString *)absolutePathFor:(NSString *)matterRootRelativePath +{ + // Start with the absolute path to our file, then remove the suffix that + // comes after the path to the Matter SDK root. + NSString * pathToTest = [NSString stringWithUTF8String:__FILE__]; + NSMutableArray * pathComponents = [[NSMutableArray alloc] init]; + [pathComponents addObject:[pathToTest substringToIndex:(pathToTest.length - @"src/darwin/Framework/CHIPTests/TestHelpers/MTRTestCase.mm".length)]]; + [pathComponents addObjectsFromArray:[matterRootRelativePath pathComponents]]; + return [NSString pathWithComponents:pathComponents]; +} + @end diff --git a/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestServerAppRunner.h b/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestServerAppRunner.h new file mode 100644 index 00000000000000..9297b76c6fcb66 --- /dev/null +++ b/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestServerAppRunner.h @@ -0,0 +1,59 @@ +/** + * Copyright (c) 2024 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 + +@class MTRTestCase; + +NS_ASSUME_NONNULL_BEGIN + +/** + * A representation of a server application instance. + * + * Server applications are assumed to be compiled into out/debug/${APPNAME}-app, + * with the binary being out/debug/${APPNAME}-app/chip-${APPNAME}-app. + */ +@interface MTRTestServerAppRunner : NSObject + +/** + * Initialize the app runner with the given app name, arguments, setup payload, and testcase + * instance. + * + * The payload will be used to determine the discriminator and passcode + * arguments the app should use, in addition to the provided arguments. + * Discriminators should be at least 1111 (see documentation for port below). + * + * The --KVS argument for the app will be automatically set to something of the + * format "/tmp/chip-${APPNAME}-kvs${UNIQUE_ID}". + * + * The port argument for the app will be determined automatically, by + * subtracting 1111 from the discriminator and adding 5542 (so as not to collide + * with any existing Matter things running on 5540/5541). + */ +- (instancetype)initWithAppName:(NSString *)name arguments:(NSArray *)arguments payload:(NSString *)payload testcase:(MTRTestCase *)testcase; + +/** + * Get the unique index that will be used for the next initialization. This + * allows including that index in the arguments provided. + * + * TODO: Should we scan the provided arguments for %u and replace with the index + * instead? + */ ++ (unsigned)nextUniqueIndex; + +@end + +NS_ASSUME_NONNULL_END diff --git a/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestServerAppRunner.m b/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestServerAppRunner.m new file mode 100644 index 00000000000000..83912e9eb2dc0b --- /dev/null +++ b/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestServerAppRunner.m @@ -0,0 +1,104 @@ +/** + * Copyright (c) 2024 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 + +#import "MTRTestCase.h" +#import "MTRTestServerAppRunner.h" + +static unsigned sAppRunnerIndex = 1; + +// kBasePort gets the discriminator added to it to figure out the port the app +// should be using. This ensures that apps with distinct discriminators use +// distinct ports. +static const uint16_t kMinDiscriminator = 1111; +static const uint16_t kBasePort = 5542 - kMinDiscriminator; + +@implementation MTRTestServerAppRunner { + unsigned _uniqueIndex; +#if HAVE_NSTASK + NSTask * _appTask; +#endif +} + +- (instancetype)initWithAppName:(NSString *)name arguments:(NSArray *)arguments payload:(NSString *)payload testcase:(MTRTestCase *)testcase +{ +#if !HAVE_NSTASK + XCTFail("Unable to start server app when we do not have NSTask"); + return nil; +#else // HAVE_NSTASK + if (!(self = [super init])) { + return nil; + } + + _uniqueIndex = sAppRunnerIndex++; + + NSError * error; + __auto_type * parsedPayload = [MTRSetupPayload setupPayloadWithOnboardingPayload:payload error:&error]; + XCTAssertNil(error); + XCTAssertNotNil(parsedPayload); + + XCTAssertFalse(parsedPayload.hasShortDiscriminator); + + NSNumber * discriminator = parsedPayload.discriminator; + XCTAssertGreaterThanOrEqual(discriminator.unsignedShortValue, kMinDiscriminator); + + NSNumber * passcode = parsedPayload.setupPasscode; + + __auto_type * executable = [NSString stringWithFormat:@"out/debug/%@-app/chip-%@-app", name, name]; + _appTask = [testcase createTaskForPath:executable]; + + __auto_type * forcedArguments = @[ + // Make sure we only advertise on the local interface. + @"--interface-id", + @"-1", + @"--secured-device-port", + [NSString stringWithFormat:@"%u", kBasePort + discriminator.unsignedShortValue], + @"--discriminator", + [NSString stringWithFormat:@"%u", discriminator.unsignedShortValue], + @"--passcode", + [NSString stringWithFormat:@"%llu", passcode.unsignedLongLongValue], + @"--KVS", + [NSString stringWithFormat:@"/tmp/chip-%@-kvs%u", name, _uniqueIndex], + ]; + + __auto_type * allArguments = [forcedArguments arrayByAddingObjectsFromArray:arguments]; + [_appTask setArguments:allArguments]; + + NSString * outFile = [NSString stringWithFormat:@"/tmp/darwin/framework-tests/%@-app-%u.log", name, _uniqueIndex]; + NSString * errorFile = [NSString stringWithFormat:@"/tmp/darwin/framework-tests/%@-app-err-%u.log", name, _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]; + + [testcase launchTask:_appTask]; + + NSLog(@"Started %@ with arguments %@ stdout=%@ and stderr=%@", name, allArguments, outFile, errorFile); + + return self; +#endif // HAVE_NSTASK +} + ++ (unsigned)nextUniqueIndex +{ + return sAppRunnerIndex; +} + +@end diff --git a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj index 36aa65dfaa2752..212c859610172b 100644 --- a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj +++ b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj @@ -231,6 +231,7 @@ 51EF279F2A2A3EB100E33F75 /* MTRBackwardsCompatShims.h in Headers */ = {isa = PBXBuildFile; fileRef = 51EF279E2A2A3EB100E33F75 /* MTRBackwardsCompatShims.h */; settings = {ATTRIBUTES = (Public, ); }; }; 51F522682AE70734000C4050 /* MTRDeviceTypeMetadata.mm in Sources */ = {isa = PBXBuildFile; fileRef = 51F522672AE70734000C4050 /* MTRDeviceTypeMetadata.mm */; }; 51F5226A2AE70761000C4050 /* MTRDeviceTypeMetadata.h in Headers */ = {isa = PBXBuildFile; fileRef = 51F522692AE70761000C4050 /* MTRDeviceTypeMetadata.h */; }; + 51F9F9D52BF7A9EE00FEA0E2 /* MTRTestServerAppRunner.m in Sources */ = {isa = PBXBuildFile; fileRef = 51F9F9D42BF7A9EE00FEA0E2 /* MTRTestServerAppRunner.m */; }; 51FE72352ACDB40000437032 /* MTRCommandPayloads_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 51FE72342ACDB40000437032 /* MTRCommandPayloads_Internal.h */; }; 51FE723F2ACDEF3E00437032 /* MTRCommandPayloadExtensions_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 51FE723E2ACDEF3E00437032 /* MTRCommandPayloadExtensions_Internal.h */; }; 5A60370827EA1FF60020DB79 /* MTRClusterStateCacheContainer+XPC.h in Headers */ = {isa = PBXBuildFile; fileRef = 5A60370727EA1FF60020DB79 /* MTRClusterStateCacheContainer+XPC.h */; }; @@ -650,6 +651,8 @@ 51F522662AE7071E000C4050 /* MTRDeviceTypeMetadata-src.zapt */ = {isa = PBXFileReference; lastKnownFileType = text; path = "MTRDeviceTypeMetadata-src.zapt"; sourceTree = ""; }; 51F522672AE70734000C4050 /* MTRDeviceTypeMetadata.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MTRDeviceTypeMetadata.mm; sourceTree = ""; }; 51F522692AE70761000C4050 /* MTRDeviceTypeMetadata.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRDeviceTypeMetadata.h; sourceTree = ""; }; + 51F9F9D32BF7A9EE00FEA0E2 /* MTRTestServerAppRunner.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRTestServerAppRunner.h; sourceTree = ""; }; + 51F9F9D42BF7A9EE00FEA0E2 /* MTRTestServerAppRunner.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MTRTestServerAppRunner.m; sourceTree = ""; }; 51FE72342ACDB40000437032 /* MTRCommandPayloads_Internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRCommandPayloads_Internal.h; sourceTree = ""; }; 51FE723E2ACDEF3E00437032 /* MTRCommandPayloadExtensions_Internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRCommandPayloadExtensions_Internal.h; sourceTree = ""; }; 5A60370727EA1FF60020DB79 /* MTRClusterStateCacheContainer+XPC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MTRClusterStateCacheContainer+XPC.h"; sourceTree = ""; }; @@ -1138,6 +1141,8 @@ 5131BF642BE2E1B000D5D6BC /* MTRTestCase.mm */, D437613F285BDC0D0051FEA2 /* MTRTestKeys.h */, 51C8E3F72825CDB600D47D00 /* MTRTestKeys.m */, + 51F9F9D32BF7A9EE00FEA0E2 /* MTRTestServerAppRunner.h */, + 51F9F9D42BF7A9EE00FEA0E2 /* MTRTestServerAppRunner.m */, D4376140285BDC0D0051FEA2 /* MTRTestStorage.h */, 51D10D2D2808E2CA00E8CA3D /* MTRTestStorage.m */, D437613E285BDC0D0051FEA2 /* MTRErrorTestUtils.h */, @@ -2027,6 +2032,7 @@ 510CECA8297F72970064E0B3 /* MTROperationalCertificateIssuerTests.m in Sources */, 5A7947DE27BEC3F500434CF2 /* MTRXPCListenerSampleTests.m in Sources */, 3DFCB3292966684500332B35 /* MTRCertificateInfoTests.m in Sources */, + 51F9F9D52BF7A9EE00FEA0E2 /* MTRTestServerAppRunner.m in Sources */, 517BF3F3282B62CB00A8B7DB /* MTRCertificateTests.m in Sources */, 5142E39829D377F000A206F0 /* MTROTAProviderTests.m in Sources */, 51E0FC102ACBBF230001E197 /* MTRSwiftDeviceTests.swift in Sources */,