From 563d8083cdb28367f0f2813333296e167a5de4ef Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Mon, 6 Nov 2023 15:15:14 -0300 Subject: [PATCH] Add DataGatherer from 46ee0b0 --- Ably.xcodeproj/project.pbxproj | 8 +++ Spec/Test Utilities/DataGatherer.swift | 75 ++++++++++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 Spec/Test Utilities/DataGatherer.swift diff --git a/Ably.xcodeproj/project.pbxproj b/Ably.xcodeproj/project.pbxproj index 35f5ab01c..93111a855 100644 --- a/Ably.xcodeproj/project.pbxproj +++ b/Ably.xcodeproj/project.pbxproj @@ -82,6 +82,9 @@ 21881E7A283BD08300CFD9E2 /* GCDTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A22171266F526600C87C42 /* GCDTests.swift */; }; 21881E7B283BD0DF00CFD9E2 /* StringifiableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D520C4DD2680A1E3000012B2 /* StringifiableTests.swift */; }; 21881E7C283BD0E100CFD9E2 /* StringifiableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D520C4DD2680A1E3000012B2 /* StringifiableTests.swift */; }; + 21D3CC572AF964AC00B3F2BD /* DataGatherer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21D3CC562AF964AC00B3F2BD /* DataGatherer.swift */; }; + 21D3CC582AF964AC00B3F2BD /* DataGatherer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21D3CC562AF964AC00B3F2BD /* DataGatherer.swift */; }; + 21D3CC592AF964AC00B3F2BD /* DataGatherer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21D3CC562AF964AC00B3F2BD /* DataGatherer.swift */; }; 560579D924AF1BA900A4D03D /* ARTDefaultTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 560579D824AF1BA900A4D03D /* ARTDefaultTests.swift */; }; 560579DA24AF1BA900A4D03D /* ARTDefaultTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 560579D824AF1BA900A4D03D /* ARTDefaultTests.swift */; }; 560579DB24AF1BA900A4D03D /* ARTDefaultTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 560579D824AF1BA900A4D03D /* ARTDefaultTests.swift */; }; @@ -1002,6 +1005,7 @@ 217D181E25421FED00DFF07E /* ARTSRSecurityPolicy.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ARTSRSecurityPolicy.m; sourceTree = ""; }; 217D181F25421FED00DFF07E /* ARTSRWebSocket.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ARTSRWebSocket.m; sourceTree = ""; }; 217D182025421FED00DFF07E /* ARTSRSecurityPolicy.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ARTSRSecurityPolicy.h; sourceTree = ""; }; + 21D3CC562AF964AC00B3F2BD /* DataGatherer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataGatherer.swift; sourceTree = ""; }; 560579D824AF1BA900A4D03D /* ARTDefaultTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ARTDefaultTests.swift; sourceTree = ""; }; 56190953238C3D3200A862A6 /* CryptoTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CryptoTest.m; sourceTree = ""; }; 841134772722205400CFA837 /* ARTArchiveTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ARTArchiveTests.m; sourceTree = ""; }; @@ -1499,6 +1503,7 @@ 21BF060A2758477E00AE4C43 /* Test Utilities */ = { isa = PBXGroup; children = ( + 21D3CC562AF964AC00B3F2BD /* DataGatherer.swift */, D7093CA8219EFA8A00723F17 /* MockDeviceStorage.swift */, D780846C1C68B3E50083009D /* NSObject+TestSuite.h */, D780846D1C68B3E50083009D /* NSObject+TestSuite.m */, @@ -2653,6 +2658,7 @@ D777EEE820650ADF002EBA03 /* PushChannelTests.swift in Sources */, D746AE2D1BBB625E003ECEF8 /* RestClientChannelsTests.swift in Sources */, EBAB9A6F1C69702800AF036B /* ReadmeExamplesTests.swift in Sources */, + 21D3CC572AF964AC00B3F2BD /* DataGatherer.swift in Sources */, 56190954238C3D3200A862A6 /* CryptoTest.m in Sources */, D7C1B8771BBEA81A0087B55F /* AuthTests.swift in Sources */, D7EBE5A31BE8391E0086E675 /* RealtimeClientConnectionTests.swift in Sources */, @@ -2783,6 +2789,7 @@ D7093C1C219E466400723F17 /* ReadmeExamplesTests.swift in Sources */, 21881E7B283BD0DF00CFD9E2 /* StringifiableTests.swift in Sources */, D7093C27219E466E00723F17 /* RealtimeClientChannelsTests.swift in Sources */, + 21D3CC582AF964AC00B3F2BD /* DataGatherer.swift in Sources */, 848ED97426E50D0F0087E800 /* ObjcppTest.mm in Sources */, D7093C28219E466E00723F17 /* RealtimeClientPresenceTests.swift in Sources */, D7093C24219E466E00723F17 /* RealtimeClientTests.swift in Sources */, @@ -2817,6 +2824,7 @@ D7093C71219EE25800723F17 /* NSObject+TestSuite.m in Sources */, D7093C70219EE25400723F17 /* TestUtilities.swift in Sources */, 84569FA826B46F5100457CF5 /* ClientOptionsTests.swift in Sources */, + 21D3CC592AF964AC00B3F2BD /* DataGatherer.swift in Sources */, D7093C80219EE26400723F17 /* StatsTests.swift in Sources */, D7093C77219EE26400723F17 /* RestClientChannelTests.swift in Sources */, D520C4E32680A1FC000012B2 /* StringifiableTests.swift in Sources */, diff --git a/Spec/Test Utilities/DataGatherer.swift b/Spec/Test Utilities/DataGatherer.swift new file mode 100644 index 000000000..7bf5391c4 --- /dev/null +++ b/Spec/Test Utilities/DataGatherer.swift @@ -0,0 +1,75 @@ +import Foundation +import XCTest + +/** + A `DataGatherer` instance initiates a user-specified data-gathering activity, and provides a method for waiting until the activity submits some data. + + The `DataGatherer` instance only cares about the _first_ data that is submitted to it, and will ignore any subsequently-submitted data. When the gathered data has value semantics, this can simplify the implementation of tests, since they do not need to be so careful about making sure to stop their data-gathering process at the right time. + */ +class DataGatherer { + private let expectation: XCTestExpectation + + // The value that the initializer’s `gather` block passed to its `submit` argument. + private var value: T? + // Synchronises access to `value`. + private let semaphore = DispatchSemaphore(value: 1) + + /** + Initiates the data-gathering process specified by `gather`. + + - Parameters: + - description: A human-readable description of the data-gathering process. + - gather: A function which implements the data-gathering process. It should call the `submit` callback with the gathered data when ready. Subsequent calls to `submit` will have no effect. `submit` can be safely called from any thread. + */ + init(description: String, gather: (_ submit: @escaping (T) -> Void) -> Void) { + expectation = XCTestExpectation(description: description) + gather(complete(withValue:)) + } + + enum Error: Swift.Error { + case unexpectedResult(XCTWaiter.Result) + } + + /** + Waits for the initializer’s `gather` function to submit data and then returns the submitted data. If data has already been submitted then it is returned immediately. This method can be safely called from any thread. + */ + func waitForData(timeout: TimeInterval) throws -> T { + semaphore.wait() + if let value { + semaphore.signal() + return value + } + semaphore.signal() + + let waiter = XCTWaiter() + let result = waiter.wait(for: [expectation], timeout: timeout) + + switch result { + case .completed: + let value: T + semaphore.wait() + value = self.value! + semaphore.signal() + return value + default: + throw Error.unexpectedResult(result) + } + } + + /** + Waits for the initializer’s `gather` function to submit data and then returns the submitted data. If data has already been submitted then it is returned immediately. This method can be safely called from any thread. + */ + func waitForData(timeout: DispatchTimeInterval) throws -> T { + return try waitForData(timeout: timeout.toTimeInterval()) + } + + private func complete(withValue value: T) { + semaphore.wait() + if self.value == nil { + self.value = value + } + semaphore.signal() + + expectation.fulfill() + } +}