diff --git a/src/darwin/Framework/CHIPTests/MTRSwiftDeviceTests.swift b/src/darwin/Framework/CHIPTests/MTRSwiftDeviceTests.swift new file mode 100644 index 00000000000000..e40c855763d60b --- /dev/null +++ b/src/darwin/Framework/CHIPTests/MTRSwiftDeviceTests.swift @@ -0,0 +1,397 @@ +import Matter +import XCTest + +// This should eventually grow into a Swift copy of MTRDeviceTests + +struct DeviceConstants { + static let testVendorID = 0xFFF1 + static let onboardingPayload = "MT:-24J0AFN00KA0648G00" + static let deviceID = 0x12344321 + static let timeoutInSeconds : UInt16 = 3 + static let pairingTimeoutInSeconds : UInt16 = 10 +} + +var sConnectedDevice: MTRBaseDevice? = nil + +var sController: MTRDeviceController? = nil + +var sTestKeys: MTRTestKeys? = nil + +// Because we are using things from Matter.framework that are flagged +// as only being available starting with macOS 13.3, we need to flag our +// code with the same availabiluty annotation. +@available(macOS, introduced: 13.3) +@available(iOS, introduced: 16.4) +class MTRSwiftDeviceTestControllerDelegate : NSObject, MTRDeviceControllerDelegate { + let expectation: XCTestExpectation + + init(withExpectation providedExpectation: XCTestExpectation) { + expectation = providedExpectation + } + + func controller(_ controller: MTRDeviceController, statusUpdate status: MTRCommissioningStatus) { + XCTAssertNotEqual(status, MTRCommissioningStatus.failed) + } + + func controller(_ controller: MTRDeviceController, commissioningSessionEstablishmentDone error: Error?) { + XCTAssertNil(error) + + do { + try controller.commissionNode(withID: DeviceConstants.deviceID as NSNumber, commissioningParams: MTRCommissioningParameters()) + } catch { + XCTFail("Could not start commissioning of node: \(error)") + } + + // Keep waiting for commissioningComplete + } + + func controller(_ controller: MTRDeviceController, commissioningComplete error: Error?, nodeID: NSNumber?) { + XCTAssertNil(error) + XCTAssertEqual(nodeID, DeviceConstants.deviceID as NSNumber) + sConnectedDevice = MTRBaseDevice(nodeID: nodeID!, controller: controller) + expectation.fulfill() + } +} + +typealias MTRDeviceTestDelegateDataHandler = ([[ String: Any ]]) -> Void + +class MTRSwiftDeviceTestDelegate : NSObject, MTRDeviceDelegate { + var onReachable : () -> Void + var onNotReachable : (() -> Void)? = nil + var onAttributeDataReceived : MTRDeviceTestDelegateDataHandler? = nil + var onEventDataReceived : MTRDeviceTestDelegateDataHandler? = nil + var onReportEnd : (() -> Void)? = nil + + init(withReachableHandler handler : @escaping () -> Void) { + onReachable = handler + } + + func device(_ device: MTRDevice, stateChanged state : MTRDeviceState) { + if (state == MTRDeviceState.reachable) { + onReachable() + } else { + onNotReachable?() + } + } + + func device(_ device : MTRDevice, receivedAttributeReport attributeReport : [[ String: Any ]]) + { + onAttributeDataReceived?(attributeReport) + } + + func device(_ device : MTRDevice, receivedEventReport eventReport : [[ String : Any ]]) + { + onEventDataReceived?(eventReport) + } + + @objc func unitTestReportEnd(forDevice : MTRDevice) + { + onReportEnd?() + } +} + +// Because we are using things from Matter.framework that are flagged +// as only being available starting with macOS 13.5, we need to flag our +// code with the same availability annotation. +@available(macOS, introduced: 14.1) +@available(iOS, introduced: 17.1) +class MTRSwiftDeviceTests : XCTestCase { + static var sStackInitRan : Bool = false + static var sNeedsStackShutdown : Bool = true + + static override func tearDown() { + // Global teardown, runs once + if (sNeedsStackShutdown) { + // We don't need to worry about ResetCommissionee. If we get here, + // we're running only one of our test methods (using + // -only-testing:MatterTests/MTRSwiftDeviceTests/testMethodName), since + // we did not run test999_TearDown. + shutdownStack() + } + } + + override func setUp() + { + // Per-test setup, runs before each test. + super.setUp() + self.continueAfterFailure = false + + if (!MTRSwiftDeviceTests.sStackInitRan) { + initStack() + } + } + + override func tearDown() + { + // Per-test teardown, runs after each test. + super.tearDown() + } + + func initStack() + { + MTRSwiftDeviceTests.sStackInitRan = true + + let factory = MTRDeviceControllerFactory.sharedInstance() + + let storage = MTRTestStorage() + let factoryParams = MTRDeviceControllerFactoryParams(storage: storage) + + do { + try factory.start(factoryParams) + } catch { + XCTFail("Count not start controller factory: \(error)") + } + XCTAssertTrue(factory.isRunning) + + let testKeys = MTRTestKeys() + + sTestKeys = testKeys + + // Needs to match what startControllerOnExistingFabric calls elsewhere in + // this file do. + let params = MTRDeviceControllerStartupParams(ipk: testKeys.ipk, fabricID: 1, nocSigner:testKeys) + params.vendorID = DeviceConstants.testVendorID as NSNumber + + let controller : MTRDeviceController + do { + controller = try factory.createController(onNewFabric: params) + } catch { + XCTFail("Could not create controller: \(error)") + return + } + XCTAssertTrue(controller.isRunning) + + sController = controller + + let expectation = expectation(description : "Commissioning Complete") + + let controllerDelegate = MTRSwiftDeviceTestControllerDelegate(withExpectation: expectation) + let serialQueue = DispatchQueue(label: "com.chip.device_controller_delegate") + + controller.setDeviceControllerDelegate(controllerDelegate, queue: serialQueue) + + let payload : MTRSetupPayload + do { + payload = try MTRSetupPayload(onboardingPayload: DeviceConstants.onboardingPayload) + } catch { + XCTFail("Could not parse setup payload: \(error)") + return + } + + do { + try controller.setupCommissioningSession(with:payload, newNodeID: DeviceConstants.deviceID as NSNumber) + } catch { + XCTFail("Could not start setting up PASE session: \(error)") + return } + + wait(for: [expectation], timeout: TimeInterval(DeviceConstants.pairingTimeoutInSeconds)) + } + + static func shutdownStack() + { + sNeedsStackShutdown = false + + let controller = sController + XCTAssertNotNil(controller) + + controller!.shutdown() + XCTAssertFalse(controller!.isRunning) + + MTRDeviceControllerFactory.sharedInstance().stop() + } + + func test000_SetUp() + { + // Nothing to do here; our setUp method handled this already. This test + // just exists to make the setup not look like it's happening inside other + // tests. + } + + func test017_TestMTRDeviceBasics() + { + let device = MTRDevice(nodeID: DeviceConstants.deviceID as NSNumber, controller:sController!) + let queue = DispatchQueue.main + + // Given reachable state becomes true before underlying OnSubscriptionEstablished callback, this expectation is necessary but + // not sufficient as a mark to the end of reports + let subscriptionExpectation = expectation(description: "Subscription has been set up") + + let delegate = MTRSwiftDeviceTestDelegate(withReachableHandler: { () -> Void in + subscriptionExpectation.fulfill() + }) + + var attributeReportsReceived : Int = 0 + delegate.onAttributeDataReceived = { (data: [[ String: Any ]]) -> Void in + attributeReportsReceived += data.count + } + + // This is dependent on current implementation that priming reports send attributes and events in that order, and also that + // events in this test would fit in one report. So receiving events would mean all attributes and events have been received, and + // can satisfy the test below. + let gotReportsExpectation = expectation(description: "Attribute and Event reports have been received") + var eventReportsReceived : Int = 0 + delegate.onEventDataReceived = { (eventReport: [[ String: Any ]]) -> Void in + eventReportsReceived += eventReport.count + + for eventDict in eventReport { + let eventTimeTypeNumber = eventDict[MTREventTimeTypeKey] as! NSNumber? + XCTAssertNotNil(eventTimeTypeNumber) + let eventTimeType = MTREventTimeType(rawValue: eventTimeTypeNumber!.uintValue) + XCTAssert((eventTimeType == MTREventTimeType.systemUpTime) || (eventTimeType == MTREventTimeType.timestampDate)) + if (eventTimeType == MTREventTimeType.systemUpTime) { + XCTAssertNotNil(eventDict[MTREventSystemUpTimeKey]) + XCTAssertNotNil(device.estimatedStartTime) + } else if (eventTimeType == MTREventTimeType.timestampDate) { + XCTAssertNotNil(eventDict[MTREventTimestampDateKey]) + } + } + } + delegate.onReportEnd = { () -> Void in + gotReportsExpectation.fulfill() + } + + device.setDelegate(_: delegate, queue:queue) + + // Test batching and duplicate check + // - Read 13 different attributes in a row, expect that the 1st to go out by itself, the next 9 batch, and then the 3 after + // are correctly queued in one batch + // - Then read 3 duplicates and expect them to be filtered + // - Note that these tests can only be verified via logs + device.readAttribute(withEndpointID: 1, clusterID: NSNumber(value: MTRClusterIDType.scenesID.rawValue), attributeID: 0, params: nil) + + device.readAttribute(withEndpointID: 1, clusterID: NSNumber(value: MTRClusterIDType.scenesID.rawValue), attributeID: 1, params: nil) + device.readAttribute(withEndpointID: 1, clusterID: NSNumber(value: MTRClusterIDType.scenesID.rawValue), attributeID: 2, params: nil) + device.readAttribute(withEndpointID: 1, clusterID: NSNumber(value: MTRClusterIDType.scenesID.rawValue), attributeID: 3, params: nil) + device.readAttribute(withEndpointID: 1, clusterID: NSNumber(value: MTRClusterIDType.scenesID.rawValue), attributeID: 4, params: nil) + device.readAttribute(withEndpointID: 1, clusterID: NSNumber(value: MTRClusterIDType.scenesID.rawValue), attributeID: 5, params: nil) + + device.readAttribute(withEndpointID: 1, clusterID: NSNumber(value: MTRClusterIDType.scenesID.rawValue), attributeID: 6, params: nil) + device.readAttribute(withEndpointID: 1, clusterID: NSNumber(value: MTRClusterIDType.scenesID.rawValue), attributeID: 7, params: nil) + device.readAttribute(withEndpointID: 1, clusterID: NSNumber(value: MTRClusterIDType.levelControlID.rawValue), attributeID: 0, params: nil) + device.readAttribute(withEndpointID: 1, clusterID: NSNumber(value: MTRClusterIDType.levelControlID.rawValue), attributeID: 1, params: nil) + + device.readAttribute(withEndpointID: 1, clusterID: NSNumber(value: MTRClusterIDType.levelControlID.rawValue), attributeID: 2, params: nil) + device.readAttribute(withEndpointID: 1, clusterID: NSNumber(value: MTRClusterIDType.levelControlID.rawValue), attributeID: 3, params: nil) + device.readAttribute(withEndpointID: 1, clusterID: NSNumber(value: MTRClusterIDType.levelControlID.rawValue), attributeID: 4, params: nil) + + device.readAttribute(withEndpointID: 1, clusterID: NSNumber(value: MTRClusterIDType.levelControlID.rawValue), attributeID: 4, params: nil) + device.readAttribute(withEndpointID: 1, clusterID: NSNumber(value: MTRClusterIDType.levelControlID.rawValue), attributeID: 4, params: nil) + device.readAttribute(withEndpointID: 1, clusterID: NSNumber(value: MTRClusterIDType.levelControlID.rawValue), attributeID: 4, params: nil) + + wait(for: [ subscriptionExpectation, gotReportsExpectation ], timeout:60) + + delegate.onReportEnd = nil + + XCTAssertNotEqual(attributeReportsReceived, 0) + XCTAssertNotEqual(eventReportsReceived, 0) + + // Before resubscribe, first test write failure and expected value effects + let testEndpointID = 1 as NSNumber + let testClusterID = 8 as NSNumber + let testAttributeID = 10000 as NSNumber // choose a nonexistent attribute to cause a failure + let expectedValueReportedExpectation = expectation(description: "Expected value reported") + let expectedValueRemovedExpectation = expectation(description: "Expected value removed") + delegate.onAttributeDataReceived = { (attributeReport: [[ String: Any ]]) -> Void in + for attributeDict in attributeReport { + let attributePath = attributeDict[MTRAttributePathKey] as! MTRAttributePath + XCTAssertNotNil(attributePath) + if (attributePath.endpoint == testEndpointID && + attributePath.cluster == testClusterID && + attributePath.attribute == testAttributeID) { + let data = attributeDict[MTRDataKey] + if (data != nil) { + expectedValueReportedExpectation.fulfill() + } else { + expectedValueRemovedExpectation.fulfill() + } + } + } + } + + let writeValue = [ "type": "UnsignedInteger", "value": 200 ] as [String: Any] + device.writeAttribute(withEndpointID: testEndpointID, + clusterID: testClusterID, + attributeID: testAttributeID, + value: writeValue, + expectedValueInterval: 20000, + timedWriteTimeout:nil) + + // expected value interval is 20s but expect it get reverted immediately as the write fails because it's writing to a + // nonexistent attribute + wait(for: [ expectedValueReportedExpectation, expectedValueRemovedExpectation ], timeout: 5, enforceOrder: true) + + // Test if errors are properly received + let attributeReportErrorExpectation = expectation(description: "Attribute read error") + delegate.onAttributeDataReceived = { (data: [[ String: Any ]]) -> Void in + for attributeReponseValue in data { + if (attributeReponseValue[MTRErrorKey] != nil) { + attributeReportErrorExpectation.fulfill() + } + } + } + // use the nonexistent attribute and expect read error + device.readAttribute(withEndpointID: testEndpointID, clusterID: testClusterID, attributeID: testAttributeID, params: nil) + wait(for: [ attributeReportErrorExpectation ], timeout: 10) + + // Resubscription test setup + let subscriptionDroppedExpectation = expectation(description: "Subscription has dropped") + delegate.onNotReachable = { () -> Void in + subscriptionDroppedExpectation.fulfill() + }; + let resubscriptionExpectation = expectation(description: "Resubscription has happened") + delegate.onReachable = { () -> Void in + resubscriptionExpectation.fulfill() + }; + + // reset the onAttributeDataReceived to validate the following resubscribe test + attributeReportsReceived = 0; + eventReportsReceived = 0; + delegate.onAttributeDataReceived = { (data: [[ String: Any ]]) -> Void in + attributeReportsReceived += data.count; + }; + + delegate.onEventDataReceived = { (eventReport: [[ String: Any ]]) -> Void in + eventReportsReceived += eventReport.count; + }; + + // Now trigger another subscription which will cause ours to drop; we should re-subscribe after that. + let baseDevice = sConnectedDevice + let params = MTRSubscribeParams(minInterval: 1, maxInterval: 2) + params.shouldResubscribeAutomatically = false; + params.shouldReplaceExistingSubscriptions = true; + // Create second subscription which will cancel the first subscription. We + // can use a non-existent path here to cut down on the work that gets done. + baseDevice?.subscribeToAttributes(withEndpointID: 10000, + clusterID: 6, + attributeID: 0, + params: params, + queue: queue, + reportHandler: { (_: [[String : Any]]?, _: Error?) -> Void in + }) + + wait(for: [ subscriptionDroppedExpectation ], timeout:60) + + // Check that device resets start time on subscription drop + XCTAssertNil(device.estimatedStartTime) + + wait(for: [ resubscriptionExpectation ], timeout:60) + + // Now make sure we ignore later tests. Ideally we would just unsubscribe + // or remove the delegate, but there's no good way to do that. + delegate.onReachable = { () -> Void in } + delegate.onNotReachable = nil + delegate.onAttributeDataReceived = nil + delegate.onEventDataReceived = nil + + // Make sure we got no updated reports (because we had a cluster state cache + // with data versions) during the resubscribe. + XCTAssertEqual(attributeReportsReceived, 0); + XCTAssertEqual(eventReportsReceived, 0); + } + + func test999_TearDown() + { + ResetCommissionee(sConnectedDevice, DispatchQueue.main, self, DeviceConstants.timeoutInSeconds) + type(of: self).shutdownStack() + } +} diff --git a/src/darwin/Framework/CHIPTests/MTRSwiftPairingTests.swift b/src/darwin/Framework/CHIPTests/MTRSwiftPairingTests.swift index 6761322640e169..5d88db56277631 100644 --- a/src/darwin/Framework/CHIPTests/MTRSwiftPairingTests.swift +++ b/src/darwin/Framework/CHIPTests/MTRSwiftPairingTests.swift @@ -3,7 +3,7 @@ import XCTest // This more or less parallels the "no delegate" case in MTRPairingTests -struct Constants { +struct PairingConstants { static let localPort = 5541 static let vendorID = 0xFFF1 static let onboardingPayload = "MT:Y.K90SO527JA0648G00" @@ -15,6 +15,7 @@ struct Constants { // as only being available starting with macOS 13.3, we need to flag our // code with the same availabiluty annotation. @available(macOS, introduced: 13.3) +@available(iOS, introduced: 16.4) class MTRSwiftPairingTestControllerDelegate : NSObject, MTRDeviceControllerDelegate { let expectation: XCTestExpectation @@ -30,7 +31,7 @@ class MTRSwiftPairingTestControllerDelegate : NSObject, MTRDeviceControllerDeleg XCTAssertNil(error) do { - try controller.commissionNode(withID: Constants.deviceID as NSNumber, commissioningParams: MTRCommissioningParameters()) + try controller.commissionNode(withID: PairingConstants.deviceID as NSNumber, commissioningParams: MTRCommissioningParameters()) } catch { XCTFail("Could not start commissioning of node: \(error)") } @@ -40,7 +41,7 @@ class MTRSwiftPairingTestControllerDelegate : NSObject, MTRDeviceControllerDeleg func controller(_ controller: MTRDeviceController, commissioningComplete error: Error?, nodeID: NSNumber?) { XCTAssertNil(error) - XCTAssertEqual(nodeID, Constants.deviceID as NSNumber) + XCTAssertEqual(nodeID, PairingConstants.deviceID as NSNumber) expectation.fulfill() } } @@ -50,12 +51,13 @@ class MTRSwiftPairingTests : XCTestCase { // as only being available starting with macOS 13.3, we need to flag our // code with the same availabiluty annotation. @available(macOS, introduced: 13.3) + @available(iOS, introduced: 16.4) func test001_BasicPairing() { let factory = MTRDeviceControllerFactory.sharedInstance() let storage = MTRTestStorage() let factoryParams = MTRDeviceControllerFactoryParams(storage: storage) - factoryParams.port = Constants.localPort as NSNumber + factoryParams.port = PairingConstants.localPort as NSNumber do { try factory.start(factoryParams) @@ -68,7 +70,7 @@ class MTRSwiftPairingTests : XCTestCase { let testKeys = MTRTestKeys() let params = MTRDeviceControllerStartupParams(ipk: testKeys.ipk, fabricID: 1, nocSigner: testKeys) - params.vendorID = Constants.vendorID as NSNumber + params.vendorID = PairingConstants.vendorID as NSNumber let controller: MTRDeviceController do { @@ -88,22 +90,22 @@ class MTRSwiftPairingTests : XCTestCase { let payload : MTRSetupPayload do { - payload = try MTRSetupPayload(onboardingPayload: Constants.onboardingPayload) + payload = try MTRSetupPayload(onboardingPayload: PairingConstants.onboardingPayload) } catch { XCTFail("Could not parse setup payload: \(error)") return } do { - try controller.setupCommissioningSession(with: payload, newNodeID: Constants.deviceID as NSNumber) + try controller.setupCommissioningSession(with: payload, newNodeID: PairingConstants.deviceID as NSNumber) } catch { XCTFail("Could not start setting up PASE session: \(error)") return } - wait(for: [expectation], timeout: TimeInterval(Constants.timeoutInSeconds)) + wait(for: [expectation], timeout: TimeInterval(PairingConstants.timeoutInSeconds)) - ResetCommissionee(MTRBaseDevice(nodeID: Constants.deviceID as NSNumber, controller: controller), DispatchQueue.main, self, Constants.timeoutInSeconds) + ResetCommissionee(MTRBaseDevice(nodeID: PairingConstants.deviceID as NSNumber, controller: controller), DispatchQueue.main, self, PairingConstants.timeoutInSeconds) controller.shutdown() XCTAssertFalse(controller.isRunning) diff --git a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj index 0f5cf8dedc20dd..87196b6063c07d 100644 --- a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj +++ b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj @@ -182,6 +182,7 @@ 51CFDDB12AC5F78F00DA7CA5 /* EmptyDataModelHandler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 51CFDDB02AC5F78F00DA7CA5 /* EmptyDataModelHandler.cpp */; }; 51CFDDB22AC5F78F00DA7CA5 /* EmptyDataModelHandler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 51CFDDB02AC5F78F00DA7CA5 /* EmptyDataModelHandler.cpp */; }; 51D10D2E2808E2CA00E8CA3D /* MTRTestStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = 51D10D2D2808E2CA00E8CA3D /* MTRTestStorage.m */; }; + 51E0FC102ACBBF230001E197 /* MTRSwiftDeviceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E0FC0F2ACBBF230001E197 /* MTRSwiftDeviceTests.swift */; }; 51E24E73274E0DAC007CCF6E /* MTRErrorTestUtils.mm in Sources */ = {isa = PBXBuildFile; fileRef = 51E24E72274E0DAC007CCF6E /* MTRErrorTestUtils.mm */; }; 51E4D121291D0EB400C8C535 /* MTRBaseClusterUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 51E4D120291D0EB400C8C535 /* MTRBaseClusterUtils.h */; }; 51E51FBF282AD37A00FC978D /* MTRDeviceControllerStartupParams.h in Headers */ = {isa = PBXBuildFile; fileRef = 51E51FBC282AD37A00FC978D /* MTRDeviceControllerStartupParams.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -541,6 +542,7 @@ 51C984612A61CE2A00B0AD9A /* MTRFabricInfoChecker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRFabricInfoChecker.h; sourceTree = ""; }; 51CFDDB02AC5F78F00DA7CA5 /* EmptyDataModelHandler.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = EmptyDataModelHandler.cpp; path = ../controller/EmptyDataModelHandler.cpp; sourceTree = ""; }; 51D10D2D2808E2CA00E8CA3D /* MTRTestStorage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MTRTestStorage.m; sourceTree = ""; }; + 51E0FC0F2ACBBF230001E197 /* MTRSwiftDeviceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MTRSwiftDeviceTests.swift; sourceTree = ""; }; 51E24E72274E0DAC007CCF6E /* MTRErrorTestUtils.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MTRErrorTestUtils.mm; sourceTree = ""; }; 51E4D120291D0EB400C8C535 /* MTRBaseClusterUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRBaseClusterUtils.h; sourceTree = ""; }; 51E51FBC282AD37A00FC978D /* MTRDeviceControllerStartupParams.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRDeviceControllerStartupParams.h; sourceTree = ""; }; @@ -1248,6 +1250,7 @@ 51339B1E2A0DA64D00C798C1 /* MTRCertificateValidityTests.m */, 519498312A25581C00B3BABE /* MTRSetupPayloadSerializerTests.m */, 5143851D2A65885500EDC8E6 /* MTRSwiftPairingTests.swift */, + 51E0FC0F2ACBBF230001E197 /* MTRSwiftDeviceTests.swift */, 51E95DF72A78110900A434F0 /* MTRPerControllerStorageTests.m */, 518D3F842AA14006008E0007 /* MTRControllerAdvertisingTests.m */, B202529D2459E34F00F97062 /* Info.plist */, @@ -1789,6 +1792,7 @@ 3DFCB3292966684500332B35 /* MTRCertificateInfoTests.m in Sources */, 517BF3F3282B62CB00A8B7DB /* MTRCertificateTests.m in Sources */, 5142E39829D377F000A206F0 /* MTROTAProviderTests.m in Sources */, + 51E0FC102ACBBF230001E197 /* MTRSwiftDeviceTests.swift in Sources */, 51E24E73274E0DAC007CCF6E /* MTRErrorTestUtils.mm in Sources */, 519498322A25581C00B3BABE /* MTRSetupPayloadSerializerTests.m in Sources */, 51A2F1322A00402A00F03298 /* MTRDataValueParserTests.m in Sources */,