Skip to content

Commit

Permalink
test: WIP testing cancellable tasks
Browse files Browse the repository at this point in the history
  • Loading branch information
fabriziodemaria committed Dec 9, 2024
1 parent ec6bc1e commit 72ace80
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 10 deletions.
26 changes: 16 additions & 10 deletions Sources/Confidence/Confidence.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,20 @@ public class Confidence: ConfidenceEventSender {
// Synchronization and task management resources
private var cancellables = Set<AnyCancellable>()
private let cacheQueue = DispatchQueue(label: "com.confidence.queue.cache")
private var currentFetchTask: Task<(), Never>?
private var currentFetchTask: Task<(), Never>? {
didSet {
if let oldTask = oldValue {
print(">> Cancelling old task: \(oldTask.hashValue)")
oldTask.cancel()
}

if let newTask = currentFetchTask {
print(">> Assigned new task: \(newTask.hashValue)")
} else {
print(">> currentFetchTask is set to nil")
}
}
}

// Internal for testing
internal let remoteFlagResolver: ConfidenceResolveClient
Expand Down Expand Up @@ -155,7 +168,6 @@ public class Confidence: ConfidenceEventSender {
}

public func putContextAndWait(key: String, value: ConfidenceValue) async {
self.currentFetchTask?.cancel()
self.currentFetchTask = Task {
let newContext = contextManager.updateContext(withValues: [key: value], removedKeys: [])
do {
Expand All @@ -169,7 +181,6 @@ public class Confidence: ConfidenceEventSender {
}

public func putContextAndWait(context: ConfidenceStruct, removedKeys: [String] = []) async {
self.currentFetchTask?.cancel()
self.currentFetchTask = Task {
let newContext = contextManager.updateContext(withValues: context, removedKeys: removedKeys)
do {
Expand All @@ -183,7 +194,6 @@ public class Confidence: ConfidenceEventSender {
}

public func putContextAndWait(context: ConfidenceStruct) async {
self.currentFetchTask?.cancel()
self.currentFetchTask = Task {
let newContext = contextManager.updateContext(withValues: context, removedKeys: [])
do {
Expand All @@ -201,7 +211,6 @@ public class Confidence: ConfidenceEventSender {
}

public func removeContextAndWait(key: String) async {
self.currentFetchTask?.cancel()
self.currentFetchTask = Task {
let newContext = contextManager.updateContext(withValues: [:], removedKeys: [key])
do {
Expand Down Expand Up @@ -229,35 +238,30 @@ public class Confidence: ConfidenceEventSender {
}

public func putContext(key: String, value: ConfidenceValue) {
self.currentFetchTask?.cancel()
self.currentFetchTask = Task {
await putContextAndWait(key: key, value: value)
}
}

public func putContext(context: ConfidenceStruct) {
self.currentFetchTask?.cancel()
self.currentFetchTask = Task {
await putContextAndWait(context: context)
}
}

public func putContext(context: ConfidenceStruct, removeKeys removedKeys: [String] = []) {
self.currentFetchTask?.cancel()
self.currentFetchTask = Task {
await putContextAndWait(context: context, removedKeys: removedKeys)
}
}

public func removeContext(key: String) {
self.currentFetchTask?.cancel()
self.currentFetchTask = Task {
await removeContextAndWait(key: key)
}
}

public func putContext(context: ConfidenceStruct, removedKeys: [String]) {
self.currentFetchTask?.cancel()
self.currentFetchTask = Task {
let newContext = contextManager.updateContext(withValues: context, removedKeys: removedKeys)
do {
Expand All @@ -278,7 +282,9 @@ public class Confidence: ConfidenceEventSender {
*/
public func awaitReconciliation() async {
if let task = self.currentFetchTask {
print(">> AWAITING task \(task.hashValue)")
await task.value
print(">> AWAITED task \(task.hashValue)")
}
}

Expand Down
61 changes: 61 additions & 0 deletions Tests/ConfidenceTests/ConfidenceTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,67 @@ class ConfidenceTest: XCTestCase {
XCTAssertEqual(flagApplier.applyCallCount, 1)
}

func testAwaitReconciliationFailingTask() async throws {
class FakeClient: XCTestCase, ConfidenceResolveClient {
var resolveStats: Int = 0
var resolvedValues: [ResolvedValue] = []

func resolve(ctx: ConfidenceStruct) async throws -> ResolvesResult {
self.resolveStats += 1
if resolveStats == 1 {
try await Task.sleep(nanoseconds: 3_000_000_000) // Sleep for 3 seconds
return .init(resolvedValues: [], resolveToken: "token")
} else {
try await Task.sleep(nanoseconds: 1_000_000_000) // Sleep for 1 second
if ctx["hello"] == .init(string: "world") {
return .init(resolvedValues: resolvedValues, resolveToken: "token")
} else {
return .init(resolvedValues: [], resolveToken: "token")
}
}
}
}

let client = FakeClient()
client.resolvedValues = [
ResolvedValue(
variant: "control",
value: .init(structure: ["size": .init(integer: 3)]),
flag: "flag",
resolveReason: .match)
]

let confidence = Confidence.Builder(clientSecret: "test")
.withContext(initialContext: ["targeting_key": .init(string: "user2")])
.withFlagResolverClient(flagResolver: client)
.withFlagApplier(flagApplier: flagApplier)
.withStorage(storage: storage)
.build()

confidence.putContext(context: ["hello": .init(string: "not-world")])

Task {
try await Task.sleep(nanoseconds: 1_000_000_000) // Sleep for 1 second
confidence.putContext(context: ["hello": .init(string: "world")])
}

await confidence.awaitReconciliation()
print(">> Final eval context \(confidence.getContext())")
let evaluation = confidence.getEvaluation(
key: "flag.size",
defaultValue: 0
)

XCTAssertEqual(client.resolveStats, 2)
XCTAssertEqual(evaluation.value, 3)
XCTAssertNil(evaluation.errorCode)
XCTAssertNil(evaluation.errorMessage)
XCTAssertEqual(evaluation.reason, .match)
XCTAssertEqual(evaluation.variant, "control")
await fulfillment(of: [flagApplier.applyExpectation], timeout: 1)
XCTAssertEqual(flagApplier.applyCallCount, 1)
}

func testResolveBooleanFlag() async throws {
class FakeClient: ConfidenceResolveClient {
var resolveStats: Int = 0
Expand Down

0 comments on commit 72ace80

Please sign in to comment.