From 9fcc1b30e81fd2c0a2b451388e082ad5344eeb23 Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Fri, 8 Aug 2025 14:10:29 -0300 Subject: [PATCH 01/16] Initial commit --- Split/Common/Structs/BlockingQueue.swift | 10 ++--- Split/Events/SplitEventsManager.swift | 2 +- Split/Events/SplitInternalEvent.swift | 41 +++++++++++++++++++ .../Refresh/PeriodicSyncWorker.swift | 2 +- 4 files changed, 48 insertions(+), 7 deletions(-) diff --git a/Split/Common/Structs/BlockingQueue.swift b/Split/Common/Structs/BlockingQueue.swift index 8fd45f67f..df5bfbb6f 100644 --- a/Split/Common/Structs/BlockingQueue.swift +++ b/Split/Common/Structs/BlockingQueue.swift @@ -73,18 +73,18 @@ class GenericBlockingQueue { // Protocol to allow mocking protocol InternalEventBlockingQueue { - func add(_ item: SplitInternalEvent) - func take() throws -> SplitInternalEvent + func add(_ item: SplitInternalEventWithMetadata) + func take() throws -> SplitInternalEventWithMetadata func stop() } class DefaultInternalEventBlockingQueue: InternalEventBlockingQueue { - let blockingQueue = GenericBlockingQueue() - func add(_ item: SplitInternalEvent) { + let blockingQueue = GenericBlockingQueue() + func add(_ item: SplitInternalEventWithMetadata) { blockingQueue.add(item) } - func take() throws -> SplitInternalEvent { + func take() throws -> SplitInternalEventWithMetadata { let value = try blockingQueue.take() return value } diff --git a/Split/Events/SplitEventsManager.swift b/Split/Events/SplitEventsManager.swift index ea64d0279..037643561 100644 --- a/Split/Events/SplitEventsManager.swift +++ b/Split/Events/SplitEventsManager.swift @@ -179,7 +179,7 @@ class DefaultSplitEventsManager: SplitEventsManager { func isTriggered(external event: SplitEvent) -> Bool { var triggered = false dataAccessQueue.sync { - if let times = executionTimes[event.toString()] { + if let times = executionTimes[event] { triggered = (times == 0) } else { triggered = false diff --git a/Split/Events/SplitInternalEvent.swift b/Split/Events/SplitInternalEvent.swift index 4c9521204..424926a8f 100644 --- a/Split/Events/SplitInternalEvent.swift +++ b/Split/Events/SplitInternalEvent.swift @@ -7,6 +7,47 @@ import Foundation +// All events (internal & external) support metadata. +// Internal errors are propagated to the customer as events "(.sdkError)". The error info will travel as the event metadata. +struct SplitInternalEventWithMetadata { + let type: SplitInternalEvent + let metadata: EventMetadata? + + init(_ type: SplitInternalEvent, metadata: EventMetadata? = nil) { + self.type = type + self.metadata = metadata + } + + static func == (lhs: SplitInternalEventWithMetadata, rhs: SplitInternalEventWithMetadata) -> Bool { + return lhs.type == rhs.type + } +} + +@objc public class EventMetadata: NSObject { + var type: EventMetadataType + var data: String = "" + + init(type: EventMetadataType, data: String) { + self.type = type + self.data = data + } +} + +enum EventMetadataType: Int { + case FEATURE_FLAGS_SYNC_ERROR + case SEGMENTS_SYNC_ERROR + + public func toString() -> String { + switch self { + case .FEATURE_FLAGS_SYNC_ERROR: + return "FEATURE_FLAGS_SYNC_ERROR" + case .SEGMENTS_SYNC_ERROR: + return "SEGMENTS_SYNC_ERROR" + + } + } +} + enum SplitInternalEvent { case mySegmentsUpdated case myLargeSegmentsUpdated diff --git a/Split/FetcherEngine/Refresh/PeriodicSyncWorker.swift b/Split/FetcherEngine/Refresh/PeriodicSyncWorker.swift index b6fd71c81..a41a03205 100644 --- a/Split/FetcherEngine/Refresh/PeriodicSyncWorker.swift +++ b/Split/FetcherEngine/Refresh/PeriodicSyncWorker.swift @@ -225,7 +225,7 @@ class PeriodicMySegmentsSyncWorker: BasePeriodicSyncWorker { if result.success { if result.msUpdated || result.mlsUpdated { // For now is not necessary specify which entity was updated - notifyUpdate([.mySegmentsUpdated]) + notifyUpdate([.mySegmentsUpdated(nil)]) } } } catch { From d3424316a88e36fdf9a24c3587ffdf846dc8b19f Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Fri, 8 Aug 2025 15:32:38 -0300 Subject: [PATCH 02/16] New SplitEventWithMetadata --- Split/Api/SplitClient.swift | 1 + Split/Common/Structs/BlockingQueue.swift | 5 + Split/Events/EventsManagerCoordinator.swift | 15 ++- Split/Events/SplitEvent.swift | 18 +++ Split/Events/SplitEventActionTask.swift | 8 +- Split/Events/SplitEventTask.swift | 2 +- Split/Events/SplitEventsManager.swift | 111 +++++++++++------- Split/Events/SplitInternalEvent.swift | 1 + .../Refresh/PeriodicSyncWorker.swift | 2 +- .../Collections/BlockingQueueTest.swift | 28 ++--- 10 files changed, 127 insertions(+), 64 deletions(-) diff --git a/Split/Api/SplitClient.swift b/Split/Api/SplitClient.swift index ff4df2005..49726585c 100644 --- a/Split/Api/SplitClient.swift +++ b/Split/Api/SplitClient.swift @@ -9,6 +9,7 @@ import Foundation public typealias SplitAction = () -> Void +public typealias SplitActionWithMetadata = (EventMetadata) -> Void @objc public protocol SplitClient { diff --git a/Split/Common/Structs/BlockingQueue.swift b/Split/Common/Structs/BlockingQueue.swift index df5bfbb6f..739a29507 100644 --- a/Split/Common/Structs/BlockingQueue.swift +++ b/Split/Common/Structs/BlockingQueue.swift @@ -80,6 +80,11 @@ protocol InternalEventBlockingQueue { class DefaultInternalEventBlockingQueue: InternalEventBlockingQueue { let blockingQueue = GenericBlockingQueue() + + func add(_ item: SplitInternalEvent) { + blockingQueue.add(SplitInternalEventWithMetadata(item, metadata: nil)) + } + func add(_ item: SplitInternalEventWithMetadata) { blockingQueue.add(item) } diff --git a/Split/Events/EventsManagerCoordinator.swift b/Split/Events/EventsManagerCoordinator.swift index 3ddf4fe93..fc265513d 100644 --- a/Split/Events/EventsManagerCoordinator.swift +++ b/Split/Events/EventsManagerCoordinator.swift @@ -21,19 +21,24 @@ class MainSplitEventsManager: SplitEventsManagerCoordinator { private let eventsToHandle: Set = Set( [.splitsLoadedFromCache, .splitsUpdated, - .splitKilledNotification] + .splitKilledNotification, + .sdkError] ) - + func notifyInternalEvent(_ event: SplitInternalEvent) { - if !eventsToHandle.contains(event) { + notifyInternalEventWithMetadata(SplitInternalEventWithMetadata(event, metadata: nil)) + } + + func notifyInternalEventWithMetadata(_ event: SplitInternalEventWithMetadata) { + if !eventsToHandle.contains(event.type) { return } queue.async { [weak self] in guard let self = self else { return } - self.triggered.insert(event) + self.triggered.insert(event.type) self.managers.forEach { _, manager in - manager.notifyInternalEvent(event) + manager.notifyInternalEvent(event.type) } } } diff --git a/Split/Events/SplitEvent.swift b/Split/Events/SplitEvent.swift index d2561e2d9..09e354c69 100644 --- a/Split/Events/SplitEvent.swift +++ b/Split/Events/SplitEvent.swift @@ -7,11 +7,27 @@ import Foundation +@objcMembers public class SplitEventWithMetadata: NSObject { + let type: SplitEvent + let metadata: EventMetadata? + + @objc public init(type: SplitEvent, metadata: EventMetadata? = nil) { + self.type = type + self.metadata = metadata + } + + public override func isEqual(_ object: Any?) -> Bool { + guard let other = object as? SplitEventWithMetadata else { return false } + return self.type == other.type + } +} + @objc public enum SplitEvent: Int { case sdkReady case sdkReadyTimedOut case sdkReadyFromCache case sdkUpdated + case sdkError public func toString() -> String { switch self { @@ -23,6 +39,8 @@ import Foundation return "SDK_READY_TIMED_OUT" case .sdkReadyFromCache: return "SDK_READY_FROM_CACHE" + case .sdkError: + return "SDK_ERROR" } } } diff --git a/Split/Events/SplitEventActionTask.swift b/Split/Events/SplitEventActionTask.swift index be1368d04..13d96f706 100644 --- a/Split/Events/SplitEventActionTask.swift +++ b/Split/Events/SplitEventActionTask.swift @@ -10,7 +10,9 @@ import Foundation class SplitEventActionTask: SplitEventTask { private var eventHandler: SplitAction? + private var eventHandlerWithMetadata: SplitActionWithMetadata? private var queue: DispatchQueue? + var event: SplitEvent var runInBackground: Bool = false var factory: SplitFactory @@ -33,7 +35,9 @@ class SplitEventActionTask: SplitEventTask { return queue } - func run() { - eventHandler?() + func run(_ metadata: EventMetadata?) { + if let metadata = metadata { + eventHandlerWithMetadata?(metadata) + } } } diff --git a/Split/Events/SplitEventTask.swift b/Split/Events/SplitEventTask.swift index 1655e2b25..147bc8441 100644 --- a/Split/Events/SplitEventTask.swift +++ b/Split/Events/SplitEventTask.swift @@ -11,5 +11,5 @@ protocol SplitEventTask { var event: SplitEvent { get } var runInBackground: Bool { get } func takeQueue() -> DispatchQueue? - func run() + func run(_ metadata: EventMetadata?) } diff --git a/Split/Events/SplitEventsManager.swift b/Split/Events/SplitEventsManager.swift index 037643561..dd33d42d1 100644 --- a/Split/Events/SplitEventsManager.swift +++ b/Split/Events/SplitEventsManager.swift @@ -23,7 +23,7 @@ class DefaultSplitEventsManager: SplitEventsManager { private var subscriptions = [SplitEvent: [SplitEventTask]]() private var executionTimes: [String: Int] - private var triggered: [SplitInternalEvent] + private var triggered: [SplitInternalEventWithMetadata] private let processQueue: DispatchQueue private let dataAccessQueue: DispatchQueue private var isStarted: Bool @@ -35,7 +35,7 @@ class DefaultSplitEventsManager: SplitEventsManager { self.isStarted = false self.sdkReadyTimeStart = Date().unixTimestampInMiliseconds() self.readingRefreshTime = 300 - self.triggered = [SplitInternalEvent]() + self.triggered = [SplitInternalEventWithMetadata]() self.eventsQueue = DefaultInternalEventBlockingQueue() self.executionTimes = [String: Int]() registerMaxAllowedExecutionTimesPerEvent() @@ -49,11 +49,20 @@ class DefaultSplitEventsManager: SplitEventsManager { } } + func notifyInternalEvent(_ event: SplitInternalEventWithMetadata) { + processQueue.async { [weak self] in + if let self = self { + Logger.v("Event \(event.type) notified") + self.eventsQueue.add(event) + } + } + } + func notifyInternalEvent(_ event: SplitInternalEvent) { processQueue.async { [weak self] in if let self = self { Logger.v("Event \(event) notified") - self.eventsQueue.add(event) + self.eventsQueue.add(SplitInternalEventWithMetadata(event, metadata: nil)) } } } @@ -70,6 +79,19 @@ class DefaultSplitEventsManager: SplitEventsManager { self.subscribe(task: task, to: event) } } + + func register(event: SplitEventWithMetadata, task: SplitEventTask) { + let eventName = event.type.toString() + processQueue.async { [weak self] in + guard let self = self else { return } + // If event is already triggered, execute the task + if let times = self.executionTimes(for: eventName), times == 0 { + self.executeTask(event: event, task: task) + return + } + self.subscribe(task: task, to: event) + } + } func start() { dataAccessQueue.sync { @@ -128,7 +150,7 @@ class DefaultSplitEventsManager: SplitEventsManager { return isRunning } - private func takeEvent() -> SplitInternalEvent? { + private func takeEvent() -> SplitInternalEventWithMetadata? { do { return try eventsQueue.take() } catch BlockingQueueError.hasBeenStopped { @@ -144,42 +166,45 @@ class DefaultSplitEventsManager: SplitEventsManager { guard let event = takeEvent() else { return } - self.triggered.append(event) - switch event { - case .splitsUpdated, .mySegmentsUpdated, .myLargeSegmentsUpdated: - if isTriggered(external: .sdkReady) { - trigger(event: .sdkUpdated) - continue - } - self.triggerSdkReadyIfNeeded() - - case .mySegmentsLoadedFromCache, .myLargeSegmentsLoadedFromCache, - .splitsLoadedFromCache, .attributesLoadedFromCache: - Logger.v("Event \(event) triggered") - if isTriggered(internal: .splitsLoadedFromCache), - isTriggered(internal: .mySegmentsLoadedFromCache), - isTriggered(internal: .myLargeSegmentsLoadedFromCache), - isTriggered(internal: .attributesLoadedFromCache) { - trigger(event: SplitEvent.sdkReadyFromCache) - } - case .splitKilledNotification: - if isTriggered(external: .sdkReady) { - trigger(event: .sdkUpdated) - continue - } - case .sdkReadyTimeoutReached: - if !isTriggered(external: .sdkReady) { - trigger(event: SplitEvent.sdkReadyTimedOut) - } + triggered.append(event.type) + switch event.type { + case .splitsUpdated, .mySegmentsUpdated, .myLargeSegmentsUpdated: + if isTriggered(external: .sdkReady) { + trigger(event: .sdkUpdated) + continue + } + triggerSdkReadyIfNeeded() + + case .mySegmentsLoadedFromCache, .myLargeSegmentsLoadedFromCache, + .splitsLoadedFromCache, .attributesLoadedFromCache: + Logger.v("Event \(event) triggered") + if isTriggered(internal: .splitsLoadedFromCache), + isTriggered(internal: .mySegmentsLoadedFromCache), + isTriggered(internal: .myLargeSegmentsLoadedFromCache), + isTriggered(internal: .attributesLoadedFromCache) { + trigger(event: .sdkReadyFromCache) + } + case .splitKilledNotification: + if isTriggered(external: .sdkReady) { + trigger(event: .sdkUpdated) + } + case .sdkReadyTimeoutReached: + if !isTriggered(external: .sdkReady) { + trigger(event: .sdkReadyTimedOut) + } + case .sdkError: + if !isTriggered(external: .sdkReady) { + trigger(event: SplitEventWithMetadata(type: .sdkUpdated, metadata: event.metadata)) + } } } } - // MARK: Helper functions. + // MARK: Helper functions func isTriggered(external event: SplitEvent) -> Bool { var triggered = false dataAccessQueue.sync { - if let times = executionTimes[event] { + if let times = executionTimes[event.toString()] { triggered = (times == 0) } else { triggered = false @@ -199,9 +224,9 @@ class DefaultSplitEventsManager: SplitEventsManager { self.trigger(event: .sdkReady) } } - - private func trigger(event: SplitEvent) { - let eventName = event.toString() + + private func trigger(event: SplitInternalEvent) { + let eventName = event.type.toString() // If executionTimes is zero, maximum executions has been reached if executionTimes(for: eventName) == 0 { @@ -222,7 +247,7 @@ class DefaultSplitEventsManager: SplitEventsManager { } } - private func executeTask(event: SplitEvent, task: SplitEventTask) { + private func executeTask(event: SplitEventWithMetadata, task: SplitEventTask) { let eventName = task.event.toString() @@ -232,7 +257,7 @@ class DefaultSplitEventsManager: SplitEventsManager { let queue = task.takeQueue() ?? DispatchQueue.general queue.async { TimeChecker.logInterval("Running \(eventName) in Background queue \(queue)") - task.run() + task.run(event.metadata) } return } @@ -240,13 +265,17 @@ class DefaultSplitEventsManager: SplitEventsManager { DispatchQueue.main.async { TimeChecker.logInterval("Running event on main: \(eventName)") // UI Updates - task.run() + task.run(event.metadata) } } - - private func isTriggered(internal event: SplitInternalEvent) -> Bool { + + private func isTriggered(internal event: SplitInternalEventWithMetadata) -> Bool { return triggered.filter { $0 == event }.count > 0 } + + private func isTriggered(internal event: SplitInternalEvent) -> Bool { + return isTriggered(internal: SplitInternalEventWithMetadata(event, metadata: nil)) + } // MARK: Safe Data Access func executionTimes(for eventName: String) -> Int? { diff --git a/Split/Events/SplitInternalEvent.swift b/Split/Events/SplitInternalEvent.swift index 424926a8f..c38f89cf4 100644 --- a/Split/Events/SplitInternalEvent.swift +++ b/Split/Events/SplitInternalEvent.swift @@ -58,4 +58,5 @@ enum SplitInternalEvent { case attributesLoadedFromCache case sdkReadyTimeoutReached case splitKilledNotification + case sdkError } diff --git a/Split/FetcherEngine/Refresh/PeriodicSyncWorker.swift b/Split/FetcherEngine/Refresh/PeriodicSyncWorker.swift index a41a03205..b6fd71c81 100644 --- a/Split/FetcherEngine/Refresh/PeriodicSyncWorker.swift +++ b/Split/FetcherEngine/Refresh/PeriodicSyncWorker.swift @@ -225,7 +225,7 @@ class PeriodicMySegmentsSyncWorker: BasePeriodicSyncWorker { if result.success { if result.msUpdated || result.mlsUpdated { // For now is not necessary specify which entity was updated - notifyUpdate([.mySegmentsUpdated(nil)]) + notifyUpdate([.mySegmentsUpdated]) } } } catch { diff --git a/SplitTests/Collections/BlockingQueueTest.swift b/SplitTests/Collections/BlockingQueueTest.swift index 04c4b5df3..ace9ad58b 100644 --- a/SplitTests/Collections/BlockingQueueTest.swift +++ b/SplitTests/Collections/BlockingQueueTest.swift @@ -25,7 +25,7 @@ class BlockingQueueTest: XCTestCase { while true { do { let event = try queue.take() - local.append(event) + local.append(event.type) if local.count == 4 { endExp.fulfill() } @@ -60,7 +60,7 @@ class BlockingQueueTest: XCTestCase { while true { do { let event = try queue.take() - local.append(event) + local.append(event.type) } catch { endExp.fulfill() interrupted = true @@ -105,8 +105,8 @@ class BlockingQueueTest: XCTestCase { for _ in 0..<50000 { do { let event = try queue.take() - local.append(event) - print("Took: \(event)") + local.append(event.type) + print("Took: \(event.type)") } catch { } } @@ -117,8 +117,8 @@ class BlockingQueueTest: XCTestCase { for _ in 0..<50000 { do { let event = try queue.take() - local.append(event) - print("Took QA1: \(event)") + local.append(event.type) + print("Took QA1: \(event.type)") } catch { print("\n\n\nERROR!!!!: \(error) \n\n\n") } @@ -129,9 +129,9 @@ class BlockingQueueTest: XCTestCase { for _ in 0..<50000 { do { let event = try queue.take() - local.append(event) + local.append(event.type) Thread.sleep(forTimeInterval: 0.3) - print("Took QA2: \(event)") + print("Took QA2: \(event.type)") } catch { } } @@ -142,8 +142,8 @@ class BlockingQueueTest: XCTestCase { do { Thread.sleep(forTimeInterval: 0.5) let event = try queue.take() - local.append(event) - print("Took QA3: \(event)") + local.append(event.type) + print("Took QA3: \(event.type)") } catch { } } @@ -151,7 +151,7 @@ class BlockingQueueTest: XCTestCase { qu1.async { for _ in 1..<100000 { - queue.add(SplitInternalEvent.splitsUpdated) + queue.add(SplitInternalEventWithMetadata(.splitsUpdated, metadata: nil)) print("qu1 add") Thread.sleep(forTimeInterval: 0.2) } @@ -160,7 +160,7 @@ class BlockingQueueTest: XCTestCase { qu2.async { for _ in 1..<10000 { print("qu2 add") - queue.add(SplitInternalEvent.sdkReadyTimeoutReached) + queue.add(SplitInternalEventWithMetadata(.sdkReadyTimeoutReached, metadata: nil)) Thread.sleep(forTimeInterval: 0.5) } } @@ -168,7 +168,7 @@ class BlockingQueueTest: XCTestCase { qu3.async { for _ in 1..<10000 { print("qu3 add") - queue.add(SplitInternalEvent.splitsUpdated) + queue.add(SplitInternalEventWithMetadata(.splitsUpdated, metadata: nil)) Thread.sleep(forTimeInterval: 0.8) } } @@ -176,7 +176,7 @@ class BlockingQueueTest: XCTestCase { qu4.async { for _ in 1..<10000 { print("qu4 add") - queue.add(SplitInternalEvent.mySegmentsUpdated) + queue.add(SplitInternalEventWithMetadata(.mySegmentsUpdated, metadata: nil)) sleep(1) } } From 7378c38248708196f2a149f5f94df394bc9f9a28 Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Fri, 8 Aug 2025 19:08:16 -0300 Subject: [PATCH 03/16] Switching branch --- Split/Events/SplitEventsManager.swift | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Split/Events/SplitEventsManager.swift b/Split/Events/SplitEventsManager.swift index dd33d42d1..7af5f682d 100644 --- a/Split/Events/SplitEventsManager.swift +++ b/Split/Events/SplitEventsManager.swift @@ -89,7 +89,7 @@ class DefaultSplitEventsManager: SplitEventsManager { self.executeTask(event: event, task: task) return } - self.subscribe(task: task, to: event) + self.subscribe(task: task, to: event.type) } } @@ -166,7 +166,7 @@ class DefaultSplitEventsManager: SplitEventsManager { guard let event = takeEvent() else { return } - triggered.append(event.type) + triggered.append(event) switch event.type { case .splitsUpdated, .mySegmentsUpdated, .myLargeSegmentsUpdated: if isTriggered(external: .sdkReady) { @@ -225,8 +225,8 @@ class DefaultSplitEventsManager: SplitEventsManager { } } - private func trigger(event: SplitInternalEvent) { - let eventName = event.type.toString() + private func trigger(event: SplitEvent) { + let eventName = event.toString() // If executionTimes is zero, maximum executions has been reached if executionTimes(for: eventName) == 0 { @@ -246,6 +246,10 @@ class DefaultSplitEventsManager: SplitEventsManager { } } } + + private func executeTask(event: SplitEvent, task: SplitEventTask) { + executeTask(event: SplitEventWithMetadata(type: event, metadata: nil), task: task) + } private func executeTask(event: SplitEventWithMetadata, task: SplitEventTask) { From f68cfb0a8d712b04d7fe3eb7c4c342d0b81645e3 Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Mon, 11 Aug 2025 12:25:14 -0300 Subject: [PATCH 04/16] Stable point --- Split/Api/DefaultSplitClient.swift | 21 +++++++--- Split/Api/FailHelpers.swift | 4 ++ Split/Api/LocalhostSplitClient.swift | 4 ++ Split/Api/SplitClient.swift | 1 + Split/Events/EventsManagerCoordinator.swift | 3 ++ Split/Events/SplitEventActionTask.swift | 27 ++++++++----- Split/Events/SplitEventsManager.swift | 29 +++++--------- SplitTests/Fake/InternalSplitClientStub.swift | 4 ++ .../SplitEventsManagerCoordinatorStub.swift | 4 ++ SplitTests/Fake/SplitClientStub.swift | 3 ++ SplitTests/Fake/SplitEventsManagerMock.swift | 4 ++ SplitTests/Fake/SplitEventsManagerStub.swift | 4 ++ SplitTests/Helpers/IntegrationHelper.swift | 38 +++++++++--------- SplitTests/SplitEventsManagerTest.swift | 40 ++++++++++++------- 14 files changed, 118 insertions(+), 68 deletions(-) diff --git a/Split/Api/DefaultSplitClient.swift b/Split/Api/DefaultSplitClient.swift index bbf3aa8a7..1173d3e57 100644 --- a/Split/Api/DefaultSplitClient.swift +++ b/Split/Api/DefaultSplitClient.swift @@ -88,15 +88,24 @@ extension DefaultSplitClient { task.event = event on(event: event, executeTask: task) } + + private func onWithMetadata(event: SplitEventWithMetadata, runInBackground: Bool, queue: DispatchQueue?, execute actionWithMetadata: @escaping SplitActionWithMetadata) { + guard let factory = clientManager?.splitFactory else { return } + let task = SplitEventActionTask(action: actionWithMetadata, event: event.type, runInBackground: runInBackground, factory: factory, queue: queue) + on(event: event.type, executeTask: task) + } + + public func on(event: SplitEvent, executeWithMetadata action: SplitActionWithMetadata?) { + guard let action = action else { return } + onWithMetadata(event: SplitEventWithMetadata(type: event, metadata: nil), runInBackground: true, queue: nil, execute: action) + } - private func on(event: SplitEvent, executeTask task: SplitEventTask) { - if event != .sdkReadyFromCache, - eventsManager.eventAlreadyTriggered(event: event) { - Logger.w("A handler was added for \(event.toString()) on the SDK, " + - "which has already fired and won’t be emitted again. The callback won’t be executed.") + private func on(event: SplitEvent, executeTask task: SplitEventActionTask) { + if event != .sdkReadyFromCache, eventsManager.eventAlreadyTriggered(event: event) { + Logger.w("A handler was added for \(event.toString()) on the SDK, which has already fired and won’t be emitted again. The callback won’t be executed.") return } - eventsManager.register(event: event, task: task) + eventsManager.register(event: SplitEventWithMetadata(type: event, metadata: nil), task: task) } } diff --git a/Split/Api/FailHelpers.swift b/Split/Api/FailHelpers.swift index 6f90ba7b7..2e9a78020 100644 --- a/Split/Api/FailHelpers.swift +++ b/Split/Api/FailHelpers.swift @@ -64,6 +64,10 @@ class FailedClient: SplitClient { func on(event: SplitEvent, queue: DispatchQueue, execute action: @escaping SplitAction) { } + + func on(event: SplitEvent, executeWithMetadata: @escaping SplitActionWithMetadata) { + + } func track(trafficType: String, eventType: String) -> Bool { return false diff --git a/Split/Api/LocalhostSplitClient.swift b/Split/Api/LocalhostSplitClient.swift index 2aaacca6b..203370ada 100644 --- a/Split/Api/LocalhostSplitClient.swift +++ b/Split/Api/LocalhostSplitClient.swift @@ -146,6 +146,10 @@ public final class LocalhostSplitClient: NSObject, SplitClient { eventsManager.register(event: event, task: task) } } + + public func on(event: SplitEvent, executeWithMetadata: @escaping SplitActionWithMetadata) { + + } public func track(trafficType: String, eventType: String) -> Bool { return true diff --git a/Split/Api/SplitClient.swift b/Split/Api/SplitClient.swift index 49726585c..4c81639d6 100644 --- a/Split/Api/SplitClient.swift +++ b/Split/Api/SplitClient.swift @@ -35,6 +35,7 @@ public typealias SplitActionWithMetadata = (EventMetadata) -> Void func getTreatmentsWithConfig(splits: [String], attributes: [String: Any]?, evaluationOptions: EvaluationOptions?) -> [String: SplitResult] func on(event: SplitEvent, execute action: @escaping SplitAction) + func on(event: SplitEvent, executeWithMetadata: @escaping SplitActionWithMetadata) func on(event: SplitEvent, runInBackground: Bool, execute action: @escaping SplitAction) func on(event: SplitEvent, queue: DispatchQueue, execute action: @escaping SplitAction) diff --git a/Split/Events/EventsManagerCoordinator.swift b/Split/Events/EventsManagerCoordinator.swift index fc265513d..8ce38ab1a 100644 --- a/Split/Events/EventsManagerCoordinator.swift +++ b/Split/Events/EventsManagerCoordinator.swift @@ -14,6 +14,7 @@ protocol SplitEventsManagerCoordinator: SplitEventsManager { } class MainSplitEventsManager: SplitEventsManagerCoordinator { + private var defaultManager: SplitEventsManager? private var managers = [Key: SplitEventsManager]() private var triggered = Set() @@ -82,4 +83,6 @@ class MainSplitEventsManager: SplitEventsManagerCoordinator { } func register(event: SplitEvent, task: SplitEventTask) {} + + func register(event: SplitEventWithMetadata, task: any SplitEventTask) {} } diff --git a/Split/Events/SplitEventActionTask.swift b/Split/Events/SplitEventActionTask.swift index 13d96f706..3d879e83e 100644 --- a/Split/Events/SplitEventActionTask.swift +++ b/Split/Events/SplitEventActionTask.swift @@ -17,18 +17,21 @@ class SplitEventActionTask: SplitEventTask { var runInBackground: Bool = false var factory: SplitFactory - init(action: @escaping SplitAction, - event: SplitEvent, - runInBackground: Bool = false, - factory: SplitFactory, - queue: DispatchQueue? = nil) { - - self.eventHandler = action - self.event = event - self.runInBackground = runInBackground - self.queue = queue - self.factory = factory + init(action: @escaping SplitActionWithMetadata, event: SplitEvent, runInBackground: Bool = false, factory: SplitFactory, queue: DispatchQueue? = nil) { + self.eventHandlerWithMetadata = action + self.event = event + self.runInBackground = runInBackground + self.queue = queue + self.factory = factory } + + init(action: @escaping SplitAction, event: SplitEvent, runInBackground: Bool = false, factory: SplitFactory, queue: DispatchQueue? = nil) { + self.eventHandler = action + self.event = event + self.runInBackground = runInBackground + self.queue = queue + self.factory = factory + } func takeQueue() -> DispatchQueue? { defer { queue = nil } @@ -36,6 +39,8 @@ class SplitEventActionTask: SplitEventTask { } func run(_ metadata: EventMetadata?) { + eventHandler?() + if let metadata = metadata { eventHandlerWithMetadata?(metadata) } diff --git a/Split/Events/SplitEventsManager.swift b/Split/Events/SplitEventsManager.swift index 7af5f682d..878ef97d9 100644 --- a/Split/Events/SplitEventsManager.swift +++ b/Split/Events/SplitEventsManager.swift @@ -10,6 +10,7 @@ import Foundation protocol SplitEventsManager: AnyObject { func register(event: SplitEvent, task: SplitEventTask) + func register(event: SplitEventWithMetadata, task: SplitEventTask) func notifyInternalEvent(_ event: SplitInternalEvent) func start() func stop() @@ -59,25 +60,11 @@ class DefaultSplitEventsManager: SplitEventsManager { } func notifyInternalEvent(_ event: SplitInternalEvent) { - processQueue.async { [weak self] in - if let self = self { - Logger.v("Event \(event) notified") - self.eventsQueue.add(SplitInternalEventWithMetadata(event, metadata: nil)) - } - } + notifyInternalEvent(SplitInternalEventWithMetadata(event, metadata: nil)) } func register(event: SplitEvent, task: SplitEventTask) { - let eventName = event.toString() - processQueue.async { [weak self] in - guard let self = self else { return } - // If event is already triggered, execute the task - if let times = self.executionTimes(for: eventName), times == 0 { - self.executeTask(event: event, task: task) - return - } - self.subscribe(task: task, to: event) - } + register(event: SplitEventWithMetadata(type: event, metadata: nil), task: task) } func register(event: SplitEventWithMetadata, task: SplitEventTask) { @@ -170,7 +157,7 @@ class DefaultSplitEventsManager: SplitEventsManager { switch event.type { case .splitsUpdated, .mySegmentsUpdated, .myLargeSegmentsUpdated: if isTriggered(external: .sdkReady) { - trigger(event: .sdkUpdated) + trigger(event: SplitEventWithMetadata(type: .sdkUpdated, metadata: event.metadata)) continue } triggerSdkReadyIfNeeded() @@ -226,7 +213,11 @@ class DefaultSplitEventsManager: SplitEventsManager { } private func trigger(event: SplitEvent) { - let eventName = event.toString() + trigger(event: SplitEventWithMetadata(type: event, metadata: nil)) + } + + private func trigger(event: SplitEventWithMetadata) { + let eventName = event.type.toString() // If executionTimes is zero, maximum executions has been reached if executionTimes(for: eventName) == 0 { @@ -240,7 +231,7 @@ class DefaultSplitEventsManager: SplitEventsManager { Logger.d("Triggering SDK event \(eventName)") // If executionTimes is lower than zero, execute it without limitation - if let subscriptions = getSubscriptions(for: event) { + if let subscriptions = getSubscriptions(for: event.type) { for task in subscriptions { executeTask(event: event, task: task) } diff --git a/SplitTests/Fake/InternalSplitClientStub.swift b/SplitTests/Fake/InternalSplitClientStub.swift index 5b792fb3e..d5d0a48df 100644 --- a/SplitTests/Fake/InternalSplitClientStub.swift +++ b/SplitTests/Fake/InternalSplitClientStub.swift @@ -103,6 +103,10 @@ class InternalSplitClientStub: InternalSplitClient { func on(event: SplitEvent, execute action: @escaping SplitAction) { } + + func on(event: SplitEvent, executeWithMetadata: @escaping SplitActionWithMetadata) { + + } func track(trafficType: String, eventType: String) -> Bool { return true diff --git a/SplitTests/Fake/Service/SplitEventsManagerCoordinatorStub.swift b/SplitTests/Fake/Service/SplitEventsManagerCoordinatorStub.swift index 94cbb8991..80e9abd08 100644 --- a/SplitTests/Fake/Service/SplitEventsManagerCoordinatorStub.swift +++ b/SplitTests/Fake/Service/SplitEventsManagerCoordinatorStub.swift @@ -24,6 +24,10 @@ class SplitEventsManagerCoordinatorStub: SplitEventsManagerCoordinator { func register(event: SplitEvent, task: SplitEventTask) { + } + + func register(event: SplitEventWithMetadata, task: SplitEventTask) { + } var notifiedEvents = Set() diff --git a/SplitTests/Fake/SplitClientStub.swift b/SplitTests/Fake/SplitClientStub.swift index a19acdcb7..46f9739f1 100644 --- a/SplitTests/Fake/SplitClientStub.swift +++ b/SplitTests/Fake/SplitClientStub.swift @@ -94,6 +94,9 @@ class SplitClientStub: SplitClient { func on(event: SplitEvent, runInBackground: Bool, queue: DispatchQueue?, execute action: @escaping SplitAction) { } + + func on(event: SplitEvent, executeWithMetadata: @escaping SplitActionWithMetadata) { + } func track(trafficType: String, eventType: String) -> Bool { return true diff --git a/SplitTests/Fake/SplitEventsManagerMock.swift b/SplitTests/Fake/SplitEventsManagerMock.swift index 1132148df..1313a7267 100644 --- a/SplitTests/Fake/SplitEventsManagerMock.swift +++ b/SplitTests/Fake/SplitEventsManagerMock.swift @@ -57,6 +57,10 @@ class SplitEventsManagerMock: SplitEventsManager { registeredEvents[event] = task } + func register(event: SplitEventWithMetadata, task: any SplitEventTask) { + registeredEvents[event.type] = task + } + func start() { } diff --git a/SplitTests/Fake/SplitEventsManagerStub.swift b/SplitTests/Fake/SplitEventsManagerStub.swift index 1d7685dc3..53a926ec7 100644 --- a/SplitTests/Fake/SplitEventsManagerStub.swift +++ b/SplitTests/Fake/SplitEventsManagerStub.swift @@ -43,6 +43,10 @@ class SplitEventsManagerStub: SplitEventsManager { func register(event: SplitEvent, task: SplitEventTask) { registeredEvents[event] = task } + + func register(event: SplitEventWithMetadata, task: SplitEventTask) { + registeredEvents[event.type] = task + } func start() { startCalled = true diff --git a/SplitTests/Helpers/IntegrationHelper.swift b/SplitTests/Helpers/IntegrationHelper.swift index b75db36cf..6f34a5564 100644 --- a/SplitTests/Helpers/IntegrationHelper.swift +++ b/SplitTests/Helpers/IntegrationHelper.swift @@ -209,24 +209,26 @@ class IntegrationHelper { static func describeEvent(_ event: SplitInternalEvent) -> String { switch event { - case .mySegmentsUpdated: - return "mySegmentsUpdated" - case .splitsUpdated: - return "splitsUpdated" - case .mySegmentsLoadedFromCache: - return "mySegmentsLoadedFromCache" - case .splitsLoadedFromCache: - return "splitsLoadedFromCache" - case .attributesLoadedFromCache: - return "attributesLoadedFromCache" - case .sdkReadyTimeoutReached: - return "sdkReadyTimeoutReached" - case .splitKilledNotification: - return "splitKilledNotification" - case .myLargeSegmentsUpdated: - return "myLargeSegmentsUpdated" - case .myLargeSegmentsLoadedFromCache: - return "myLargeSegmentsLoadedFromCache" + case .mySegmentsUpdated: + return "mySegmentsUpdated" + case .splitsUpdated: + return "splitsUpdated" + case .mySegmentsLoadedFromCache: + return "mySegmentsLoadedFromCache" + case .splitsLoadedFromCache: + return "splitsLoadedFromCache" + case .attributesLoadedFromCache: + return "attributesLoadedFromCache" + case .sdkReadyTimeoutReached: + return "sdkReadyTimeoutReached" + case .splitKilledNotification: + return "splitKilledNotification" + case .myLargeSegmentsUpdated: + return "myLargeSegmentsUpdated" + case .myLargeSegmentsLoadedFromCache: + return "myLargeSegmentsLoadedFromCache" + case .sdkError: + return "sdkError" } } diff --git a/SplitTests/SplitEventsManagerTest.swift b/SplitTests/SplitEventsManagerTest.swift index 11d8603ae..166d9f9bf 100644 --- a/SplitTests/SplitEventsManagerTest.swift +++ b/SplitTests/SplitEventsManagerTest.swift @@ -296,6 +296,25 @@ class SplitEventsManagerTest: XCTestCase { eventManager.stop() } + func testEventWithMetadata() { + + let taskExp = XCTestExpectation() + + // Build Task + let metadata = EventMetadata(type: .FEATURE_FLAGS_SYNC_ERROR, data: "TEST_FLAG") + + let handler: SplitActionWithMetadata = { handlerMetadata in + XCTAssertEqual(metadata.type, handlerMetadata.type) + XCTAssertEqual(metadata.data, "TEST_FLAG") + taskExp.fulfill() + } + let task = SplitEventActionTask(action: handler, event: .sdkReady, runInBackground: false, factory: SplitFactoryStub(apiKey: IntegrationHelper.dummyApiKey), queue: nil) + + // Run & test + task.run(metadata) + wait(for: [taskExp], timeout: 3.0) + } + // MARK: Helpers func currentTimestamp() -> Int { return Int(Date().unixTimestamp()) @@ -306,29 +325,22 @@ class SplitEventsManagerTest: XCTestCase { } } -class TestTask: SplitEventTask { - - var event: SplitEvent = .sdkReady - - var runInBackground: Bool = false +class TestTask: SplitEventActionTask { - var queue: DispatchQueue? - var taskTriggered = false let label: String var exp: XCTestExpectation? - init(exp: XCTestExpectation?, label: String = "") { + + init(exp: XCTestExpectation?, label: String = "", action: SplitActionWithMetadata? = nil, metadata: EventMetadata? = nil) { self.exp = exp self.label = label + super.init(action: action ?? { _ in }, event: .sdkReady, factory: SplitFactoryStub(apiKey: IntegrationHelper.dummyApiKey)) } - - func takeQueue() -> DispatchQueue? { - return nil - } - - func run() { + + override func run(_ metadata: EventMetadata?) { print("run: \(self.label)") taskTriggered = true + super.run(metadata) if let exp = self.exp { exp.fulfill() } From ba7328c68e2e939e424ac78d7a7dea3aa2cc6da6 Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Mon, 11 Aug 2025 12:31:46 -0300 Subject: [PATCH 05/16] Simplified tests --- SplitTests/Collections/BlockingQueueTest.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/SplitTests/Collections/BlockingQueueTest.swift b/SplitTests/Collections/BlockingQueueTest.swift index c0a927c3c..f32343f42 100644 --- a/SplitTests/Collections/BlockingQueueTest.swift +++ b/SplitTests/Collections/BlockingQueueTest.swift @@ -60,7 +60,7 @@ class BlockingQueueTest: XCTestCase { while true { do { let event = try queue.take() - local.append(event) + local.append(event.type) } catch BlockingQueueError.noElementAvailable { continue } catch { @@ -153,7 +153,7 @@ class BlockingQueueTest: XCTestCase { qu1.async { for _ in 1..<100000 { - queue.add(SplitInternalEventWithMetadata(.splitsUpdated, metadata: nil)) + queue.add(.splitsUpdated) print("qu1 add") Thread.sleep(forTimeInterval: 0.2) } @@ -162,7 +162,7 @@ class BlockingQueueTest: XCTestCase { qu2.async { for _ in 1..<10000 { print("qu2 add") - queue.add(SplitInternalEventWithMetadata(.sdkReadyTimeoutReached, metadata: nil)) + queue.add(.sdkReadyTimeoutReached) Thread.sleep(forTimeInterval: 0.5) } } @@ -170,7 +170,7 @@ class BlockingQueueTest: XCTestCase { qu3.async { for _ in 1..<10000 { print("qu3 add") - queue.add(SplitInternalEventWithMetadata(.splitsUpdated, metadata: nil)) + queue.add(.splitsUpdated) Thread.sleep(forTimeInterval: 0.8) } } @@ -178,7 +178,7 @@ class BlockingQueueTest: XCTestCase { qu4.async { for _ in 1..<10000 { print("qu4 add") - queue.add(SplitInternalEventWithMetadata(.mySegmentsUpdated, metadata: nil)) + queue.add(.mySegmentsUpdated) sleep(1) } } From 740f7935e5c01b6e42c28abbe3612b9c492e7379 Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Mon, 11 Aug 2025 13:24:03 -0300 Subject: [PATCH 06/16] Equatable removed from SplitActionWithMetadata --- Split/Events/SplitEvent.swift | 5 ----- Split/Events/SplitEventsManager.swift | 2 +- Split/Events/SplitInternalEvent.swift | 4 ---- 3 files changed, 1 insertion(+), 10 deletions(-) diff --git a/Split/Events/SplitEvent.swift b/Split/Events/SplitEvent.swift index 09e354c69..a7d067ef6 100644 --- a/Split/Events/SplitEvent.swift +++ b/Split/Events/SplitEvent.swift @@ -15,11 +15,6 @@ import Foundation self.type = type self.metadata = metadata } - - public override func isEqual(_ object: Any?) -> Bool { - guard let other = object as? SplitEventWithMetadata else { return false } - return self.type == other.type - } } @objc public enum SplitEvent: Int { diff --git a/Split/Events/SplitEventsManager.swift b/Split/Events/SplitEventsManager.swift index 3c6347bdc..335e7a469 100644 --- a/Split/Events/SplitEventsManager.swift +++ b/Split/Events/SplitEventsManager.swift @@ -267,7 +267,7 @@ class DefaultSplitEventsManager: SplitEventsManager { } private func isTriggered(internal event: SplitInternalEventWithMetadata) -> Bool { - return triggered.filter { $0 == event }.count > 0 + return triggered.filter { $0.type == event.type }.count > 0 } private func isTriggered(internal event: SplitInternalEvent) -> Bool { diff --git a/Split/Events/SplitInternalEvent.swift b/Split/Events/SplitInternalEvent.swift index c38f89cf4..37d409257 100644 --- a/Split/Events/SplitInternalEvent.swift +++ b/Split/Events/SplitInternalEvent.swift @@ -17,10 +17,6 @@ struct SplitInternalEventWithMetadata { self.type = type self.metadata = metadata } - - static func == (lhs: SplitInternalEventWithMetadata, rhs: SplitInternalEventWithMetadata) -> Bool { - return lhs.type == rhs.type - } } @objc public class EventMetadata: NSObject { From 8dd3be64155bd1030db87dfcfe434d75d0dfd463 Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Mon, 11 Aug 2025 13:42:28 -0300 Subject: [PATCH 07/16] EventsManager Mock and Stub now record the events correctly --- SplitTests/Fake/SplitEventsManagerMock.swift | 10 +++++----- SplitTests/Fake/SplitEventsManagerStub.swift | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/SplitTests/Fake/SplitEventsManagerMock.swift b/SplitTests/Fake/SplitEventsManagerMock.swift index 1313a7267..e6849f193 100644 --- a/SplitTests/Fake/SplitEventsManagerMock.swift +++ b/SplitTests/Fake/SplitEventsManagerMock.swift @@ -52,13 +52,13 @@ class SplitEventsManagerMock: SplitEventsManager { } } - var registeredEvents = [SplitEvent: SplitEventTask]() func register(event: SplitEvent, task: SplitEventTask) { - registeredEvents[event] = task + register(event: SplitEventWithMetadata(type: event, metadata: nil), task: task) } - - func register(event: SplitEventWithMetadata, task: any SplitEventTask) { - registeredEvents[event.type] = task + + var registeredEvents = [SplitEventWithMetadata: SplitEventTask]() + func register(event: SplitEventWithMetadata, task: SplitEventTask) { + registeredEvents[event] = task } func start() { diff --git a/SplitTests/Fake/SplitEventsManagerStub.swift b/SplitTests/Fake/SplitEventsManagerStub.swift index 53a926ec7..fe09fe197 100644 --- a/SplitTests/Fake/SplitEventsManagerStub.swift +++ b/SplitTests/Fake/SplitEventsManagerStub.swift @@ -39,13 +39,13 @@ class SplitEventsManagerStub: SplitEventsManager { } } - var registeredEvents = [SplitEvent: SplitEventTask]() func register(event: SplitEvent, task: SplitEventTask) { - registeredEvents[event] = task + register(event: SplitEventWithMetadata(type: event, metadata: nil), task: task) } - + + var registeredEvents = [SplitEventWithMetadata: SplitEventTask]() func register(event: SplitEventWithMetadata, task: SplitEventTask) { - registeredEvents[event.type] = task + registeredEvents[event] = task } func start() { From 0b885a4be1913b894674c0594575e544c8d8c809 Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Mon, 11 Aug 2025 14:03:09 -0300 Subject: [PATCH 08/16] Tests fixed --- Split/Common/Structs/BlockingQueue.swift | 8 +++--- Split/Events/SplitInternalEvent.swift | 4 +++ .../Collections/BlockingQueueTest.swift | 4 +-- SplitTests/Init/SplitClientTests.swift | 6 ++--- SplitTests/SplitEventsManagerTest.swift | 25 ++++++++++--------- 5 files changed, 26 insertions(+), 21 deletions(-) diff --git a/Split/Common/Structs/BlockingQueue.swift b/Split/Common/Structs/BlockingQueue.swift index 46f93a2a1..4fb01e5d9 100644 --- a/Split/Common/Structs/BlockingQueue.swift +++ b/Split/Common/Structs/BlockingQueue.swift @@ -81,13 +81,13 @@ protocol InternalEventBlockingQueue { class DefaultInternalEventBlockingQueue: InternalEventBlockingQueue { let blockingQueue = GenericBlockingQueue() - func add(_ item: SplitInternalEvent) { - blockingQueue.add(SplitInternalEventWithMetadata(item, metadata: nil)) - } - func add(_ item: SplitInternalEventWithMetadata) { blockingQueue.add(item) } + + func add(_ item: SplitInternalEvent) { + blockingQueue.add(SplitInternalEventWithMetadata(item, metadata: nil)) + } func take() throws -> SplitInternalEventWithMetadata { let value = try blockingQueue.take() diff --git a/Split/Events/SplitInternalEvent.swift b/Split/Events/SplitInternalEvent.swift index 37d409257..c38f89cf4 100644 --- a/Split/Events/SplitInternalEvent.swift +++ b/Split/Events/SplitInternalEvent.swift @@ -17,6 +17,10 @@ struct SplitInternalEventWithMetadata { self.type = type self.metadata = metadata } + + static func == (lhs: SplitInternalEventWithMetadata, rhs: SplitInternalEventWithMetadata) -> Bool { + return lhs.type == rhs.type + } } @objc public class EventMetadata: NSObject { diff --git a/SplitTests/Collections/BlockingQueueTest.swift b/SplitTests/Collections/BlockingQueueTest.swift index f32343f42..7e6b97b69 100644 --- a/SplitTests/Collections/BlockingQueueTest.swift +++ b/SplitTests/Collections/BlockingQueueTest.swift @@ -34,9 +34,9 @@ class BlockingQueueTest: XCTestCase { } } globalQ.asyncAfter(deadline: .now() + 1) { - queue.add(SplitInternalEvent.mySegmentsLoadedFromCache) + queue.add(.mySegmentsLoadedFromCache) globalQ.asyncAfter(deadline: .now() + 1) { - queue.add(SplitInternalEvent.splitsLoadedFromCache) + queue.add(.splitsLoadedFromCache) } } diff --git a/SplitTests/Init/SplitClientTests.swift b/SplitTests/Init/SplitClientTests.swift index 25c6b3260..6d374be3d 100644 --- a/SplitTests/Init/SplitClientTests.swift +++ b/SplitTests/Init/SplitClientTests.swift @@ -45,7 +45,7 @@ class SplitClientTests: XCTestCase { } for event in events { - guard let task = eventsManager.registeredEvents[event] else { + guard let task = eventsManager.registeredEvents[SplitEventWithMetadata(type: event, metadata: nil)] else { XCTAssertTrue(false) continue } @@ -61,7 +61,7 @@ class SplitClientTests: XCTestCase { } for event in events { - guard let task = eventsManager.registeredEvents[event] else { + guard let task = eventsManager.registeredEvents[SplitEventWithMetadata(type: event, metadata: nil)] else { XCTAssertTrue(false) continue } @@ -77,7 +77,7 @@ class SplitClientTests: XCTestCase { } for event in events { - guard let task = eventsManager.registeredEvents[event] else { + guard let task = eventsManager.registeredEvents[SplitEventWithMetadata(type: event, metadata: nil)] else { XCTAssertTrue(false) continue } diff --git a/SplitTests/SplitEventsManagerTest.swift b/SplitTests/SplitEventsManagerTest.swift index 166d9f9bf..ae1132a89 100644 --- a/SplitTests/SplitEventsManagerTest.swift +++ b/SplitTests/SplitEventsManagerTest.swift @@ -298,21 +298,22 @@ class SplitEventsManagerTest: XCTestCase { func testEventWithMetadata() { - let taskExp = XCTestExpectation() + let taskExp = XCTestExpectation() + let data = "TEST_DATA_123456" - // Build Task - let metadata = EventMetadata(type: .FEATURE_FLAGS_SYNC_ERROR, data: "TEST_FLAG") + // Build Task + let metadata = EventMetadata(type: .FEATURE_FLAGS_SYNC_ERROR, data: data) - let handler: SplitActionWithMetadata = { handlerMetadata in - XCTAssertEqual(metadata.type, handlerMetadata.type) - XCTAssertEqual(metadata.data, "TEST_FLAG") - taskExp.fulfill() - } - let task = SplitEventActionTask(action: handler, event: .sdkReady, runInBackground: false, factory: SplitFactoryStub(apiKey: IntegrationHelper.dummyApiKey), queue: nil) + let handler: SplitActionWithMetadata = { handlerMetadata in + XCTAssertEqual(metadata.type, handlerMetadata.type) + XCTAssertEqual(metadata.data, data) + taskExp.fulfill() + } + let task = SplitEventActionTask(action: handler, event: .sdkReady, runInBackground: false, factory: SplitFactoryStub(apiKey: IntegrationHelper.dummyApiKey), queue: nil) - // Run & test - task.run(metadata) - wait(for: [taskExp], timeout: 3.0) + // Run & test + task.run(metadata) + wait(for: [taskExp], timeout: 1) } // MARK: Helpers From ad58ec328d2e17ec1274902b37f8c9e649232438 Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Mon, 11 Aug 2025 14:45:14 -0300 Subject: [PATCH 09/16] Tests fixed --- Split/Events/SplitInternalEvent.swift | 8 ++------ SplitTests/Init/SplitClientTests.swift | 6 +++--- SplitTests/SplitEventsManagerTest.swift | 2 +- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/Split/Events/SplitInternalEvent.swift b/Split/Events/SplitInternalEvent.swift index c38f89cf4..3b583c6a9 100644 --- a/Split/Events/SplitInternalEvent.swift +++ b/Split/Events/SplitInternalEvent.swift @@ -17,17 +17,13 @@ struct SplitInternalEventWithMetadata { self.type = type self.metadata = metadata } - - static func == (lhs: SplitInternalEventWithMetadata, rhs: SplitInternalEventWithMetadata) -> Bool { - return lhs.type == rhs.type - } } @objc public class EventMetadata: NSObject { var type: EventMetadataType - var data: String = "" + var data: [String] = [] - init(type: EventMetadataType, data: String) { + init(type: EventMetadataType, data: [String] = []) { self.type = type self.data = data } diff --git a/SplitTests/Init/SplitClientTests.swift b/SplitTests/Init/SplitClientTests.swift index 6d374be3d..ff1658039 100644 --- a/SplitTests/Init/SplitClientTests.swift +++ b/SplitTests/Init/SplitClientTests.swift @@ -45,7 +45,7 @@ class SplitClientTests: XCTestCase { } for event in events { - guard let task = eventsManager.registeredEvents[SplitEventWithMetadata(type: event, metadata: nil)] else { + guard let task = eventsManager.registeredEvents.first(where: { $0.key.type == event })?.value else { XCTAssertTrue(false) continue } @@ -61,7 +61,7 @@ class SplitClientTests: XCTestCase { } for event in events { - guard let task = eventsManager.registeredEvents[SplitEventWithMetadata(type: event, metadata: nil)] else { + guard let task = eventsManager.registeredEvents.first(where: { $0.key.type == event })?.value else { XCTAssertTrue(false) continue } @@ -77,7 +77,7 @@ class SplitClientTests: XCTestCase { } for event in events { - guard let task = eventsManager.registeredEvents[SplitEventWithMetadata(type: event, metadata: nil)] else { + guard let task = eventsManager.registeredEvents.first(where: { $0.key.type == event })?.value else { XCTAssertTrue(false) continue } diff --git a/SplitTests/SplitEventsManagerTest.swift b/SplitTests/SplitEventsManagerTest.swift index ae1132a89..98acad400 100644 --- a/SplitTests/SplitEventsManagerTest.swift +++ b/SplitTests/SplitEventsManagerTest.swift @@ -299,7 +299,7 @@ class SplitEventsManagerTest: XCTestCase { func testEventWithMetadata() { let taskExp = XCTestExpectation() - let data = "TEST_DATA_123456" + let data = ["TEST_DATA_123456"] // Build Task let metadata = EventMetadata(type: .FEATURE_FLAGS_SYNC_ERROR, data: data) From 30e203a3381798e7ff4d741e3eac1626d2eb57db Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Mon, 11 Aug 2025 18:11:21 -0300 Subject: [PATCH 10/16] Tests covering the new events listeners with metadata --- Split/Api/DefaultSplitClient.swift | 34 ++++++++++++------ Split/Api/FailHelpers.swift | 10 +++++- Split/Api/LocalhostSplitClient.swift | 10 +++++- Split/Api/SplitClient.swift | 7 +++- SplitTests/Fake/InternalSplitClientStub.swift | 10 +++++- SplitTests/Fake/SplitClientStub.swift | 9 +++++ SplitTests/Init/SplitClientTests.swift | 36 +++++++++++++++++++ 7 files changed, 101 insertions(+), 15 deletions(-) diff --git a/Split/Api/DefaultSplitClient.swift b/Split/Api/DefaultSplitClient.swift index 1173d3e57..1d3beb8be 100644 --- a/Split/Api/DefaultSplitClient.swift +++ b/Split/Api/DefaultSplitClient.swift @@ -89,17 +89,6 @@ extension DefaultSplitClient { on(event: event, executeTask: task) } - private func onWithMetadata(event: SplitEventWithMetadata, runInBackground: Bool, queue: DispatchQueue?, execute actionWithMetadata: @escaping SplitActionWithMetadata) { - guard let factory = clientManager?.splitFactory else { return } - let task = SplitEventActionTask(action: actionWithMetadata, event: event.type, runInBackground: runInBackground, factory: factory, queue: queue) - on(event: event.type, executeTask: task) - } - - public func on(event: SplitEvent, executeWithMetadata action: SplitActionWithMetadata?) { - guard let action = action else { return } - onWithMetadata(event: SplitEventWithMetadata(type: event, metadata: nil), runInBackground: true, queue: nil, execute: action) - } - private func on(event: SplitEvent, executeTask task: SplitEventActionTask) { if event != .sdkReadyFromCache, eventsManager.eventAlreadyTriggered(event: event) { Logger.w("A handler was added for \(event.toString()) on the SDK, which has already fired and won’t be emitted again. The callback won’t be executed.") @@ -107,6 +96,29 @@ extension DefaultSplitClient { } eventsManager.register(event: SplitEventWithMetadata(type: event, metadata: nil), task: task) } + + // MARK: Listeners with Metadata + public func on(event: SplitEvent, executeWithMetadata action: @escaping SplitActionWithMetadata) { + on(event: event, runInBackground: true, queue: nil, executeWithMetadata: action) + } + + public func on(event: SplitEvent, runInBackground: Bool, executeWithMetadata: @escaping SplitActionWithMetadata) { + on(event: event, runInBackground: runInBackground, queue: nil, executeWithMetadata: executeWithMetadata) + } + + public func on(event: SplitEvent, runInBackground: Bool, queue: DispatchQueue? = nil, executeWithMetadata action: @escaping SplitActionWithMetadata) { + on(event: event, runInBackground: runInBackground, queue: queue, action: action) + } + + public func on(event: SplitEvent, queue: DispatchQueue, action: @escaping SplitActionWithMetadata) { + on(event: event, runInBackground: true, queue: queue, action: action) + } + + private func on(event: SplitEvent, runInBackground: Bool, queue: DispatchQueue? = nil, action: @escaping SplitActionWithMetadata) { + guard let factory = clientManager?.splitFactory else { return } + let task = SplitEventActionTask(action: action, event: event, runInBackground: runInBackground, factory: factory, queue: queue) + on(event: event, executeTask: task) + } } // MARK: Treatment / Evaluation diff --git a/Split/Api/FailHelpers.swift b/Split/Api/FailHelpers.swift index 2e9a78020..0c2bf13cf 100644 --- a/Split/Api/FailHelpers.swift +++ b/Split/Api/FailHelpers.swift @@ -66,7 +66,15 @@ class FailedClient: SplitClient { } func on(event: SplitEvent, executeWithMetadata: @escaping SplitActionWithMetadata) { - + /* Intentionally unimplemented */ + } + + func on(event: SplitEvent, runInBackground: Bool, executeWithMetadata: @escaping SplitActionWithMetadata) { + /* Intentionally unimplemented */ + } + + func on(event: SplitEvent, queue: DispatchQueue, action: @escaping SplitActionWithMetadata) { + /* Intentionally unimplemented */ } func track(trafficType: String, eventType: String) -> Bool { diff --git a/Split/Api/LocalhostSplitClient.swift b/Split/Api/LocalhostSplitClient.swift index 203370ada..eb15e21fe 100644 --- a/Split/Api/LocalhostSplitClient.swift +++ b/Split/Api/LocalhostSplitClient.swift @@ -148,7 +148,15 @@ public final class LocalhostSplitClient: NSObject, SplitClient { } public func on(event: SplitEvent, executeWithMetadata: @escaping SplitActionWithMetadata) { - + /* Intentionally unimplemented */ + } + + public func on(event: SplitEvent, runInBackground: Bool, executeWithMetadata: @escaping SplitActionWithMetadata) { + /* Intentionally unimplemented */ + } + + public func on(event: SplitEvent, queue: DispatchQueue, action: @escaping SplitActionWithMetadata) { + /* Intentionally unimplemented */ } public func track(trafficType: String, eventType: String) -> Bool { diff --git a/Split/Api/SplitClient.swift b/Split/Api/SplitClient.swift index 4c81639d6..7c87983d3 100644 --- a/Split/Api/SplitClient.swift +++ b/Split/Api/SplitClient.swift @@ -34,10 +34,15 @@ public typealias SplitActionWithMetadata = (EventMetadata) -> Void @objc(getTreatmentsWithConfigForSplits:attributes:evaluationOptions:) func getTreatmentsWithConfig(splits: [String], attributes: [String: Any]?, evaluationOptions: EvaluationOptions?) -> [String: SplitResult] + + // MARK: Events Listeners func on(event: SplitEvent, execute action: @escaping SplitAction) - func on(event: SplitEvent, executeWithMetadata: @escaping SplitActionWithMetadata) func on(event: SplitEvent, runInBackground: Bool, execute action: @escaping SplitAction) func on(event: SplitEvent, queue: DispatchQueue, execute action: @escaping SplitAction) + + func on(event: SplitEvent, executeWithMetadata: @escaping SplitActionWithMetadata) + func on(event: SplitEvent, runInBackground: Bool, executeWithMetadata: @escaping SplitActionWithMetadata) + func on(event: SplitEvent, queue: DispatchQueue, action: @escaping SplitActionWithMetadata) // MARK: Track feature func track(trafficType: String, eventType: String) -> Bool diff --git a/SplitTests/Fake/InternalSplitClientStub.swift b/SplitTests/Fake/InternalSplitClientStub.swift index d5d0a48df..26fc89caa 100644 --- a/SplitTests/Fake/InternalSplitClientStub.swift +++ b/SplitTests/Fake/InternalSplitClientStub.swift @@ -105,7 +105,15 @@ class InternalSplitClientStub: InternalSplitClient { } func on(event: SplitEvent, executeWithMetadata: @escaping SplitActionWithMetadata) { - + /* Intentionally unimplemented */ + } + + func on(event: SplitEvent, runInBackground: Bool, executeWithMetadata: @escaping SplitActionWithMetadata) { + /* Intentionally unimplemented */ + } + + func on(event: SplitEvent, queue: DispatchQueue, action: @escaping SplitActionWithMetadata) { + /* Intentionally unimplemented */ } func track(trafficType: String, eventType: String) -> Bool { diff --git a/SplitTests/Fake/SplitClientStub.swift b/SplitTests/Fake/SplitClientStub.swift index 46f9739f1..2cb2b2492 100644 --- a/SplitTests/Fake/SplitClientStub.swift +++ b/SplitTests/Fake/SplitClientStub.swift @@ -96,6 +96,15 @@ class SplitClientStub: SplitClient { } func on(event: SplitEvent, executeWithMetadata: @escaping SplitActionWithMetadata) { + /* Intentionally unimplemented */ + } + + func on(event: SplitEvent, runInBackground: Bool, executeWithMetadata: @escaping SplitActionWithMetadata) { + /* Intentionally unimplemented */ + } + + func on(event: SplitEvent, queue: DispatchQueue, action: @escaping SplitActionWithMetadata) { + /* Intentionally unimplemented */ } func track(trafficType: String, eventType: String) -> Bool { diff --git a/SplitTests/Init/SplitClientTests.swift b/SplitTests/Init/SplitClientTests.swift index ff1658039..8403505dc 100644 --- a/SplitTests/Init/SplitClientTests.swift +++ b/SplitTests/Init/SplitClientTests.swift @@ -70,6 +70,42 @@ class SplitClientTests: XCTestCase { XCTAssertNil(task.takeQueue()) } } + + func testOnBgWithMetadata() { + for event in events { + client.on(event: event, executeWithMetadata: { metadata in + print("Metadata: \(metadata.data)") + }) + } + + for event in events { + guard let task = eventsManager.registeredEvents.first(where: { $0.key.type == event })?.value else { + XCTAssertTrue(false) + continue + } + + XCTAssertEqual(true, task.runInBackground) + XCTAssertNil(task.takeQueue()) + } + } + + func testOnMainWithMetadata() { + for event in events { + client.on(event: event, runInBackground: false) { metadata in + print("Metadata: \(metadata.data)") + } + } + + for event in events { + guard let task = eventsManager.registeredEvents.first(where: { $0.key.type == event })?.value else { + XCTAssertTrue(false) + continue + } + + XCTAssertEqual(false, task.runInBackground) + XCTAssertNil(task.takeQueue()) + } + } func testOnQueue() { for event in events { From ed50d1cf6fefdf6846b6184aed3b3ef1e3a83185 Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Mon, 11 Aug 2025 18:13:50 -0300 Subject: [PATCH 11/16] Removed code from the next feature (Events Errors) --- Split/Events/EventsManagerCoordinator.swift | 3 +-- Split/Events/SplitEvent.swift | 3 --- Split/Events/SplitEventsManager.swift | 4 ---- Split/Events/SplitInternalEvent.swift | 1 - SplitTests/Helpers/IntegrationHelper.swift | 2 -- 5 files changed, 1 insertion(+), 12 deletions(-) diff --git a/Split/Events/EventsManagerCoordinator.swift b/Split/Events/EventsManagerCoordinator.swift index 8ce38ab1a..cf99106a5 100644 --- a/Split/Events/EventsManagerCoordinator.swift +++ b/Split/Events/EventsManagerCoordinator.swift @@ -22,8 +22,7 @@ class MainSplitEventsManager: SplitEventsManagerCoordinator { private let eventsToHandle: Set = Set( [.splitsLoadedFromCache, .splitsUpdated, - .splitKilledNotification, - .sdkError] + .splitKilledNotification] ) func notifyInternalEvent(_ event: SplitInternalEvent) { diff --git a/Split/Events/SplitEvent.swift b/Split/Events/SplitEvent.swift index a7d067ef6..ab624200b 100644 --- a/Split/Events/SplitEvent.swift +++ b/Split/Events/SplitEvent.swift @@ -22,7 +22,6 @@ import Foundation case sdkReadyTimedOut case sdkReadyFromCache case sdkUpdated - case sdkError public func toString() -> String { switch self { @@ -34,8 +33,6 @@ import Foundation return "SDK_READY_TIMED_OUT" case .sdkReadyFromCache: return "SDK_READY_FROM_CACHE" - case .sdkError: - return "SDK_ERROR" } } } diff --git a/Split/Events/SplitEventsManager.swift b/Split/Events/SplitEventsManager.swift index 335e7a469..6dbfd94f8 100644 --- a/Split/Events/SplitEventsManager.swift +++ b/Split/Events/SplitEventsManager.swift @@ -181,10 +181,6 @@ class DefaultSplitEventsManager: SplitEventsManager { if !isTriggered(external: .sdkReady) { trigger(event: .sdkReadyTimedOut) } - case .sdkError: - if !isTriggered(external: .sdkReady) { - trigger(event: SplitEventWithMetadata(type: .sdkUpdated, metadata: event.metadata)) - } } } } diff --git a/Split/Events/SplitInternalEvent.swift b/Split/Events/SplitInternalEvent.swift index 3b583c6a9..60ce80c63 100644 --- a/Split/Events/SplitInternalEvent.swift +++ b/Split/Events/SplitInternalEvent.swift @@ -54,5 +54,4 @@ enum SplitInternalEvent { case attributesLoadedFromCache case sdkReadyTimeoutReached case splitKilledNotification - case sdkError } diff --git a/SplitTests/Helpers/IntegrationHelper.swift b/SplitTests/Helpers/IntegrationHelper.swift index 6f34a5564..a40e360b0 100644 --- a/SplitTests/Helpers/IntegrationHelper.swift +++ b/SplitTests/Helpers/IntegrationHelper.swift @@ -227,8 +227,6 @@ class IntegrationHelper { return "myLargeSegmentsUpdated" case .myLargeSegmentsLoadedFromCache: return "myLargeSegmentsLoadedFromCache" - case .sdkError: - return "sdkError" } } From a759fa7de8fe895adcd3ca6b462a8178e564c286 Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Mon, 11 Aug 2025 18:35:02 -0300 Subject: [PATCH 12/16] Added nested comment for SonarQuabe quality gate --- Split/Events/EventsManagerCoordinator.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Split/Events/EventsManagerCoordinator.swift b/Split/Events/EventsManagerCoordinator.swift index cf99106a5..ac65fd2a9 100644 --- a/Split/Events/EventsManagerCoordinator.swift +++ b/Split/Events/EventsManagerCoordinator.swift @@ -83,5 +83,7 @@ class MainSplitEventsManager: SplitEventsManagerCoordinator { func register(event: SplitEvent, task: SplitEventTask) {} - func register(event: SplitEventWithMetadata, task: any SplitEventTask) {} + func register(event: SplitEventWithMetadata, task: any SplitEventTask) { + /* Intentionally unimplemented */ + } } From 9270d6526d85043d77bf406f3bde101dc11a04b9 Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Mon, 11 Aug 2025 18:52:04 -0300 Subject: [PATCH 13/16] Tests added to conform to SonarQube quality gate --- Split.xcodeproj/project.pbxproj | 4 ++++ Split/Events/SplitInternalEvent.swift | 1 - SplitTests/SplitEventsTests.swift | 13 +++++++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 SplitTests/SplitEventsTests.swift diff --git a/Split.xcodeproj/project.pbxproj b/Split.xcodeproj/project.pbxproj index 3d7b9fe2f..3cc7ad0da 100644 --- a/Split.xcodeproj/project.pbxproj +++ b/Split.xcodeproj/project.pbxproj @@ -353,6 +353,7 @@ 59FB7C35220329B900ECC96A /* SplitFactoryBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59FB7C34220329B900ECC96A /* SplitFactoryBuilderTests.swift */; }; 59FB7C3C2203795F00ECC96A /* LocalhostSplitsParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59FB7C3B2203795F00ECC96A /* LocalhostSplitsParser.swift */; }; 59FB7C3E22037B9400ECC96A /* SpaceDelimitedLocalhostSplitsParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59FB7C3D22037B9400ECC96A /* SpaceDelimitedLocalhostSplitsParser.swift */; }; + 5B0162682E4A9C7A0009D3B7 /* SplitEventsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B0162672E4A9C5D0009D3B7 /* SplitEventsTests.swift */; }; 5B48D8172DEA2CED00351925 /* PrerequisitesMatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BF52DF52DE0B60300FEDAFE /* PrerequisitesMatcher.swift */; }; 5B91B8392DDE4A3B000510F0 /* SplitDTOTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B91B8382DDE4A30000510F0 /* SplitDTOTests.swift */; }; 5BF52DF72DE0B60700FEDAFE /* PrerequisitesMatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BF52DF52DE0B60300FEDAFE /* PrerequisitesMatcher.swift */; }; @@ -1557,6 +1558,7 @@ 59FB7C34220329B900ECC96A /* SplitFactoryBuilderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitFactoryBuilderTests.swift; sourceTree = ""; }; 59FB7C3B2203795F00ECC96A /* LocalhostSplitsParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalhostSplitsParser.swift; sourceTree = ""; }; 59FB7C3D22037B9400ECC96A /* SpaceDelimitedLocalhostSplitsParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpaceDelimitedLocalhostSplitsParser.swift; sourceTree = ""; }; + 5B0162672E4A9C5D0009D3B7 /* SplitEventsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitEventsTests.swift; sourceTree = ""; }; 5B91B8382DDE4A30000510F0 /* SplitDTOTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitDTOTests.swift; sourceTree = ""; }; 5BF52DF52DE0B60300FEDAFE /* PrerequisitesMatcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrerequisitesMatcher.swift; sourceTree = ""; }; 5BF52DF82DE4B8CA00FEDAFE /* PrerequisitesMatcherTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrerequisitesMatcherTest.swift; sourceTree = ""; }; @@ -2887,6 +2889,7 @@ isa = PBXGroup; children = ( C539CAE52D947D2A0050C732 /* Common */, + 5B0162672E4A9C5D0009D3B7 /* SplitEventsTests.swift */, 5B91B8382DDE4A30000510F0 /* SplitDTOTests.swift */, C53EDFCC2DD4E10A000DCDBC /* SplitsSyncHelperWithProxyHandlerTests.swift */, C53EDFCA2DD3E257000DCDBC /* OutdatedSplitProxyHandlerTests.swift */, @@ -4429,6 +4432,7 @@ 95B1801E2763BF7E002DC9DF /* TelemetryStatsRecorderWorkerTests.swift in Sources */, 95F3F002258D3EE700084AF8 /* HttpEventsRecorderStub.swift in Sources */, 59D84BE3221734F5003DA248 /* LocalhostSplitClientTests.swift in Sources */, + 5B0162682E4A9C7A0009D3B7 /* SplitEventsTests.swift in Sources */, 59B2043924F5667A0092F2E9 /* SseNotificationProcessorTest.swift in Sources */, 592C6AC6211B718E002D120C /* SplitEventsManagerTest.swift in Sources */, 95C7569D2696457500696148 /* NotificationHelperStub.swift in Sources */, diff --git a/Split/Events/SplitInternalEvent.swift b/Split/Events/SplitInternalEvent.swift index 60ce80c63..e05385198 100644 --- a/Split/Events/SplitInternalEvent.swift +++ b/Split/Events/SplitInternalEvent.swift @@ -39,7 +39,6 @@ enum EventMetadataType: Int { return "FEATURE_FLAGS_SYNC_ERROR" case .SEGMENTS_SYNC_ERROR: return "SEGMENTS_SYNC_ERROR" - } } } diff --git a/SplitTests/SplitEventsTests.swift b/SplitTests/SplitEventsTests.swift new file mode 100644 index 000000000..e921fbf6a --- /dev/null +++ b/SplitTests/SplitEventsTests.swift @@ -0,0 +1,13 @@ +// Created by Martin Cardozo on 11/08/2025 + +import XCTest +@testable import Split + +class SplitEventsTests { + func testSplitInternalEventsWithMetadata() { + let event = SplitInternalEventWithMetadata(.splitsUpdated, metadata: EventMetadata(type: .FEATURE_FLAGS_SYNC_ERROR, data: [])) + XCTAssertEqual(event.metadata!.type.toString(), "FEATURE_FLAGS_SYNC_ERROR") + event = SplitInternalEventWithMetadata(.splitsUpdated, metadata: EventMetadata(type: .SEGMENTS_SYNC_ERROR, data: [])) + XCTAssertEqual(event.metadata!.type.toString(), "SEGMENTS_SYNC_ERROR") + } +} From 71c5f6801656785c7b3e6b59147f8dc33cf36852 Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Mon, 11 Aug 2025 18:52:56 -0300 Subject: [PATCH 14/16] Typo fixed: --- SplitTests/SplitEventsTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SplitTests/SplitEventsTests.swift b/SplitTests/SplitEventsTests.swift index e921fbf6a..490290514 100644 --- a/SplitTests/SplitEventsTests.swift +++ b/SplitTests/SplitEventsTests.swift @@ -5,7 +5,7 @@ import XCTest class SplitEventsTests { func testSplitInternalEventsWithMetadata() { - let event = SplitInternalEventWithMetadata(.splitsUpdated, metadata: EventMetadata(type: .FEATURE_FLAGS_SYNC_ERROR, data: [])) + var event = SplitInternalEventWithMetadata(.splitsUpdated, metadata: EventMetadata(type: .FEATURE_FLAGS_SYNC_ERROR, data: [])) XCTAssertEqual(event.metadata!.type.toString(), "FEATURE_FLAGS_SYNC_ERROR") event = SplitInternalEventWithMetadata(.splitsUpdated, metadata: EventMetadata(type: .SEGMENTS_SYNC_ERROR, data: [])) XCTAssertEqual(event.metadata!.type.toString(), "SEGMENTS_SYNC_ERROR") From 93c1b9660537475962882f8e616510341ae9b488 Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Mon, 11 Aug 2025 19:10:26 -0300 Subject: [PATCH 15/16] SplitEventsTests added to TestPlan --- SplitTests/SplitEventsTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SplitTests/SplitEventsTests.swift b/SplitTests/SplitEventsTests.swift index 490290514..0920480cd 100644 --- a/SplitTests/SplitEventsTests.swift +++ b/SplitTests/SplitEventsTests.swift @@ -3,7 +3,7 @@ import XCTest @testable import Split -class SplitEventsTests { +class SplitEventsTests: XCTestCase { func testSplitInternalEventsWithMetadata() { var event = SplitInternalEventWithMetadata(.splitsUpdated, metadata: EventMetadata(type: .FEATURE_FLAGS_SYNC_ERROR, data: [])) XCTAssertEqual(event.metadata!.type.toString(), "FEATURE_FLAGS_SYNC_ERROR") From e9b139d0e92d0802d3c482a40fe5ba111081eb0b Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Mon, 11 Aug 2025 20:49:57 -0300 Subject: [PATCH 16/16] Test added to cover listening with metadata on a Queue --- SplitTests/Init/SplitClientTests.swift | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/SplitTests/Init/SplitClientTests.swift b/SplitTests/Init/SplitClientTests.swift index 8403505dc..4a831906f 100644 --- a/SplitTests/Init/SplitClientTests.swift +++ b/SplitTests/Init/SplitClientTests.swift @@ -122,6 +122,21 @@ class SplitClientTests: XCTestCase { XCTAssertNotNil(task.takeQueue()) } } + + func testOnQueueWithMetadata() { + for event in events { + client.on(event: event, queue: DispatchQueue(label: "queuemetadata1"), action: { _ in print("exec")}) + } + + for event in events { + guard let task = eventsManager.registeredEvents.first(where: { $0.key.type == event })?.value else { + XCTAssertTrue(false) + continue + } + + XCTAssertNotNil(task.takeQueue()) + } + } func testGetTreatmentWithEvaluationOptions() { testEvaluationOptionsPassedCorrectly(