From 15a175d30c838a98ac3f8c8823f6c48797f220c8 Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Mon, 9 Jun 2025 14:30:02 -0300 Subject: [PATCH 01/62] New Events and InternalEvents with metadata --- Split/Events/SplitEvent.swift | 26 +++++++++++++++++++++----- Split/Events/SplitInternalEvent.swift | 19 ++++++++++++++----- 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/Split/Events/SplitEvent.swift b/Split/Events/SplitEvent.swift index d2561e2d9..41229d90b 100644 --- a/Split/Events/SplitEvent.swift +++ b/Split/Events/SplitEvent.swift @@ -1,17 +1,31 @@ -// -// SplitEvent.swift -// Split -// // Created by Sebastian Arrubia on 4/17/18. -// 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. + +@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 +37,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/SplitInternalEvent.swift b/Split/Events/SplitInternalEvent.swift index 4c9521204..a7530532d 100644 --- a/Split/Events/SplitInternalEvent.swift +++ b/Split/Events/SplitInternalEvent.swift @@ -1,12 +1,21 @@ -// -// SplitInternalEvent.swift -// Split -// // Created by Sebastian Arrubia on 4/16/18. -// import Foundation +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 + } +} + enum SplitInternalEvent { case mySegmentsUpdated case myLargeSegmentsUpdated From 52f912b80ab952c4c1753aef6f03b2041c582004 Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Mon, 9 Jun 2025 17:47:16 -0300 Subject: [PATCH 02/62] ActionTask updated --- Split/Api/SplitClient.swift | 2 -- Split/Events/SplitEventActionTask.swift | 23 ++++++++++++++++++----- Split/Events/SplitEventTask.swift | 1 + SplitTests/SplitEventsManagerTest.swift | 13 ++++++++++++- 4 files changed, 31 insertions(+), 8 deletions(-) diff --git a/Split/Api/SplitClient.swift b/Split/Api/SplitClient.swift index ff4df2005..b563f9a36 100644 --- a/Split/Api/SplitClient.swift +++ b/Split/Api/SplitClient.swift @@ -8,8 +8,6 @@ import Foundation -public typealias SplitAction = () -> Void - @objc public protocol SplitClient { // MARK: Evaluation feature diff --git a/Split/Events/SplitEventActionTask.swift b/Split/Events/SplitEventActionTask.swift index be1368d04..15cd4409e 100644 --- a/Split/Events/SplitEventActionTask.swift +++ b/Split/Events/SplitEventActionTask.swift @@ -1,16 +1,17 @@ -// -// SplitEventActionTask.swift -// Split -// // Created by Javier L. Avrudsky on 7/6/18. -// import Foundation +public typealias SplitAction = () -> Void +public typealias SplitActionWithMetadata = (_ metadata: EventMetadata?) -> Void + class SplitEventActionTask: SplitEventTask { + // Private private var eventHandler: SplitAction? + private var eventHandlerWithMetadata: SplitActionWithMetadata? private var queue: DispatchQueue? + var event: SplitEvent var runInBackground: Bool = false var factory: SplitFactory @@ -27,6 +28,14 @@ class SplitEventActionTask: SplitEventTask { 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 + } func takeQueue() -> DispatchQueue? { defer { queue = nil } @@ -36,4 +45,8 @@ class SplitEventActionTask: SplitEventTask { func run() { eventHandler?() } + + func run(_ metadata: EventMetadata) { + eventHandlerWithMetadata?(metadata) + } } diff --git a/Split/Events/SplitEventTask.swift b/Split/Events/SplitEventTask.swift index 1655e2b25..c18f14d91 100644 --- a/Split/Events/SplitEventTask.swift +++ b/Split/Events/SplitEventTask.swift @@ -12,4 +12,5 @@ protocol SplitEventTask { var runInBackground: Bool { get } func takeQueue() -> DispatchQueue? func run() + func run(_ metadata: EventMetadata) } diff --git a/SplitTests/SplitEventsManagerTest.swift b/SplitTests/SplitEventsManagerTest.swift index 11d8603ae..e173b74f9 100644 --- a/SplitTests/SplitEventsManagerTest.swift +++ b/SplitTests/SplitEventsManagerTest.swift @@ -307,13 +307,15 @@ class SplitEventsManagerTest: XCTestCase { } class TestTask: SplitEventTask { - + var event: SplitEvent = .sdkReady var runInBackground: Bool = false var queue: DispatchQueue? + var metadata: EventMetadata? = nil + var taskTriggered = false let label: String var exp: XCTestExpectation? @@ -333,4 +335,13 @@ class TestTask: SplitEventTask { exp.fulfill() } } + + func run(_ metadata: EventMetadata) { + print("run: \(self.label)") + self.metadata = metadata + taskTriggered = true + if let exp = self.exp { + exp.fulfill() + } + } } From d58c5d6b671dfa934e55e254104b64ba96896b90 Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Mon, 9 Jun 2025 17:55:20 -0300 Subject: [PATCH 03/62] Cleanup --- SplitTests/SplitEventsManagerTest.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SplitTests/SplitEventsManagerTest.swift b/SplitTests/SplitEventsManagerTest.swift index e173b74f9..8a7e49aac 100644 --- a/SplitTests/SplitEventsManagerTest.swift +++ b/SplitTests/SplitEventsManagerTest.swift @@ -307,7 +307,7 @@ class SplitEventsManagerTest: XCTestCase { } class TestTask: SplitEventTask { - + var event: SplitEvent = .sdkReady var runInBackground: Bool = false From 4d77b9f895499cfedecee411fd8c10eda93a3459 Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Mon, 9 Jun 2025 18:15:14 -0300 Subject: [PATCH 04/62] Test added --- SplitTests/SplitEventsManagerTest.swift | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/SplitTests/SplitEventsManagerTest.swift b/SplitTests/SplitEventsManagerTest.swift index 8a7e49aac..1563e3b11 100644 --- a/SplitTests/SplitEventsManagerTest.swift +++ b/SplitTests/SplitEventsManagerTest.swift @@ -262,6 +262,25 @@ class SplitEventsManagerTest: XCTestCase { eventManager.stop() } + + func testEventWithMetadata() { + + let taskExp = XCTestExpectation() + + // Build Task + let metadata = EventMetadata(type: .FLAGS_KILLED, 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) + } func testSdkReadyFromCacheWithoutFlagCachedValues() { From 568d378ff6594e573fa8f60a2d2755ae87fbbb2a Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Tue, 10 Jun 2025 13:05:41 -0300 Subject: [PATCH 05/62] Add new channels --- Split/Api/SplitClient.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Split/Api/SplitClient.swift b/Split/Api/SplitClient.swift index b563f9a36..91180b83a 100644 --- a/Split/Api/SplitClient.swift +++ b/Split/Api/SplitClient.swift @@ -9,6 +9,8 @@ import Foundation @objc public protocol SplitClient { + + // TODO: Add new channels // MARK: Evaluation feature func getTreatment(_ split: String, attributes: [String: Any]?) -> String From 2a4c597f7007a0bb6c9426930504121dd2ec65c0 Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Tue, 10 Jun 2025 13:08:11 -0300 Subject: [PATCH 06/62] Initial commit --- Split/Events/SplitEventsManager.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Split/Events/SplitEventsManager.swift b/Split/Events/SplitEventsManager.swift index ea64d0279..e3e2de877 100644 --- a/Split/Events/SplitEventsManager.swift +++ b/Split/Events/SplitEventsManager.swift @@ -8,6 +8,8 @@ import Foundation +//TODO: Adapt for new events + protocol SplitEventsManager: AnyObject { func register(event: SplitEvent, task: SplitEventTask) func notifyInternalEvent(_ event: SplitInternalEvent) From d8657430df0f12b5bd0889573201718a05ebc917 Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Tue, 10 Jun 2025 13:45:58 -0300 Subject: [PATCH 07/62] Action handlers unified --- Split/Events/SplitEventActionTask.swift | 12 ++++++------ Split/Events/SplitEventTask.swift | 3 +-- Split/Events/SplitEventsManager.swift | 4 ++-- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/Split/Events/SplitEventActionTask.swift b/Split/Events/SplitEventActionTask.swift index 15cd4409e..906f027bb 100644 --- a/Split/Events/SplitEventActionTask.swift +++ b/Split/Events/SplitEventActionTask.swift @@ -41,12 +41,12 @@ class SplitEventActionTask: SplitEventTask { defer { queue = nil } return queue } - - func run() { - eventHandler?() - } - func run(_ metadata: EventMetadata) { - eventHandlerWithMetadata?(metadata) + func run(_ metadata: EventMetadata?) { + eventHandler?() + + if let metadata = metadata { + eventHandlerWithMetadata?(metadata) + } } } diff --git a/Split/Events/SplitEventTask.swift b/Split/Events/SplitEventTask.swift index c18f14d91..147bc8441 100644 --- a/Split/Events/SplitEventTask.swift +++ b/Split/Events/SplitEventTask.swift @@ -11,6 +11,5 @@ protocol SplitEventTask { var event: SplitEvent { get } var runInBackground: Bool { get } func takeQueue() -> DispatchQueue? - func run() - func run(_ metadata: EventMetadata) + func run(_ metadata: EventMetadata?) } diff --git a/Split/Events/SplitEventsManager.swift b/Split/Events/SplitEventsManager.swift index ea64d0279..bff3e7199 100644 --- a/Split/Events/SplitEventsManager.swift +++ b/Split/Events/SplitEventsManager.swift @@ -232,7 +232,7 @@ class DefaultSplitEventsManager: SplitEventsManager { let queue = task.takeQueue() ?? DispatchQueue.general queue.async { TimeChecker.logInterval("Running \(eventName) in Background queue \(queue)") - task.run() + task.run(nil) } return } @@ -240,7 +240,7 @@ class DefaultSplitEventsManager: SplitEventsManager { DispatchQueue.main.async { TimeChecker.logInterval("Running event on main: \(eventName)") // UI Updates - task.run() + task.run(nil) } } From 2df6b41ab0f3386ec35ad2e12b32fba8b9afa26c Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Tue, 10 Jun 2025 13:55:03 -0300 Subject: [PATCH 08/62] Executing with metadata --- Split/Events/SplitEventsManager.swift | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/Split/Events/SplitEventsManager.swift b/Split/Events/SplitEventsManager.swift index e3e2de877..ce99899d0 100644 --- a/Split/Events/SplitEventsManager.swift +++ b/Split/Events/SplitEventsManager.swift @@ -8,11 +8,12 @@ import Foundation -//TODO: Adapt for new events - protocol SplitEventsManager: AnyObject { func register(event: SplitEvent, task: SplitEventTask) + func register(event: SplitEvent, task: SplitEventActionTask) + func register(event: SplitEventWithMetadata, task: SplitEventActionTask) func notifyInternalEvent(_ event: SplitInternalEvent) + func notifyInternalEvent(_ event: SplitInternalEvent, metadata: EventMetadata?) func start() func stop() func eventAlreadyTriggered(event: SplitEvent) -> Bool @@ -25,7 +26,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 @@ -37,7 +38,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() @@ -60,8 +61,8 @@ class DefaultSplitEventsManager: SplitEventsManager { } } - func register(event: SplitEvent, task: SplitEventTask) { - let eventName = event.toString() + func register(event: SplitEventWithMetadata, task: SplitEventActionTask) { + let eventName = event.type.toString() processQueue.async { [weak self] in guard let self = self else { return } // If event is already triggered, execute the task @@ -69,9 +70,14 @@ class DefaultSplitEventsManager: SplitEventsManager { self.executeTask(event: event, task: task) return } - self.subscribe(task: task, to: event) + self.subscribe(task: task, to: event.type) } } + + // Method kept for backwards compatibility. Allows registering an event without metadata. + func register(event: SplitEvent, task: SplitEventActionTask) { + register(event: SplitEventWithMetadata(type: event, metadata: nil), task: task) + } func start() { dataAccessQueue.sync { @@ -224,7 +230,7 @@ class DefaultSplitEventsManager: SplitEventsManager { } } - private func executeTask(event: SplitEvent, task: SplitEventTask) { + private func executeTask(event: SplitEventWithMetadata, task: SplitEventTask) { let eventName = task.event.toString() @@ -234,7 +240,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 } @@ -242,7 +248,7 @@ class DefaultSplitEventsManager: SplitEventsManager { DispatchQueue.main.async { TimeChecker.logInterval("Running event on main: \(eventName)") // UI Updates - task.run() + task.run(event.metadata) } } From d030345c7cc5234fee51c47682d30d9f9ee2ba12 Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Tue, 10 Jun 2025 14:33:37 -0300 Subject: [PATCH 09/62] Tests updated. Minimum classes required updated too --- .../Collections/BlockingQueueTest.swift | 20 ++++---- .../SplitEventsManagerCoordinatorStub.swift | 6 ++- SplitTests/Fake/SplitEventsManagerStub.swift | 16 +++++- SplitTests/SplitEventsManagerTest.swift | 49 ++++++++++--------- 4 files changed, 54 insertions(+), 37 deletions(-) diff --git a/SplitTests/Collections/BlockingQueueTest.swift b/SplitTests/Collections/BlockingQueueTest.swift index 04c4b5df3..a38051a20 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 { } } diff --git a/SplitTests/Fake/Service/SplitEventsManagerCoordinatorStub.swift b/SplitTests/Fake/Service/SplitEventsManagerCoordinatorStub.swift index 94cbb8991..26e0d03f7 100644 --- a/SplitTests/Fake/Service/SplitEventsManagerCoordinatorStub.swift +++ b/SplitTests/Fake/Service/SplitEventsManagerCoordinatorStub.swift @@ -22,9 +22,13 @@ class SplitEventsManagerCoordinatorStub: SplitEventsManagerCoordinator { managers[key] = nil } - func register(event: SplitEvent, task: SplitEventTask) { + func register(event: SplitEvent, task: SplitEventActionTask) { } + + func register(event: SplitEventWithMetadata, task: SplitEventActionTask) {} + + func notifyInternalEvent(_ event: SplitInternalEvent, metadata: EventMetadata?) {} var notifiedEvents = Set() func notifyInternalEvent(_ event: SplitInternalEvent) { diff --git a/SplitTests/Fake/SplitEventsManagerStub.swift b/SplitTests/Fake/SplitEventsManagerStub.swift index 1d7685dc3..df390d772 100644 --- a/SplitTests/Fake/SplitEventsManagerStub.swift +++ b/SplitTests/Fake/SplitEventsManagerStub.swift @@ -15,11 +15,19 @@ class SplitEventsManagerStub: SplitEventsManager { var splitsKilledEventFiredCount = 0 var splitsUpdatedEventFiredCount = 0 var mySegmentsLoadedEventFiredCount = 0 + var metadata: EventMetadata? var mySegmentsLoadedEventExp: XCTestExpectation? var startCalled = false var stopCalled = false func notifyInternalEvent(_ event: SplitInternalEvent) { + notifyInternalEvent(event, metadata: nil) + } + + func notifyInternalEvent(_ event: SplitInternalEvent, metadata: EventMetadata? = nil) { + + self.metadata = metadata + switch event { case .mySegmentsLoadedFromCache: mySegmentsLoadedEventFiredCount+=1 @@ -39,8 +47,12 @@ class SplitEventsManagerStub: SplitEventsManager { } } - var registeredEvents = [SplitEvent: SplitEventTask]() - func register(event: SplitEvent, task: SplitEventTask) { + var registeredEvents = [SplitEventWithMetadata: SplitEventActionTask]() + func register(event: SplitEvent, task: SplitEventActionTask) { + register(event: SplitEventWithMetadata(type: event), task: task) + } + + func register(event: SplitEventWithMetadata, task: SplitEventActionTask) { registeredEvents[event] = task } diff --git a/SplitTests/SplitEventsManagerTest.swift b/SplitTests/SplitEventsManagerTest.swift index 1563e3b11..2e5b99d23 100644 --- a/SplitTests/SplitEventsManagerTest.swift +++ b/SplitTests/SplitEventsManagerTest.swift @@ -315,6 +315,26 @@ class SplitEventsManagerTest: XCTestCase { eventManager.stop() } + func testSplitEventActionTaskMetadata() { + + // Dummy event with metadata + let metadataTypeToCheck: EventMetadataType = .FLAGS_KILLED + let metadataDataToCheck: String = "Test-flag-42" + let dummyMetadata = EventMetadata(type: metadataTypeToCheck, data: metadataDataToCheck) + + // This will be the task's "run()" + let action: SplitActionWithMetadata = { metadata in + XCTAssertEqual(metadataTypeToCheck, metadata!.type) + XCTAssertEqual(metadataDataToCheck,metadata!.data) + } + + // SUT + let SUT = TestTask(exp: nil, action: action, metadata: dummyMetadata) + + SUT.run(dummyMetadata) + XCTAssertTrue(SUT.taskTriggered) + } + // MARK: Helpers func currentTimestamp() -> Int { return Int(Date().unixTimestamp()) @@ -325,40 +345,21 @@ class SplitEventsManagerTest: XCTestCase { } } -class TestTask: SplitEventTask { - - var event: SplitEvent = .sdkReady - - var runInBackground: Bool = false - - var queue: DispatchQueue? - - var metadata: EventMetadata? = nil +class TestTask: SplitEventActionTask { 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 - } - - func takeQueue() -> DispatchQueue? { - return nil - } - - func run() { - print("run: \(self.label)") - taskTriggered = true - if let exp = self.exp { - exp.fulfill() - } + super.init(action: action ?? { _ in }, event: .sdkReady, factory: SplitFactoryStub(apiKey: IntegrationHelper.dummyApiKey)) } - func run(_ metadata: EventMetadata) { + override func run(_ metadata: EventMetadata?) { print("run: \(self.label)") - self.metadata = metadata taskTriggered = true + super.run(metadata) if let exp = self.exp { exp.fulfill() } From e6964ece34bcf4a711df72fd7ae0ad84d92e491e Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Tue, 10 Jun 2025 18:05:46 -0300 Subject: [PATCH 10/62] Clients updated --- Split/Api/FailHelpers.swift | 2 ++ Split/Api/LocalhostSplitClient.swift | 32 +++++++++++++++---- Split/Api/SplitClient.swift | 3 +- SplitTests/Fake/InternalSplitClientStub.swift | 3 ++ SplitTests/Fake/SplitClientStub.swift | 3 ++ 5 files changed, 34 insertions(+), 9 deletions(-) diff --git a/Split/Api/FailHelpers.swift b/Split/Api/FailHelpers.swift index 6f90ba7b7..5a9346ece 100644 --- a/Split/Api/FailHelpers.swift +++ b/Split/Api/FailHelpers.swift @@ -64,6 +64,8 @@ class FailedClient: SplitClient { func on(event: SplitEvent, queue: DispatchQueue, execute action: @escaping SplitAction) { } + + func on(event: SplitEvent, executeWithMetadata: SplitActionWithMetadata) {} func track(trafficType: String, eventType: String) -> Bool { return false diff --git a/Split/Api/LocalhostSplitClient.swift b/Split/Api/LocalhostSplitClient.swift index 2aaacca6b..a2a3f6f6c 100644 --- a/Split/Api/LocalhostSplitClient.swift +++ b/Split/Api/LocalhostSplitClient.swift @@ -121,25 +121,43 @@ public final class LocalhostSplitClient: NSObject, SplitClient { return results } - public func on(event: SplitEvent, runInBackground: Bool, - execute action: @escaping SplitAction) { + public func on(event: SplitEvent, execute action: @escaping SplitAction) { + on(event: event, runInBackground: false, queue: nil, execute: action) + } + + public func on(event: SplitEvent, executeWithMetadata: @escaping SplitActionWithMetadata) { + on(eventWithMetadata: SplitEventWithMetadata(type: event, metadata: nil), runInBackground: false, queue: nil, execute: executeWithMetadata) + } + + public func on(event: SplitEvent, runInBackground: Bool, execute action: @escaping SplitAction) { on(event: event, runInBackground: runInBackground, queue: nil, execute: action) } + + private func on(event: SplitEvent, runInBackground: Bool, queue: DispatchQueue?, execute action: @escaping SplitAction) { + on(eventWithMetadata: SplitEventWithMetadata(type: event, metadata: nil), runInBackground: runInBackground, queue: queue, execute: action) + } public func on(event: SplitEvent, queue: DispatchQueue, execute action: @escaping SplitAction) { on(event: event, runInBackground: true, queue: queue, execute: action) } - public func on(event: SplitEvent, execute action: @escaping SplitAction) { - on(event: event, runInBackground: false, queue: nil, execute: action) + private func on(eventWithMetadata event: SplitEventWithMetadata, runInBackground: Bool, queue: DispatchQueue?, execute action: @escaping SplitAction) { + + guard let factory = clientManger?.splitFactory else { return } + if let eventsManager = self.eventsManager { + let task = SplitEventActionTask(action: action, event: event.type, + runInBackground: runInBackground, + factory: factory, + queue: queue) + eventsManager.register(event: event, task: task) + } } - private func on(event: SplitEvent, runInBackground: Bool, - queue: DispatchQueue?, execute action: @escaping SplitAction) { + private func on(eventWithMetadata event: SplitEventWithMetadata, runInBackground: Bool, queue: DispatchQueue?, execute action: @escaping SplitActionWithMetadata) { guard let factory = clientManger?.splitFactory else { return } if let eventsManager = self.eventsManager { - let task = SplitEventActionTask(action: action, event: event, + let task = SplitEventActionTask(action: action, event: event.type, runInBackground: runInBackground, factory: factory, queue: queue) diff --git a/Split/Api/SplitClient.swift b/Split/Api/SplitClient.swift index 91180b83a..3aff37add 100644 --- a/Split/Api/SplitClient.swift +++ b/Split/Api/SplitClient.swift @@ -9,8 +9,6 @@ import Foundation @objc public protocol SplitClient { - - // TODO: Add new channels // MARK: Evaluation feature func getTreatment(_ split: String, attributes: [String: Any]?) -> String @@ -34,6 +32,7 @@ import Foundation 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) -> Void func on(event: SplitEvent, runInBackground: Bool, execute action: @escaping SplitAction) func on(event: SplitEvent, queue: DispatchQueue, execute action: @escaping SplitAction) diff --git a/SplitTests/Fake/InternalSplitClientStub.swift b/SplitTests/Fake/InternalSplitClientStub.swift index 5b792fb3e..2d54d6e3a 100644 --- a/SplitTests/Fake/InternalSplitClientStub.swift +++ b/SplitTests/Fake/InternalSplitClientStub.swift @@ -103,6 +103,9 @@ 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/SplitClientStub.swift b/SplitTests/Fake/SplitClientStub.swift index a19acdcb7..5927d9a06 100644 --- a/SplitTests/Fake/SplitClientStub.swift +++ b/SplitTests/Fake/SplitClientStub.swift @@ -95,6 +95,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 } From a767914885fa4a7c06f94a86338cb828b5223372 Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Wed, 11 Jun 2025 12:09:21 -0300 Subject: [PATCH 11/62] Update Split/Api/FailHelpers.swift Co-authored-by: gthea --- Split/Api/FailHelpers.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Split/Api/FailHelpers.swift b/Split/Api/FailHelpers.swift index 5a9346ece..fc6eae22c 100644 --- a/Split/Api/FailHelpers.swift +++ b/Split/Api/FailHelpers.swift @@ -65,7 +65,7 @@ class FailedClient: SplitClient { queue: DispatchQueue, execute action: @escaping SplitAction) { } - func on(event: SplitEvent, executeWithMetadata: SplitActionWithMetadata) {} + func on(event: SplitEvent, executeWithMetadata: @escaping SplitActionWithMetadata) {} func track(trafficType: String, eventType: String) -> Bool { return false From e73495ac6ad66961c4cf6342c918afbd951d130f Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Wed, 11 Jun 2025 14:19:30 -0300 Subject: [PATCH 12/62] Removed unnecessary method --- Split/Events/SplitEvent.swift | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Split/Events/SplitEvent.swift b/Split/Events/SplitEvent.swift index 41229d90b..55596e428 100644 --- a/Split/Events/SplitEvent.swift +++ b/Split/Events/SplitEvent.swift @@ -13,11 +13,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 { From 4db5ec724eb1a9b04df259c1eeb7e5718377d673 Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Wed, 11 Jun 2025 14:22:59 -0300 Subject: [PATCH 13/62] Removed unnecessary method --- Split/Events/SplitInternalEvent.swift | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Split/Events/SplitInternalEvent.swift b/Split/Events/SplitInternalEvent.swift index a7530532d..3dda69a4d 100644 --- a/Split/Events/SplitInternalEvent.swift +++ b/Split/Events/SplitInternalEvent.swift @@ -10,10 +10,6 @@ struct SplitInternalEventWithMetadata { self.type = type self.metadata = metadata } - - static func == (lhs: SplitInternalEventWithMetadata, rhs: SplitInternalEventWithMetadata) -> Bool { - return lhs.type == rhs.type - } } enum SplitInternalEvent { From 34552993709dd04c5fe621cc6e6b3ba698d16cad Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Thu, 12 Jun 2025 10:24:59 -0300 Subject: [PATCH 14/62] Equatables added --- Split/Events/EventMetadataType.swift | 4 ++++ Split/Events/SplitEvent.swift | 8 ++++++++ Split/Events/SplitEventsManager.swift | 2 +- Split/Events/SplitInternalEvent.swift | 10 +++++++++- 4 files changed, 22 insertions(+), 2 deletions(-) diff --git a/Split/Events/EventMetadataType.swift b/Split/Events/EventMetadataType.swift index da441c028..7bc95b123 100644 --- a/Split/Events/EventMetadataType.swift +++ b/Split/Events/EventMetadataType.swift @@ -10,6 +10,10 @@ import Foundation self.type = type self.data = data } + + public static func == (lhs: EventMetadata, rhs: EventMetadata) -> Bool { + return lhs.type == rhs.type && lhs.data == rhs.data + } } @objc enum EventMetadataType: Int { diff --git a/Split/Events/SplitEvent.swift b/Split/Events/SplitEvent.swift index 55596e428..5a3f5f345 100644 --- a/Split/Events/SplitEvent.swift +++ b/Split/Events/SplitEvent.swift @@ -13,6 +13,14 @@ import Foundation self.type = type self.metadata = metadata } + + public static func == (lhs: SplitEventWithMetadata, rhs: SplitEventWithMetadata) -> Bool { + return lhs.type == rhs.type && lhs.metadata == rhs.metadata + } + + func isSameType(_ other: SplitEventWithMetadata) -> Bool { + return self.type == other.type + } } @objc public enum SplitEvent: Int { diff --git a/Split/Events/SplitEventsManager.swift b/Split/Events/SplitEventsManager.swift index d55a9df0d..3662dde9f 100644 --- a/Split/Events/SplitEventsManager.swift +++ b/Split/Events/SplitEventsManager.swift @@ -262,7 +262,7 @@ class DefaultSplitEventsManager: SplitEventsManager { } private func isTriggered(internal event: SplitInternalEventWithMetadata) -> Bool { - return triggered.filter { $0 == event }.count > 0 + return triggered.filter { event.isSameType($0) }.count > 0 } private func isTriggered(internal event: SplitInternalEvent) -> Bool { diff --git a/Split/Events/SplitInternalEvent.swift b/Split/Events/SplitInternalEvent.swift index 3dda69a4d..f35c922ae 100644 --- a/Split/Events/SplitInternalEvent.swift +++ b/Split/Events/SplitInternalEvent.swift @@ -2,7 +2,7 @@ import Foundation -struct SplitInternalEventWithMetadata { +struct SplitInternalEventWithMetadata: Equatable { let type: SplitInternalEvent let metadata: EventMetadata? @@ -10,6 +10,14 @@ struct SplitInternalEventWithMetadata { self.type = type self.metadata = metadata } + + public static func == (lhs: SplitInternalEventWithMetadata, rhs: SplitInternalEventWithMetadata) -> Bool { + return lhs.type == rhs.type && lhs.metadata == rhs.metadata + } + + func isSameType(_ other: SplitInternalEventWithMetadata) -> Bool { + return self.type == other.type + } } enum SplitInternalEvent { From e5bd9274540e3d9e462782f24e4314d969d0098e Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Thu, 12 Jun 2025 10:49:31 -0300 Subject: [PATCH 15/62] Removed unnecessary comparations and methods --- Split/Events/SplitEvent.swift | 8 -------- Split/Events/SplitEventsManager.swift | 12 ++++-------- Split/Events/SplitInternalEvent.swift | 8 -------- 3 files changed, 4 insertions(+), 24 deletions(-) diff --git a/Split/Events/SplitEvent.swift b/Split/Events/SplitEvent.swift index 5a3f5f345..55596e428 100644 --- a/Split/Events/SplitEvent.swift +++ b/Split/Events/SplitEvent.swift @@ -13,14 +13,6 @@ import Foundation self.type = type self.metadata = metadata } - - public static func == (lhs: SplitEventWithMetadata, rhs: SplitEventWithMetadata) -> Bool { - return lhs.type == rhs.type && lhs.metadata == rhs.metadata - } - - func isSameType(_ other: SplitEventWithMetadata) -> Bool { - return self.type == other.type - } } @objc public enum SplitEvent: Int { diff --git a/Split/Events/SplitEventsManager.swift b/Split/Events/SplitEventsManager.swift index 3662dde9f..1b21eba83 100644 --- a/Split/Events/SplitEventsManager.swift +++ b/Split/Events/SplitEventsManager.swift @@ -25,7 +25,7 @@ class DefaultSplitEventsManager: SplitEventsManager { private var subscriptions = [SplitEvent: [SplitEventTask]]() private var executionTimes: [String: Int] - private var triggered: [SplitInternalEventWithMetadata] + private var triggered: [SplitInternalEvent] private let processQueue: DispatchQueue private let dataAccessQueue: DispatchQueue private var isStarted: Bool @@ -37,7 +37,7 @@ class DefaultSplitEventsManager: SplitEventsManager { self.isStarted = false self.sdkReadyTimeStart = Date().unixTimestampInMiliseconds() self.readingRefreshTime = 300 - self.triggered = [SplitInternalEventWithMetadata]() + self.triggered = [SplitInternalEvent]() self.eventsQueue = DefaultInternalEventBlockingQueue() self.executionTimes = [String: Int]() registerMaxAllowedExecutionTimesPerEvent() @@ -157,7 +157,7 @@ class DefaultSplitEventsManager: SplitEventsManager { guard let event = takeEvent() else { return } - self.triggered.append(event) + self.triggered.append(event.type) switch event.type { case .splitsUpdated, .mySegmentsUpdated, .myLargeSegmentsUpdated: if isTriggered(external: .sdkReady) { @@ -260,13 +260,9 @@ class DefaultSplitEventsManager: SplitEventsManager { task.run(event.metadata) } } - - private func isTriggered(internal event: SplitInternalEventWithMetadata) -> Bool { - return triggered.filter { event.isSameType($0) }.count > 0 - } private func isTriggered(internal event: SplitInternalEvent) -> Bool { - return isTriggered(internal: SplitInternalEventWithMetadata(event, metadata: nil)) + return triggered.filter { $0 == event }.count > 0 } // MARK: Safe Data Access diff --git a/Split/Events/SplitInternalEvent.swift b/Split/Events/SplitInternalEvent.swift index f35c922ae..573a39c4f 100644 --- a/Split/Events/SplitInternalEvent.swift +++ b/Split/Events/SplitInternalEvent.swift @@ -10,14 +10,6 @@ struct SplitInternalEventWithMetadata: Equatable { self.type = type self.metadata = metadata } - - public static func == (lhs: SplitInternalEventWithMetadata, rhs: SplitInternalEventWithMetadata) -> Bool { - return lhs.type == rhs.type && lhs.metadata == rhs.metadata - } - - func isSameType(_ other: SplitInternalEventWithMetadata) -> Bool { - return self.type == other.type - } } enum SplitInternalEvent { From cfa37ef66c0a4ca27cef43a6790d8659b190574e Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Thu, 12 Jun 2025 11:14:21 -0300 Subject: [PATCH 16/62] Cleanup --- Split/Events/EventMetadataType.swift | 4 ---- Split/Events/EventsManagerCoordinator.swift | 9 +++++++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Split/Events/EventMetadataType.swift b/Split/Events/EventMetadataType.swift index 7bc95b123..da441c028 100644 --- a/Split/Events/EventMetadataType.swift +++ b/Split/Events/EventMetadataType.swift @@ -10,10 +10,6 @@ import Foundation self.type = type self.data = data } - - public static func == (lhs: EventMetadata, rhs: EventMetadata) -> Bool { - return lhs.type == rhs.type && lhs.data == rhs.data - } } @objc enum EventMetadataType: Int { diff --git a/Split/Events/EventsManagerCoordinator.swift b/Split/Events/EventsManagerCoordinator.swift index 44f910596..f08fccc64 100644 --- a/Split/Events/EventsManagerCoordinator.swift +++ b/Split/Events/EventsManagerCoordinator.swift @@ -80,6 +80,11 @@ class MainSplitEventsManager: SplitEventsManagerCoordinator { } } - func register(event: SplitEvent, task: SplitEventActionTask) {} - func register(event: SplitEventWithMetadata, task: SplitEventActionTask) {} + func register(event: SplitEvent, task: SplitEventActionTask) { + // Intentionally empty + } + + func register(event: SplitEventWithMetadata, task: SplitEventActionTask) { + // Intentionally empty + } } From f0bbdb0a401436a295fab61d0bd0cbaa320d0dc2 Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Thu, 12 Jun 2025 11:54:34 -0300 Subject: [PATCH 17/62] Cleanup --- Split/Events/EventsManagerCoordinator.swift | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Split/Events/EventsManagerCoordinator.swift b/Split/Events/EventsManagerCoordinator.swift index f08fccc64..6d5ce9a54 100644 --- a/Split/Events/EventsManagerCoordinator.swift +++ b/Split/Events/EventsManagerCoordinator.swift @@ -80,11 +80,7 @@ class MainSplitEventsManager: SplitEventsManagerCoordinator { } } - func register(event: SplitEvent, task: SplitEventActionTask) { - // Intentionally empty - } + func register(event: SplitEvent, task: SplitEventActionTask) {} - func register(event: SplitEventWithMetadata, task: SplitEventActionTask) { - // Intentionally empty - } + func register(event: SplitEventWithMetadata, task: SplitEventActionTask) {} } From c67bcd632066c7843fe506b731aa7f79382e0792 Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Thu, 12 Jun 2025 12:48:03 -0300 Subject: [PATCH 18/62] Added logic to show updated flags. Tests pending. --- Split/Localhost/LocalhostSynchronizer.swift | 6 ++++-- Split/Network/Streaming/SyncUpdateWorker.swift | 18 +++++++++++------- .../Sync/FeatureFlagsSynchronizer.swift | 7 ++++--- Split/Network/Sync/Synchronizer.swift | 6 +++--- 4 files changed, 22 insertions(+), 15 deletions(-) diff --git a/Split/Localhost/LocalhostSynchronizer.swift b/Split/Localhost/LocalhostSynchronizer.swift index 0081bf272..714a65281 100644 --- a/Split/Localhost/LocalhostSynchronizer.swift +++ b/Split/Localhost/LocalhostSynchronizer.swift @@ -44,7 +44,7 @@ class LocalhostSynchronizer: FeatureFlagsSynchronizer { func notifyKilled() { } - func notifyUpdated() { + func notifyUpdated(flagsList: [String]) { } func pause() { @@ -74,7 +74,9 @@ class LocalhostSynchronizer: FeatureFlagsSynchronizer { // Update will remove all records before insert new ones _ = self.featureFlagsStorage.update(splitChange: change) - self.eventsManager.notifyInternalEvent(.splitsUpdated) + // Notify event + let metadata = EventMetadata(type: .FLAGS_UPDATED, data: featureFlags.values.description) + self.eventsManager.notifyInternalEvent(.splitsUpdated, metadata: metadata) } } } diff --git a/Split/Network/Streaming/SyncUpdateWorker.swift b/Split/Network/Streaming/SyncUpdateWorker.swift index 5aede0758..112724640 100644 --- a/Split/Network/Streaming/SyncUpdateWorker.swift +++ b/Split/Network/Streaming/SyncUpdateWorker.swift @@ -143,8 +143,12 @@ class SplitsUpdateWorker: UpdateWorker { Logger.v("Split update received: \(change)") - if self.splitsStorage.update(splitChange: self.splitChangeProcessor.process(change)) { - self.synchronizer.notifyFeatureFlagsUpdated() + let processedFlags = self.splitChangeProcessor.process(change) + + if self.splitsStorage.update(splitChange: processedFlags) { + var updatedFlags: [String] = processedFlags.activeSplits.compactMap(\.name) + updatedFlags += processedFlags.archivedSplits.compactMap(\.name) + self.synchronizer.notifyFeatureFlagsUpdated(flagsList: updatedFlags) } self.telemetryProducer?.recordUpdatesFromSse(type: .splits) @@ -161,9 +165,9 @@ class SplitsUpdateWorker: UpdateWorker { previousChangeNumber: Int64, changeNumber: Int64) -> Bool { do { - let rbs = try self.ruleBasedSegmentsPayloadDecoder.decode( + let rbs = try ruleBasedSegmentsPayloadDecoder.decode( payload: payload, - compressionUtil: self.decomProvider.decompressor(for: compressionType)) + compressionUtil: decomProvider.decompressor(for: compressionType)) let change = RuleBasedSegmentChange(segments: [rbs], since: previousChangeNumber, @@ -173,13 +177,13 @@ class SplitsUpdateWorker: UpdateWorker { let processedChange = ruleBasedSegmentsChangeProcessor.process(change) - if self.ruleBasedSegmentsStorage.update(toAdd: processedChange.toAdd, + if ruleBasedSegmentsStorage.update(toAdd: processedChange.toAdd, toRemove: processedChange.toRemove, changeNumber: processedChange.changeNumber) { - self.synchronizer.notifyFeatureFlagsUpdated() + synchronizer.notifyFeatureFlagsUpdated(flagsList: []) //TODO: Notify segments updated (new notification method?) } - self.telemetryProducer?.recordUpdatesFromSse(type: .splits) + telemetryProducer?.recordUpdatesFromSse(type: .splits) return true } catch { Logger.e("Error decoding rule based segments payload from notification: \(error)") diff --git a/Split/Network/Sync/FeatureFlagsSynchronizer.swift b/Split/Network/Sync/FeatureFlagsSynchronizer.swift index 5434c865a..314dc9ade 100644 --- a/Split/Network/Sync/FeatureFlagsSynchronizer.swift +++ b/Split/Network/Sync/FeatureFlagsSynchronizer.swift @@ -15,7 +15,7 @@ protocol FeatureFlagsSynchronizer { func startPeriodicSync() func stopPeriodicSync() func notifyKilled() - func notifyUpdated() + func notifyUpdated(flagsList: [String]) func pause() func resume() func destroy() @@ -148,8 +148,9 @@ class DefaultFeatureFlagsSynchronizer: FeatureFlagsSynchronizer { splitEventsManager.notifyInternalEvent(.splitKilledNotification) } - func notifyUpdated() { - splitEventsManager.notifyInternalEvent(.splitsUpdated) + func notifyUpdated(flagsList: [String]) { + let eventMetadata = EventMetadata(type: .FLAGS_UPDATED, data: flagsList.description) + splitEventsManager.notifyInternalEvent(.splitsUpdated, metadata: eventMetadata) } func pause() { diff --git a/Split/Network/Sync/Synchronizer.swift b/Split/Network/Sync/Synchronizer.swift index d3155a193..ec789a033 100644 --- a/Split/Network/Sync/Synchronizer.swift +++ b/Split/Network/Sync/Synchronizer.swift @@ -30,7 +30,7 @@ protocol Synchronizer: ImpressionLogger { func startRecordingTelemetry() func stopRecordingTelemetry() func pushEvent(event: EventDTO) - func notifyFeatureFlagsUpdated() + func notifyFeatureFlagsUpdated(flagsList: [String]) func notifySegmentsUpdated(forKey key: String) func notifyLargeSegmentsUpdated(forKey key: String) func notifySplitKilled() @@ -207,8 +207,8 @@ class DefaultSynchronizer: Synchronizer { } } - func notifyFeatureFlagsUpdated() { - featureFlagsSynchronizer.notifyUpdated() + func notifyFeatureFlagsUpdated(flagsList: [String]) { + featureFlagsSynchronizer.notifyUpdated(flagsList: flagsList) } func notifySegmentsUpdated(forKey key: String) { From 932ac8ac24f7b4047bff606a56fd3d1b3b4fda73 Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Thu, 12 Jun 2025 13:37:29 -0300 Subject: [PATCH 19/62] Tests updated --- .../Fake/Streaming/FeatureFlagsSynchronizerStub.swift | 3 ++- SplitTests/Fake/Streaming/SynchronizerSpy.swift | 6 ++++-- SplitTests/Fake/Streaming/SynchronizerStub.swift | 4 +++- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/SplitTests/Fake/Streaming/FeatureFlagsSynchronizerStub.swift b/SplitTests/Fake/Streaming/FeatureFlagsSynchronizerStub.swift index 8e01f5f8e..b96f9b087 100644 --- a/SplitTests/Fake/Streaming/FeatureFlagsSynchronizerStub.swift +++ b/SplitTests/Fake/Streaming/FeatureFlagsSynchronizerStub.swift @@ -40,7 +40,8 @@ class FeatureFlagsSynchronizerStub: FeatureFlagsSynchronizer { } var notifyUpdatedCalled = false - func notifyUpdated() { + var updatedFlagsList = [String]() + func notifyUpdated(flagsList: [String]) { notifyUpdatedCalled = true } diff --git a/SplitTests/Fake/Streaming/SynchronizerSpy.swift b/SplitTests/Fake/Streaming/SynchronizerSpy.swift index fb6e59353..0716a5936 100644 --- a/SplitTests/Fake/Streaming/SynchronizerSpy.swift +++ b/SplitTests/Fake/Streaming/SynchronizerSpy.swift @@ -206,10 +206,12 @@ class SynchronizerSpy: Synchronizer { } var notifyFeatureFlagsUpdatedCalled = false - func notifyFeatureFlagsUpdated() { + var updatedFlagsList = [String]() + func notifyFeatureFlagsUpdated(flagsList: [String]) { notifyFeatureFlagsUpdatedCalled = true + updatedFlagsList = flagsList } - + func notifySplitKilled() { notifySplitKilledCalled = true splitSynchronizer.notifySplitKilled() diff --git a/SplitTests/Fake/Streaming/SynchronizerStub.swift b/SplitTests/Fake/Streaming/SynchronizerStub.swift index 4bcdba666..d4c96f1f6 100644 --- a/SplitTests/Fake/Streaming/SynchronizerStub.swift +++ b/SplitTests/Fake/Streaming/SynchronizerStub.swift @@ -237,8 +237,10 @@ class SynchronizerStub: Synchronizer { } var notifyFeatureFlagsUpdatedCalled = true - func notifyFeatureFlagsUpdated() { + var updatedFlagsList = [String]() + func notifyFeatureFlagsUpdated(flagsList: [String]) { notifyFeatureFlagsUpdatedCalled = true + updatedFlagsList = flagsList } func notifySplitKilled() { From 06b39fe77b157f196e47639cc29fc3a203435b61 Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Thu, 12 Jun 2025 13:49:06 -0300 Subject: [PATCH 20/62] Cleanup --- SplitTests/Fake/Streaming/FeatureFlagsSynchronizerStub.swift | 1 + SplitTests/Fake/Streaming/SynchronizerSpy.swift | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/SplitTests/Fake/Streaming/FeatureFlagsSynchronizerStub.swift b/SplitTests/Fake/Streaming/FeatureFlagsSynchronizerStub.swift index b96f9b087..7059a1815 100644 --- a/SplitTests/Fake/Streaming/FeatureFlagsSynchronizerStub.swift +++ b/SplitTests/Fake/Streaming/FeatureFlagsSynchronizerStub.swift @@ -43,6 +43,7 @@ class FeatureFlagsSynchronizerStub: FeatureFlagsSynchronizer { var updatedFlagsList = [String]() func notifyUpdated(flagsList: [String]) { notifyUpdatedCalled = true + updatedFlagsList = flagsList } var pauseCalled = false diff --git a/SplitTests/Fake/Streaming/SynchronizerSpy.swift b/SplitTests/Fake/Streaming/SynchronizerSpy.swift index 0716a5936..859b79ddc 100644 --- a/SplitTests/Fake/Streaming/SynchronizerSpy.swift +++ b/SplitTests/Fake/Streaming/SynchronizerSpy.swift @@ -211,7 +211,7 @@ class SynchronizerSpy: Synchronizer { notifyFeatureFlagsUpdatedCalled = true updatedFlagsList = flagsList } - + func notifySplitKilled() { notifySplitKilledCalled = true splitSynchronizer.notifySplitKilled() From d1a3d93fb7abe137d31320cdd58ebfc53aa04b0f Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Thu, 12 Jun 2025 15:28:15 -0300 Subject: [PATCH 21/62] Initial commit --- Split/Network/Streaming/SyncUpdateWorker.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Split/Network/Streaming/SyncUpdateWorker.swift b/Split/Network/Streaming/SyncUpdateWorker.swift index 112724640..35c5482e5 100644 --- a/Split/Network/Streaming/SyncUpdateWorker.swift +++ b/Split/Network/Streaming/SyncUpdateWorker.swift @@ -180,7 +180,7 @@ class SplitsUpdateWorker: UpdateWorker { if ruleBasedSegmentsStorage.update(toAdd: processedChange.toAdd, toRemove: processedChange.toRemove, changeNumber: processedChange.changeNumber) { - synchronizer.notifyFeatureFlagsUpdated(flagsList: []) //TODO: Notify segments updated (new notification method?) + synchronizer.notifyFeatureFlagsUpdated(flagsList: []) //TODO: Make new notify segments updated (new notification method?) } telemetryProducer?.recordUpdatesFromSse(type: .splits) From 582192d4d65ec90db504bd56c34485fef8612fb4 Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Sat, 14 Jun 2025 21:04:38 -0300 Subject: [PATCH 22/62] Marks --- Split/Events/SplitEventsManager.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Split/Events/SplitEventsManager.swift b/Split/Events/SplitEventsManager.swift index 1b21eba83..6581eaa8d 100644 --- a/Split/Events/SplitEventsManager.swift +++ b/Split/Events/SplitEventsManager.swift @@ -51,6 +51,7 @@ class DefaultSplitEventsManager: SplitEventsManager { } } + // MARK: Notifiers func notifyInternalEvent(_ event: SplitInternalEvent, metadata: EventMetadata? = nil) { let event = SplitInternalEventWithMetadata(event, metadata: metadata) @@ -65,7 +66,8 @@ class DefaultSplitEventsManager: SplitEventsManager { func notifyInternalEvent(_ event: SplitInternalEvent) { notifyInternalEvent(event, metadata: nil) } - + + // MARK: Registers func register(event: SplitEventWithMetadata, task: SplitEventActionTask) { let eventName = event.type.toString() processQueue.async { [weak self] in @@ -84,6 +86,7 @@ class DefaultSplitEventsManager: SplitEventsManager { register(event: SplitEventWithMetadata(type: event, metadata: nil), task: task) } + // MARK: Flow func start() { dataAccessQueue.sync { if self.isStarted { From f181aeb90d8d4c32d73e203125eeffd76dc6c251 Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Mon, 16 Jun 2025 17:23:25 -0300 Subject: [PATCH 23/62] Changing branch --- Split/Network/Streaming/SyncUpdateWorker.swift | 12 +++++++----- Split/Network/Sync/Synchronizer.swift | 6 ++++++ 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/Split/Network/Streaming/SyncUpdateWorker.swift b/Split/Network/Streaming/SyncUpdateWorker.swift index 35c5482e5..fac6f5a7d 100644 --- a/Split/Network/Streaming/SyncUpdateWorker.swift +++ b/Split/Network/Streaming/SyncUpdateWorker.swift @@ -175,14 +175,16 @@ class SplitsUpdateWorker: UpdateWorker { Logger.v("RBS update received: \(change)") - let processedChange = ruleBasedSegmentsChangeProcessor.process(change) + let processedSegments = ruleBasedSegmentsChangeProcessor.process(change) - if ruleBasedSegmentsStorage.update(toAdd: processedChange.toAdd, - toRemove: processedChange.toRemove, - changeNumber: processedChange.changeNumber) { + if ruleBasedSegmentsStorage.update(toAdd: processedSegments.toAdd, + toRemove: processedSegments.toRemove, + changeNumber: processedSegments.changeNumber) { + var updatedSegments: [String] = processedSegments.activeSegments.compactMap(\.name) + updatedSegments += processedSegments.archivedSegments.compactMap(\.name) synchronizer.notifyFeatureFlagsUpdated(flagsList: []) //TODO: Make new notify segments updated (new notification method?) } - + telemetryProducer?.recordUpdatesFromSse(type: .splits) return true } catch { diff --git a/Split/Network/Sync/Synchronizer.swift b/Split/Network/Sync/Synchronizer.swift index ec789a033..986fee0e9 100644 --- a/Split/Network/Sync/Synchronizer.swift +++ b/Split/Network/Sync/Synchronizer.swift @@ -31,6 +31,7 @@ protocol Synchronizer: ImpressionLogger { func stopRecordingTelemetry() func pushEvent(event: EventDTO) func notifyFeatureFlagsUpdated(flagsList: [String]) + func notifySegmentsUpdated(segmentsList: [String]) func notifySegmentsUpdated(forKey key: String) func notifyLargeSegmentsUpdated(forKey key: String) func notifySplitKilled() @@ -210,6 +211,11 @@ class DefaultSynchronizer: Synchronizer { func notifyFeatureFlagsUpdated(flagsList: [String]) { featureFlagsSynchronizer.notifyUpdated(flagsList: flagsList) } + + func notifySegmentsUpdated(segmentsList: [String]) { + + byKeySynchronizer.notifyMySegmentsUpdated(forKey: key) + } func notifySegmentsUpdated(forKey key: String) { byKeySynchronizer.notifyMySegmentsUpdated(forKey: key) From 2e93d053a97894f1ca8d86d3d44ee7c8e670a651 Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Tue, 17 Jun 2025 12:09:36 -0300 Subject: [PATCH 24/62] Initial commit --- Split/Api/DefaultSplitClient.swift | 29 +++---- Split/Api/FailHelpers.swift | 50 +++++------ Split/Api/LocalhostSplitClient.swift | 82 +++++++++---------- Split/Api/SplitClient.swift | 50 +++++------ Split/Engine/DefaultTreatmentManager.swift | 4 +- Split/Engine/Evaluator.swift | 80 +++++++++--------- Split/Events/SplitEventsManager.swift | 4 +- Split/Network/Sync/ByKeyFacade.swift | 4 +- .../Sync/FeatureFlagsSynchronizer.swift | 2 +- SplitTests/Fake/InternalSplitClientStub.swift | 53 +++++------- SplitTests/Fake/SplitClientStub.swift | 52 +++++------- 11 files changed, 177 insertions(+), 233 deletions(-) diff --git a/Split/Api/DefaultSplitClient.swift b/Split/Api/DefaultSplitClient.swift index fe328c932..3da3cadbd 100644 --- a/Split/Api/DefaultSplitClient.swift +++ b/Split/Api/DefaultSplitClient.swift @@ -1,11 +1,5 @@ -// -// DefaultSplitClient.swift -// Split -// // Created by Brian Sztamfater on 20/9/17. // Modified by Natalia Stele on 11/10/17. -// -// import Foundation @@ -55,7 +49,7 @@ public final class DefaultSplitClient: NSObject, SplitClient, TelemetrySplitClie } } -// MARK: Events +// MARK: Customers Listeners extension DefaultSplitClient { public func on(event: SplitEvent, execute action: @escaping SplitAction) { @@ -173,7 +167,7 @@ extension DefaultSplitClient { } } -// MARK: Track Events +// MARK: Tracking extension DefaultSplitClient { public func track(trafficType: String, eventType: String) -> Bool { @@ -223,7 +217,7 @@ extension DefaultSplitClient { } } -// MARK: Persistent attributes feature +// MARK: Persistence extension DefaultSplitClient { public func setAttribute(name: String, value: Any) -> Bool { @@ -265,8 +259,7 @@ extension DefaultSplitClient { } private func isValidAttribute(_ value: Any) -> Bool { - return anyValueValidator.isPrimitiveValue(value: value) || - anyValueValidator.isList(value: value) + return anyValueValidator.isPrimitiveValue(value: value) || anyValueValidator.isList(value: value) } private func logInvalidAttribute(name: String) { @@ -275,31 +268,31 @@ extension DefaultSplitClient { } private func attributesStorage() -> AttributesStorage { - return storageContainer.attributesStorage + storageContainer.attributesStorage } } -// MARK: By Sets evaluation +// MARK: By Flagsets extension DefaultSplitClient { public func getTreatmentsByFlagSet(_ flagSet: String, attributes: [String: Any]?) -> [String: String] { - return treatmentManager.getTreatmentsByFlagSet(flagSet: flagSet, attributes: attributes, evaluationOptions: nil) + treatmentManager.getTreatmentsByFlagSet(flagSet: flagSet, attributes: attributes, evaluationOptions: nil) } public func getTreatmentsByFlagSets(_ flagSets: [String], attributes: [String: Any]?) -> [String: String] { - return treatmentManager.getTreatmentsByFlagSets(flagSets: flagSets, attributes: attributes, evaluationOptions: nil) + treatmentManager.getTreatmentsByFlagSets(flagSets: flagSets, attributes: attributes, evaluationOptions: nil) } public func getTreatmentsWithConfigByFlagSet(_ flagSet: String, attributes: [String: Any]?) -> [String: SplitResult] { - return treatmentManager.getTreatmentsWithConfigByFlagSet(flagSet: flagSet, attributes: attributes, evaluationOptions: nil) + treatmentManager.getTreatmentsWithConfigByFlagSet(flagSet: flagSet, attributes: attributes, evaluationOptions: nil) } public func getTreatmentsWithConfigByFlagSets(_ flagSets: [String], attributes: [String: Any]?) -> [String: SplitResult] { - return treatmentManager.getTreatmentsWithConfigByFlagSets(flagSets: flagSets, attributes: attributes, evaluationOptions: nil) + treatmentManager.getTreatmentsWithConfigByFlagSets(flagSets: flagSets, attributes: attributes, evaluationOptions: nil) } } -// MARK: Flush / Destroy +// MARK: Lifecycle extension DefaultSplitClient { private func syncFlush() { diff --git a/Split/Api/FailHelpers.swift b/Split/Api/FailHelpers.swift index fc6eae22c..95234cedf 100644 --- a/Split/Api/FailHelpers.swift +++ b/Split/Api/FailHelpers.swift @@ -1,10 +1,5 @@ -// -// FailHelpers.swift -// Split -// // Created by Javier Avrudsky on 24-Apr-2022. // Copyright © 2022 Split. All rights reserved. -// import Foundation @@ -57,42 +52,38 @@ class FailedClient: SplitClient { func on(event: SplitEvent, execute action: @escaping SplitAction) { } - func on(event: SplitEvent, runInBackground: Bool, - execute action: @escaping SplitAction) { - } + func on(event: SplitEvent, runInBackground: Bool, execute action: @escaping SplitAction) {} - func on(event: SplitEvent, - queue: DispatchQueue, execute action: @escaping SplitAction) { - } + 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 + false } func track(trafficType: String, eventType: String, value: Double) -> Bool { - return false + false } func track(eventType: String) -> Bool { - return false + false } func track(eventType: String, value: Double) -> Bool { - return false + false } func setAttribute(name: String, value: Any) -> Bool { - return false + false } func getAttribute(name: String) -> Any? { - return false + false } func setAttributes(_ values: [String: Any]) -> Bool { - return false + false } func getAttributes() -> [String: Any]? { @@ -100,11 +91,11 @@ class FailedClient: SplitClient { } func removeAttribute(name: String) -> Bool { - return false + false } func clearAttributes() -> Bool { - return false + false } func getTreatmentsByFlagSet(_ flagSet: String, attributes: [String: Any]?) -> [String: String] { @@ -139,33 +130,30 @@ class FailedClient: SplitClient { return [:] } - func setUserConsent(enabled: Bool) { - } + func setUserConsent(enabled: Bool) {} - func flush() { - } + func flush() {} - func destroy() { - } + func destroy() {} func destroy(completion: (() -> Void)?) { completion?() } func track(trafficType: String, eventType: String, properties: [String: Any]?) -> Bool { - return false + false } func track(trafficType: String, eventType: String, value: Double, properties: [String: Any]?) -> Bool { - return false + false } func track(eventType: String, properties: [String: Any]?) -> Bool { - return false + false } func track(eventType: String, value: Double, properties: [String: Any]?) -> Bool { - return false + false } } @@ -175,6 +163,6 @@ class FailedManager: SplitManager { var splitNames: [String] = [] func split(featureName: String) -> SplitView? { - return nil + nil } } diff --git a/Split/Api/LocalhostSplitClient.swift b/Split/Api/LocalhostSplitClient.swift index a2a3f6f6c..c2496d08f 100644 --- a/Split/Api/LocalhostSplitClient.swift +++ b/Split/Api/LocalhostSplitClient.swift @@ -65,27 +65,27 @@ public final class LocalhostSplitClient: NSObject, SplitClient { } public func getTreatment(_ split: String, attributes: [String: Any]?) -> String { - return getTreatmentWithConfig(split).treatment + getTreatmentWithConfig(split).treatment } public func getTreatment(_ split: String) -> String { - return getTreatment(split, attributes: nil) + getTreatment(split, attributes: nil) } public func getTreatment(_ split: String, attributes: [String: Any]?, evaluationOptions: EvaluationOptions?) -> String { - return getTreatmentWithConfig(split, attributes: attributes, evaluationOptions: evaluationOptions).treatment + getTreatmentWithConfig(split, attributes: attributes, evaluationOptions: evaluationOptions).treatment } public func getTreatments(splits: [String], attributes: [String: Any]?) -> [String: String] { - return getTreatmentsWithConfig(splits: splits, attributes: nil).mapValues({ $0.treatment }) + getTreatmentsWithConfig(splits: splits, attributes: nil).mapValues({ $0.treatment }) } public func getTreatments(splits: [String], attributes: [String: Any]?, evaluationOptions: EvaluationOptions?) -> [String: String] { - return getTreatmentsWithConfig(splits: splits, attributes: attributes, evaluationOptions: evaluationOptions).mapValues({ $0.treatment }) + getTreatmentsWithConfig(splits: splits, attributes: attributes, evaluationOptions: evaluationOptions).mapValues({ $0.treatment }) } public func getTreatmentWithConfig(_ split: String) -> SplitResult { - return getTreatmentWithConfig(split, attributes: nil) + getTreatmentWithConfig(split, attributes: nil) } public func getTreatmentWithConfig(_ split: String, attributes: [String: Any]?) -> SplitResult { @@ -102,7 +102,7 @@ public final class LocalhostSplitClient: NSObject, SplitClient { } public func getTreatmentWithConfig(_ split: String, attributes: [String: Any]?, evaluationOptions: EvaluationOptions?) -> SplitResult { - return getTreatmentWithConfig(split, attributes: attributes) + getTreatmentWithConfig(split, attributes: attributes) } public func getTreatmentsWithConfig(splits: [String], attributes: [String: Any]?) -> [String: SplitResult] { @@ -144,11 +144,9 @@ public final class LocalhostSplitClient: NSObject, SplitClient { private func on(eventWithMetadata event: SplitEventWithMetadata, runInBackground: Bool, queue: DispatchQueue?, execute action: @escaping SplitAction) { guard let factory = clientManger?.splitFactory else { return } + if let eventsManager = self.eventsManager { - let task = SplitEventActionTask(action: action, event: event.type, - runInBackground: runInBackground, - factory: factory, - queue: queue) + let task = SplitEventActionTask(action: action, event: event.type, runInBackground: runInBackground, factory: factory, queue: queue) eventsManager.register(event: event, task: task) } } @@ -156,52 +154,48 @@ public final class LocalhostSplitClient: NSObject, SplitClient { private func on(eventWithMetadata event: SplitEventWithMetadata, runInBackground: Bool, queue: DispatchQueue?, execute action: @escaping SplitActionWithMetadata) { guard let factory = clientManger?.splitFactory else { return } + if let eventsManager = self.eventsManager { - let task = SplitEventActionTask(action: action, event: event.type, - runInBackground: runInBackground, - factory: factory, - queue: queue) + let task = SplitEventActionTask(action: action, event: event.type, runInBackground: runInBackground, factory: factory, queue: queue) eventsManager.register(event: event, task: task) } } public func track(trafficType: String, eventType: String) -> Bool { - return true + true } public func track(trafficType: String, eventType: String, value: Double) -> Bool { - return true + true } public func track(eventType: String) -> Bool { - return true + true } public func track(eventType: String, value: Double) -> Bool { - return true + true } public func track(trafficType: String, eventType: String, properties: [String: Any]?) -> Bool { - return true + true } public func track(trafficType: String, eventType: String, value: Double, properties: [String: Any]?) -> Bool { - return true + true } public func track(eventType: String, properties: [String: Any]?) -> Bool { - return true + true } public func track(eventType: String, value: Double, properties: [String: Any]?) -> Bool { - return true + true } - public func setUserConsent(enabled: Bool) { - } + public func setUserConsent(enabled: Bool) {} - public func flush() { - } + public func flush() {} public func destroy() { splitsStorage.destroy() @@ -217,63 +211,61 @@ public final class LocalhostSplitClient: NSObject, SplitClient { extension LocalhostSplitClient { public func setAttribute(name: String, value: Any) -> Bool { - return true + true } public func getAttribute(name: String) -> Any? { - return nil + nil } public func setAttributes(_ values: [String: Any]) -> Bool { - return true + true } public func getAttributes() -> [String: Any]? { - return nil + nil } public func removeAttribute(name: String) -> Bool { - return true + true } public func clearAttributes() -> Bool { - return true + true } } // MARK: TreatmentBySets Feature extension LocalhostSplitClient { public func getTreatmentsByFlagSet(_ flagSet: String, attributes: [String: Any]?) -> [String: String] { - return [String: String]() + [String: String]() } public func getTreatmentsByFlagSets(_ flagSets: [String], attributes: [String: Any]?) -> [String: String] { - return [String: String]() + [String: String]() } public func getTreatmentsWithConfigByFlagSet(_ flagSet: String, attributes: [String: Any]?) -> [String: SplitResult] { - return [String: SplitResult]() + [String: SplitResult]() } public func getTreatmentsWithConfigByFlagSets(_ flagSets: [String], attributes: [String: Any]?) -> [String: SplitResult] { - return [String: SplitResult]() + [String: SplitResult]() } public func getTreatmentsByFlagSet(_ flagSet: String, attributes: [String: Any]?, evaluationOptions: EvaluationOptions?) -> [String: String] { - return [String: String]() + [String: String]() } public func getTreatmentsByFlagSets(_ flagSets: [String], attributes: [String: Any]?, evaluationOptions: EvaluationOptions?) -> [String: String] { - return [String: String]() + [String: String]() } - public func getTreatmentsWithConfigByFlagSet(_ flagSet: String, - attributes: [String: Any]?, evaluationOptions: EvaluationOptions?) -> [String: SplitResult] { - return [String: SplitResult]() + public func getTreatmentsWithConfigByFlagSet(_ flagSet: String, attributes: [String: Any]?, evaluationOptions: EvaluationOptions?) -> [String: SplitResult] { + [String: SplitResult]() } - public func getTreatmentsWithConfigByFlagSets(_ flagSets: [String], - attributes: [String: Any]?, evaluationOptions: EvaluationOptions?) -> [String: SplitResult] { - return [String: SplitResult]() + public func getTreatmentsWithConfigByFlagSets(_ flagSets: [String], attributes: [String: Any]?, evaluationOptions: EvaluationOptions?) -> [String: SplitResult] { + [String: SplitResult]() } } diff --git a/Split/Api/SplitClient.swift b/Split/Api/SplitClient.swift index 3aff37add..2ff8a0f7c 100644 --- a/Split/Api/SplitClient.swift +++ b/Split/Api/SplitClient.swift @@ -1,20 +1,13 @@ -// -// SplitClient.swift -// Split -// // Created by Brian Sztamfater on 18/9/17. -// -// import Foundation @objc public protocol SplitClient { - // MARK: Evaluation feature + // MARK: Evaluation func getTreatment(_ split: String, attributes: [String: Any]?) -> String func getTreatment(_ split: String) -> String - @objc(getTreatmentsForSplits:attributes:) func getTreatments(splits: [String], - attributes: [String: Any]?) -> [String: String] + @objc(getTreatmentsForSplits:attributes:) func getTreatments(splits: [String], attributes: [String: Any]?) -> [String: String] func getTreatmentWithConfig(_ split: String) -> SplitResult func getTreatmentWithConfig(_ split: String, attributes: [String: Any]?) -> SplitResult @@ -22,27 +15,27 @@ import Foundation @objc(getTreatmentsWithConfigForSplits:attributes:) func getTreatmentsWithConfig(splits: [String], attributes: [String: Any]?) -> [String: SplitResult] - // MARK: Evaluation with Properties + // MARK: With Properties func getTreatment(_ split: String, attributes: [String: Any]?, evaluationOptions: EvaluationOptions?) -> String - @objc(getTreatmentsForSplits:attributes:evaluationOptions:) func getTreatments(splits: [String], - attributes: [String: Any]?, - evaluationOptions: EvaluationOptions?) -> [String: String] + @objc(getTreatmentsForSplits:attributes:evaluationOptions:) + func getTreatments(splits: [String], attributes: [String: Any]?, evaluationOptions: EvaluationOptions?) -> [String: String] func getTreatmentWithConfig(_ split: String, attributes: [String: Any]?, evaluationOptions: EvaluationOptions?) -> SplitResult @objc(getTreatmentsWithConfigForSplits:attributes:evaluationOptions:) func getTreatmentsWithConfig(splits: [String], attributes: [String: Any]?, evaluationOptions: EvaluationOptions?) -> [String: SplitResult] + // MARK: Customer listeners func on(event: SplitEvent, execute action: @escaping SplitAction) func on(event: SplitEvent, executeWithMetadata: @escaping SplitActionWithMetadata) -> Void func on(event: SplitEvent, runInBackground: Bool, execute action: @escaping SplitAction) func on(event: SplitEvent, queue: DispatchQueue, execute action: @escaping SplitAction) - // MARK: Track feature + // MARK: Tracking func track(trafficType: String, eventType: String) -> Bool func track(trafficType: String, eventType: String, value: Double) -> Bool func track(eventType: String) -> Bool func track(eventType: String, value: Double) -> Bool - // MARK: Persistent attributes feature + // MARK: Persistence /// Creates or updates the value for the given attribute func setAttribute(name: String, value: Any) -> Bool @@ -62,34 +55,31 @@ import Foundation /// Clears all attributes stored in the SDK. func clearAttributes() -> Bool - // MARK: Client lifecycle + // MARK: Lifecycle func flush() func destroy() func destroy(completion: (() -> Void)?) - @objc(trackWithTrafficType:eventType:properties:) func track(trafficType: String, - eventType: String, - properties: [String: Any]?) -> Bool + // MARK: With Properties + @objc(trackWithTrafficType:eventType:properties:) + func track(trafficType: String, eventType: String, properties: [String: Any]?) -> Bool - @objc(trackWithTrafficType:eventType:value:properties:) func track(trafficType: String, - eventType: String, - value: Double, - properties: [String: Any]?) -> Bool + @objc(trackWithTrafficType:eventType:value:properties:) + func track(trafficType: String, eventType: String, value: Double, properties: [String: Any]?) -> Bool - @objc(trackWithEventType:properties:) func track(eventType: String, - properties: [String: Any]?) -> Bool + @objc(trackWithEventType:properties:) + func track(eventType: String, properties: [String: Any]?) -> Bool - @objc(trackWithEventType:value:properties:) func track(eventType: String, - value: Double, - properties: [String: Any]?) -> Bool + @objc(trackWithEventType:value:properties:) + func track(eventType: String, value: Double, properties: [String: Any]?) -> Bool - // MARK: Evaluation with flagsets + // MARK: With Flagsets func getTreatmentsByFlagSet(_ flagSet: String, attributes: [String: Any]?) -> [String: String] func getTreatmentsByFlagSets(_ flagSets: [String], attributes: [String: Any]?) -> [String: String] func getTreatmentsWithConfigByFlagSet(_ flagSet: String, attributes: [String: Any]?) -> [String: SplitResult] func getTreatmentsWithConfigByFlagSets(_ flagSets: [String], attributes: [String: Any]?) -> [String: SplitResult] - // MARK: Evaluation with flagsets and properties + // MARK: With flagsets and properties func getTreatmentsByFlagSet(_ flagSet: String, attributes: [String: Any]?, evaluationOptions: EvaluationOptions?) -> [String: String] func getTreatmentsByFlagSets(_ flagSets: [String], attributes: [String: Any]?, evaluationOptions: EvaluationOptions?) -> [String: String] func getTreatmentsWithConfigByFlagSet(_ flagSet: String, attributes: [String: Any]?, evaluationOptions: EvaluationOptions?) -> [String: SplitResult] diff --git a/Split/Engine/DefaultTreatmentManager.swift b/Split/Engine/DefaultTreatmentManager.swift index 73594b32e..716191529 100644 --- a/Split/Engine/DefaultTreatmentManager.swift +++ b/Split/Engine/DefaultTreatmentManager.swift @@ -337,8 +337,8 @@ extension DefaultTreatmentManager { } private func isSdkReady() -> Bool { - return eventsManager.eventAlreadyTriggered(event: SplitEvent.sdkReadyFromCache) || - eventsManager.eventAlreadyTriggered(event: SplitEvent.sdkReady) + return eventsManager.eventAlreadyTriggered(event: .sdkReadyFromCache) || + eventsManager.eventAlreadyTriggered(event: .sdkReady) } private func checkAndLogIfDestroyed(logTag: String) -> Bool { diff --git a/Split/Engine/Evaluator.swift b/Split/Engine/Evaluator.swift index 8d2098103..5deaf4fa0 100644 --- a/Split/Engine/Evaluator.swift +++ b/Split/Engine/Evaluator.swift @@ -6,49 +6,10 @@ // import Foundation -// swiftlint:disable function_body_length -struct EvaluationResult { - var treatment: String - var label: String - var changeNumber: Int64? - var configuration: String? - var impressionsDisabled: Bool - - init(treatment: String, label: String, changeNumber: Int64? = nil, configuration: String? = nil, - impressionsDisabled: Bool = false) { - self.treatment = treatment - self.label = label - self.changeNumber = changeNumber - self.configuration = configuration - self.impressionsDisabled = impressionsDisabled - } -} - -struct EvalValues { - let matchValue: Any? - let matchingKey: String - let bucketingKey: String? - let attributes: [String: Any]? - init(matchValue: Any?, matchingKey: String, bucketingKey: String? = nil, attributes: [String: Any]? = nil) { - self.matchValue = matchValue - self.matchingKey = matchingKey - self.bucketingKey = bucketingKey - self.attributes = attributes - } -} - -// Components needed -struct EvalContext { - let evaluator: Evaluator? - let mySegmentsStorage: MySegmentsStorage? - let myLargeSegmentsStorage: MySegmentsStorage? - let ruleBasedSegmentsStorage: RuleBasedSegmentsStorage? -} protocol Evaluator { - func evalTreatment(matchingKey: String, bucketingKey: String?, - splitName: String, attributes: [String: Any]?) throws -> EvaluationResult + func evalTreatment(matchingKey: String, bucketingKey: String?, splitName: String, attributes: [String: Any]?) throws -> EvaluationResult } class DefaultEvaluator: Evaluator { @@ -184,3 +145,42 @@ private extension Split { return self.impressionsDisabled ?? false } } + +//MARK: Components needed +struct EvaluationResult { + var treatment: String + var label: String + var changeNumber: Int64? + var configuration: String? + var impressionsDisabled: Bool + + init(treatment: String, label: String, changeNumber: Int64? = nil, configuration: String? = nil, + impressionsDisabled: Bool = false) { + self.treatment = treatment + self.label = label + self.changeNumber = changeNumber + self.configuration = configuration + self.impressionsDisabled = impressionsDisabled + } +} + +struct EvalValues { + let matchValue: Any? + let matchingKey: String + let bucketingKey: String? + let attributes: [String: Any]? + + init(matchValue: Any?, matchingKey: String, bucketingKey: String? = nil, attributes: [String: Any]? = nil) { + self.matchValue = matchValue + self.matchingKey = matchingKey + self.bucketingKey = bucketingKey + self.attributes = attributes + } +} + +struct EvalContext { + let evaluator: Evaluator? + let mySegmentsStorage: MySegmentsStorage? + let myLargeSegmentsStorage: MySegmentsStorage? + let ruleBasedSegmentsStorage: RuleBasedSegmentsStorage? +} diff --git a/Split/Events/SplitEventsManager.swift b/Split/Events/SplitEventsManager.swift index 1b21eba83..8b24c770c 100644 --- a/Split/Events/SplitEventsManager.swift +++ b/Split/Events/SplitEventsManager.swift @@ -173,7 +173,7 @@ class DefaultSplitEventsManager: SplitEventsManager { isTriggered(internal: .mySegmentsLoadedFromCache), isTriggered(internal: .myLargeSegmentsLoadedFromCache), isTriggered(internal: .attributesLoadedFromCache) { - trigger(event: SplitEvent.sdkReadyFromCache) + trigger(event: .sdkReadyFromCache) } case .splitKilledNotification: if isTriggered(external: .sdkReady) { @@ -182,7 +182,7 @@ class DefaultSplitEventsManager: SplitEventsManager { } case .sdkReadyTimeoutReached: if !isTriggered(external: .sdkReady) { - trigger(event: SplitEvent.sdkReadyTimedOut) + trigger(event: .sdkReadyTimedOut) } } } diff --git a/Split/Network/Sync/ByKeyFacade.swift b/Split/Network/Sync/ByKeyFacade.swift index 5f7212bdf..e311bf3ab 100644 --- a/Split/Network/Sync/ByKeyFacade.swift +++ b/Split/Network/Sync/ByKeyFacade.swift @@ -178,9 +178,7 @@ class DefaultByKeyFacade: ByKeyFacade { } } - private func doInAll(forMatchingKey key: String, - action: (ByKeyComponentGroup) -> Void) { - + private func doInAll(forMatchingKey key: String, action: (ByKeyComponentGroup) -> Void) { byKeyComponents.values(forMatchingKey: key).forEach { group in action(group) } diff --git a/Split/Network/Sync/FeatureFlagsSynchronizer.swift b/Split/Network/Sync/FeatureFlagsSynchronizer.swift index 5434c865a..bd3e61654 100644 --- a/Split/Network/Sync/FeatureFlagsSynchronizer.swift +++ b/Split/Network/Sync/FeatureFlagsSynchronizer.swift @@ -78,7 +78,7 @@ class DefaultFeatureFlagsSynchronizer: FeatureFlagsSynchronizer { return } - let splitsStorage = self.storageContainer.splitsStorage + let splitsStorage = storageContainer.splitsStorage DispatchQueue.general.async { let start = Date.nowMillis() self.filterSplitsInCache() diff --git a/SplitTests/Fake/InternalSplitClientStub.swift b/SplitTests/Fake/InternalSplitClientStub.swift index 2d54d6e3a..1abe94524 100644 --- a/SplitTests/Fake/InternalSplitClientStub.swift +++ b/SplitTests/Fake/InternalSplitClientStub.swift @@ -95,86 +95,77 @@ class InternalSplitClientStub: InternalSplitClient { return ["": SplitResult(treatment: SplitConstants.control)] } - func on(event: SplitEvent, queue: DispatchQueue, execute action: @escaping SplitAction) { - } + func on(event: SplitEvent, queue: DispatchQueue, execute action: @escaping SplitAction) {} - func on(event: SplitEvent, runInBackground: Bool, execute action: @escaping SplitAction) { - } + func on(event: SplitEvent, runInBackground: Bool, execute action: @escaping SplitAction) {} - func on(event: SplitEvent, execute action: @escaping SplitAction) { - } + func on(event: SplitEvent, execute action: @escaping SplitAction) {} - func on(event: SplitEvent, executeWithMetadata: @escaping SplitActionWithMetadata) { - } + func on(event: SplitEvent, executeWithMetadata: @escaping SplitActionWithMetadata) {} func track(trafficType: String, eventType: String) -> Bool { - return true + true } func track(trafficType: String, eventType: String, value: Double) -> Bool { - return true + true } func track(eventType: String) -> Bool { - return true + true } func track(eventType: String, value: Double) -> Bool { - return true + true } func track(trafficType: String, eventType: String, properties: [String: Any]?) -> Bool { - return true + true } func track(trafficType: String, eventType: String, value: Double, properties: [String: Any]?) -> Bool { - return true + true } func track(eventType: String, properties: [String: Any]?) -> Bool { - return true + true } func track(eventType: String, value: Double, properties: [String: Any]?) -> Bool { - return true + true } func setAttribute(name: String, value: Any) -> Bool { - return true + true } func getAttribute(name: String) -> Any? { - return nil + nil } func setAttributes(_ values: [String: Any]) -> Bool { - return true + true } func getAttributes() -> [String: Any]? { - return nil + nil } func removeAttribute(name: String) -> Bool { - return true + true } func clearAttributes() -> Bool { - return true + true } - func flush() { - } + func flush() {} - func destroy() { - } - - func destroy(completion: (() -> Void)?) { - } + func destroy() {} - func on(event: SplitEvent, executeTask: SplitEventTask) { + func destroy(completion: (() -> Void)?) {} - } + func on(event: SplitEvent, executeTask: SplitEventTask) {} private func createControlTreatmentsDictionary(splits: [String]) -> [String: T] where T: Any { var result = [String: T]() diff --git a/SplitTests/Fake/SplitClientStub.swift b/SplitTests/Fake/SplitClientStub.swift index 5927d9a06..26453c685 100644 --- a/SplitTests/Fake/SplitClientStub.swift +++ b/SplitTests/Fake/SplitClientStub.swift @@ -83,83 +83,75 @@ class SplitClientStub: SplitClient { return ["feature": SplitResult(treatment: SplitConstants.control)] } - func on(event: SplitEvent, queue: DispatchQueue, execute action: @escaping SplitAction) { - } + func on(event: SplitEvent, queue: DispatchQueue, execute action: @escaping SplitAction) {} - func on(event: SplitEvent, execute action: @escaping SplitAction) { - } + func on(event: SplitEvent, execute action: @escaping SplitAction) {} - func on(event: SplitEvent, runInBackground: Bool, execute action: @escaping SplitAction) { - } + func on(event: SplitEvent, runInBackground: Bool, execute action: @escaping SplitAction) {} - func on(event: SplitEvent, runInBackground: Bool, queue: DispatchQueue?, execute action: @escaping SplitAction) { - } + func on(event: SplitEvent, runInBackground: Bool, queue: DispatchQueue?, execute action: @escaping SplitAction) {} - func on(event: SplitEvent, executeWithMetadata: @escaping SplitActionWithMetadata) { - } + func on(event: SplitEvent, executeWithMetadata: @escaping SplitActionWithMetadata) {} func track(trafficType: String, eventType: String) -> Bool { - return true + true } func track(trafficType: String, eventType: String, value: Double) -> Bool { - return true + true } func track(eventType: String) -> Bool { - return true + true } func track(eventType: String, value: Double) -> Bool { - return true + true } func track(trafficType: String, eventType: String, properties: [String:Any]?) -> Bool { - return true + true } func track(trafficType: String, eventType: String, value: Double, properties: [String:Any]?) -> Bool { - return true + true } func track(eventType: String, properties: [String:Any]?) -> Bool { - return true + true } func track(eventType: String, value: Double, properties: [String:Any]?) -> Bool { - return true + true } func setAttribute(name: String, value: Any) -> Bool { - return true + true } func getAttribute(name: String) -> Any? { - return nil + nil } func setAttributes(_ values: [String: Any]) -> Bool { - return true + true } func getAttributes() -> [String: Any]? { - return nil + nil } func removeAttribute(name: String) -> Bool { - return true + true } func clearAttributes() -> Bool { - return true + true } - func flush() { - } + func flush() {} - func destroy() { - } + func destroy() {} - func destroy(completion: (() -> Void)?) { - } + func destroy(completion: (() -> Void)?) {} } From f600d620da0dcb03c53d51a02e08873816e841ad Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Tue, 17 Jun 2025 13:38:20 -0300 Subject: [PATCH 25/62] Entities made public --- Split/Events/EventMetadataType.swift | 6 +++--- .../Streaming/SyncSegmentsUpdateWorker.swift | 15 ++++++++------- Split/Network/Sync/ByKeyFacade.swift | 12 ++++++------ Split/Network/Sync/Synchronizer.swift | 18 ++++++------------ 4 files changed, 23 insertions(+), 28 deletions(-) diff --git a/Split/Events/EventMetadataType.swift b/Split/Events/EventMetadataType.swift index da441c028..fa9660d10 100644 --- a/Split/Events/EventMetadataType.swift +++ b/Split/Events/EventMetadataType.swift @@ -3,8 +3,8 @@ import Foundation @objc public class EventMetadata: NSObject { - var type: EventMetadataType - var data: String = "" + public var type: EventMetadataType + public var data: String = "" init(type: EventMetadataType, data: String) { self.type = type @@ -12,7 +12,7 @@ import Foundation } } -@objc enum EventMetadataType: Int { +@objc public enum EventMetadataType: Int { case FLAGS_UPDATED case FLAGS_KILLED case SEGMENTS_UPDATED diff --git a/Split/Network/Streaming/SyncSegmentsUpdateWorker.swift b/Split/Network/Streaming/SyncSegmentsUpdateWorker.swift index c8ec78ae4..4b4b015d6 100644 --- a/Split/Network/Streaming/SyncSegmentsUpdateWorker.swift +++ b/Split/Network/Streaming/SyncSegmentsUpdateWorker.swift @@ -110,7 +110,7 @@ class SegmentsUpdateWorker: UpdateWorker { if segments.count > newSegments.count { mySegmentsStorage.set(SegmentChange(segments: newSegments.asArray()), forKey: key) - synchronizer.notifyUpdate(forKey: key) + synchronizer.notifyUpdate(forKey: key, EventMetadata(type: .SEGMENTS_UPDATED, data: newSegments.asArray().joined(separator: ","))) telemetryProducer?.recordUpdatesFromSse(type: resource) } } @@ -128,7 +128,7 @@ class SegmentsUpdateWorker: UpdateWorker { if oldSegments.count < newSegments.count { mySegmentsStorage.set(SegmentChange(segments: newSegments.asArray()), forKey: userKey) - synchronizer.notifyUpdate(forKey: userKey) + synchronizer.notifyUpdate(forKey: userKey, EventMetadata(type: .SEGMENTS_UPDATED, data: newSegments.asArray().joined(separator: ","))) telemetryProducer?.recordUpdatesFromSse(type: .mySegments) } return @@ -171,7 +171,7 @@ class SegmentsUpdateWorker: UpdateWorker { protocol SegmentsSynchronizerWrapper { func fetch(byKey: String, changeNumbers: SegmentsChangeNumber, delay: Int64) - func notifyUpdate(forKey: String) + func notifyUpdate(forKey: String, _ metadata: EventMetadata?) } class MySegmentsSynchronizerWrapper: SegmentsSynchronizerWrapper { @@ -185,12 +185,13 @@ class MySegmentsSynchronizerWrapper: SegmentsSynchronizerWrapper { synchronizer.forceMySegmentsSync(forKey: key, changeNumbers: changeNumbers, delay: delay) } - func notifyUpdate(forKey key: String) { - synchronizer.notifySegmentsUpdated(forKey: key) + func notifyUpdate(forKey key: String, _ metadata: EventMetadata? = nil) { + synchronizer.notifySegmentsUpdated(forKey: key, metadata) } } class MyLargeSegmentsSynchronizerWrapper: SegmentsSynchronizerWrapper { + private let synchronizer: Synchronizer init(synchronizer: Synchronizer) { @@ -201,8 +202,8 @@ class MyLargeSegmentsSynchronizerWrapper: SegmentsSynchronizerWrapper { synchronizer.forceMySegmentsSync(forKey: key, changeNumbers: changeNumbers, delay: delay) } - func notifyUpdate(forKey key: String) { - synchronizer.notifyLargeSegmentsUpdated(forKey: key) + func notifyUpdate(forKey key: String, _ metadata: EventMetadata? = nil) { + synchronizer.notifyLargeSegmentsUpdated(forKey: key, metadata) } } diff --git a/Split/Network/Sync/ByKeyFacade.swift b/Split/Network/Sync/ByKeyFacade.swift index 5f7212bdf..df1c1435e 100644 --- a/Split/Network/Sync/ByKeyFacade.swift +++ b/Split/Network/Sync/ByKeyFacade.swift @@ -19,8 +19,8 @@ protocol ByKeyRegistry { protocol ByKeySynchronizer { func loadMySegmentsFromCache(forKey: String) func loadAttributesFromCache(forKey: String) - func notifyMySegmentsUpdated(forKey: String) - func notifyMyLargeSegmentsUpdated(forKey: String) + func notifyMySegmentsUpdated(forKey: String, _ metadata: EventMetadata?) + func notifyMyLargeSegmentsUpdated(forKey: String, _ metadata: EventMetadata?) func startSync(forKey key: Key) func startPeriodicSync() func stopPeriodicSync() @@ -120,15 +120,15 @@ class DefaultByKeyFacade: ByKeyFacade { byKeyComponents.value(forKey: key)?.mySegmentsSynchronizer.synchronizeMySegments() } - func notifyMySegmentsUpdated(forKey key: String) { + func notifyMySegmentsUpdated(forKey key: String, _ metadata: EventMetadata?) { doInAll(forMatchingKey: key) { group in - group.eventsManager.notifyInternalEvent(.mySegmentsUpdated) + group.eventsManager.notifyInternalEvent(.mySegmentsUpdated, metadata: metadata) } } - func notifyMyLargeSegmentsUpdated(forKey key: String) { + func notifyMyLargeSegmentsUpdated(forKey key: String, _ metadata: EventMetadata? = nil) { doInAll(forMatchingKey: key) { group in - group.eventsManager.notifyInternalEvent(.myLargeSegmentsUpdated) + group.eventsManager.notifyInternalEvent(.myLargeSegmentsUpdated, metadata: metadata) } } diff --git a/Split/Network/Sync/Synchronizer.swift b/Split/Network/Sync/Synchronizer.swift index 986fee0e9..631e02526 100644 --- a/Split/Network/Sync/Synchronizer.swift +++ b/Split/Network/Sync/Synchronizer.swift @@ -31,9 +31,8 @@ protocol Synchronizer: ImpressionLogger { func stopRecordingTelemetry() func pushEvent(event: EventDTO) func notifyFeatureFlagsUpdated(flagsList: [String]) - func notifySegmentsUpdated(segmentsList: [String]) - func notifySegmentsUpdated(forKey key: String) - func notifyLargeSegmentsUpdated(forKey key: String) + func notifySegmentsUpdated(forKey key: String, _ metadata: EventMetadata?) + func notifyLargeSegmentsUpdated(forKey key: String, _ metadata: EventMetadata?) func notifySplitKilled() func pause() func resume() @@ -211,18 +210,13 @@ class DefaultSynchronizer: Synchronizer { func notifyFeatureFlagsUpdated(flagsList: [String]) { featureFlagsSynchronizer.notifyUpdated(flagsList: flagsList) } - - func notifySegmentsUpdated(segmentsList: [String]) { - - byKeySynchronizer.notifyMySegmentsUpdated(forKey: key) - } - func notifySegmentsUpdated(forKey key: String) { - byKeySynchronizer.notifyMySegmentsUpdated(forKey: key) + func notifySegmentsUpdated(forKey key: String, _ metadata: EventMetadata? = nil) { + byKeySynchronizer.notifyMySegmentsUpdated(forKey: key, metadata) } - func notifyLargeSegmentsUpdated(forKey key: String) { - byKeySynchronizer.notifyMyLargeSegmentsUpdated(forKey: key) + func notifyLargeSegmentsUpdated(forKey key: String, _ metadata: EventMetadata? = nil) { + byKeySynchronizer.notifyMyLargeSegmentsUpdated(forKey: key, metadata) } func notifySplitKilled() { From d432609e371246889412892fa702b8594e09811c Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Tue, 17 Jun 2025 13:45:46 -0300 Subject: [PATCH 26/62] Entities made public --- Split/Events/EventMetadataType.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Split/Events/EventMetadataType.swift b/Split/Events/EventMetadataType.swift index da441c028..3476c1236 100644 --- a/Split/Events/EventMetadataType.swift +++ b/Split/Events/EventMetadataType.swift @@ -12,7 +12,7 @@ import Foundation } } -@objc enum EventMetadataType: Int { +@objc public enum EventMetadataType: Int { case FLAGS_UPDATED case FLAGS_KILLED case SEGMENTS_UPDATED From 8319054a947a22ca061b42644ea0d10e73a530da Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Tue, 17 Jun 2025 13:49:33 -0300 Subject: [PATCH 27/62] Properties made public --- Split/Events/EventMetadataType.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Split/Events/EventMetadataType.swift b/Split/Events/EventMetadataType.swift index 3476c1236..fa9660d10 100644 --- a/Split/Events/EventMetadataType.swift +++ b/Split/Events/EventMetadataType.swift @@ -3,8 +3,8 @@ import Foundation @objc public class EventMetadata: NSObject { - var type: EventMetadataType - var data: String = "" + public var type: EventMetadataType + public var data: String = "" init(type: EventMetadataType, data: String) { self.type = type From cddd930db338a0bbe4514e59cfb7a12878f6fc56 Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Mon, 23 Jun 2025 12:50:47 -0300 Subject: [PATCH 28/62] Initial Commit --- Split/Events/EventsManagerCoordinator.swift | 2 +- Split/Events/SplitEventsManager.swift | 7 +++---- Split/Events/SplitInternalEvent.swift | 1 + .../Refresh/PeriodicSyncWorker.swift | 20 +++++++++++------- .../Refresh/RetryableSyncWorker.swift | 21 ++++++++++++------- .../Refresh/SplitsSyncHelper.swift | 14 +++++++------ Split/Localhost/LocalhostSynchronizer.swift | 2 +- .../Streaming/PushNotificationManager.swift | 9 +++++++- .../Network/Streaming/SyncUpdateWorker.swift | 11 +++++++--- .../Sync/FeatureFlagsSynchronizer.swift | 9 +++++--- Split/Network/Sync/Synchronizer.swift | 6 +++--- 11 files changed, 66 insertions(+), 36 deletions(-) diff --git a/Split/Events/EventsManagerCoordinator.swift b/Split/Events/EventsManagerCoordinator.swift index 6d5ce9a54..7a74ca7b0 100644 --- a/Split/Events/EventsManagerCoordinator.swift +++ b/Split/Events/EventsManagerCoordinator.swift @@ -66,7 +66,7 @@ class MainSplitEventsManager: SplitEventsManagerCoordinator { manager.start() managers[key] = manager triggered.forEach { - manager.notifyInternalEvent($0) + manager.notifyInternalEvent($0, metadata: nil) } } } diff --git a/Split/Events/SplitEventsManager.swift b/Split/Events/SplitEventsManager.swift index 8b24c770c..359232b36 100644 --- a/Split/Events/SplitEventsManager.swift +++ b/Split/Events/SplitEventsManager.swift @@ -61,7 +61,6 @@ class DefaultSplitEventsManager: SplitEventsManager { } } - // Method kept for backwards compatibility. Allows notifying an event without metadata. func notifyInternalEvent(_ event: SplitInternalEvent) { notifyInternalEvent(event, metadata: nil) } @@ -161,7 +160,7 @@ class DefaultSplitEventsManager: SplitEventsManager { switch event.type { case .splitsUpdated, .mySegmentsUpdated, .myLargeSegmentsUpdated: if isTriggered(external: .sdkReady) { - trigger(event: .sdkUpdated) + trigger(event: .sdkUpdated, metadata: event.metadata) continue } self.triggerSdkReadyIfNeeded() @@ -213,8 +212,8 @@ class DefaultSplitEventsManager: SplitEventsManager { } } - private func trigger(event: SplitEvent) { - trigger(event: SplitEventWithMetadata(type: event, metadata: nil)) + private func trigger(event: SplitEvent, metadata: EventMetadata? = nil) { + trigger(event: SplitEventWithMetadata(type: event, metadata: metadata)) } private func trigger(event: SplitEventWithMetadata) { diff --git a/Split/Events/SplitInternalEvent.swift b/Split/Events/SplitInternalEvent.swift index 573a39c4f..f53efce56 100644 --- a/Split/Events/SplitInternalEvent.swift +++ b/Split/Events/SplitInternalEvent.swift @@ -23,3 +23,4 @@ enum SplitInternalEvent { case sdkReadyTimeoutReached case splitKilledNotification } + diff --git a/Split/FetcherEngine/Refresh/PeriodicSyncWorker.swift b/Split/FetcherEngine/Refresh/PeriodicSyncWorker.swift index b6fd71c81..d57777529 100644 --- a/Split/FetcherEngine/Refresh/PeriodicSyncWorker.swift +++ b/Split/FetcherEngine/Refresh/PeriodicSyncWorker.swift @@ -130,10 +130,8 @@ class BasePeriodicSyncWorker: PeriodicSyncWorker { Logger.i("Fetch from remote not implemented") } - func notifyUpdate(_ events: [SplitInternalEvent]) { - events.forEach { - eventsManager.notifyInternalEvent($0) - } + func notifyUpdate(_ event: SplitInternalEvent, _ metadata: EventMetadata? = nil) { + eventsManager.notifyInternalEvent(event, metadata: metadata) } } @@ -183,8 +181,16 @@ class PeriodicSplitsSyncWorker: BasePeriodicSyncWorker { guard let result = try? syncHelper.sync(since: changeNumber, rbSince: rbChangeNumber) else { return } - if result.success, result.featureFlagsUpdated || result.rbsUpdated { - notifyUpdate([.splitsUpdated]) + + if result.success, result.featureFlagsUpdated.count > 0 { + + var updatedFlags = result.featureFlagsUpdated + for flag in updatedFlags { + updatedFlags.append(flag) + } + + let metadata = EventMetadata(type: .FLAGS_UPDATED, data: updatedFlags.description) + notifyUpdate(.splitsUpdated, metadata) } } } @@ -225,7 +231,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) } } } catch { diff --git a/Split/FetcherEngine/Refresh/RetryableSyncWorker.swift b/Split/FetcherEngine/Refresh/RetryableSyncWorker.swift index 28c0a6f9c..2a11fc0f3 100644 --- a/Split/FetcherEngine/Refresh/RetryableSyncWorker.swift +++ b/Split/FetcherEngine/Refresh/RetryableSyncWorker.swift @@ -78,10 +78,16 @@ class BaseRetryableSyncWorker: RetryableSyncWorker { handler(success) } } - + func notifyUpdate(_ events: [SplitInternalEvent]) { events.forEach { - eventsManager.notifyInternalEvent($0) + eventsManager.notifyInternalEvent($0, metadata: nil) + } + } + + func notifyUpdate(_ events: [SplitInternalEventWithMetadata]) { + events.forEach { + eventsManager.notifyInternalEvent($0.type, metadata: $0.metadata) } } @@ -141,9 +147,9 @@ class RetryableSplitsSyncWorker: BaseRetryableSyncWorker { let rbChangeNumber = ruleBasedSegmentsStorage.changeNumber let result = try syncHelper.sync(since: changeNumber, rbSince: rbChangeNumber, clearBeforeUpdate: false) if result.success { - if !isSdkReadyTriggered() || - result.featureFlagsUpdated { - notifyUpdate([.splitsUpdated]) + if !isSdkReadyTriggered() || result.featureFlagsUpdated.count > 0 { + let metadata = EventMetadata(type: .FLAGS_UPDATED, data: result.featureFlagsUpdated.description) + notifyUpdate([SplitInternalEventWithMetadata(.splitsUpdated, metadata: metadata)]) } resetBackoffCounter() return true @@ -217,8 +223,9 @@ class RetryableSplitsUpdateWorker: BaseRetryableSyncWorker { clearBeforeUpdate: false, headers: ServiceConstants.controlNoCacheHeader) if result.success { - if result.featureFlagsUpdated { - notifyUpdate([.splitsUpdated]) + if result.featureFlagsUpdated.count > 0 { + let metadata = EventMetadata(type: .FLAGS_UPDATED, data: result.featureFlagsUpdated.description) + notifyUpdate([SplitInternalEventWithMetadata(.splitsUpdated, metadata: metadata)]) } resetBackoffCounter() return true diff --git a/Split/FetcherEngine/Refresh/SplitsSyncHelper.swift b/Split/FetcherEngine/Refresh/SplitsSyncHelper.swift index 6b5a3e8d2..03c4f2efa 100644 --- a/Split/FetcherEngine/Refresh/SplitsSyncHelper.swift +++ b/Split/FetcherEngine/Refresh/SplitsSyncHelper.swift @@ -12,7 +12,7 @@ struct SyncResult { let success: Bool let changeNumber: Int64 let rbChangeNumber: Int64? - let featureFlagsUpdated: Bool + let featureFlagsUpdated: [String] let rbsUpdated: Bool } @@ -21,7 +21,7 @@ class SplitsSyncHelper { struct FetchResult { let till: Int64 let rbTill: Int64? - let featureFlagsUpdated: Bool + let featureFlagsUpdated: [String] let rbsUpdated: Bool } @@ -163,7 +163,7 @@ class SplitsSyncHelper { return SyncResult(success: false, changeNumber: nextSince, rbChangeNumber: nextRbSince, - featureFlagsUpdated: false, + featureFlagsUpdated: [], rbsUpdated: false) } @@ -177,7 +177,7 @@ class SplitsSyncHelper { var firstFetch = true var nextSince = since var nextRbSince = rbSince - var featureFlagsUpdated = false + var featureFlagsUpdated: [String] = [] var rbsUpdated = false while true { clearCache = clearCache && firstFetch @@ -202,8 +202,10 @@ class SplitsSyncHelper { ruleBasedSegmentsStorage.clear() } firstFetch = false - if splitsStorage.update(splitChange: splitChangeProcessor.process(targetingRulesChange.featureFlags)) { - featureFlagsUpdated = true + let processedSplits = splitChangeProcessor.process(flagsChange) + if splitsStorage.update(splitChange: processedSplits) { + featureFlagsUpdated += processedSplits.archivedSplits.compactMap(\.name) + featureFlagsUpdated += processedSplits.activeSplits.compactMap(\.name) } let processedChange = ruleBasedSegmentsChangeProcessor.process(targetingRulesChange.ruleBasedSegments) diff --git a/Split/Localhost/LocalhostSynchronizer.swift b/Split/Localhost/LocalhostSynchronizer.swift index 0081bf272..0b7a1c8d0 100644 --- a/Split/Localhost/LocalhostSynchronizer.swift +++ b/Split/Localhost/LocalhostSynchronizer.swift @@ -44,7 +44,7 @@ class LocalhostSynchronizer: FeatureFlagsSynchronizer { func notifyKilled() { } - func notifyUpdated() { + func notifyUpdated(flagsList: [String]) { } func pause() { diff --git a/Split/Network/Streaming/PushNotificationManager.swift b/Split/Network/Streaming/PushNotificationManager.swift index 38aa4f28f..59e46de8e 100644 --- a/Split/Network/Streaming/PushNotificationManager.swift +++ b/Split/Network/Streaming/PushNotificationManager.swift @@ -134,7 +134,14 @@ class DefaultPushNotificationManager: PushNotificationManager { } Logger.d("Streaming authentication success") - let connectionDelay = result.sseConnectionDelay + var connectionDelay: Int64 = 0 + + #if DEBUG + connectionDelay = 1 + #else + connectionDelay = result.sseConnectionDelay + #endif + self.broadcasterChannel.push(event: .pushDelayReceived(delaySeconds: connectionDelay)) let lastId = lastConnId.value if connectionDelay > 0 { diff --git a/Split/Network/Streaming/SyncUpdateWorker.swift b/Split/Network/Streaming/SyncUpdateWorker.swift index 5aede0758..bedbde42d 100644 --- a/Split/Network/Streaming/SyncUpdateWorker.swift +++ b/Split/Network/Streaming/SyncUpdateWorker.swift @@ -143,8 +143,13 @@ class SplitsUpdateWorker: UpdateWorker { Logger.v("Split update received: \(change)") - if self.splitsStorage.update(splitChange: self.splitChangeProcessor.process(change)) { - self.synchronizer.notifyFeatureFlagsUpdated() + let processedFlags = self.splitChangeProcessor.process(change) + + if self.splitsStorage.update(splitChange: processedFlags) { + var updatedFlags: [String] = processedFlags.activeSplits.compactMap(\.name) + updatedFlags += processedFlags.archivedSplits.compactMap(\.name) + self.synchronizer.notifyFeatureFlagsUpdated(flagsList: updatedFlags) + } self.telemetryProducer?.recordUpdatesFromSse(type: .splits) @@ -176,7 +181,7 @@ class SplitsUpdateWorker: UpdateWorker { if self.ruleBasedSegmentsStorage.update(toAdd: processedChange.toAdd, toRemove: processedChange.toRemove, changeNumber: processedChange.changeNumber) { - self.synchronizer.notifyFeatureFlagsUpdated() + self.synchronizer.notifyFeatureFlagsUpdated(flagsList: []) //TODO: RBS Update } self.telemetryProducer?.recordUpdatesFromSse(type: .splits) diff --git a/Split/Network/Sync/FeatureFlagsSynchronizer.swift b/Split/Network/Sync/FeatureFlagsSynchronizer.swift index bd3e61654..4ed7b0d93 100644 --- a/Split/Network/Sync/FeatureFlagsSynchronizer.swift +++ b/Split/Network/Sync/FeatureFlagsSynchronizer.swift @@ -15,7 +15,7 @@ protocol FeatureFlagsSynchronizer { func startPeriodicSync() func stopPeriodicSync() func notifyKilled() - func notifyUpdated() + func notifyUpdated(flagsList: [String]) func pause() func resume() func destroy() @@ -147,9 +147,12 @@ class DefaultFeatureFlagsSynchronizer: FeatureFlagsSynchronizer { func notifyKilled() { splitEventsManager.notifyInternalEvent(.splitKilledNotification) } + + func foo(_ a: Int) {} + func foo(_ a: Int, _ b: Int = 42) {} - func notifyUpdated() { - splitEventsManager.notifyInternalEvent(.splitsUpdated) + func notifyUpdated(flagsList: [String]) { + splitEventsManager.notifyInternalEvent(.splitsUpdated, metadata: EventMetadata(type: .FLAGS_UPDATED, data: flagsList.description)) } func pause() { diff --git a/Split/Network/Sync/Synchronizer.swift b/Split/Network/Sync/Synchronizer.swift index d3155a193..ec789a033 100644 --- a/Split/Network/Sync/Synchronizer.swift +++ b/Split/Network/Sync/Synchronizer.swift @@ -30,7 +30,7 @@ protocol Synchronizer: ImpressionLogger { func startRecordingTelemetry() func stopRecordingTelemetry() func pushEvent(event: EventDTO) - func notifyFeatureFlagsUpdated() + func notifyFeatureFlagsUpdated(flagsList: [String]) func notifySegmentsUpdated(forKey key: String) func notifyLargeSegmentsUpdated(forKey key: String) func notifySplitKilled() @@ -207,8 +207,8 @@ class DefaultSynchronizer: Synchronizer { } } - func notifyFeatureFlagsUpdated() { - featureFlagsSynchronizer.notifyUpdated() + func notifyFeatureFlagsUpdated(flagsList: [String]) { + featureFlagsSynchronizer.notifyUpdated(flagsList: flagsList) } func notifySegmentsUpdated(forKey key: String) { From 50ceecd48f882befd426ab54457391e5965f7e69 Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Mon, 23 Jun 2025 13:08:25 -0300 Subject: [PATCH 29/62] Tests adapted --- Split/Localhost/LocalhostSynchronizer.swift | 2 +- Split/Network/Sync/ByKeyFacade.swift | 2 +- SplitTests/Fake/Streaming/FeatureFlagsSynchronizerStub.swift | 4 +++- SplitTests/Fake/Streaming/SynchronizerSpy.swift | 4 +++- SplitTests/Fake/Streaming/SynchronizerStub.swift | 4 +++- 5 files changed, 11 insertions(+), 5 deletions(-) diff --git a/Split/Localhost/LocalhostSynchronizer.swift b/Split/Localhost/LocalhostSynchronizer.swift index 0b7a1c8d0..457c4c00e 100644 --- a/Split/Localhost/LocalhostSynchronizer.swift +++ b/Split/Localhost/LocalhostSynchronizer.swift @@ -74,7 +74,7 @@ class LocalhostSynchronizer: FeatureFlagsSynchronizer { // Update will remove all records before insert new ones _ = self.featureFlagsStorage.update(splitChange: change) - self.eventsManager.notifyInternalEvent(.splitsUpdated) + self.eventsManager.notifyInternalEvent(.splitsUpdated, metadata: EventMetadata(type: .FLAGS_UPDATED, data: values.description)) } } } diff --git a/Split/Network/Sync/ByKeyFacade.swift b/Split/Network/Sync/ByKeyFacade.swift index e311bf3ab..56c549147 100644 --- a/Split/Network/Sync/ByKeyFacade.swift +++ b/Split/Network/Sync/ByKeyFacade.swift @@ -77,7 +77,7 @@ class DefaultByKeyFacade: ByKeyFacade { func loadAttributesFromCache(forKey key: String) { doInAll(forMatchingKey: key) { group in group.attributesStorage.loadLocal() - group.eventsManager.notifyInternalEvent(.attributesLoadedFromCache) + group.eventsManager.notifyInternalEvent(.attributesLoadedFromCache, metadata: nil) } TimeChecker.logInterval("Time until attributes loaded from cache") } diff --git a/SplitTests/Fake/Streaming/FeatureFlagsSynchronizerStub.swift b/SplitTests/Fake/Streaming/FeatureFlagsSynchronizerStub.swift index 8e01f5f8e..ad42cd21a 100644 --- a/SplitTests/Fake/Streaming/FeatureFlagsSynchronizerStub.swift +++ b/SplitTests/Fake/Streaming/FeatureFlagsSynchronizerStub.swift @@ -40,7 +40,9 @@ class FeatureFlagsSynchronizerStub: FeatureFlagsSynchronizer { } var notifyUpdatedCalled = false - func notifyUpdated() { + var updatedFlags: [String] = [] + func notifyUpdated(flagsList: [String]) { + updatedFlags = flagsList notifyUpdatedCalled = true } diff --git a/SplitTests/Fake/Streaming/SynchronizerSpy.swift b/SplitTests/Fake/Streaming/SynchronizerSpy.swift index fb6e59353..ed659f543 100644 --- a/SplitTests/Fake/Streaming/SynchronizerSpy.swift +++ b/SplitTests/Fake/Streaming/SynchronizerSpy.swift @@ -206,7 +206,9 @@ class SynchronizerSpy: Synchronizer { } var notifyFeatureFlagsUpdatedCalled = false - func notifyFeatureFlagsUpdated() { + var updatedFlags: [String] = [] + func notifyFeatureFlagsUpdated(flagsList: [String]) { + updatedFlags = flagsList notifyFeatureFlagsUpdatedCalled = true } diff --git a/SplitTests/Fake/Streaming/SynchronizerStub.swift b/SplitTests/Fake/Streaming/SynchronizerStub.swift index 4bcdba666..0c8019699 100644 --- a/SplitTests/Fake/Streaming/SynchronizerStub.swift +++ b/SplitTests/Fake/Streaming/SynchronizerStub.swift @@ -237,7 +237,9 @@ class SynchronizerStub: Synchronizer { } var notifyFeatureFlagsUpdatedCalled = true - func notifyFeatureFlagsUpdated() { + var updatedFlags: [String] = [] + func notifyFeatureFlagsUpdated(flagsList: [String]) { + updatedFlags = flagsList notifyFeatureFlagsUpdatedCalled = true } From a21d9ec7102946ac83508e0e2febf4b5fecc49fa Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Mon, 23 Jun 2025 13:22:56 -0300 Subject: [PATCH 30/62] Simplified logic of notifications --- Split/Events/SplitInternalEvent.swift | 1 - .../Refresh/RetryableSegmentsSyncWorker.swift | 6 +++--- .../Refresh/RetryableSyncWorker.swift | 16 ++++++---------- 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/Split/Events/SplitInternalEvent.swift b/Split/Events/SplitInternalEvent.swift index f53efce56..573a39c4f 100644 --- a/Split/Events/SplitInternalEvent.swift +++ b/Split/Events/SplitInternalEvent.swift @@ -23,4 +23,3 @@ enum SplitInternalEvent { case sdkReadyTimeoutReached case splitKilledNotification } - diff --git a/Split/FetcherEngine/Refresh/RetryableSegmentsSyncWorker.swift b/Split/FetcherEngine/Refresh/RetryableSegmentsSyncWorker.swift index 36973b66b..d16c567ca 100644 --- a/Split/FetcherEngine/Refresh/RetryableSegmentsSyncWorker.swift +++ b/Split/FetcherEngine/Refresh/RetryableSegmentsSyncWorker.swift @@ -46,11 +46,11 @@ class RetryableMySegmentsSyncWorker: BaseRetryableSyncWorker { if result.success { if !isSdkReadyTriggered() { // Notifying both to trigger SDK Ready - notifyUpdate([.mySegmentsUpdated]) - notifyUpdate([.myLargeSegmentsUpdated]) + notifyUpdate(.mySegmentsUpdated) + notifyUpdate(.myLargeSegmentsUpdated) } else if result.msUpdated || result.mlsUpdated { // For now is not necessary specify which entity was updated - notifyUpdate([.mySegmentsUpdated]) + notifyUpdate(.mySegmentsUpdated) } return true } diff --git a/Split/FetcherEngine/Refresh/RetryableSyncWorker.swift b/Split/FetcherEngine/Refresh/RetryableSyncWorker.swift index 2a11fc0f3..a350bfe57 100644 --- a/Split/FetcherEngine/Refresh/RetryableSyncWorker.swift +++ b/Split/FetcherEngine/Refresh/RetryableSyncWorker.swift @@ -79,16 +79,12 @@ class BaseRetryableSyncWorker: RetryableSyncWorker { } } - func notifyUpdate(_ events: [SplitInternalEvent]) { - events.forEach { - eventsManager.notifyInternalEvent($0, metadata: nil) - } + func notifyUpdate(_ event: SplitInternalEvent) { + eventsManager.notifyInternalEvent(event, metadata: nil) } - func notifyUpdate(_ events: [SplitInternalEventWithMetadata]) { - events.forEach { - eventsManager.notifyInternalEvent($0.type, metadata: $0.metadata) - } + func notifyUpdate(_ event: SplitInternalEvent, metadata: EventMetadata? = nil) { + eventsManager.notifyInternalEvent(event, metadata: metadata) } func isSdkReadyTriggered() -> Bool { @@ -149,7 +145,7 @@ class RetryableSplitsSyncWorker: BaseRetryableSyncWorker { if result.success { if !isSdkReadyTriggered() || result.featureFlagsUpdated.count > 0 { let metadata = EventMetadata(type: .FLAGS_UPDATED, data: result.featureFlagsUpdated.description) - notifyUpdate([SplitInternalEventWithMetadata(.splitsUpdated, metadata: metadata)]) + notifyUpdate(.splitsUpdated, metadata: metadata) } resetBackoffCounter() return true @@ -225,7 +221,7 @@ class RetryableSplitsUpdateWorker: BaseRetryableSyncWorker { if result.success { if result.featureFlagsUpdated.count > 0 { let metadata = EventMetadata(type: .FLAGS_UPDATED, data: result.featureFlagsUpdated.description) - notifyUpdate([SplitInternalEventWithMetadata(.splitsUpdated, metadata: metadata)]) + notifyUpdate(.splitsUpdated, metadata: metadata) } resetBackoffCounter() return true From 5cd8a48c8759bad26c9de01b72ad4eb0951a876c Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Mon, 23 Jun 2025 13:39:07 -0300 Subject: [PATCH 31/62] Removed methods --- Split/Network/Sync/FeatureFlagsSynchronizer.swift | 3 --- 1 file changed, 3 deletions(-) diff --git a/Split/Network/Sync/FeatureFlagsSynchronizer.swift b/Split/Network/Sync/FeatureFlagsSynchronizer.swift index 4ed7b0d93..3d82b58a7 100644 --- a/Split/Network/Sync/FeatureFlagsSynchronizer.swift +++ b/Split/Network/Sync/FeatureFlagsSynchronizer.swift @@ -147,9 +147,6 @@ class DefaultFeatureFlagsSynchronizer: FeatureFlagsSynchronizer { func notifyKilled() { splitEventsManager.notifyInternalEvent(.splitKilledNotification) } - - func foo(_ a: Int) {} - func foo(_ a: Int, _ b: Int = 42) {} func notifyUpdated(flagsList: [String]) { splitEventsManager.notifyInternalEvent(.splitsUpdated, metadata: EventMetadata(type: .FLAGS_UPDATED, data: flagsList.description)) From ee859722bb701369bb5ab440c2d08973d97ea1da Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Mon, 23 Jun 2025 18:38:17 -0300 Subject: [PATCH 32/62] Initial commit --- Split/Events/SplitEventsManager.swift | 2 +- Split/Localhost/LocalhostSynchronizer.swift | 2 +- Split/Network/Streaming/SyncUpdateWorker.swift | 2 +- Split/Network/Sync/FeatureFlagsSynchronizer.swift | 8 ++++---- Split/Network/Sync/Synchronizer.swift | 6 +++--- .../Fake/Streaming/FeatureFlagsSynchronizerStub.swift | 4 +++- SplitTests/Fake/Streaming/SynchronizerSpy.swift | 6 ++++-- SplitTests/Fake/Streaming/SynchronizerStub.swift | 4 +++- 8 files changed, 20 insertions(+), 14 deletions(-) diff --git a/Split/Events/SplitEventsManager.swift b/Split/Events/SplitEventsManager.swift index 359232b36..5781b3fff 100644 --- a/Split/Events/SplitEventsManager.swift +++ b/Split/Events/SplitEventsManager.swift @@ -176,7 +176,7 @@ class DefaultSplitEventsManager: SplitEventsManager { } case .splitKilledNotification: if isTriggered(external: .sdkReady) { - trigger(event: .sdkUpdated) + trigger(event: .sdkUpdated, metadata: event.metadata) continue } case .sdkReadyTimeoutReached: diff --git a/Split/Localhost/LocalhostSynchronizer.swift b/Split/Localhost/LocalhostSynchronizer.swift index 457c4c00e..6ec29138b 100644 --- a/Split/Localhost/LocalhostSynchronizer.swift +++ b/Split/Localhost/LocalhostSynchronizer.swift @@ -41,7 +41,7 @@ class LocalhostSynchronizer: FeatureFlagsSynchronizer { func stopPeriodicSync() { } - func notifyKilled() { + func notifyKilled(flag: String) { } func notifyUpdated(flagsList: [String]) { diff --git a/Split/Network/Streaming/SyncUpdateWorker.swift b/Split/Network/Streaming/SyncUpdateWorker.swift index bedbde42d..2c6d35875 100644 --- a/Split/Network/Streaming/SyncUpdateWorker.swift +++ b/Split/Network/Streaming/SyncUpdateWorker.swift @@ -234,7 +234,7 @@ class SplitKillWorker: UpdateWorker { splitToKill.changeNumber = notification.changeNumber splitToKill.killed = true splitsStorage.updateWithoutChecks(split: splitToKill) - synchronizer.notifySplitKilled() + synchronizer.notifySplitKilled(flag: splitToKill.name!) } } synchronizer.synchronizeSplits(changeNumber: notification.changeNumber) diff --git a/Split/Network/Sync/FeatureFlagsSynchronizer.swift b/Split/Network/Sync/FeatureFlagsSynchronizer.swift index 3d82b58a7..4ed57ddda 100644 --- a/Split/Network/Sync/FeatureFlagsSynchronizer.swift +++ b/Split/Network/Sync/FeatureFlagsSynchronizer.swift @@ -14,7 +14,7 @@ protocol FeatureFlagsSynchronizer { func synchronize(changeNumber: Int64?, rbsChangeNumber: Int64?) func startPeriodicSync() func stopPeriodicSync() - func notifyKilled() + func notifyKilled(flag: String) func notifyUpdated(flagsList: [String]) func pause() func resume() @@ -144,12 +144,12 @@ class DefaultFeatureFlagsSynchronizer: FeatureFlagsSynchronizer { periodicSplitsSyncWorker?.stop() } - func notifyKilled() { - splitEventsManager.notifyInternalEvent(.splitKilledNotification) + func notifyKilled(flag: String) { + splitEventsManager.notifyInternalEvent(.splitKilledNotification, metadata: EventMetadata(type: .FLAGS_KILLED, data: flag)) } func notifyUpdated(flagsList: [String]) { - splitEventsManager.notifyInternalEvent(.splitsUpdated, metadata: EventMetadata(type: .FLAGS_UPDATED, data: flagsList.description)) + splitEventsManager.notifyInternalEvent(.splitsUpdated, metadata: EventMetadata(type: .FLAGS_UPDATED, data: flagsList.joined(separator: ","))) } func pause() { diff --git a/Split/Network/Sync/Synchronizer.swift b/Split/Network/Sync/Synchronizer.swift index ec789a033..5554802a1 100644 --- a/Split/Network/Sync/Synchronizer.swift +++ b/Split/Network/Sync/Synchronizer.swift @@ -33,7 +33,7 @@ protocol Synchronizer: ImpressionLogger { func notifyFeatureFlagsUpdated(flagsList: [String]) func notifySegmentsUpdated(forKey key: String) func notifyLargeSegmentsUpdated(forKey key: String) - func notifySplitKilled() + func notifySplitKilled(flag: String) func pause() func resume() func flush() @@ -219,8 +219,8 @@ class DefaultSynchronizer: Synchronizer { byKeySynchronizer.notifyMyLargeSegmentsUpdated(forKey: key) } - func notifySplitKilled() { - featureFlagsSynchronizer.notifyKilled() + func notifySplitKilled(flag: String) { + featureFlagsSynchronizer.notifyKilled(flag: flag) } func pause() { diff --git a/SplitTests/Fake/Streaming/FeatureFlagsSynchronizerStub.swift b/SplitTests/Fake/Streaming/FeatureFlagsSynchronizerStub.swift index ad42cd21a..43435555c 100644 --- a/SplitTests/Fake/Streaming/FeatureFlagsSynchronizerStub.swift +++ b/SplitTests/Fake/Streaming/FeatureFlagsSynchronizerStub.swift @@ -35,7 +35,9 @@ class FeatureFlagsSynchronizerStub: FeatureFlagsSynchronizer { } var notifyKilledCalled = false - func notifyKilled() { + var killedFlag = "" + func notifyKilled(flag: String) { + killedFlag = flag notifyKilledCalled = true } diff --git a/SplitTests/Fake/Streaming/SynchronizerSpy.swift b/SplitTests/Fake/Streaming/SynchronizerSpy.swift index ed659f543..423a56a30 100644 --- a/SplitTests/Fake/Streaming/SynchronizerSpy.swift +++ b/SplitTests/Fake/Streaming/SynchronizerSpy.swift @@ -212,9 +212,11 @@ class SynchronizerSpy: Synchronizer { notifyFeatureFlagsUpdatedCalled = true } - func notifySplitKilled() { + var killedFlag = "" + func notifySplitKilled(flag: String) { notifySplitKilledCalled = true - splitSynchronizer.notifySplitKilled() + killedFlag = flag + splitSynchronizer.notifySplitKilled(flag: flag) } func start(forKey key: Key) { diff --git a/SplitTests/Fake/Streaming/SynchronizerStub.swift b/SplitTests/Fake/Streaming/SynchronizerStub.swift index 0c8019699..db9be8019 100644 --- a/SplitTests/Fake/Streaming/SynchronizerStub.swift +++ b/SplitTests/Fake/Streaming/SynchronizerStub.swift @@ -243,7 +243,9 @@ class SynchronizerStub: Synchronizer { notifyFeatureFlagsUpdatedCalled = true } - func notifySplitKilled() { + var killedFlag = "" + func notifySplitKilled(flag: String) { + killedFlag = flag notifySplitKilledCalled = true } From b9ff6cbbb3b87a389d158234690b1c65d8874a7c Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Tue, 24 Jun 2025 13:16:57 -0300 Subject: [PATCH 33/62] Update Split/Network/Streaming/SyncUpdateWorker.swift Co-authored-by: gthea --- Split/Network/Streaming/SyncUpdateWorker.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Split/Network/Streaming/SyncUpdateWorker.swift b/Split/Network/Streaming/SyncUpdateWorker.swift index 2c6d35875..7ffe17105 100644 --- a/Split/Network/Streaming/SyncUpdateWorker.swift +++ b/Split/Network/Streaming/SyncUpdateWorker.swift @@ -234,7 +234,7 @@ class SplitKillWorker: UpdateWorker { splitToKill.changeNumber = notification.changeNumber splitToKill.killed = true splitsStorage.updateWithoutChecks(split: splitToKill) - synchronizer.notifySplitKilled(flag: splitToKill.name!) + synchronizer.notifySplitKilled(flag: splitToKill.name ?? "") } } synchronizer.synchronizeSplits(changeNumber: notification.changeNumber) From f4b0fb52192a9bd175102953d2a4f1b84c115b53 Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Wed, 25 Jun 2025 11:23:15 -0300 Subject: [PATCH 34/62] New metadata structure --- Split/Events/EventMetadataType.swift | 4 ++-- SplitTests/SplitEventsManagerTest.swift | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Split/Events/EventMetadataType.swift b/Split/Events/EventMetadataType.swift index fa9660d10..95d3244fe 100644 --- a/Split/Events/EventMetadataType.swift +++ b/Split/Events/EventMetadataType.swift @@ -4,9 +4,9 @@ import Foundation @objc public class EventMetadata: NSObject { public var type: EventMetadataType - public var data: String = "" + public var data: [String] = [] - init(type: EventMetadataType, data: String) { + init(type: EventMetadataType, data: [String]) { self.type = type self.data = data } diff --git a/SplitTests/SplitEventsManagerTest.swift b/SplitTests/SplitEventsManagerTest.swift index 2e5b99d23..ad666b4d1 100644 --- a/SplitTests/SplitEventsManagerTest.swift +++ b/SplitTests/SplitEventsManagerTest.swift @@ -268,11 +268,11 @@ class SplitEventsManagerTest: XCTestCase { let taskExp = XCTestExpectation() // Build Task - let metadata = EventMetadata(type: .FLAGS_KILLED, data: "TEST_FLAG") + let metadata = EventMetadata(type: .FLAGS_KILLED, data: ["TEST_FLAG"]) let handler: SplitActionWithMetadata = { handlerMetadata in XCTAssertEqual(metadata.type, handlerMetadata?.type) - XCTAssertEqual(metadata.data, "TEST_FLAG") + XCTAssertEqual(metadata.data, ["TEST_FLAG"]) taskExp.fulfill() } let task = SplitEventActionTask(action: handler, event: .sdkReady, runInBackground: false, factory: SplitFactoryStub(apiKey: IntegrationHelper.dummyApiKey), queue: nil) @@ -319,7 +319,7 @@ class SplitEventsManagerTest: XCTestCase { // Dummy event with metadata let metadataTypeToCheck: EventMetadataType = .FLAGS_KILLED - let metadataDataToCheck: String = "Test-flag-42" + let metadataDataToCheck: [String] = ["Test-flag-42"] let dummyMetadata = EventMetadata(type: metadataTypeToCheck, data: metadataDataToCheck) // This will be the task's "run()" From 712839adf0dda40da560d984dcd7d025a5ab92bb Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Wed, 25 Jun 2025 11:50:27 -0300 Subject: [PATCH 35/62] Adapted to new data type --- Split/FetcherEngine/Refresh/PeriodicSyncWorker.swift | 2 +- Split/FetcherEngine/Refresh/RetryableSyncWorker.swift | 4 ++-- Split/Localhost/LocalhostSynchronizer.swift | 2 +- Split/Network/Sync/FeatureFlagsSynchronizer.swift | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Split/FetcherEngine/Refresh/PeriodicSyncWorker.swift b/Split/FetcherEngine/Refresh/PeriodicSyncWorker.swift index d57777529..bd8ab4dec 100644 --- a/Split/FetcherEngine/Refresh/PeriodicSyncWorker.swift +++ b/Split/FetcherEngine/Refresh/PeriodicSyncWorker.swift @@ -189,7 +189,7 @@ class PeriodicSplitsSyncWorker: BasePeriodicSyncWorker { updatedFlags.append(flag) } - let metadata = EventMetadata(type: .FLAGS_UPDATED, data: updatedFlags.description) + let metadata = EventMetadata(type: .FLAGS_UPDATED, data: updatedFlags) notifyUpdate(.splitsUpdated, metadata) } } diff --git a/Split/FetcherEngine/Refresh/RetryableSyncWorker.swift b/Split/FetcherEngine/Refresh/RetryableSyncWorker.swift index a350bfe57..76523429e 100644 --- a/Split/FetcherEngine/Refresh/RetryableSyncWorker.swift +++ b/Split/FetcherEngine/Refresh/RetryableSyncWorker.swift @@ -144,7 +144,7 @@ class RetryableSplitsSyncWorker: BaseRetryableSyncWorker { let result = try syncHelper.sync(since: changeNumber, rbSince: rbChangeNumber, clearBeforeUpdate: false) if result.success { if !isSdkReadyTriggered() || result.featureFlagsUpdated.count > 0 { - let metadata = EventMetadata(type: .FLAGS_UPDATED, data: result.featureFlagsUpdated.description) + let metadata = EventMetadata(type: .FLAGS_UPDATED, data: result.featureFlagsUpdated ) notifyUpdate(.splitsUpdated, metadata: metadata) } resetBackoffCounter() @@ -220,7 +220,7 @@ class RetryableSplitsUpdateWorker: BaseRetryableSyncWorker { headers: ServiceConstants.controlNoCacheHeader) if result.success { if result.featureFlagsUpdated.count > 0 { - let metadata = EventMetadata(type: .FLAGS_UPDATED, data: result.featureFlagsUpdated.description) + let metadata = EventMetadata(type: .FLAGS_UPDATED, data: result.featureFlagsUpdated) notifyUpdate(.splitsUpdated, metadata: metadata) } resetBackoffCounter() diff --git a/Split/Localhost/LocalhostSynchronizer.swift b/Split/Localhost/LocalhostSynchronizer.swift index 457c4c00e..78fd2ed01 100644 --- a/Split/Localhost/LocalhostSynchronizer.swift +++ b/Split/Localhost/LocalhostSynchronizer.swift @@ -74,7 +74,7 @@ class LocalhostSynchronizer: FeatureFlagsSynchronizer { // Update will remove all records before insert new ones _ = self.featureFlagsStorage.update(splitChange: change) - self.eventsManager.notifyInternalEvent(.splitsUpdated, metadata: EventMetadata(type: .FLAGS_UPDATED, data: values.description)) + self.eventsManager.notifyInternalEvent(.splitsUpdated, metadata: EventMetadata(type: .FLAGS_UPDATED, data: values.map { $0.name ?? "" } )) } } } diff --git a/Split/Network/Sync/FeatureFlagsSynchronizer.swift b/Split/Network/Sync/FeatureFlagsSynchronizer.swift index 3d82b58a7..05db133bf 100644 --- a/Split/Network/Sync/FeatureFlagsSynchronizer.swift +++ b/Split/Network/Sync/FeatureFlagsSynchronizer.swift @@ -149,7 +149,7 @@ class DefaultFeatureFlagsSynchronizer: FeatureFlagsSynchronizer { } func notifyUpdated(flagsList: [String]) { - splitEventsManager.notifyInternalEvent(.splitsUpdated, metadata: EventMetadata(type: .FLAGS_UPDATED, data: flagsList.description)) + splitEventsManager.notifyInternalEvent(.splitsUpdated, metadata: EventMetadata(type: .FLAGS_UPDATED, data: flagsList)) } func pause() { From cc83434a073809c7edf8201542a78bbe8199c37d Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Wed, 25 Jun 2025 12:26:23 -0300 Subject: [PATCH 36/62] Initial commit --- .../Refresh/SplitsSyncHelper.swift | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/Split/FetcherEngine/Refresh/SplitsSyncHelper.swift b/Split/FetcherEngine/Refresh/SplitsSyncHelper.swift index 03c4f2efa..03c811121 100644 --- a/Split/FetcherEngine/Refresh/SplitsSyncHelper.swift +++ b/Split/FetcherEngine/Refresh/SplitsSyncHelper.swift @@ -13,7 +13,7 @@ struct SyncResult { let changeNumber: Int64 let rbChangeNumber: Int64? let featureFlagsUpdated: [String] - let rbsUpdated: Bool + let rbsUpdated: [String] } class SplitsSyncHelper { @@ -22,7 +22,7 @@ class SplitsSyncHelper { let till: Int64 let rbTill: Int64? let featureFlagsUpdated: [String] - let rbsUpdated: Bool + let rbsUpdated: [String] } private let splitFetcher: HttpSplitFetcher @@ -164,7 +164,7 @@ class SplitsSyncHelper { changeNumber: nextSince, rbChangeNumber: nextRbSince, featureFlagsUpdated: [], - rbsUpdated: false) + rbsUpdated: []) } func fetchUntil(since: Int64, @@ -178,7 +178,7 @@ class SplitsSyncHelper { var nextSince = since var nextRbSince = rbSince var featureFlagsUpdated: [String] = [] - var rbsUpdated = false + var rbsUpdated: [String] = [] while true { clearCache = clearCache && firstFetch // Determine which spec version to use and whether to include rbSince @@ -202,15 +202,19 @@ class SplitsSyncHelper { ruleBasedSegmentsStorage.clear() } firstFetch = false + + // FLAGS PROCESSING let processedSplits = splitChangeProcessor.process(flagsChange) if splitsStorage.update(splitChange: processedSplits) { featureFlagsUpdated += processedSplits.archivedSplits.compactMap(\.name) featureFlagsUpdated += processedSplits.activeSplits.compactMap(\.name) } - let processedChange = ruleBasedSegmentsChangeProcessor.process(targetingRulesChange.ruleBasedSegments) - if ruleBasedSegmentsStorage.update(toAdd: processedChange.toAdd, toRemove: processedChange.toRemove, changeNumber: processedChange.changeNumber) { - rbsUpdated = true + // RULE BASED SEGMENTS PROCESSING + let processedRuleBasedSegmentChange = ruleBasedSegmentsChangeProcessor.process(targetingRulesChange.ruleBasedSegments) + if ruleBasedSegmentsStorage.update(toAdd: processedRuleBasedSegmentChange.toAdd, toRemove: processedRuleBasedSegmentChange.toRemove, changeNumber: processedRuleBasedSegmentChange.changeNumber) { + rbsUpdated += processedRuleBasedSegmentChange.archivedSegments.compactMap(\.name) + rbsUpdated += processedRuleBasedSegmentChange.activeSegments.compactMap(\.name) } Logger.i("Feature flag definitions have been updated") From e41a5ca2ac68aabb78f52c236e6579fe951440b8 Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Wed, 25 Jun 2025 12:54:23 -0300 Subject: [PATCH 37/62] Tests and logic updated --- .../Refresh/ChangesChecker.swift | 7 +++++- .../Refresh/PeriodicSyncWorker.swift | 7 +++++- .../Refresh/RetryableSegmentsSyncWorker.swift | 23 +++++++++++-------- .../Fake/Service/ChangesCheckerMock.swift | 14 +++++++---- SplitTests/Helpers/TestingHelper.swift | 6 ++--- .../MySegments/SegmentsSyncHelperTests.swift | 8 +++---- 6 files changed, 42 insertions(+), 23 deletions(-) diff --git a/Split/FetcherEngine/Refresh/ChangesChecker.swift b/Split/FetcherEngine/Refresh/ChangesChecker.swift index 6c3e0d743..a20a23f46 100644 --- a/Split/FetcherEngine/Refresh/ChangesChecker.swift +++ b/Split/FetcherEngine/Refresh/ChangesChecker.swift @@ -22,6 +22,7 @@ protocol MySegmentsChangesChecker { func mySegmentsHaveChanged(old: SegmentChange, new: SegmentChange) -> Bool func mySegmentsHaveChanged(oldSegments: [Segment], newSegments: [Segment]) -> Bool func mySegmentsHaveChanged(oldSegments: [String], newSegments: [String]) -> Bool + func getSegmentsDiff(oldSegments: [Segment], newSegments: [Segment]) -> [String] } struct DefaultMySegmentsChangesChecker: MySegmentsChangesChecker { @@ -42,5 +43,9 @@ struct DefaultMySegmentsChangesChecker: MySegmentsChangesChecker { return !(oldSegments.count == newSegments.count && oldSegments.sorted() == newSegments.sorted()) } - + + func getSegmentsDiff(oldSegments: [Segment], newSegments: [Segment]) -> [String] { + oldSegments.filter { !Set(newSegments.map { $0.name }).contains($0.name) } + .map { $0.name } + } } diff --git a/Split/FetcherEngine/Refresh/PeriodicSyncWorker.swift b/Split/FetcherEngine/Refresh/PeriodicSyncWorker.swift index bd8ab4dec..975e8012c 100644 --- a/Split/FetcherEngine/Refresh/PeriodicSyncWorker.swift +++ b/Split/FetcherEngine/Refresh/PeriodicSyncWorker.swift @@ -229,8 +229,13 @@ class PeriodicMySegmentsSyncWorker: BasePeriodicSyncWorker { mlsTill: myLargeSegmentsStorage.changeNumber, headers: nil) if result.success { - if result.msUpdated || result.mlsUpdated { + if !result.msUpdated.isEmpty || !result.mlsUpdated.isEmpty { // For now is not necessary specify which entity was updated + var updatedSegments = result.msUpdated + result.mlsUpdated + for segment in updatedSegments { + updatedSegments.append(segment) + } + notifyUpdate(.mySegmentsUpdated) } } diff --git a/Split/FetcherEngine/Refresh/RetryableSegmentsSyncWorker.swift b/Split/FetcherEngine/Refresh/RetryableSegmentsSyncWorker.swift index d16c567ca..86cf76833 100644 --- a/Split/FetcherEngine/Refresh/RetryableSegmentsSyncWorker.swift +++ b/Split/FetcherEngine/Refresh/RetryableSegmentsSyncWorker.swift @@ -48,9 +48,9 @@ class RetryableMySegmentsSyncWorker: BaseRetryableSyncWorker { // Notifying both to trigger SDK Ready notifyUpdate(.mySegmentsUpdated) notifyUpdate(.myLargeSegmentsUpdated) - } else if result.msUpdated || result.mlsUpdated { + } else if !result.msUpdated.isEmpty || !result.mlsUpdated.isEmpty { // For now is not necessary specify which entity was updated - notifyUpdate(.mySegmentsUpdated) + notifyUpdate(.mySegmentsUpdated, metadata: EventMetadata(type: .SEGMENTS_UPDATED, data: result.mlsUpdated + result.msUpdated)) } return true } @@ -71,8 +71,8 @@ struct SegmentsSyncResult { let success: Bool let msChangeNumber: Int64 let mlsChangeNumber: Int64 - let msUpdated: Bool - let mlsUpdated: Bool + let msUpdated: [String] + let mlsUpdated: [String] } protocol SegmentsSyncHelper { @@ -86,8 +86,8 @@ class DefaultSegmentsSyncHelper: SegmentsSyncHelper { struct FetchResult { let msTill: Int64 let mlsTill: Int64 - let msUpdated: Bool - let mlsUdated: Bool + let msUpdated: [String] + let mlsUdated: [String] } private let segmentsFetcher: HttpMySegmentsFetcher @@ -175,8 +175,8 @@ class DefaultSegmentsSyncHelper: SegmentsSyncHelper { return SegmentsSyncResult(success: false, msChangeNumber: -1, mlsChangeNumber: -1, - msUpdated: false, - mlsUpdated: false) + msUpdated: [], + mlsUpdated: []) } private func fetchUntil(till: Int64?, @@ -210,10 +210,13 @@ class DefaultSegmentsSyncHelper: SegmentsSyncHelper { Logger.d("Checking my large segments update") checkAndUpdate(isChanged: mlsChanged, change: myLargeSegmentsChange, storage: myLargeSegmentsStorage) + let segmentsDiff = changeChecker.getSegmentsDiff(oldSegments: oldChange.segments, newSegments: mySegmentsChange.segments) + let largeSegmentsDiff = changeChecker.getSegmentsDiff(oldSegments: oldLargeChange.segments, newSegments: myLargeSegmentsChange.segments) + return FetchResult(msTill: mySegmentsChange.unwrappedChangeNumber, mlsTill: myLargeSegmentsChange.unwrappedChangeNumber, - msUpdated: msChanged, - mlsUdated: mlsChanged) + msUpdated: segmentsDiff, + mlsUdated: largeSegmentsDiff) } prevChange = change } diff --git a/SplitTests/Fake/Service/ChangesCheckerMock.swift b/SplitTests/Fake/Service/ChangesCheckerMock.swift index 0b6b0f1e5..07feb90aa 100644 --- a/SplitTests/Fake/Service/ChangesCheckerMock.swift +++ b/SplitTests/Fake/Service/ChangesCheckerMock.swift @@ -11,23 +11,29 @@ import Foundation @testable import Split class MySegmentsChangesCheckerMock: MySegmentsChangesChecker { + var haveChanged = false + var segmentsDiff = [String]() func mySegmentsHaveChanged(old: SegmentChange, new: SegmentChange) -> Bool { - return haveChanged + haveChanged } func mySegmentsHaveChanged(oldSegments old: [Segment], newSegments new: [Segment]) -> Bool { - return haveChanged + haveChanged } func mySegmentsHaveChanged(oldSegments old: [String], newSegments new: [String]) -> Bool { - return haveChanged + haveChanged + } + + func getSegmentsDiff(oldSegments: [Segment], newSegments: [Segment]) -> [String] { + segmentsDiff } } struct SplitsChangesCheckerMock: SplitsChangesChecker { var haveChanged = false func splitsHaveChanged(oldChangeNumber: Int64, newChangeNumber: Int64) -> Bool { - return haveChanged + haveChanged } } diff --git a/SplitTests/Helpers/TestingHelper.swift b/SplitTests/Helpers/TestingHelper.swift index c908d1cd7..ceefc5ffc 100644 --- a/SplitTests/Helpers/TestingHelper.swift +++ b/SplitTests/Helpers/TestingHelper.swift @@ -214,7 +214,7 @@ struct TestingHelper { static func buildSegmentsChange(count: Int64 = 5, msAscOrder: Bool = true, mlsAscOrder: Bool = true, - segmentsChanged: Bool = false) -> [AllSegmentsChange] { + segmentsChanged: [String]) -> [AllSegmentsChange] { // Eventualy cn will be greater than the first let baseCn: Int64 = 100 let lastMsCn = baseCn * count + 1 @@ -233,7 +233,7 @@ struct TestingHelper { res.append(newAllSegmentsChange(ms: msSeg, msCn: msC, mls: mlsSeg, mlsCn: mlsC)) } - if segmentsChanged { + if !segmentsChanged.isEmpty { msSeg.append("s3") mlsSeg.append("sl3") } @@ -261,7 +261,7 @@ struct TestingHelper { static func segmentsSyncResult(_ result: Bool = true, msCn: Int64 = 300, mlsCn: Int64 = 400, - msUpd: Bool = true, mlsUpd: Bool = true) -> SegmentsSyncResult { + msUpd: [String] = ["SegmentTest1"], mlsUpd: [String] = ["LargeSegmentTest1"]) -> SegmentsSyncResult { return SegmentsSyncResult(success: result, msChangeNumber: msCn, mlsChangeNumber: mlsCn, msUpdated: msUpd, mlsUpdated: mlsUpd) diff --git a/SplitTests/Service/MySegments/SegmentsSyncHelperTests.swift b/SplitTests/Service/MySegments/SegmentsSyncHelperTests.swift index 395e88c25..d9b20665b 100644 --- a/SplitTests/Service/MySegments/SegmentsSyncHelperTests.swift +++ b/SplitTests/Service/MySegments/SegmentsSyncHelperTests.swift @@ -40,18 +40,18 @@ class SegmentsSyncHelperTests: XCTestCase { } func testCdnByPassNoTillNoChange() throws { - try cdnByPassNoTill(segmentsChanged: false) + try cdnByPassNoTill(segmentsChanged: []) } func testCdnByPassNoTillChange() throws { - try cdnByPassNoTill(segmentsChanged: true) + try cdnByPassNoTill(segmentsChanged: ["Segment1"]) } - func cdnByPassNoTill(segmentsChanged: Bool) throws { + func cdnByPassNoTill(segmentsChanged: [String]) throws { let goalCn: Int64 = 300 mySegmentsStorage.changeNumber = 200 myLargeSegmentsStorage.changeNumber = 200 - changeChecker.haveChanged = segmentsChanged + changeChecker.haveChanged = segmentsChanged.isEmpty let exp = XCTestExpectation() mySegmentsFetcher.countExp = exp From b69b9012cebe572e7b7246edb34468c5fdf944b5 Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Wed, 25 Jun 2025 15:25:08 -0300 Subject: [PATCH 38/62] Code reviews corrections --- Split/FetcherEngine/Refresh/ChangesChecker.swift | 4 ++-- Split/FetcherEngine/Refresh/PeriodicSyncWorker.swift | 10 +++------- .../Refresh/RetryableSegmentsSyncWorker.swift | 6 +++--- SplitTests/Helpers/TestingHelper.swift | 7 +++++-- .../Service/MySegments/SegmentsSyncHelperTests.swift | 3 ++- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Split/FetcherEngine/Refresh/ChangesChecker.swift b/Split/FetcherEngine/Refresh/ChangesChecker.swift index a20a23f46..adae12b3c 100644 --- a/Split/FetcherEngine/Refresh/ChangesChecker.swift +++ b/Split/FetcherEngine/Refresh/ChangesChecker.swift @@ -45,7 +45,7 @@ struct DefaultMySegmentsChangesChecker: MySegmentsChangesChecker { } func getSegmentsDiff(oldSegments: [Segment], newSegments: [Segment]) -> [String] { - oldSegments.filter { !Set(newSegments.map { $0.name }).contains($0.name) } - .map { $0.name } + let result = oldSegments.filter { !Set(newSegments.map { $0.name }).contains($0.name) }.map { $0.name } + return result } } diff --git a/Split/FetcherEngine/Refresh/PeriodicSyncWorker.swift b/Split/FetcherEngine/Refresh/PeriodicSyncWorker.swift index 975e8012c..b2a6fab87 100644 --- a/Split/FetcherEngine/Refresh/PeriodicSyncWorker.swift +++ b/Split/FetcherEngine/Refresh/PeriodicSyncWorker.swift @@ -130,7 +130,7 @@ class BasePeriodicSyncWorker: PeriodicSyncWorker { Logger.i("Fetch from remote not implemented") } - func notifyUpdate(_ event: SplitInternalEvent, _ metadata: EventMetadata? = nil) { + func notifyUpdate(_ event: SplitInternalEvent, metadata: EventMetadata? = nil) { eventsManager.notifyInternalEvent(event, metadata: metadata) } } @@ -190,7 +190,7 @@ class PeriodicSplitsSyncWorker: BasePeriodicSyncWorker { } let metadata = EventMetadata(type: .FLAGS_UPDATED, data: updatedFlags) - notifyUpdate(.splitsUpdated, metadata) + notifyUpdate(.splitsUpdated, metadata: metadata) } } } @@ -232,11 +232,7 @@ class PeriodicMySegmentsSyncWorker: BasePeriodicSyncWorker { if !result.msUpdated.isEmpty || !result.mlsUpdated.isEmpty { // For now is not necessary specify which entity was updated var updatedSegments = result.msUpdated + result.mlsUpdated - for segment in updatedSegments { - updatedSegments.append(segment) - } - - notifyUpdate(.mySegmentsUpdated) + notifyUpdate(.mySegmentsUpdated, metadata: EventMetadata(type: .SEGMENTS_UPDATED, data: updatedSegments)) } } } catch { diff --git a/Split/FetcherEngine/Refresh/RetryableSegmentsSyncWorker.swift b/Split/FetcherEngine/Refresh/RetryableSegmentsSyncWorker.swift index 86cf76833..3c6aab142 100644 --- a/Split/FetcherEngine/Refresh/RetryableSegmentsSyncWorker.swift +++ b/Split/FetcherEngine/Refresh/RetryableSegmentsSyncWorker.swift @@ -87,7 +87,7 @@ class DefaultSegmentsSyncHelper: SegmentsSyncHelper { let msTill: Int64 let mlsTill: Int64 let msUpdated: [String] - let mlsUdated: [String] + let mlsUpdated: [String] } private let segmentsFetcher: HttpMySegmentsFetcher @@ -165,7 +165,7 @@ class DefaultSegmentsSyncHelper: SegmentsSyncHelper { msChangeNumber: result.msTill, mlsChangeNumber: result.mlsTill, msUpdated: result.msUpdated, - mlsUpdated: result.mlsUdated) + mlsUpdated: result.mlsUpdated) } attemptCount+=1 if attemptCount < maxAttempts { @@ -216,7 +216,7 @@ class DefaultSegmentsSyncHelper: SegmentsSyncHelper { return FetchResult(msTill: mySegmentsChange.unwrappedChangeNumber, mlsTill: myLargeSegmentsChange.unwrappedChangeNumber, msUpdated: segmentsDiff, - mlsUdated: largeSegmentsDiff) + mlsUpdated: largeSegmentsDiff) } prevChange = change } diff --git a/SplitTests/Helpers/TestingHelper.swift b/SplitTests/Helpers/TestingHelper.swift index ceefc5ffc..a3bd9184b 100644 --- a/SplitTests/Helpers/TestingHelper.swift +++ b/SplitTests/Helpers/TestingHelper.swift @@ -234,9 +234,12 @@ struct TestingHelper { mls: mlsSeg, mlsCn: mlsC)) } if !segmentsChanged.isEmpty { - msSeg.append("s3") - mlsSeg.append("sl3") + segmentsChanged.forEach { segment in + msSeg.append(segment) + } } + msSeg.append("s3") + mlsSeg.append("sl3") res.append(newAllSegmentsChange(ms: msSeg, msCn: lastMsCn, mls: mlsSeg, mlsCn: lastMlsCn)) diff --git a/SplitTests/Service/MySegments/SegmentsSyncHelperTests.swift b/SplitTests/Service/MySegments/SegmentsSyncHelperTests.swift index d9b20665b..481ca282e 100644 --- a/SplitTests/Service/MySegments/SegmentsSyncHelperTests.swift +++ b/SplitTests/Service/MySegments/SegmentsSyncHelperTests.swift @@ -51,7 +51,8 @@ class SegmentsSyncHelperTests: XCTestCase { let goalCn: Int64 = 300 mySegmentsStorage.changeNumber = 200 myLargeSegmentsStorage.changeNumber = 200 - changeChecker.haveChanged = segmentsChanged.isEmpty + changeChecker.haveChanged = !segmentsChanged.isEmpty + changeChecker.segmentsDiff = segmentsChanged let exp = XCTestExpectation() mySegmentsFetcher.countExp = exp From bc56f40c5eb7886dd674e76cc4ad4a0206930791 Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Wed, 25 Jun 2025 15:26:17 -0300 Subject: [PATCH 39/62] Update ChangesChecker.swift --- Split/FetcherEngine/Refresh/ChangesChecker.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Split/FetcherEngine/Refresh/ChangesChecker.swift b/Split/FetcherEngine/Refresh/ChangesChecker.swift index adae12b3c..cb4e6a5df 100644 --- a/Split/FetcherEngine/Refresh/ChangesChecker.swift +++ b/Split/FetcherEngine/Refresh/ChangesChecker.swift @@ -45,7 +45,6 @@ struct DefaultMySegmentsChangesChecker: MySegmentsChangesChecker { } func getSegmentsDiff(oldSegments: [Segment], newSegments: [Segment]) -> [String] { - let result = oldSegments.filter { !Set(newSegments.map { $0.name }).contains($0.name) }.map { $0.name } - return result + oldSegments.filter { !Set(newSegments.map { $0.name }).contains($0.name) }.map { $0.name } } } From 11a5fdbe35be68b2e24d0ab7d4ba39e0e5370532 Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Wed, 25 Jun 2025 15:26:48 -0300 Subject: [PATCH 40/62] Update ChangesChecker.swift --- Split/FetcherEngine/Refresh/ChangesChecker.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Split/FetcherEngine/Refresh/ChangesChecker.swift b/Split/FetcherEngine/Refresh/ChangesChecker.swift index cb4e6a5df..c067c0309 100644 --- a/Split/FetcherEngine/Refresh/ChangesChecker.swift +++ b/Split/FetcherEngine/Refresh/ChangesChecker.swift @@ -43,7 +43,7 @@ struct DefaultMySegmentsChangesChecker: MySegmentsChangesChecker { return !(oldSegments.count == newSegments.count && oldSegments.sorted() == newSegments.sorted()) } - + func getSegmentsDiff(oldSegments: [Segment], newSegments: [Segment]) -> [String] { oldSegments.filter { !Set(newSegments.map { $0.name }).contains($0.name) }.map { $0.name } } From d7d811cbce98e310c6b50c963cfffc8cd1665aad Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Wed, 25 Jun 2025 19:21:31 -0300 Subject: [PATCH 41/62] Code review corrections --- .../Refresh/RetryableSyncWorker.swift | 18 +++---- .../Refresh/SplitsSyncHelper.swift | 3 +- .../Network/Streaming/SyncUpdateWorker.swift | 49 ++++++++----------- 3 files changed, 30 insertions(+), 40 deletions(-) diff --git a/Split/FetcherEngine/Refresh/RetryableSyncWorker.swift b/Split/FetcherEngine/Refresh/RetryableSyncWorker.swift index 76523429e..016154455 100644 --- a/Split/FetcherEngine/Refresh/RetryableSyncWorker.swift +++ b/Split/FetcherEngine/Refresh/RetryableSyncWorker.swift @@ -79,12 +79,9 @@ class BaseRetryableSyncWorker: RetryableSyncWorker { } } - func notifyUpdate(_ event: SplitInternalEvent) { - eventsManager.notifyInternalEvent(event, metadata: nil) - } - - func notifyUpdate(_ event: SplitInternalEvent, metadata: EventMetadata? = nil) { - eventsManager.notifyInternalEvent(event, metadata: metadata) + func notifyUpdate(_ result: SyncResult) { + let metadata = EventMetadata(type: .FLAGS_UPDATED, data: result.featureFlagsUpdated) + eventsManager.notifyInternalEvent(.splitsUpdated, metadata: metadata) } func isSdkReadyTriggered() -> Bool { @@ -144,8 +141,7 @@ class RetryableSplitsSyncWorker: BaseRetryableSyncWorker { let result = try syncHelper.sync(since: changeNumber, rbSince: rbChangeNumber, clearBeforeUpdate: false) if result.success { if !isSdkReadyTriggered() || result.featureFlagsUpdated.count > 0 { - let metadata = EventMetadata(type: .FLAGS_UPDATED, data: result.featureFlagsUpdated ) - notifyUpdate(.splitsUpdated, metadata: metadata) + notifyUpdate(result) } resetBackoffCounter() return true @@ -213,15 +209,17 @@ class RetryableSplitsUpdateWorker: BaseRetryableSyncWorker { } do { + // Try Sync let result = try syncHelper.sync(since: storedChangeNumber, rbSince: storedRbChangeNumber, till: flagsChangeNumber ?? rbsChangeNumber, clearBeforeUpdate: false, headers: ServiceConstants.controlNoCacheHeader) + + // Success if result.success { if result.featureFlagsUpdated.count > 0 { - let metadata = EventMetadata(type: .FLAGS_UPDATED, data: result.featureFlagsUpdated) - notifyUpdate(.splitsUpdated, metadata: metadata) + notifyUpdate(result) } resetBackoffCounter() return true diff --git a/Split/FetcherEngine/Refresh/SplitsSyncHelper.swift b/Split/FetcherEngine/Refresh/SplitsSyncHelper.swift index 03c4f2efa..db0133dba 100644 --- a/Split/FetcherEngine/Refresh/SplitsSyncHelper.swift +++ b/Split/FetcherEngine/Refresh/SplitsSyncHelper.swift @@ -204,8 +204,7 @@ class SplitsSyncHelper { firstFetch = false let processedSplits = splitChangeProcessor.process(flagsChange) if splitsStorage.update(splitChange: processedSplits) { - featureFlagsUpdated += processedSplits.archivedSplits.compactMap(\.name) - featureFlagsUpdated += processedSplits.activeSplits.compactMap(\.name) + featureFlagsUpdated = processedSplits.archivedSplits.compactMap(\.name) + processedSplits.activeSplits.compactMap(\.name) } let processedChange = ruleBasedSegmentsChangeProcessor.process(targetingRulesChange.ruleBasedSegments) diff --git a/Split/Network/Streaming/SyncUpdateWorker.swift b/Split/Network/Streaming/SyncUpdateWorker.swift index bedbde42d..05a2d21cb 100644 --- a/Split/Network/Streaming/SyncUpdateWorker.swift +++ b/Split/Network/Streaming/SyncUpdateWorker.swift @@ -124,14 +124,11 @@ class SplitsUpdateWorker: UpdateWorker { } /// Process a split update notification - private func processSplitUpdate(payload: String, - compressionType: CompressionType, - previousChangeNumber: Int64, - changeNumber: Int64) -> Bool { + private func processSplitUpdate(payload: String,compressionType: CompressionType, + previousChangeNumber: Int64, + changeNumber: Int64) -> Bool { do { - let split = try self.payloadDecoder.decode( - payload: payload, - compressionUtil: self.decomProvider.decompressor(for: compressionType)) + let split = try payloadDecoder.decode(payload: payload, compressionUtil: decomProvider.decompressor(for: compressionType)) if !allRuleBasedSegmentsExist(in: split) { return false @@ -143,16 +140,14 @@ class SplitsUpdateWorker: UpdateWorker { Logger.v("Split update received: \(change)") - let processedFlags = self.splitChangeProcessor.process(change) - - if self.splitsStorage.update(splitChange: processedFlags) { - var updatedFlags: [String] = processedFlags.activeSplits.compactMap(\.name) - updatedFlags += processedFlags.archivedSplits.compactMap(\.name) - self.synchronizer.notifyFeatureFlagsUpdated(flagsList: updatedFlags) - + let processedFlags = splitChangeProcessor.process(change) + + if splitsStorage.update(splitChange: processedFlags) { + let updatedFlags = processedFlags.activeSplits.compactMap(\.name) + processedFlags.archivedSplits.compactMap(\.name) + synchronizer.notifyFeatureFlagsUpdated(flagsList: updatedFlags) } - self.telemetryProducer?.recordUpdatesFromSse(type: .splits) + telemetryProducer?.recordUpdatesFromSse(type: .splits) return true } catch { Logger.e("Error decoding feature flags payload from notification: \(error)") @@ -162,29 +157,27 @@ class SplitsUpdateWorker: UpdateWorker { /// Process a rule-based segment update notification private func processRuleBasedSegmentUpdate(payload: String, - compressionType: CompressionType, - previousChangeNumber: Int64, - changeNumber: Int64) -> Bool { + compressionType: CompressionType, + previousChangeNumber: Int64, + changeNumber: Int64) -> Bool { do { - let rbs = try self.ruleBasedSegmentsPayloadDecoder.decode( - payload: payload, - compressionUtil: self.decomProvider.decompressor(for: compressionType)) + let rbs = try ruleBasedSegmentsPayloadDecoder.decode(payload: payload, compressionUtil: decomProvider.decompressor(for: compressionType)) let change = RuleBasedSegmentChange(segments: [rbs], - since: previousChangeNumber, - till: changeNumber) + since: previousChangeNumber, + till: changeNumber) Logger.v("RBS update received: \(change)") let processedChange = ruleBasedSegmentsChangeProcessor.process(change) - if self.ruleBasedSegmentsStorage.update(toAdd: processedChange.toAdd, - toRemove: processedChange.toRemove, - changeNumber: processedChange.changeNumber) { - self.synchronizer.notifyFeatureFlagsUpdated(flagsList: []) //TODO: RBS Update + if ruleBasedSegmentsStorage.update(toAdd: processedChange.toAdd, + toRemove: processedChange.toRemove, + changeNumber: processedChange.changeNumber) { + synchronizer.notifyFeatureFlagsUpdated(flagsList: []) //TODO: RBS Update } - self.telemetryProducer?.recordUpdatesFromSse(type: .splits) + telemetryProducer?.recordUpdatesFromSse(type: .splits) return true } catch { Logger.e("Error decoding rule based segments payload from notification: \(error)") From c37697c22ea01d887ae72bacef9b7c07629164b5 Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Wed, 25 Jun 2025 19:40:23 -0300 Subject: [PATCH 42/62] Method extracted --- .../FetcherEngine/Refresh/RetryableSyncWorker.swift | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Split/FetcherEngine/Refresh/RetryableSyncWorker.swift b/Split/FetcherEngine/Refresh/RetryableSyncWorker.swift index 016154455..014dd1696 100644 --- a/Split/FetcherEngine/Refresh/RetryableSyncWorker.swift +++ b/Split/FetcherEngine/Refresh/RetryableSyncWorker.swift @@ -79,9 +79,13 @@ class BaseRetryableSyncWorker: RetryableSyncWorker { } } - func notifyUpdate(_ result: SyncResult) { + func notifyFlagsUpdate(_ result: SyncResult) { let metadata = EventMetadata(type: .FLAGS_UPDATED, data: result.featureFlagsUpdated) - eventsManager.notifyInternalEvent(.splitsUpdated, metadata: metadata) + notifyUpdate(.splitsUpdated, metadata: metadata) + } + + func notifyUpdate(_ event: SplitInternalEvent, metadata: EventMetadata? = nil) { + eventsManager.notifyInternalEvent(event, metadata: metadata) } func isSdkReadyTriggered() -> Bool { @@ -141,7 +145,7 @@ class RetryableSplitsSyncWorker: BaseRetryableSyncWorker { let result = try syncHelper.sync(since: changeNumber, rbSince: rbChangeNumber, clearBeforeUpdate: false) if result.success { if !isSdkReadyTriggered() || result.featureFlagsUpdated.count > 0 { - notifyUpdate(result) + notifyFlagsUpdate(result) } resetBackoffCounter() return true @@ -219,7 +223,7 @@ class RetryableSplitsUpdateWorker: BaseRetryableSyncWorker { // Success if result.success { if result.featureFlagsUpdated.count > 0 { - notifyUpdate(result) + notifyFlagsUpdate(result) } resetBackoffCounter() return true From 519f4d08f79d2a82f411518d3034ca5817c25781 Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Thu, 26 Jun 2025 10:48:42 -0300 Subject: [PATCH 43/62] Changing branch --- Split/Events/SplitEventsManager.swift | 58 +++++++++---------- Split/Events/SplitInternalEvent.swift | 1 + .../FeatureFlagsPayloadDecoder.swift | 4 +- .../Network/Streaming/SyncUpdateWorker.swift | 19 +++--- Split/Network/Sync/ByKeyFacade.swift | 9 +++ Split/Network/Sync/Synchronizer.swift | 7 +++ 6 files changed, 58 insertions(+), 40 deletions(-) diff --git a/Split/Events/SplitEventsManager.swift b/Split/Events/SplitEventsManager.swift index 8406866c8..859ec1d9b 100644 --- a/Split/Events/SplitEventsManager.swift +++ b/Split/Events/SplitEventsManager.swift @@ -155,37 +155,37 @@ class DefaultSplitEventsManager: SplitEventsManager { } private func processEvents() { + while isRunning() { - guard let event = takeEvent() else { - return - } - self.triggered.append(event.type) + guard let event = takeEvent() else { return } + triggered.append(event.type) + switch event.type { - case .splitsUpdated, .mySegmentsUpdated, .myLargeSegmentsUpdated: - if isTriggered(external: .sdkReady) { - trigger(event: .sdkUpdated, metadata: event.metadata) - 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: .sdkReadyFromCache) - } - case .splitKilledNotification: - if isTriggered(external: .sdkReady) { - trigger(event: .sdkUpdated, metadata: event.metadata) - continue - } - case .sdkReadyTimeoutReached: - if !isTriggered(external: .sdkReady) { - trigger(event: .sdkReadyTimedOut) - } + case .splitsUpdated, .mySegmentsUpdated, .myLargeSegmentsUpdated, .ruleBasedSegmentsUpdated: + if isTriggered(external: .sdkReady) { + trigger(event: .sdkUpdated, metadata: event.metadata) + 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: .sdkReadyFromCache) + } + case .splitKilledNotification: + if isTriggered(external: .sdkReady) { + trigger(event: .sdkUpdated, metadata: event.metadata) + continue + } + case .sdkReadyTimeoutReached: + if !isTriggered(external: .sdkReady) { + trigger(event: .sdkReadyTimedOut) + } } } } diff --git a/Split/Events/SplitInternalEvent.swift b/Split/Events/SplitInternalEvent.swift index 573a39c4f..f4e9a9b19 100644 --- a/Split/Events/SplitInternalEvent.swift +++ b/Split/Events/SplitInternalEvent.swift @@ -15,6 +15,7 @@ struct SplitInternalEventWithMetadata: Equatable { enum SplitInternalEvent { case mySegmentsUpdated case myLargeSegmentsUpdated + case ruleBasedSegmentsUpdated case splitsUpdated case mySegmentsLoadedFromCache case myLargeSegmentsLoadedFromCache diff --git a/Split/Network/Streaming/FeatureFlagsPayloadDecoder.swift b/Split/Network/Streaming/FeatureFlagsPayloadDecoder.swift index b28aa8803..64a4cf7f1 100644 --- a/Split/Network/Streaming/FeatureFlagsPayloadDecoder.swift +++ b/Split/Network/Streaming/FeatureFlagsPayloadDecoder.swift @@ -36,5 +36,5 @@ class DefaultTargetingRulePayloadDecoder: TargetingRulePayloadDeco } } -typealias DefaultFeatureFlagsPayloadDecoder = DefaultTargetingRulePayloadDecoder -typealias DefaultRuleBasedSegmentsPayloadDecoder = DefaultTargetingRulePayloadDecoder +typealias FeatureFlagsPayloadDecoder = DefaultTargetingRulePayloadDecoder +typealias RuleBasedSegmentsPayloadDecoder = DefaultTargetingRulePayloadDecoder diff --git a/Split/Network/Streaming/SyncUpdateWorker.swift b/Split/Network/Streaming/SyncUpdateWorker.swift index 9806f0b89..c6bd36247 100644 --- a/Split/Network/Streaming/SyncUpdateWorker.swift +++ b/Split/Network/Streaming/SyncUpdateWorker.swift @@ -31,8 +31,8 @@ class SplitsUpdateWorker: UpdateWorker { private let ruleBasedSegmentsStorage: RuleBasedSegmentsStorage private let splitChangeProcessor: SplitChangeProcessor private let ruleBasedSegmentsChangeProcessor: RuleBasedSegmentChangeProcessor - private let payloadDecoder: DefaultFeatureFlagsPayloadDecoder - private let ruleBasedSegmentsPayloadDecoder: DefaultRuleBasedSegmentsPayloadDecoder + private let payloadDecoder: FeatureFlagsPayloadDecoder + private let ruleBasedSegmentsPayloadDecoder: RuleBasedSegmentsPayloadDecoder private let telemetryProducer: TelemetryRuntimeProducer? var decomProvider: CompressionProvider = DefaultDecompressionProvider() @@ -41,8 +41,8 @@ class SplitsUpdateWorker: UpdateWorker { ruleBasedSegmentsStorage: RuleBasedSegmentsStorage, splitChangeProcessor: SplitChangeProcessor, ruleBasedSegmentsChangeProcessor: RuleBasedSegmentChangeProcessor, - featureFlagsPayloadDecoder: DefaultFeatureFlagsPayloadDecoder, - ruleBasedSegmentsPayloadDecoder: DefaultRuleBasedSegmentsPayloadDecoder, + featureFlagsPayloadDecoder: FeatureFlagsPayloadDecoder, + ruleBasedSegmentsPayloadDecoder: RuleBasedSegmentsPayloadDecoder, telemetryProducer: TelemetryRuntimeProducer?) { self.synchronizer = synchronizer self.splitsStorage = splitsStorage @@ -170,8 +170,8 @@ class SplitsUpdateWorker: UpdateWorker { compressionUtil: decomProvider.decompressor(for: compressionType)) let change = RuleBasedSegmentChange(segments: [rbs], - since: previousChangeNumber, - till: changeNumber) + since: previousChangeNumber, + till: changeNumber) Logger.v("RBS update received: \(change)") @@ -180,9 +180,10 @@ class SplitsUpdateWorker: UpdateWorker { if ruleBasedSegmentsStorage.update(toAdd: processedSegments.toAdd, toRemove: processedSegments.toRemove, changeNumber: processedSegments.changeNumber) { - var updatedSegments: [String] = processedSegments.activeSegments.compactMap(\.name) - updatedSegments += processedSegments.archivedSegments.compactMap(\.name) - synchronizer.notifyFeatureFlagsUpdated(flagsList: []) //TODO: Make new notify segments updated (new notification method?) + + let updatedSegments = processedSegments.activeSegments.compactMap(\.name) + processedSegments.archivedSegments.compactMap(\.name) + synchronizer.notifyRuleBasedSegmentsUpdated(forKey: <#T##String#>, metadata: EventMetadata(type: .RULE_BASED_SEGMENTS_UPDATED, data: updatedSegments)) + //synchronizer.notifyFeatureFlagsUpdated(flagsList: []) //TODO: New notify RBS updated (new notification method?) } telemetryProducer?.recordUpdatesFromSse(type: .splits) diff --git a/Split/Network/Sync/ByKeyFacade.swift b/Split/Network/Sync/ByKeyFacade.swift index 36100ad1c..29b4d0f96 100644 --- a/Split/Network/Sync/ByKeyFacade.swift +++ b/Split/Network/Sync/ByKeyFacade.swift @@ -21,6 +21,7 @@ protocol ByKeySynchronizer { func loadAttributesFromCache(forKey: String) func notifyMySegmentsUpdated(forKey: String, metadata: EventMetadata?) func notifyMyLargeSegmentsUpdated(forKey: String, metadata: EventMetadata?) + func notifyRuleBasedSegmentsUpdated(forKey: String, metadata: EventMetadata?) func startSync(forKey key: Key) func startPeriodicSync() func stopPeriodicSync() @@ -131,7 +132,14 @@ class DefaultByKeyFacade: ByKeyFacade { group.eventsManager.notifyInternalEvent(.myLargeSegmentsUpdated, metadata: metadata) } } + + func notifyRuleBasedSegmentsUpdated(forKey key: String, metadata: EventMetadata?) { + doInAll(forMatchingKey: key) { group in + group.eventsManager.notifyInternalEvent(.ruleBasedSegmentsUpdated, metadata: metadata) + } + } + // MARK: Cycle func pause() { doInAll { group in group.mySegmentsSynchronizer.pause() @@ -171,6 +179,7 @@ class DefaultByKeyFacade: ByKeyFacade { return byKeyComponents.count == 0 } + // MARK: Helpers private func doInAll(_ action: (ByKeyComponentGroup) -> Void) { let all = byKeyComponents.all for (_, sync) in all { diff --git a/Split/Network/Sync/Synchronizer.swift b/Split/Network/Sync/Synchronizer.swift index 5b0ec75ed..df88128e8 100644 --- a/Split/Network/Sync/Synchronizer.swift +++ b/Split/Network/Sync/Synchronizer.swift @@ -23,6 +23,7 @@ protocol Synchronizer: ImpressionLogger { func synchronizeMySegments(forKey key: String) func synchronizeTelemetryConfig() func forceMySegmentsSync(forKey key: String, changeNumbers: SegmentsChangeNumber, delay: Int64) + func startPeriodicFetching() func stopPeriodicFetching() func startRecordingUserData() @@ -30,10 +31,12 @@ protocol Synchronizer: ImpressionLogger { func startRecordingTelemetry() func stopRecordingTelemetry() func pushEvent(event: EventDTO) + func notifyFeatureFlagsUpdated(flagsList: [String]) func notifySegmentsUpdated(forKey key: String, metadata: EventMetadata?) func notifyLargeSegmentsUpdated(forKey key: String, metadata: EventMetadata?) func notifySplitKilled(flag: String) + func pause() func resume() func flush() @@ -218,6 +221,10 @@ class DefaultSynchronizer: Synchronizer { func notifyLargeSegmentsUpdated(forKey key: String, metadata: EventMetadata? = nil) { byKeySynchronizer.notifyMyLargeSegmentsUpdated(forKey: key, metadata: metadata) } + + func notifyRuleBasedSegmentsUpdated(forKey key: String, metadata: EventMetadata? = nil) { + byKeySynchronizer.notifyRuleBasedSegmentsUpdated(forKey: key, metadata: metadata) + } func notifySplitKilled(flag: String) { featureFlagsSynchronizer.notifyKilled(flag: flag) From 20b45b48a748919f2308120eef865ba9b4fc82a4 Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Thu, 26 Jun 2025 10:57:17 -0300 Subject: [PATCH 44/62] Logic simplified --- Split/FetcherEngine/Refresh/SplitsSyncHelper.swift | 2 +- Split/Network/Streaming/SyncUpdateWorker.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Split/FetcherEngine/Refresh/SplitsSyncHelper.swift b/Split/FetcherEngine/Refresh/SplitsSyncHelper.swift index db0133dba..bd37d725f 100644 --- a/Split/FetcherEngine/Refresh/SplitsSyncHelper.swift +++ b/Split/FetcherEngine/Refresh/SplitsSyncHelper.swift @@ -204,7 +204,7 @@ class SplitsSyncHelper { firstFetch = false let processedSplits = splitChangeProcessor.process(flagsChange) if splitsStorage.update(splitChange: processedSplits) { - featureFlagsUpdated = processedSplits.archivedSplits.compactMap(\.name) + processedSplits.activeSplits.compactMap(\.name) + featureFlagsUpdated = (processedSplits.archivedSplits + processedSplits.activeSplits).compactMap(\.name) } let processedChange = ruleBasedSegmentsChangeProcessor.process(targetingRulesChange.ruleBasedSegments) diff --git a/Split/Network/Streaming/SyncUpdateWorker.swift b/Split/Network/Streaming/SyncUpdateWorker.swift index 05a2d21cb..56d3f6232 100644 --- a/Split/Network/Streaming/SyncUpdateWorker.swift +++ b/Split/Network/Streaming/SyncUpdateWorker.swift @@ -143,7 +143,7 @@ class SplitsUpdateWorker: UpdateWorker { let processedFlags = splitChangeProcessor.process(change) if splitsStorage.update(splitChange: processedFlags) { - let updatedFlags = processedFlags.activeSplits.compactMap(\.name) + processedFlags.archivedSplits.compactMap(\.name) + let updatedFlags = (processedFlags.activeSplits + processedFlags.archivedSplits).compactMap(\.name) synchronizer.notifyFeatureFlagsUpdated(flagsList: updatedFlags) } From b657a928271ae6db703d74388047450df5d6ec6c Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Thu, 26 Jun 2025 10:59:29 -0300 Subject: [PATCH 45/62] Logic simplified --- Split/Network/Streaming/SyncUpdateWorker.swift | 4 ++-- Split/Network/Sync/Synchronizer.swift | 6 +++--- SplitTests/Fake/Streaming/SynchronizerSpy.swift | 4 ++-- SplitTests/Fake/Streaming/SynchronizerStub.swift | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Split/Network/Streaming/SyncUpdateWorker.swift b/Split/Network/Streaming/SyncUpdateWorker.swift index 56d3f6232..7637230e6 100644 --- a/Split/Network/Streaming/SyncUpdateWorker.swift +++ b/Split/Network/Streaming/SyncUpdateWorker.swift @@ -144,7 +144,7 @@ class SplitsUpdateWorker: UpdateWorker { if splitsStorage.update(splitChange: processedFlags) { let updatedFlags = (processedFlags.activeSplits + processedFlags.archivedSplits).compactMap(\.name) - synchronizer.notifyFeatureFlagsUpdated(flagsList: updatedFlags) + synchronizer.notifyFeatureFlagsUpdated(flags: updatedFlags) } telemetryProducer?.recordUpdatesFromSse(type: .splits) @@ -174,7 +174,7 @@ class SplitsUpdateWorker: UpdateWorker { if ruleBasedSegmentsStorage.update(toAdd: processedChange.toAdd, toRemove: processedChange.toRemove, changeNumber: processedChange.changeNumber) { - synchronizer.notifyFeatureFlagsUpdated(flagsList: []) //TODO: RBS Update + synchronizer.notifyFeatureFlagsUpdated(flags: []) //TODO: RBS Update } telemetryProducer?.recordUpdatesFromSse(type: .splits) diff --git a/Split/Network/Sync/Synchronizer.swift b/Split/Network/Sync/Synchronizer.swift index ec789a033..321ca228c 100644 --- a/Split/Network/Sync/Synchronizer.swift +++ b/Split/Network/Sync/Synchronizer.swift @@ -30,7 +30,7 @@ protocol Synchronizer: ImpressionLogger { func startRecordingTelemetry() func stopRecordingTelemetry() func pushEvent(event: EventDTO) - func notifyFeatureFlagsUpdated(flagsList: [String]) + func notifyFeatureFlagsUpdated(flags: [String]) func notifySegmentsUpdated(forKey key: String) func notifyLargeSegmentsUpdated(forKey key: String) func notifySplitKilled() @@ -207,8 +207,8 @@ class DefaultSynchronizer: Synchronizer { } } - func notifyFeatureFlagsUpdated(flagsList: [String]) { - featureFlagsSynchronizer.notifyUpdated(flagsList: flagsList) + func notifyFeatureFlagsUpdated(flags: [String]) { + featureFlagsSynchronizer.notifyUpdated(flagsList: flags) } func notifySegmentsUpdated(forKey key: String) { diff --git a/SplitTests/Fake/Streaming/SynchronizerSpy.swift b/SplitTests/Fake/Streaming/SynchronizerSpy.swift index ed659f543..88420a4d8 100644 --- a/SplitTests/Fake/Streaming/SynchronizerSpy.swift +++ b/SplitTests/Fake/Streaming/SynchronizerSpy.swift @@ -207,8 +207,8 @@ class SynchronizerSpy: Synchronizer { var notifyFeatureFlagsUpdatedCalled = false var updatedFlags: [String] = [] - func notifyFeatureFlagsUpdated(flagsList: [String]) { - updatedFlags = flagsList + func notifyFeatureFlagsUpdated(flags: [String]) { + updatedFlags = flags notifyFeatureFlagsUpdatedCalled = true } diff --git a/SplitTests/Fake/Streaming/SynchronizerStub.swift b/SplitTests/Fake/Streaming/SynchronizerStub.swift index 0c8019699..9b6cca381 100644 --- a/SplitTests/Fake/Streaming/SynchronizerStub.swift +++ b/SplitTests/Fake/Streaming/SynchronizerStub.swift @@ -238,8 +238,8 @@ class SynchronizerStub: Synchronizer { var notifyFeatureFlagsUpdatedCalled = true var updatedFlags: [String] = [] - func notifyFeatureFlagsUpdated(flagsList: [String]) { - updatedFlags = flagsList + func notifyFeatureFlagsUpdated(flags: [String]) { + updatedFlags = flags notifyFeatureFlagsUpdatedCalled = true } From 430bdb91586e04e6e5024a51f4966bb285a4d112 Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Thu, 26 Jun 2025 12:37:10 -0300 Subject: [PATCH 46/62] Simplified logic --- Split/FetcherEngine/Refresh/PeriodicSyncWorker.swift | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/Split/FetcherEngine/Refresh/PeriodicSyncWorker.swift b/Split/FetcherEngine/Refresh/PeriodicSyncWorker.swift index bd8ab4dec..23d831fa4 100644 --- a/Split/FetcherEngine/Refresh/PeriodicSyncWorker.swift +++ b/Split/FetcherEngine/Refresh/PeriodicSyncWorker.swift @@ -183,13 +183,7 @@ class PeriodicSplitsSyncWorker: BasePeriodicSyncWorker { } if result.success, result.featureFlagsUpdated.count > 0 { - - var updatedFlags = result.featureFlagsUpdated - for flag in updatedFlags { - updatedFlags.append(flag) - } - - let metadata = EventMetadata(type: .FLAGS_UPDATED, data: updatedFlags) + let metadata = EventMetadata(type: .FLAGS_UPDATED, data: result.featureFlagsUpdated) notifyUpdate(.splitsUpdated, metadata) } } From 6179805ab9c429d31c40b0c6f5c06111cf9171d5 Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Thu, 26 Jun 2025 16:24:45 -0300 Subject: [PATCH 47/62] E2E test added --- .../Sync/SplitSdkUpdatePollingTest.swift | 45 ++++++++++++++++ .../Integration/splitchanges_int_test.json | 51 +++++++++++++++++++ 2 files changed, 96 insertions(+) diff --git a/SplitTests/Integration/Sync/SplitSdkUpdatePollingTest.swift b/SplitTests/Integration/Sync/SplitSdkUpdatePollingTest.swift index 9a37c6761..589ace46d 100644 --- a/SplitTests/Integration/Sync/SplitSdkUpdatePollingTest.swift +++ b/SplitTests/Integration/Sync/SplitSdkUpdatePollingTest.swift @@ -207,6 +207,51 @@ class SplitSdkUpdatePollingTest: XCTestCase { }) semaphore.wait() } + + func testSdkUpdateSplitsWithMetadata() throws { + let apiKey = IntegrationHelper.dummyApiKey + let trafficType = "client" + + let sdkReady = XCTestExpectation(description: "SDK READY Expectation") + let sdkUpdateWithMetadata = XCTestExpectation(description: "SDK Update With Metadata Expectation") + + let splitConfig: SplitClientConfig = SplitClientConfig() + splitConfig.segmentsRefreshRate = 99999 + splitConfig.featuresRefreshRate = 2 + splitConfig.impressionRefreshRate = 99999 + splitConfig.sdkReadyTimeOut = 60000 + splitConfig.trafficType = trafficType + splitConfig.streamingEnabled = false + splitConfig.serviceEndpoints = ServiceEndpoints.builder() + .set(sdkEndpoint: serverUrl).set(eventsEndpoint: serverUrl).build() + + let key: Key = Key(matchingKey: kMatchingKey, bucketingKey: nil) + let builder = DefaultSplitFactoryBuilder() + _ = builder.setTestDatabase(TestingHelper.createTestDatabase(name: "SplitChangesTest")) + _ = builder.setHttpClient(httpClient) + factory = builder.setApiKey(apiKey).setKey(key).setConfig(splitConfig).build() + + let client = factory!.client + + client.on(event: .sdkReady) { + sdkReady.fulfill() + } + + client.on(event: .sdkUpdated) { metadata in + XCTAssertNotNil(metadata) + XCTAssertEqual(metadata?.type, .FLAGS_UPDATED) + XCTAssertEqual(metadata?.data, ["test_feature", "test_feature_killed"]) + sdkUpdateWithMetadata.fulfill() + } + + wait(for: [sdkReady, sdkUpdateWithMetadata], timeout: 30) + + let semaphore = DispatchSemaphore(value: 0) + client.destroy(completion: { + _ = semaphore.signal() + }) + semaphore.wait() + } func testSdkUpdateMySegments() throws { let apiKey = IntegrationHelper.dummyApiKey diff --git a/SplitTests/Resources/Integration/splitchanges_int_test.json b/SplitTests/Resources/Integration/splitchanges_int_test.json index 131f340b9..faf058971 100644 --- a/SplitTests/Resources/Integration/splitchanges_int_test.json +++ b/SplitTests/Resources/Integration/splitchanges_int_test.json @@ -14,6 +14,57 @@ "algo":2, "configurations":{ + }, + "conditions":[ + { + "conditionType":"ROLLOUT", + "matcherGroup":{ + "combiner":"AND", + "matchers":[ + { + "keySelector":{ + "trafficType":"client", + "attribute":null + }, + "matcherType":"ALL_KEYS", + "negate":false, + "userDefinedSegmentMatcherData":null, + "whitelistMatcherData":null, + "unaryNumericMatcherData":null, + "betweenMatcherData":null, + "booleanMatcherData":null, + "dependencyMatcherData":null, + "stringMatcherData":null + } + ] + }, + "partitions":[ + { + "treatment":"si", + "size":0 + }, + { + "treatment":"no", + "size":100 + } + ], + "label":"default rule" + } + ] + }, + { + "trafficTypeName":"client", + "name":"test_feature_killed", + "trafficAllocation":100, + "trafficAllocationSeed":-2049557248, + "seed":1188118899, + "status":"ACTIVE", + "killed":true, + "defaultTreatment":"yes", + "changeNumber":1567456937865, + "algo":2, + "configurations":{ + }, "conditions":[ { From d3e4c7f341ad4a875ba77e01e6d4cb3309208f7a Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Thu, 26 Jun 2025 16:25:50 -0300 Subject: [PATCH 48/62] Test improved --- .../Sync/SplitSdkUpdatePollingTest.swift | 3 +- .../Integration/splitchanges_int_test.json | 51 ------------------- 2 files changed, 1 insertion(+), 53 deletions(-) diff --git a/SplitTests/Integration/Sync/SplitSdkUpdatePollingTest.swift b/SplitTests/Integration/Sync/SplitSdkUpdatePollingTest.swift index 589ace46d..ef12b0851 100644 --- a/SplitTests/Integration/Sync/SplitSdkUpdatePollingTest.swift +++ b/SplitTests/Integration/Sync/SplitSdkUpdatePollingTest.swift @@ -238,9 +238,8 @@ class SplitSdkUpdatePollingTest: XCTestCase { } client.on(event: .sdkUpdated) { metadata in - XCTAssertNotNil(metadata) XCTAssertEqual(metadata?.type, .FLAGS_UPDATED) - XCTAssertEqual(metadata?.data, ["test_feature", "test_feature_killed"]) + XCTAssertEqual(metadata?.data, ["test_feature"]) sdkUpdateWithMetadata.fulfill() } diff --git a/SplitTests/Resources/Integration/splitchanges_int_test.json b/SplitTests/Resources/Integration/splitchanges_int_test.json index faf058971..131f340b9 100644 --- a/SplitTests/Resources/Integration/splitchanges_int_test.json +++ b/SplitTests/Resources/Integration/splitchanges_int_test.json @@ -14,57 +14,6 @@ "algo":2, "configurations":{ - }, - "conditions":[ - { - "conditionType":"ROLLOUT", - "matcherGroup":{ - "combiner":"AND", - "matchers":[ - { - "keySelector":{ - "trafficType":"client", - "attribute":null - }, - "matcherType":"ALL_KEYS", - "negate":false, - "userDefinedSegmentMatcherData":null, - "whitelistMatcherData":null, - "unaryNumericMatcherData":null, - "betweenMatcherData":null, - "booleanMatcherData":null, - "dependencyMatcherData":null, - "stringMatcherData":null - } - ] - }, - "partitions":[ - { - "treatment":"si", - "size":0 - }, - { - "treatment":"no", - "size":100 - } - ], - "label":"default rule" - } - ] - }, - { - "trafficTypeName":"client", - "name":"test_feature_killed", - "trafficAllocation":100, - "trafficAllocationSeed":-2049557248, - "seed":1188118899, - "status":"ACTIVE", - "killed":true, - "defaultTreatment":"yes", - "changeNumber":1567456937865, - "algo":2, - "configurations":{ - }, "conditions":[ { From 39353f01bc8798525b8261a19fb42dd01a1ab7f1 Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Thu, 26 Jun 2025 17:33:47 -0300 Subject: [PATCH 49/62] Adding tests --- Split.xcodeproj/project.pbxproj | 4 + .../Sync/SplitSdkUpdatePollingTest.swift | 88 +++++++++++++++++++ .../Integration/split_killed_test.json | 59 +++++++++++++ 3 files changed, 151 insertions(+) create mode 100644 SplitTests/Resources/Integration/split_killed_test.json diff --git a/Split.xcodeproj/project.pbxproj b/Split.xcodeproj/project.pbxproj index 34e03afcf..f1a1f6002 100644 --- a/Split.xcodeproj/project.pbxproj +++ b/Split.xcodeproj/project.pbxproj @@ -356,6 +356,7 @@ 5B48D8172DEA2CED00351925 /* PrerequisitesMatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BF52DF52DE0B60300FEDAFE /* PrerequisitesMatcher.swift */; }; 5B48D8192DF360D000351925 /* EventMetadataType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B48D8182DF360CB00351925 /* EventMetadataType.swift */; }; 5B48D81A2DF360D000351925 /* EventMetadataType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B48D8182DF360CB00351925 /* EventMetadataType.swift */; }; + 5B584C182E0DD6A000C68F9B /* split_killed_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 5B584C172E0DD69300C68F9B /* split_killed_test.json */; }; 5B91B8392DDE4A3B000510F0 /* SplitDTOTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B91B8382DDE4A30000510F0 /* SplitDTOTests.swift */; }; 5BF52DF72DE0B60700FEDAFE /* PrerequisitesMatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BF52DF52DE0B60300FEDAFE /* PrerequisitesMatcher.swift */; }; 5BF52DF92DE4B8D400FEDAFE /* PrerequisitesMatcherTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BF52DF82DE4B8CA00FEDAFE /* PrerequisitesMatcherTest.swift */; }; @@ -1560,6 +1561,7 @@ 59FB7C3B2203795F00ECC96A /* LocalhostSplitsParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalhostSplitsParser.swift; sourceTree = ""; }; 59FB7C3D22037B9400ECC96A /* SpaceDelimitedLocalhostSplitsParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpaceDelimitedLocalhostSplitsParser.swift; sourceTree = ""; }; 5B48D8182DF360CB00351925 /* EventMetadataType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventMetadataType.swift; sourceTree = ""; }; + 5B584C172E0DD69300C68F9B /* split_killed_test.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = split_killed_test.json; 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 = ""; }; @@ -3035,6 +3037,7 @@ 595B66D2231DB8690030F330 /* Integration */ = { isa = PBXGroup; children = ( + 5B584C172E0DD69300C68F9B /* split_killed_test.json */, 595B66CE231DB0720030F330 /* splitchanges_int_test.json */, 95118EE7281AC6E700782F33 /* bucket_split_test.json */, 95F8F06728170473009E09B1 /* multi_client_test.json */, @@ -3971,6 +3974,7 @@ 5959C46E2278D9AD0064F968 /* segment_conta_condition.json in Resources */, 59C493052167E3B400F5F774 /* murmur3-sample-double-treatment-users.csv in Resources */, 95F7BC082C1CE84500C5F2E4 /* rsa_2048_cert.pem.der in Resources */, + 5B584C182E0DD6A000C68F9B /* split_killed_test.json in Resources */, C5977C322BF4031A003E293A /* split_changes_semver.json in Resources */, 599EA59022691D57006CBA89 /* localhost_yml.yml in Resources */, 95F7BBFB2C1C953000C5F2E4 /* ec_secp521r1_pub.der in Resources */, diff --git a/SplitTests/Integration/Sync/SplitSdkUpdatePollingTest.swift b/SplitTests/Integration/Sync/SplitSdkUpdatePollingTest.swift index 9a37c6761..fc298f3d0 100644 --- a/SplitTests/Integration/Sync/SplitSdkUpdatePollingTest.swift +++ b/SplitTests/Integration/Sync/SplitSdkUpdatePollingTest.swift @@ -207,6 +207,94 @@ class SplitSdkUpdatePollingTest: XCTestCase { }) semaphore.wait() } + + func testSdkUpdateSplitsWithMetadata() throws { + let apiKey = IntegrationHelper.dummyApiKey + let trafficType = "client" + + let sdkReady = XCTestExpectation(description: "SDK READY Expectation") + let sdkUpdateWithMetadata = XCTestExpectation(description: "SDK Update With Metadata Expectation") + + let splitConfig: SplitClientConfig = SplitClientConfig() + splitConfig.segmentsRefreshRate = 99999 + splitConfig.featuresRefreshRate = 2 + splitConfig.impressionRefreshRate = 99999 + splitConfig.sdkReadyTimeOut = 60000 + splitConfig.trafficType = trafficType + splitConfig.streamingEnabled = false + splitConfig.serviceEndpoints = ServiceEndpoints.builder() + .set(sdkEndpoint: serverUrl).set(eventsEndpoint: serverUrl).build() + + let key: Key = Key(matchingKey: kMatchingKey, bucketingKey: nil) + let builder = DefaultSplitFactoryBuilder() + _ = builder.setTestDatabase(TestingHelper.createTestDatabase(name: "SplitChangesTest")) + _ = builder.setHttpClient(httpClient) + factory = builder.setApiKey(apiKey).setKey(key).setConfig(splitConfig).build() + + let client = factory!.client + + client.on(event: .sdkReady) { + sdkReady.fulfill() + } + + client.on(event: .sdkUpdated) { metadata in + XCTAssertEqual(metadata?.type, .FLAGS_UPDATED) + XCTAssertEqual(metadata?.data, ["test_feature"]) + sdkUpdateWithMetadata.fulfill() + } + + wait(for: [sdkReady, sdkUpdateWithMetadata], timeout: 30) + + let semaphore = DispatchSemaphore(value: 0) + client.destroy(completion: { + _ = semaphore.signal() + }) + semaphore.wait() + } + + func testSdkKillSplitWithMetadata() throws { + let apiKey = IntegrationHelper.dummyApiKey + let trafficType = "client" + + let sdkReady = XCTestExpectation(description: "SDK READY Expectation") + let sdkUpdateWithMetadata = XCTestExpectation(description: "SDK Update With Metadata Expectation") + + let splitConfig: SplitClientConfig = SplitClientConfig() + splitConfig.segmentsRefreshRate = 99999 + splitConfig.featuresRefreshRate = 2 + splitConfig.impressionRefreshRate = 99999 + splitConfig.sdkReadyTimeOut = 60000 + splitConfig.trafficType = trafficType + splitConfig.streamingEnabled = false + splitConfig.serviceEndpoints = ServiceEndpoints.builder() + .set(sdkEndpoint: serverUrl).set(eventsEndpoint: serverUrl).build() + + let key: Key = Key(matchingKey: kMatchingKey, bucketingKey: nil) + let builder = DefaultSplitFactoryBuilder() + _ = builder.setTestDatabase(TestingHelper.createTestDatabase(name: "SplitChangesTest")) + _ = builder.setHttpClient(httpClient) + factory = builder.setApiKey(apiKey).setKey(key).setConfig(splitConfig).build() + + let client = factory!.client + + client.on(event: .sdkReady) { + sdkReady.fulfill() + } + + client.on(event: .sdkUpdated) { metadata in + XCTAssertEqual(metadata?.type, .FLAGS_UPDATED) + XCTAssertEqual(metadata?.data, ["test_feature"]) + sdkUpdateWithMetadata.fulfill() + } + + wait(for: [sdkReady, sdkUpdateWithMetadata], timeout: 30) + + let semaphore = DispatchSemaphore(value: 0) + client.destroy(completion: { + _ = semaphore.signal() + }) + semaphore.wait() + } func testSdkUpdateMySegments() throws { let apiKey = IntegrationHelper.dummyApiKey diff --git a/SplitTests/Resources/Integration/split_killed_test.json b/SplitTests/Resources/Integration/split_killed_test.json new file mode 100644 index 000000000..f95d325a0 --- /dev/null +++ b/SplitTests/Resources/Integration/split_killed_test.json @@ -0,0 +1,59 @@ + +{ + "ff":{ + "d":[ + { + "trafficTypeName":"client", + "name":"test_feature_kill", + "trafficAllocation":100, + "trafficAllocationSeed":-2049557248, + "seed":1188118899, + "status":"ACTIVE", + "killed":true, + "defaultTreatment":"yes", + "changeNumber":1567456937865, + "algo":2, + "configurations":{ + + }, + "conditions":[ + { + "conditionType":"ROLLOUT", + "matcherGroup":{ + "combiner":"AND", + "matchers":[ + { + "keySelector":{ + "trafficType":"client", + "attribute":null + }, + "matcherType":"ALL_KEYS", + "negate":false, + "userDefinedSegmentMatcherData":null, + "whitelistMatcherData":null, + "unaryNumericMatcherData":null, + "betweenMatcherData":null, + "booleanMatcherData":null, + "dependencyMatcherData":null, + "stringMatcherData":null + } + ] + }, + "partitions":[ + { + "treatment":"si", + "size":0 + }, + { + "treatment":"no", + "size":100 + } + ], + "label":"default rule" + } + ] + } + ], + "s":1567455995317, + "t":1567456937865 +}, "rbs": {"s":-1,"t":-1,"d":[]}} From 7e8bbb88cd074e4c886ba8728aaa300551102014 Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Sat, 28 Jun 2025 14:49:41 -0300 Subject: [PATCH 50/62] Streaming working --- Split/Events/SplitEventsManager.swift | 2 +- .../Streaming/SseNotificationProcessor.swift | 24 ++++---- .../Sync/SplitSdkUpdatePollingTest.swift | 23 +++++--- .../streaming/StreamingSplitKillTest.swift | 57 ++++++++++++++++++- 4 files changed, 82 insertions(+), 24 deletions(-) diff --git a/Split/Events/SplitEventsManager.swift b/Split/Events/SplitEventsManager.swift index 5781b3fff..971801d03 100644 --- a/Split/Events/SplitEventsManager.swift +++ b/Split/Events/SplitEventsManager.swift @@ -50,7 +50,7 @@ class DefaultSplitEventsManager: SplitEventsManager { } } } - + func notifyInternalEvent(_ event: SplitInternalEvent, metadata: EventMetadata? = nil) { let event = SplitInternalEventWithMetadata(event, metadata: metadata) diff --git a/Split/Network/Streaming/SseNotificationProcessor.swift b/Split/Network/Streaming/SseNotificationProcessor.swift index 4f0240e84..50fd30d63 100644 --- a/Split/Network/Streaming/SseNotificationProcessor.swift +++ b/Split/Network/Streaming/SseNotificationProcessor.swift @@ -36,18 +36,18 @@ class DefaultSseNotificationProcessor: SseNotificationProcessor { func process(_ notification: IncomingNotification) { Logger.d("Received notification \(notification.type)") switch notification.type { - case .splitUpdate: - processTargetingRuleUpdate(notification) - case .ruleBasedSegmentUpdate: - processTargetingRuleUpdate(notification) - case .mySegmentsUpdate: - processSegmentsUpdate(notification, updateWorker: mySegmentsUpdateWorker) - case .myLargeSegmentsUpdate: - processSegmentsUpdate(notification, updateWorker: myLargeSegmentsUpdateWorker) - case .splitKill: - processSplitKill(notification) - default: - Logger.e("Unknown notification arrived: \(notification.jsonData ?? "null" )") + case .splitUpdate: + processTargetingRuleUpdate(notification) + case .ruleBasedSegmentUpdate: + processTargetingRuleUpdate(notification) + case .mySegmentsUpdate: + processSegmentsUpdate(notification, updateWorker: mySegmentsUpdateWorker) + case .myLargeSegmentsUpdate: + processSegmentsUpdate(notification, updateWorker: myLargeSegmentsUpdateWorker) + case .splitKill: + processSplitKill(notification) + default: + Logger.e("Unknown notification arrived: \(notification.jsonData ?? "null" )") } } diff --git a/SplitTests/Integration/Sync/SplitSdkUpdatePollingTest.swift b/SplitTests/Integration/Sync/SplitSdkUpdatePollingTest.swift index fc298f3d0..bcafd6984 100644 --- a/SplitTests/Integration/Sync/SplitSdkUpdatePollingTest.swift +++ b/SplitTests/Integration/Sync/SplitSdkUpdatePollingTest.swift @@ -34,15 +34,15 @@ class SplitSdkUpdatePollingTest: XCTestCase { override func setUp() { let session = HttpSessionMock() - let reqManager = HttpRequestManagerTestDispatcher(dispatcher: buildTestDispatcher(), + let reqManager = HttpRequestManagerTestDispatcher(dispatcher: buildTestDispatcher("splitchanges_int_test"), streamingHandler: buildStreamingHandler()) httpClient = DefaultHttpClient(session: session, requestManager: reqManager) } - private func buildTestDispatcher() -> HttpClientTestDispatcher { + private func buildTestDispatcher(_ file: String) -> HttpClientTestDispatcher { - let respData = responseSplitChanges() + let respData = responseSplitChanges(file) var responses = [TestDispatcherResponse]() for data in respData { let rData = TargetingRulesChange(featureFlags: data, ruleBasedSegments: RuleBasedSegmentChange(segments: [], since: -1, till: -1)) @@ -253,6 +253,11 @@ class SplitSdkUpdatePollingTest: XCTestCase { } func testSdkKillSplitWithMetadata() throws { + let session = HttpSessionMock() + let reqManager = HttpRequestManagerTestDispatcher(dispatcher: buildTestDispatcher("split_killed_test"), + streamingHandler: buildStreamingHandler()) + httpClient = DefaultHttpClient(session: session, requestManager: reqManager) + let apiKey = IntegrationHelper.dummyApiKey let trafficType = "client" @@ -282,8 +287,8 @@ class SplitSdkUpdatePollingTest: XCTestCase { } client.on(event: .sdkUpdated) { metadata in - XCTAssertEqual(metadata?.type, .FLAGS_UPDATED) - XCTAssertEqual(metadata?.data, ["test_feature"]) + XCTAssertEqual(metadata?.type, .FLAGS_KILLED) + XCTAssertEqual(metadata?.data, ["test_feature_kill"]) sdkUpdateWithMetadata.fulfill() } @@ -351,12 +356,12 @@ class SplitSdkUpdatePollingTest: XCTestCase { semaphore.wait() } - private func responseSplitChanges() -> [SplitChange] { + private func responseSplitChanges(_ file: String) -> [SplitChange] { var changes = [SplitChange]() var prevChangeNumber: Int64 = 0 for i in 0..<4 { - let c = loadSplitsChangeFile()! + let c = loadSplitsChangeFile(file)! c.since = c.till if prevChangeNumber != 0 { c.till = prevChangeNumber + kChangeNbInterval @@ -375,8 +380,8 @@ class SplitSdkUpdatePollingTest: XCTestCase { return changes } - private func loadSplitsChangeFile() -> SplitChange? { - return FileHelper.loadSplitChangeFile(sourceClass: self, fileName: "splitchanges_int_test") + private func loadSplitsChangeFile(_ file: String) -> SplitChange? { + return FileHelper.loadSplitChangeFile(sourceClass: self, fileName: file) } private func getAndIncrement() -> Int { diff --git a/SplitTests/Integration/streaming/StreamingSplitKillTest.swift b/SplitTests/Integration/streaming/StreamingSplitKillTest.swift index 26e405131..397b0b32e 100644 --- a/SplitTests/Integration/streaming/StreamingSplitKillTest.swift +++ b/SplitTests/Integration/streaming/StreamingSplitKillTest.swift @@ -27,6 +27,7 @@ class StreamingSplitKillTest: XCTestCase { var exp2: XCTestExpectation! var exp3: XCTestExpectation! var exp4: XCTestExpectation! + var exp5: XCTestExpectation! override func setUp() { expIndex = 1 @@ -37,7 +38,7 @@ class StreamingSplitKillTest: XCTestCase { loadChanges() } - func testSplitKill() { + func testSplitKill() throws { let splitConfig: SplitClientConfig = SplitClientConfig() splitConfig.featuresRefreshRate = 9999 splitConfig.segmentsRefreshRate = 9999 @@ -55,7 +56,7 @@ class StreamingSplitKillTest: XCTestCase { .setConfig(splitConfig).build()! let client = factory.client - let expTimeout: TimeInterval = 5 + let expTimeout: TimeInterval = 5 let sdkReadyExpectation = XCTestExpectation(description: "SDK READY Expectation") exp1 = XCTestExpectation(description: "Exp1") @@ -118,6 +119,58 @@ class StreamingSplitKillTest: XCTestCase { semaphore.wait() } + func testSplitKillWithMetadata() throws { + + // Setup + let splitConfig: SplitClientConfig = SplitClientConfig() + splitConfig.featuresRefreshRate = 9999 + splitConfig.segmentsRefreshRate = 9999 + splitConfig.impressionRefreshRate = 999999 + splitConfig.sdkReadyTimeOut = 60000 + splitConfig.eventsPushRate = 999999 + + + let key: Key = Key(matchingKey: userKey) + let builder = DefaultSplitFactoryBuilder() + _ = builder.setHttpClient(httpClient) + _ = builder.setReachabilityChecker(ReachabilityMock()) + _ = builder.setTestDatabase(TestingHelper.createTestDatabase(name: "test")) + let factory = builder.setApiKey(apiKey).setKey(key).setConfig(splitConfig).build()! + let client = factory.client + let expTimeout: TimeInterval = 5 + + let sdkReadyExpectation = XCTestExpectation(description: "SDK READY Expectation") + exp1 = XCTestExpectation(description: "Streaming notification") + exp5 = XCTestExpectation(description: "Wait for killed metadata event") + + client.on(event: SplitEvent.sdkReady) { sdkReadyExpectation.fulfill() } + + // Set listener + client.on(event: .sdkUpdated) { [weak self] metadata in + if metadata?.type == .FLAGS_KILLED { + XCTAssertEqual(metadata?.data, ["workm"]) + self?.exp5.fulfill() + } + } + + // Simulate Kill + wait(for: [sdkReadyExpectation, sseConnExp], timeout: expTimeout) + streamingBinding?.push(message: ":keepalive") // send keep alive to confirm streaming connection ok + wait(for: [exp1], timeout: expTimeout) + waitForUpdate(secs: 1) + streamingBinding?.push(message: StreamingIntegrationHelper.splitKillMessagge(splitName: "workm", defaultTreatment: "conta", + timestamp: numbers[splitsChangesHits], + changeNumber: numbers[splitsChangesHits])) + + // Exit + wait(for: [exp5], timeout: expTimeout) + let semaphore = DispatchSemaphore(value: 0) + client.destroy(completion: { + _ = semaphore.signal() + }) + semaphore.wait() + } + private func getChanges(for hitNumber: Int) -> Data { if hitNumber < 4 { return Data(self.changes[hitNumber].utf8) From 2a3983989967f6167afbe8e880d856fc21e70761 Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Sun, 29 Jun 2025 21:56:16 -0300 Subject: [PATCH 51/62] Tests passing --- Split/Events/SplitEventsManager.swift | 50 +++++++------- .../Refresh/ChangesChecker.swift | 6 +- .../Refresh/PeriodicSyncWorker.swift | 8 +-- .../Refresh/RetryableSegmentsSyncWorker.swift | 29 ++++---- .../Fake/Service/ChangesCheckerMock.swift | 14 ++-- SplitTests/Helpers/TestingHelper.swift | 13 ++-- .../Sync/SplitSdkUpdatePollingTest.swift | 67 ++++++++++++++++++- .../StreamingMySegmentsSyncTest.swift | 10 ++- .../MySegments/SegmentsSyncHelperTests.swift | 9 +-- 9 files changed, 146 insertions(+), 60 deletions(-) diff --git a/Split/Events/SplitEventsManager.swift b/Split/Events/SplitEventsManager.swift index 8406866c8..ce77f031d 100644 --- a/Split/Events/SplitEventsManager.swift +++ b/Split/Events/SplitEventsManager.swift @@ -161,31 +161,31 @@ class DefaultSplitEventsManager: SplitEventsManager { } self.triggered.append(event.type) switch event.type { - case .splitsUpdated, .mySegmentsUpdated, .myLargeSegmentsUpdated: - if isTriggered(external: .sdkReady) { - trigger(event: .sdkUpdated, metadata: event.metadata) - 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: .sdkReadyFromCache) - } - case .splitKilledNotification: - if isTriggered(external: .sdkReady) { - trigger(event: .sdkUpdated, metadata: event.metadata) - continue - } - case .sdkReadyTimeoutReached: - if !isTriggered(external: .sdkReady) { - trigger(event: .sdkReadyTimedOut) - } + case .splitsUpdated, .mySegmentsUpdated, .myLargeSegmentsUpdated: + if isTriggered(external: .sdkReady) { + trigger(event: .sdkUpdated, metadata: event.metadata) + 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: .sdkReadyFromCache) + } + case .splitKilledNotification: + if isTriggered(external: .sdkReady) { + trigger(event: .sdkUpdated, metadata: event.metadata) + continue + } + case .sdkReadyTimeoutReached: + if !isTriggered(external: .sdkReady) { + trigger(event: .sdkReadyTimedOut) + } } } } diff --git a/Split/FetcherEngine/Refresh/ChangesChecker.swift b/Split/FetcherEngine/Refresh/ChangesChecker.swift index 6c3e0d743..cb4e6a5df 100644 --- a/Split/FetcherEngine/Refresh/ChangesChecker.swift +++ b/Split/FetcherEngine/Refresh/ChangesChecker.swift @@ -22,6 +22,7 @@ protocol MySegmentsChangesChecker { func mySegmentsHaveChanged(old: SegmentChange, new: SegmentChange) -> Bool func mySegmentsHaveChanged(oldSegments: [Segment], newSegments: [Segment]) -> Bool func mySegmentsHaveChanged(oldSegments: [String], newSegments: [String]) -> Bool + func getSegmentsDiff(oldSegments: [Segment], newSegments: [Segment]) -> [String] } struct DefaultMySegmentsChangesChecker: MySegmentsChangesChecker { @@ -42,5 +43,8 @@ struct DefaultMySegmentsChangesChecker: MySegmentsChangesChecker { return !(oldSegments.count == newSegments.count && oldSegments.sorted() == newSegments.sorted()) } - + + func getSegmentsDiff(oldSegments: [Segment], newSegments: [Segment]) -> [String] { + oldSegments.filter { !Set(newSegments.map { $0.name }).contains($0.name) }.map { $0.name } + } } diff --git a/Split/FetcherEngine/Refresh/PeriodicSyncWorker.swift b/Split/FetcherEngine/Refresh/PeriodicSyncWorker.swift index 23d831fa4..c25c73f2e 100644 --- a/Split/FetcherEngine/Refresh/PeriodicSyncWorker.swift +++ b/Split/FetcherEngine/Refresh/PeriodicSyncWorker.swift @@ -130,7 +130,7 @@ class BasePeriodicSyncWorker: PeriodicSyncWorker { Logger.i("Fetch from remote not implemented") } - func notifyUpdate(_ event: SplitInternalEvent, _ metadata: EventMetadata? = nil) { + func notifyUpdate(_ event: SplitInternalEvent, metadata: EventMetadata? = nil) { eventsManager.notifyInternalEvent(event, metadata: metadata) } } @@ -184,7 +184,7 @@ class PeriodicSplitsSyncWorker: BasePeriodicSyncWorker { if result.success, result.featureFlagsUpdated.count > 0 { let metadata = EventMetadata(type: .FLAGS_UPDATED, data: result.featureFlagsUpdated) - notifyUpdate(.splitsUpdated, metadata) + notifyUpdate(.splitsUpdated, metadata: metadata) } } } @@ -223,9 +223,9 @@ class PeriodicMySegmentsSyncWorker: BasePeriodicSyncWorker { mlsTill: myLargeSegmentsStorage.changeNumber, headers: nil) if result.success { - if result.msUpdated || result.mlsUpdated { + if !result.msUpdated.isEmpty || !result.mlsUpdated.isEmpty { // For now is not necessary specify which entity was updated - notifyUpdate(.mySegmentsUpdated) + notifyUpdate(.mySegmentsUpdated, metadata: EventMetadata(type: .SEGMENTS_UPDATED, data: result.msUpdated + result.mlsUpdated)) } } } catch { diff --git a/Split/FetcherEngine/Refresh/RetryableSegmentsSyncWorker.swift b/Split/FetcherEngine/Refresh/RetryableSegmentsSyncWorker.swift index d16c567ca..831cbdca7 100644 --- a/Split/FetcherEngine/Refresh/RetryableSegmentsSyncWorker.swift +++ b/Split/FetcherEngine/Refresh/RetryableSegmentsSyncWorker.swift @@ -46,11 +46,11 @@ class RetryableMySegmentsSyncWorker: BaseRetryableSyncWorker { if result.success { if !isSdkReadyTriggered() { // Notifying both to trigger SDK Ready - notifyUpdate(.mySegmentsUpdated) - notifyUpdate(.myLargeSegmentsUpdated) - } else if result.msUpdated || result.mlsUpdated { + notifyUpdate(.mySegmentsUpdated, metadata: EventMetadata(type: .SEGMENTS_UPDATED, data: result.msUpdated)) + notifyUpdate(.myLargeSegmentsUpdated, metadata: EventMetadata(type: .LARGE_SEGMENTS_UPDATED, data: result.mlsUpdated)) + } else if !result.msUpdated.isEmpty || !result.mlsUpdated.isEmpty { // For now is not necessary specify which entity was updated - notifyUpdate(.mySegmentsUpdated) + notifyUpdate(.mySegmentsUpdated, metadata: EventMetadata(type: .SEGMENTS_UPDATED, data: result.msUpdated + result.mlsUpdated)) } return true } @@ -71,8 +71,8 @@ struct SegmentsSyncResult { let success: Bool let msChangeNumber: Int64 let mlsChangeNumber: Int64 - let msUpdated: Bool - let mlsUpdated: Bool + let msUpdated: [String] + let mlsUpdated: [String] } protocol SegmentsSyncHelper { @@ -86,8 +86,8 @@ class DefaultSegmentsSyncHelper: SegmentsSyncHelper { struct FetchResult { let msTill: Int64 let mlsTill: Int64 - let msUpdated: Bool - let mlsUdated: Bool + let msUpdated: [String] + let mlsUpdated: [String] } private let segmentsFetcher: HttpMySegmentsFetcher @@ -165,7 +165,7 @@ class DefaultSegmentsSyncHelper: SegmentsSyncHelper { msChangeNumber: result.msTill, mlsChangeNumber: result.mlsTill, msUpdated: result.msUpdated, - mlsUpdated: result.mlsUdated) + mlsUpdated: result.mlsUpdated) } attemptCount+=1 if attemptCount < maxAttempts { @@ -175,8 +175,8 @@ class DefaultSegmentsSyncHelper: SegmentsSyncHelper { return SegmentsSyncResult(success: false, msChangeNumber: -1, mlsChangeNumber: -1, - msUpdated: false, - mlsUpdated: false) + msUpdated: [], + mlsUpdated: []) } private func fetchUntil(till: Int64?, @@ -210,10 +210,13 @@ class DefaultSegmentsSyncHelper: SegmentsSyncHelper { Logger.d("Checking my large segments update") checkAndUpdate(isChanged: mlsChanged, change: myLargeSegmentsChange, storage: myLargeSegmentsStorage) +// let segmentsDiff = changeChecker.getSegmentsDiff(oldSegments: oldChange.segments, newSegments: mySegmentsChange.segments) +// let largeSegmentsDiff = changeChecker.getSegmentsDiff(oldSegments: oldLargeChange.segments, newSegments: myLargeSegmentsChange.segments) + return FetchResult(msTill: mySegmentsChange.unwrappedChangeNumber, mlsTill: myLargeSegmentsChange.unwrappedChangeNumber, - msUpdated: msChanged, - mlsUdated: mlsChanged) + msUpdated: mySegmentsChange.segments.compactMap(\.name), + mlsUpdated: myLargeSegmentsChange.segments.compactMap(\.name)) } prevChange = change } diff --git a/SplitTests/Fake/Service/ChangesCheckerMock.swift b/SplitTests/Fake/Service/ChangesCheckerMock.swift index 0b6b0f1e5..abdf97712 100644 --- a/SplitTests/Fake/Service/ChangesCheckerMock.swift +++ b/SplitTests/Fake/Service/ChangesCheckerMock.swift @@ -11,23 +11,29 @@ import Foundation @testable import Split class MySegmentsChangesCheckerMock: MySegmentsChangesChecker { + var haveChanged = false + var diffSegments: [String] = [] func mySegmentsHaveChanged(old: SegmentChange, new: SegmentChange) -> Bool { - return haveChanged + haveChanged } func mySegmentsHaveChanged(oldSegments old: [Segment], newSegments new: [Segment]) -> Bool { - return haveChanged + haveChanged } func mySegmentsHaveChanged(oldSegments old: [String], newSegments new: [String]) -> Bool { - return haveChanged + haveChanged + } + + func getSegmentsDiff(oldSegments: [Segment], newSegments: [Segment]) -> [String] { + diffSegments } } struct SplitsChangesCheckerMock: SplitsChangesChecker { var haveChanged = false func splitsHaveChanged(oldChangeNumber: Int64, newChangeNumber: Int64) -> Bool { - return haveChanged + haveChanged } } diff --git a/SplitTests/Helpers/TestingHelper.swift b/SplitTests/Helpers/TestingHelper.swift index c908d1cd7..f9eec3d1f 100644 --- a/SplitTests/Helpers/TestingHelper.swift +++ b/SplitTests/Helpers/TestingHelper.swift @@ -214,7 +214,7 @@ struct TestingHelper { static func buildSegmentsChange(count: Int64 = 5, msAscOrder: Bool = true, mlsAscOrder: Bool = true, - segmentsChanged: Bool = false) -> [AllSegmentsChange] { + segmentsChanged: [String] = []) -> [AllSegmentsChange] { // Eventualy cn will be greater than the first let baseCn: Int64 = 100 let lastMsCn = baseCn * count + 1 @@ -233,10 +233,13 @@ struct TestingHelper { res.append(newAllSegmentsChange(ms: msSeg, msCn: msC, mls: mlsSeg, mlsCn: mlsC)) } - if segmentsChanged { - msSeg.append("s3") - mlsSeg.append("sl3") + if !segmentsChanged.isEmpty { + segmentsChanged.forEach { segment in + msSeg.append(segment) + } } + msSeg.append("s3") + mlsSeg.append("sl3") res.append(newAllSegmentsChange(ms: msSeg, msCn: lastMsCn, mls: mlsSeg, mlsCn: lastMlsCn)) @@ -261,7 +264,7 @@ struct TestingHelper { static func segmentsSyncResult(_ result: Bool = true, msCn: Int64 = 300, mlsCn: Int64 = 400, - msUpd: Bool = true, mlsUpd: Bool = true) -> SegmentsSyncResult { + msUpd: [String] = ["SegmentTest1"], mlsUpd: [String] = ["LargeSegmentTest1"]) -> SegmentsSyncResult { return SegmentsSyncResult(success: result, msChangeNumber: msCn, mlsChangeNumber: mlsCn, msUpdated: msUpd, mlsUpdated: mlsUpd) diff --git a/SplitTests/Integration/Sync/SplitSdkUpdatePollingTest.swift b/SplitTests/Integration/Sync/SplitSdkUpdatePollingTest.swift index bcafd6984..d2dd0bcc8 100644 --- a/SplitTests/Integration/Sync/SplitSdkUpdatePollingTest.swift +++ b/SplitTests/Integration/Sync/SplitSdkUpdatePollingTest.swift @@ -300,7 +300,7 @@ class SplitSdkUpdatePollingTest: XCTestCase { }) semaphore.wait() } - + func testSdkUpdateMySegments() throws { let apiKey = IntegrationHelper.dummyApiKey let trafficType = "client" @@ -330,12 +330,12 @@ class SplitSdkUpdatePollingTest: XCTestCase { var sdkReadyFired = false var sdkUpdatedFired = false - client.on(event: SplitEvent.sdkReady) { + client.on(event: .sdkReady) { sdkReadyFired = true sdkReady.fulfill() } - client.on(event: SplitEvent.sdkUpdated) { + client.on(event: .sdkUpdated) { sdkUpdatedFired = true sdkUpdate.fulfill() } @@ -348,6 +348,67 @@ class SplitSdkUpdatePollingTest: XCTestCase { XCTAssertTrue(sdkReadyFired) XCTAssertTrue(sdkUpdatedFired) + let semaphore = DispatchSemaphore(value: 0) + client.destroy(completion: { + _ = semaphore.signal() + }) + semaphore.wait() + } + + func testSdkUpdateMySegmentsWithMetadata() throws { + let apiKey = IntegrationHelper.dummyApiKey + let trafficType = "client" + + let sdkReady = XCTestExpectation(description: "SDK READY Expectation") + let sdkUpdate = XCTestExpectation(description: "SDK Update Expectation") + let sdkUpdateWithMetadata = XCTestExpectation(description: "SDK Update With Metadata Expectation") + + let splitConfig: SplitClientConfig = SplitClientConfig() + splitConfig.segmentsRefreshRate = 2 + splitConfig.featuresRefreshRate = 999999 + splitConfig.impressionRefreshRate = 999999 + splitConfig.sdkReadyTimeOut = 60000 + splitConfig.trafficType = trafficType + splitConfig.streamingEnabled = false + splitConfig.logLevel = .verbose + splitConfig.serviceEndpoints = ServiceEndpoints.builder() + .set(sdkEndpoint: serverUrl).set(eventsEndpoint: serverUrl).build() + + let key: Key = Key(matchingKey: kMatchingKey, bucketingKey: nil) + let builder = DefaultSplitFactoryBuilder() + _ = builder.setTestDatabase(TestingHelper.createTestDatabase(name: "SplitChangesTest")) + _ = builder.setHttpClient(httpClient) + factory = builder.setApiKey(apiKey).setKey(key).setConfig(splitConfig).build() + + let client = factory!.client + + var sdkReadyFired = false + var sdkUpdatedFired = false + + client.on(event: .sdkReady) { + sdkReadyFired = true + sdkReady.fulfill() + } + + client.on(event: .sdkUpdated) { + sdkUpdatedFired = true + sdkUpdate.fulfill() + } + + client.on(event: .sdkUpdated) { metadata in + if metadata?.type == .SEGMENTS_UPDATED { + XCTAssertEqual(metadata?.data, ["segment1", "segment2", "segment3"]) + sdkUpdateWithMetadata.fulfill() + } + } + + wait(for: [sdkReady, sdkUpdate, sdkUpdateWithMetadata], timeout: 30) + + // wait for sdk update + ThreadUtils.delay(seconds: 1.0) + + XCTAssertTrue(sdkReadyFired) + XCTAssertTrue(sdkUpdatedFired) let semaphore = DispatchSemaphore(value: 0) client.destroy(completion: { diff --git a/SplitTests/Integration/streaming/StreamingMySegmentsSyncTest.swift b/SplitTests/Integration/streaming/StreamingMySegmentsSyncTest.swift index 67ac38424..00d764b14 100644 --- a/SplitTests/Integration/streaming/StreamingMySegmentsSyncTest.swift +++ b/SplitTests/Integration/streaming/StreamingMySegmentsSyncTest.swift @@ -70,7 +70,7 @@ class StreamingMySegmentsSyncTest: XCTestCase { splitConfig.impressionRefreshRate = 999999 splitConfig.sdkReadyTimeOut = 60000 splitConfig.eventsPushRate = 999999 - splitConfig.logLevel = .verbose + splitConfig.logLevel = .info let key: Key = Key(matchingKey: userKey) let builder = DefaultSplitFactoryBuilder() @@ -93,6 +93,14 @@ class StreamingMySegmentsSyncTest: XCTestCase { client.on(event: SplitEvent.sdkReadyTimedOut) { sdkReadyExpectation.fulfill() } + + client.on(event: .sdkUpdated) { metadata in + print("COOL ::: \(metadata?.type) \(metadata?.data)") + } + + client.on(event: .sdkUpdated) { + print("COOL :: ") + } wait(for: [sdkReadyExpectation, sseExp], timeout: expTimeout) diff --git a/SplitTests/Service/MySegments/SegmentsSyncHelperTests.swift b/SplitTests/Service/MySegments/SegmentsSyncHelperTests.swift index 395e88c25..ab24d7fdf 100644 --- a/SplitTests/Service/MySegments/SegmentsSyncHelperTests.swift +++ b/SplitTests/Service/MySegments/SegmentsSyncHelperTests.swift @@ -40,18 +40,19 @@ class SegmentsSyncHelperTests: XCTestCase { } func testCdnByPassNoTillNoChange() throws { - try cdnByPassNoTill(segmentsChanged: false) + try cdnByPassNoTill(segmentsChanged: []) } func testCdnByPassNoTillChange() throws { - try cdnByPassNoTill(segmentsChanged: true) + try cdnByPassNoTill(segmentsChanged: ["Segment1"]) } - func cdnByPassNoTill(segmentsChanged: Bool) throws { + func cdnByPassNoTill(segmentsChanged: [String]) throws { let goalCn: Int64 = 300 mySegmentsStorage.changeNumber = 200 myLargeSegmentsStorage.changeNumber = 200 - changeChecker.haveChanged = segmentsChanged + changeChecker.haveChanged = !segmentsChanged.isEmpty + changeChecker.diffSegments = segmentsChanged let exp = XCTestExpectation() mySegmentsFetcher.countExp = exp From 88d03a8dfb97d0d9e676a93e94b119ed8316a738 Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Mon, 30 Jun 2025 01:02:38 -0300 Subject: [PATCH 52/62] Change --- .../Refresh/RetryableSegmentsSyncWorker.swift | 8 +- .../Sync/SplitSdkUpdatePollingTest.swift | 186 ++++++++---------- 2 files changed, 82 insertions(+), 112 deletions(-) diff --git a/Split/FetcherEngine/Refresh/RetryableSegmentsSyncWorker.swift b/Split/FetcherEngine/Refresh/RetryableSegmentsSyncWorker.swift index 831cbdca7..3881ea511 100644 --- a/Split/FetcherEngine/Refresh/RetryableSegmentsSyncWorker.swift +++ b/Split/FetcherEngine/Refresh/RetryableSegmentsSyncWorker.swift @@ -210,13 +210,13 @@ class DefaultSegmentsSyncHelper: SegmentsSyncHelper { Logger.d("Checking my large segments update") checkAndUpdate(isChanged: mlsChanged, change: myLargeSegmentsChange, storage: myLargeSegmentsStorage) -// let segmentsDiff = changeChecker.getSegmentsDiff(oldSegments: oldChange.segments, newSegments: mySegmentsChange.segments) -// let largeSegmentsDiff = changeChecker.getSegmentsDiff(oldSegments: oldLargeChange.segments, newSegments: myLargeSegmentsChange.segments) + let segmentsDiff = changeChecker.getSegmentsDiff(oldSegments: oldChange.segments, newSegments: mySegmentsChange.segments) + let largeSegmentsDiff = changeChecker.getSegmentsDiff(oldSegments: oldLargeChange.segments, newSegments: myLargeSegmentsChange.segments) return FetchResult(msTill: mySegmentsChange.unwrappedChangeNumber, mlsTill: myLargeSegmentsChange.unwrappedChangeNumber, - msUpdated: mySegmentsChange.segments.compactMap(\.name), - mlsUpdated: myLargeSegmentsChange.segments.compactMap(\.name)) + msUpdated: segmentsDiff, + mlsUpdated: largeSegmentsDiff) } prevChange = change } diff --git a/SplitTests/Integration/Sync/SplitSdkUpdatePollingTest.swift b/SplitTests/Integration/Sync/SplitSdkUpdatePollingTest.swift index d2dd0bcc8..84ccba9fe 100644 --- a/SplitTests/Integration/Sync/SplitSdkUpdatePollingTest.swift +++ b/SplitTests/Integration/Sync/SplitSdkUpdatePollingTest.swift @@ -29,82 +29,15 @@ class SplitSdkUpdatePollingTest: XCTestCase { ] let impExp = XCTestExpectation(description: "impressions") - var impHit: [ImpressionsTest]? override func setUp() { let session = HttpSessionMock() - let reqManager = HttpRequestManagerTestDispatcher(dispatcher: buildTestDispatcher("splitchanges_int_test"), - streamingHandler: buildStreamingHandler()) + let reqManager = HttpRequestManagerTestDispatcher(dispatcher: buildTestDispatcher("splitchanges_int_test"), streamingHandler: buildStreamingHandler()) httpClient = DefaultHttpClient(session: session, requestManager: reqManager) } - - private func buildTestDispatcher(_ file: String) -> HttpClientTestDispatcher { - - let respData = responseSplitChanges(file) - var responses = [TestDispatcherResponse]() - for data in respData { - let rData = TargetingRulesChange(featureFlags: data, ruleBasedSegments: RuleBasedSegmentChange(segments: [], since: -1, till: -1)) - responses.append(TestDispatcherResponse(code: 200, data: Data(try! Json.encodeToJson(rData).utf8))) - } - - return { request in - if request.isSplitEndpoint() { - let index = self.getAndIncrement() - if index < self.spExp.count { - if index > 0 { - self.spExp[index - 1].fulfill() - } - return responses[index] - } else if index == self.spExp.count { - self.spExp[index - 1].fulfill() - } - return TestDispatcherResponse(code: 200, data: Data(IntegrationHelper.emptySplitChanges(since: 99999999, till: 99999999).utf8)) - } - - if request.isMySegmentsEndpoint() { - self.mySegmentsHits+=1 - let hit = self.mySegmentsHits - var json = IntegrationHelper.emptyMySegments - if hit > 2 { - var mySegments = [String]() - for i in 1...hit { - mySegments.append("segment\(i)") - } - - json = IntegrationHelper.buildSegments(regular: mySegments) - return TestDispatcherResponse(code: 200, data: Data(json.utf8)) - } - return TestDispatcherResponse(code: 200, data: Data(IntegrationHelper.emptyMySegments.utf8)) - } - - if request.isAuthEndpoint() { - return TestDispatcherResponse(code: 200, data: Data(IntegrationHelper.dummySseResponse().utf8)) - } - - if request.isImpressionsEndpoint() { - self.impHit = try? TestUtils.impressionsFromHit(request: request) - self.impExp.fulfill() - return TestDispatcherResponse(code: 200) - } - - if request.isEventsEndpoint() { - return TestDispatcherResponse(code: 200) - } - - return TestDispatcherResponse(code: 500) - } - } - - private func buildStreamingHandler() -> TestStreamResponseBindingHandler { - return { request in - self.streamingBinding = TestStreamResponseBinding.createFor(request: request, code: 200) - return self.streamingBinding! - } - } - - // MARK: Test + // MARK: Tests func testSdkReadyOnly() throws { let apiKey = IntegrationHelper.dummyApiKey let trafficType = "client" @@ -253,17 +186,14 @@ class SplitSdkUpdatePollingTest: XCTestCase { } func testSdkKillSplitWithMetadata() throws { + + // Setup let session = HttpSessionMock() - let reqManager = HttpRequestManagerTestDispatcher(dispatcher: buildTestDispatcher("split_killed_test"), - streamingHandler: buildStreamingHandler()) + let reqManager = HttpRequestManagerTestDispatcher(dispatcher: buildTestDispatcher("split_killed_test"), streamingHandler: buildStreamingHandler()) httpClient = DefaultHttpClient(session: session, requestManager: reqManager) - let apiKey = IntegrationHelper.dummyApiKey let trafficType = "client" - - let sdkReady = XCTestExpectation(description: "SDK READY Expectation") let sdkUpdateWithMetadata = XCTestExpectation(description: "SDK Update With Metadata Expectation") - let splitConfig: SplitClientConfig = SplitClientConfig() splitConfig.segmentsRefreshRate = 99999 splitConfig.featuresRefreshRate = 2 @@ -273,27 +203,24 @@ class SplitSdkUpdatePollingTest: XCTestCase { splitConfig.streamingEnabled = false splitConfig.serviceEndpoints = ServiceEndpoints.builder() .set(sdkEndpoint: serverUrl).set(eventsEndpoint: serverUrl).build() - let key: Key = Key(matchingKey: kMatchingKey, bucketingKey: nil) let builder = DefaultSplitFactoryBuilder() _ = builder.setTestDatabase(TestingHelper.createTestDatabase(name: "SplitChangesTest")) _ = builder.setHttpClient(httpClient) factory = builder.setApiKey(apiKey).setKey(key).setConfig(splitConfig).build() - let client = factory!.client - client.on(event: .sdkReady) { - sdkReady.fulfill() - } - + // Test client.on(event: .sdkUpdated) { metadata in - XCTAssertEqual(metadata?.type, .FLAGS_KILLED) - XCTAssertEqual(metadata?.data, ["test_feature_kill"]) - sdkUpdateWithMetadata.fulfill() + if metadata?.type == .FLAGS_KILLED { + XCTAssertEqual(metadata?.data, ["test_feature_kill"]) + sdkUpdateWithMetadata.fulfill() + } } - wait(for: [sdkReady, sdkUpdateWithMetadata], timeout: 30) + wait(for: [sdkUpdateWithMetadata], timeout: 30) + // Clenaup let semaphore = DispatchSemaphore(value: 0) client.destroy(completion: { _ = semaphore.signal() @@ -327,16 +254,11 @@ class SplitSdkUpdatePollingTest: XCTestCase { let client = factory!.client - var sdkReadyFired = false - var sdkUpdatedFired = false - client.on(event: .sdkReady) { - sdkReadyFired = true sdkReady.fulfill() } client.on(event: .sdkUpdated) { - sdkUpdatedFired = true sdkUpdate.fulfill() } @@ -345,9 +267,6 @@ class SplitSdkUpdatePollingTest: XCTestCase { // wait for sdk update ThreadUtils.delay(seconds: 1.0) - XCTAssertTrue(sdkReadyFired) - XCTAssertTrue(sdkUpdatedFired) - let semaphore = DispatchSemaphore(value: 0) client.destroy(completion: { _ = semaphore.signal() @@ -360,7 +279,6 @@ class SplitSdkUpdatePollingTest: XCTestCase { let trafficType = "client" let sdkReady = XCTestExpectation(description: "SDK READY Expectation") - let sdkUpdate = XCTestExpectation(description: "SDK Update Expectation") let sdkUpdateWithMetadata = XCTestExpectation(description: "SDK Update With Metadata Expectation") let splitConfig: SplitClientConfig = SplitClientConfig() @@ -379,21 +297,11 @@ class SplitSdkUpdatePollingTest: XCTestCase { _ = builder.setTestDatabase(TestingHelper.createTestDatabase(name: "SplitChangesTest")) _ = builder.setHttpClient(httpClient) factory = builder.setApiKey(apiKey).setKey(key).setConfig(splitConfig).build() - let client = factory!.client - var sdkReadyFired = false - var sdkUpdatedFired = false - client.on(event: .sdkReady) { - sdkReadyFired = true sdkReady.fulfill() } - - client.on(event: .sdkUpdated) { - sdkUpdatedFired = true - sdkUpdate.fulfill() - } client.on(event: .sdkUpdated) { metadata in if metadata?.type == .SEGMENTS_UPDATED { @@ -402,14 +310,11 @@ class SplitSdkUpdatePollingTest: XCTestCase { } } - wait(for: [sdkReady, sdkUpdate, sdkUpdateWithMetadata], timeout: 30) + wait(for: [sdkUpdateWithMetadata], timeout: 40) // wait for sdk update ThreadUtils.delay(seconds: 1.0) - XCTAssertTrue(sdkReadyFired) - XCTAssertTrue(sdkUpdatedFired) - let semaphore = DispatchSemaphore(value: 0) client.destroy(completion: { _ = semaphore.signal() @@ -417,6 +322,71 @@ class SplitSdkUpdatePollingTest: XCTestCase { semaphore.wait() } + //MARK: Testing Helpers + private func buildTestDispatcher(_ file: String) -> HttpClientTestDispatcher { + + let respData = responseSplitChanges(file) + var responses = [TestDispatcherResponse]() + for data in respData { + let rData = TargetingRulesChange(featureFlags: data, ruleBasedSegments: RuleBasedSegmentChange(segments: [], since: -1, till: -1)) + responses.append(TestDispatcherResponse(code: 200, data: Data(try! Json.encodeToJson(rData).utf8))) + } + + return { request in + if request.isSplitEndpoint() { + let index = self.getAndIncrement() + if index < self.spExp.count { + if index > 0 { + self.spExp[index - 1].fulfill() + } + return responses[index] + } else if index == self.spExp.count { + self.spExp[index - 1].fulfill() + } + return TestDispatcherResponse(code: 200, data: Data(IntegrationHelper.emptySplitChanges(since: 99999999, till: 99999999).utf8)) + } + + if request.isMySegmentsEndpoint() { + self.mySegmentsHits+=1 + let hit = self.mySegmentsHits + var json = IntegrationHelper.emptyMySegments + if hit > 2 { + var mySegments = [String]() + for i in 1...hit { + mySegments.append("segment\(i)") + } + + json = IntegrationHelper.buildSegments(regular: mySegments) + return TestDispatcherResponse(code: 200, data: Data(json.utf8)) + } + return TestDispatcherResponse(code: 200, data: Data(IntegrationHelper.emptyMySegments.utf8)) + } + + if request.isAuthEndpoint() { + return TestDispatcherResponse(code: 200, data: Data(IntegrationHelper.dummySseResponse().utf8)) + } + + if request.isImpressionsEndpoint() { + self.impHit = try? TestUtils.impressionsFromHit(request: request) + self.impExp.fulfill() + return TestDispatcherResponse(code: 200) + } + + if request.isEventsEndpoint() { + return TestDispatcherResponse(code: 200) + } + + return TestDispatcherResponse(code: 500) + } + } + + private func buildStreamingHandler() -> TestStreamResponseBindingHandler { + return { request in + self.streamingBinding = TestStreamResponseBinding.createFor(request: request, code: 200) + return self.streamingBinding! + } + } + private func responseSplitChanges(_ file: String) -> [SplitChange] { var changes = [SplitChange]() From 4c7942e1f2d6f2333288cd5c6964f9890df5cd8c Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Mon, 30 Jun 2025 01:07:40 -0300 Subject: [PATCH 53/62] Removed unused test --- .../Sync/SplitSdkUpdatePollingTest.swift | 49 ------------------- .../streaming/StreamingSplitKillTest.swift | 5 +- 2 files changed, 2 insertions(+), 52 deletions(-) diff --git a/SplitTests/Integration/Sync/SplitSdkUpdatePollingTest.swift b/SplitTests/Integration/Sync/SplitSdkUpdatePollingTest.swift index bcafd6984..603241cf6 100644 --- a/SplitTests/Integration/Sync/SplitSdkUpdatePollingTest.swift +++ b/SplitTests/Integration/Sync/SplitSdkUpdatePollingTest.swift @@ -251,55 +251,6 @@ class SplitSdkUpdatePollingTest: XCTestCase { }) semaphore.wait() } - - func testSdkKillSplitWithMetadata() throws { - let session = HttpSessionMock() - let reqManager = HttpRequestManagerTestDispatcher(dispatcher: buildTestDispatcher("split_killed_test"), - streamingHandler: buildStreamingHandler()) - httpClient = DefaultHttpClient(session: session, requestManager: reqManager) - - let apiKey = IntegrationHelper.dummyApiKey - let trafficType = "client" - - let sdkReady = XCTestExpectation(description: "SDK READY Expectation") - let sdkUpdateWithMetadata = XCTestExpectation(description: "SDK Update With Metadata Expectation") - - let splitConfig: SplitClientConfig = SplitClientConfig() - splitConfig.segmentsRefreshRate = 99999 - splitConfig.featuresRefreshRate = 2 - splitConfig.impressionRefreshRate = 99999 - splitConfig.sdkReadyTimeOut = 60000 - splitConfig.trafficType = trafficType - splitConfig.streamingEnabled = false - splitConfig.serviceEndpoints = ServiceEndpoints.builder() - .set(sdkEndpoint: serverUrl).set(eventsEndpoint: serverUrl).build() - - let key: Key = Key(matchingKey: kMatchingKey, bucketingKey: nil) - let builder = DefaultSplitFactoryBuilder() - _ = builder.setTestDatabase(TestingHelper.createTestDatabase(name: "SplitChangesTest")) - _ = builder.setHttpClient(httpClient) - factory = builder.setApiKey(apiKey).setKey(key).setConfig(splitConfig).build() - - let client = factory!.client - - client.on(event: .sdkReady) { - sdkReady.fulfill() - } - - client.on(event: .sdkUpdated) { metadata in - XCTAssertEqual(metadata?.type, .FLAGS_KILLED) - XCTAssertEqual(metadata?.data, ["test_feature_kill"]) - sdkUpdateWithMetadata.fulfill() - } - - wait(for: [sdkReady, sdkUpdateWithMetadata], timeout: 30) - - let semaphore = DispatchSemaphore(value: 0) - client.destroy(completion: { - _ = semaphore.signal() - }) - semaphore.wait() - } func testSdkUpdateMySegments() throws { let apiKey = IntegrationHelper.dummyApiKey diff --git a/SplitTests/Integration/streaming/StreamingSplitKillTest.swift b/SplitTests/Integration/streaming/StreamingSplitKillTest.swift index 397b0b32e..dc7f83293 100644 --- a/SplitTests/Integration/streaming/StreamingSplitKillTest.swift +++ b/SplitTests/Integration/streaming/StreamingSplitKillTest.swift @@ -128,8 +128,7 @@ class StreamingSplitKillTest: XCTestCase { splitConfig.impressionRefreshRate = 999999 splitConfig.sdkReadyTimeOut = 60000 splitConfig.eventsPushRate = 999999 - - + let key: Key = Key(matchingKey: userKey) let builder = DefaultSplitFactoryBuilder() _ = builder.setHttpClient(httpClient) @@ -162,7 +161,7 @@ class StreamingSplitKillTest: XCTestCase { timestamp: numbers[splitsChangesHits], changeNumber: numbers[splitsChangesHits])) - // Exit + // Cleanup wait(for: [exp5], timeout: expTimeout) let semaphore = DispatchSemaphore(value: 0) client.destroy(completion: { From 06ac5a25f559fdffd14e573c12ccfa517f81b779 Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Mon, 30 Jun 2025 01:39:07 -0300 Subject: [PATCH 54/62] Test fixed --- .../streaming/StreamingSplitKillTest.swift | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/SplitTests/Integration/streaming/StreamingSplitKillTest.swift b/SplitTests/Integration/streaming/StreamingSplitKillTest.swift index dc7f83293..50e25d1b4 100644 --- a/SplitTests/Integration/streaming/StreamingSplitKillTest.swift +++ b/SplitTests/Integration/streaming/StreamingSplitKillTest.swift @@ -32,12 +32,12 @@ class StreamingSplitKillTest: XCTestCase { override func setUp() { expIndex = 1 let session = HttpSessionMock() - let reqManager = HttpRequestManagerTestDispatcher(dispatcher: buildTestDispatcher(), - streamingHandler: buildStreamingHandler()) + let reqManager = HttpRequestManagerTestDispatcher(dispatcher: buildTestDispatcher(), streamingHandler: buildStreamingHandler()) httpClient = DefaultHttpClient(session: session, requestManager: reqManager) loadChanges() } + // MARK: Tests func testSplitKill() throws { let splitConfig: SplitClientConfig = SplitClientConfig() splitConfig.featuresRefreshRate = 9999 @@ -64,11 +64,11 @@ class StreamingSplitKillTest: XCTestCase { exp3 = XCTestExpectation(description: "Exp3") exp4 = XCTestExpectation(description: "Exp4") - client.on(event: SplitEvent.sdkReady) { + client.on(event: .sdkReady) { sdkReadyExpectation.fulfill() } - client.on(event: SplitEvent.sdkReadyTimedOut) { + client.on(event: .sdkReadyTimedOut) { IntegrationHelper.tlog("TIMEOUT") } @@ -81,28 +81,25 @@ class StreamingSplitKillTest: XCTestCase { let splitName = "workm" let treatmentReady = client.getTreatment(splitName) - streamingBinding?.push(message: - StreamingIntegrationHelper.splitKillMessagge(splitName: splitName, defaultTreatment: "conta", - timestamp: numbers[splitsChangesHits], - changeNumber: numbers[splitsChangesHits])) + streamingBinding?.push(message: StreamingIntegrationHelper.splitKillMessagge(splitName: splitName, defaultTreatment: "conta", + timestamp: numbers[splitsChangesHits], + changeNumber: numbers[splitsChangesHits])) wait(for: [exp2], timeout: expTimeout) waitForUpdate(secs: 1) let treatmentKill = client.getTreatment(splitName) - streamingBinding?.push(message: - StreamingIntegrationHelper.splitUpdateMessage(timestamp: numbers[splitsChangesHits], - changeNumber: numbers[splitsChangesHits])) + streamingBinding?.push(message: StreamingIntegrationHelper.splitUpdateMessage(timestamp: numbers[splitsChangesHits], + changeNumber: numbers[splitsChangesHits])) wait(for: [exp3], timeout: expTimeout) waitForUpdate(secs: 1) let treatmentNoKill = client.getTreatment(splitName) - streamingBinding?.push(message: - StreamingIntegrationHelper.splitKillMessagge(splitName: splitName, defaultTreatment: "conta", - timestamp: numbers[0], - changeNumber: numbers[0])) + streamingBinding?.push(message: StreamingIntegrationHelper.splitKillMessagge(splitName: splitName, defaultTreatment: "conta", + timestamp: numbers[0], + changeNumber: numbers[0])) ThreadUtils.delay(seconds: 2.0) // The server should not be hit here let treatmentOldKill = client.getTreatment(splitName) @@ -140,9 +137,10 @@ class StreamingSplitKillTest: XCTestCase { let sdkReadyExpectation = XCTestExpectation(description: "SDK READY Expectation") exp1 = XCTestExpectation(description: "Streaming notification") + exp2 = XCTestExpectation(description: "Push notification") exp5 = XCTestExpectation(description: "Wait for killed metadata event") - client.on(event: SplitEvent.sdkReady) { sdkReadyExpectation.fulfill() } + client.on(event: .sdkReady) { sdkReadyExpectation.fulfill() } // Set listener client.on(event: .sdkUpdated) { [weak self] metadata in @@ -160,9 +158,10 @@ class StreamingSplitKillTest: XCTestCase { streamingBinding?.push(message: StreamingIntegrationHelper.splitKillMessagge(splitName: "workm", defaultTreatment: "conta", timestamp: numbers[splitsChangesHits], changeNumber: numbers[splitsChangesHits])) + wait(for: [exp5, exp2], timeout: expTimeout) + waitForUpdate(secs: 1) // Cleanup - wait(for: [exp5], timeout: expTimeout) let semaphore = DispatchSemaphore(value: 0) client.destroy(completion: { _ = semaphore.signal() @@ -170,6 +169,7 @@ class StreamingSplitKillTest: XCTestCase { semaphore.wait() } + //MARK: Testing Helpers private func getChanges(for hitNumber: Int) -> Data { if hitNumber < 4 { return Data(self.changes[hitNumber].utf8) From 8fd7fef666602c13195795d4a4209ddf4b633e0e Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Mon, 30 Jun 2025 10:38:27 -0300 Subject: [PATCH 55/62] Changing --- .../Sync/SplitSdkUpdatePollingTest.swift | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/SplitTests/Integration/Sync/SplitSdkUpdatePollingTest.swift b/SplitTests/Integration/Sync/SplitSdkUpdatePollingTest.swift index 42158fd2b..1cc8f3932 100644 --- a/SplitTests/Integration/Sync/SplitSdkUpdatePollingTest.swift +++ b/SplitTests/Integration/Sync/SplitSdkUpdatePollingTest.swift @@ -43,6 +43,7 @@ class SplitSdkUpdatePollingTest: XCTestCase { let trafficType = "client" let sdkReady = XCTestExpectation(description: "SDK READY Expectation") + let sdkUpdate = XCTestExpectation(description: "SDK UPDATE Expectation") let splitConfig: SplitClientConfig = SplitClientConfig() splitConfig.segmentsRefreshRate = 99999 @@ -72,6 +73,7 @@ class SplitSdkUpdatePollingTest: XCTestCase { client.on(event: SplitEvent.sdkUpdated) { sdkUpdatedFired = true + sdkUpdate.fulfill() } wait(for: [sdkReady], timeout: 30) @@ -145,7 +147,6 @@ class SplitSdkUpdatePollingTest: XCTestCase { let apiKey = IntegrationHelper.dummyApiKey let trafficType = "client" - let sdkReady = XCTestExpectation(description: "SDK READY Expectation") let sdkUpdateWithMetadata = XCTestExpectation(description: "SDK Update With Metadata Expectation") let splitConfig: SplitClientConfig = SplitClientConfig() @@ -166,17 +167,13 @@ class SplitSdkUpdatePollingTest: XCTestCase { let client = factory!.client - client.on(event: .sdkReady) { - sdkReady.fulfill() - } - client.on(event: .sdkUpdated) { metadata in XCTAssertEqual(metadata?.type, .FLAGS_UPDATED) XCTAssertEqual(metadata?.data, ["test_feature"]) sdkUpdateWithMetadata.fulfill() } - wait(for: [sdkReady, sdkUpdateWithMetadata], timeout: 30) + wait(for: [sdkUpdateWithMetadata], timeout: 30) let semaphore = DispatchSemaphore(value: 0) client.destroy(completion: { @@ -211,11 +208,16 @@ class SplitSdkUpdatePollingTest: XCTestCase { let client = factory!.client - client.on(event: .sdkReady) { + var sdkReadyFired = false + var sdkUpdatedFired = false + + client.on(event: SplitEvent.sdkReady) { + sdkReadyFired = true sdkReady.fulfill() } - client.on(event: .sdkUpdated) { + client.on(event: SplitEvent.sdkUpdated) { + sdkUpdatedFired = true sdkUpdate.fulfill() } @@ -224,6 +226,10 @@ class SplitSdkUpdatePollingTest: XCTestCase { // wait for sdk update ThreadUtils.delay(seconds: 1.0) + XCTAssertTrue(sdkReadyFired) + XCTAssertTrue(sdkUpdatedFired) + + let semaphore = DispatchSemaphore(value: 0) client.destroy(completion: { _ = semaphore.signal() @@ -255,10 +261,6 @@ class SplitSdkUpdatePollingTest: XCTestCase { _ = builder.setHttpClient(httpClient) factory = builder.setApiKey(apiKey).setKey(key).setConfig(splitConfig).build() let client = factory!.client - - client.on(event: .sdkReady) { - sdkReady.fulfill() - } client.on(event: .sdkUpdated) { metadata in if metadata?.type == .SEGMENTS_UPDATED { From e1f7a322d3d72dfc514231137c121cc48c6a6c2a Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Mon, 30 Jun 2025 10:56:27 -0300 Subject: [PATCH 56/62] Changing --- .../Sync/SplitSdkUpdatePollingTest.swift | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/SplitTests/Integration/Sync/SplitSdkUpdatePollingTest.swift b/SplitTests/Integration/Sync/SplitSdkUpdatePollingTest.swift index 1cc8f3932..207299715 100644 --- a/SplitTests/Integration/Sync/SplitSdkUpdatePollingTest.swift +++ b/SplitTests/Integration/Sync/SplitSdkUpdatePollingTest.swift @@ -33,7 +33,7 @@ class SplitSdkUpdatePollingTest: XCTestCase { override func setUp() { let session = HttpSessionMock() - let reqManager = HttpRequestManagerTestDispatcher(dispatcher: buildTestDispatcher("splitchanges_int_test"), streamingHandler: buildStreamingHandler()) + let reqManager = HttpRequestManagerTestDispatcher(dispatcher: buildTestDispatcher(), streamingHandler: buildStreamingHandler()) httpClient = DefaultHttpClient(session: session, requestManager: reqManager) } @@ -282,9 +282,9 @@ class SplitSdkUpdatePollingTest: XCTestCase { } //MARK: Testing Helpers - private func buildTestDispatcher(_ file: String) -> HttpClientTestDispatcher { + private func buildTestDispatcher() -> HttpClientTestDispatcher { - let respData = responseSplitChanges(file) + let respData = responseSplitChanges() var responses = [TestDispatcherResponse]() for data in respData { let rData = TargetingRulesChange(featureFlags: data, ruleBasedSegments: RuleBasedSegmentChange(segments: [], since: -1, till: -1)) @@ -346,12 +346,12 @@ class SplitSdkUpdatePollingTest: XCTestCase { } } - private func responseSplitChanges(_ file: String) -> [SplitChange] { + private func responseSplitChanges() -> [SplitChange] { var changes = [SplitChange]() var prevChangeNumber: Int64 = 0 for i in 0..<4 { - let c = loadSplitsChangeFile(file)! + let c = loadSplitsChangeFile()! c.since = c.till if prevChangeNumber != 0 { c.till = prevChangeNumber + kChangeNbInterval @@ -370,8 +370,8 @@ class SplitSdkUpdatePollingTest: XCTestCase { return changes } - private func loadSplitsChangeFile(_ file: String) -> SplitChange? { - return FileHelper.loadSplitChangeFile(sourceClass: self, fileName: file) + private func loadSplitsChangeFile() -> SplitChange? { + return FileHelper.loadSplitChangeFile(sourceClass: self, fileName: "splitchanges_int_test") } private func getAndIncrement() -> Int { From d1de0fcfed66b9b1a989e63003188dae7d4056da Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Mon, 30 Jun 2025 11:13:01 -0300 Subject: [PATCH 57/62] Removed file --- Split.xcodeproj/project.pbxproj | 4 -- Split/Events/SplitEventsManager.swift | 2 +- .../Integration/split_killed_test.json | 59 ------------------- 3 files changed, 1 insertion(+), 64 deletions(-) delete mode 100644 SplitTests/Resources/Integration/split_killed_test.json diff --git a/Split.xcodeproj/project.pbxproj b/Split.xcodeproj/project.pbxproj index f1a1f6002..34e03afcf 100644 --- a/Split.xcodeproj/project.pbxproj +++ b/Split.xcodeproj/project.pbxproj @@ -356,7 +356,6 @@ 5B48D8172DEA2CED00351925 /* PrerequisitesMatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BF52DF52DE0B60300FEDAFE /* PrerequisitesMatcher.swift */; }; 5B48D8192DF360D000351925 /* EventMetadataType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B48D8182DF360CB00351925 /* EventMetadataType.swift */; }; 5B48D81A2DF360D000351925 /* EventMetadataType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B48D8182DF360CB00351925 /* EventMetadataType.swift */; }; - 5B584C182E0DD6A000C68F9B /* split_killed_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 5B584C172E0DD69300C68F9B /* split_killed_test.json */; }; 5B91B8392DDE4A3B000510F0 /* SplitDTOTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B91B8382DDE4A30000510F0 /* SplitDTOTests.swift */; }; 5BF52DF72DE0B60700FEDAFE /* PrerequisitesMatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BF52DF52DE0B60300FEDAFE /* PrerequisitesMatcher.swift */; }; 5BF52DF92DE4B8D400FEDAFE /* PrerequisitesMatcherTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BF52DF82DE4B8CA00FEDAFE /* PrerequisitesMatcherTest.swift */; }; @@ -1561,7 +1560,6 @@ 59FB7C3B2203795F00ECC96A /* LocalhostSplitsParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalhostSplitsParser.swift; sourceTree = ""; }; 59FB7C3D22037B9400ECC96A /* SpaceDelimitedLocalhostSplitsParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpaceDelimitedLocalhostSplitsParser.swift; sourceTree = ""; }; 5B48D8182DF360CB00351925 /* EventMetadataType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventMetadataType.swift; sourceTree = ""; }; - 5B584C172E0DD69300C68F9B /* split_killed_test.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = split_killed_test.json; 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 = ""; }; @@ -3037,7 +3035,6 @@ 595B66D2231DB8690030F330 /* Integration */ = { isa = PBXGroup; children = ( - 5B584C172E0DD69300C68F9B /* split_killed_test.json */, 595B66CE231DB0720030F330 /* splitchanges_int_test.json */, 95118EE7281AC6E700782F33 /* bucket_split_test.json */, 95F8F06728170473009E09B1 /* multi_client_test.json */, @@ -3974,7 +3971,6 @@ 5959C46E2278D9AD0064F968 /* segment_conta_condition.json in Resources */, 59C493052167E3B400F5F774 /* murmur3-sample-double-treatment-users.csv in Resources */, 95F7BC082C1CE84500C5F2E4 /* rsa_2048_cert.pem.der in Resources */, - 5B584C182E0DD6A000C68F9B /* split_killed_test.json in Resources */, C5977C322BF4031A003E293A /* split_changes_semver.json in Resources */, 599EA59022691D57006CBA89 /* localhost_yml.yml in Resources */, 95F7BBFB2C1C953000C5F2E4 /* ec_secp521r1_pub.der in Resources */, diff --git a/Split/Events/SplitEventsManager.swift b/Split/Events/SplitEventsManager.swift index 971801d03..5781b3fff 100644 --- a/Split/Events/SplitEventsManager.swift +++ b/Split/Events/SplitEventsManager.swift @@ -50,7 +50,7 @@ class DefaultSplitEventsManager: SplitEventsManager { } } } - + func notifyInternalEvent(_ event: SplitInternalEvent, metadata: EventMetadata? = nil) { let event = SplitInternalEventWithMetadata(event, metadata: metadata) diff --git a/SplitTests/Resources/Integration/split_killed_test.json b/SplitTests/Resources/Integration/split_killed_test.json deleted file mode 100644 index f95d325a0..000000000 --- a/SplitTests/Resources/Integration/split_killed_test.json +++ /dev/null @@ -1,59 +0,0 @@ - -{ - "ff":{ - "d":[ - { - "trafficTypeName":"client", - "name":"test_feature_kill", - "trafficAllocation":100, - "trafficAllocationSeed":-2049557248, - "seed":1188118899, - "status":"ACTIVE", - "killed":true, - "defaultTreatment":"yes", - "changeNumber":1567456937865, - "algo":2, - "configurations":{ - - }, - "conditions":[ - { - "conditionType":"ROLLOUT", - "matcherGroup":{ - "combiner":"AND", - "matchers":[ - { - "keySelector":{ - "trafficType":"client", - "attribute":null - }, - "matcherType":"ALL_KEYS", - "negate":false, - "userDefinedSegmentMatcherData":null, - "whitelistMatcherData":null, - "unaryNumericMatcherData":null, - "betweenMatcherData":null, - "booleanMatcherData":null, - "dependencyMatcherData":null, - "stringMatcherData":null - } - ] - }, - "partitions":[ - { - "treatment":"si", - "size":0 - }, - { - "treatment":"no", - "size":100 - } - ], - "label":"default rule" - } - ] - } - ], - "s":1567455995317, - "t":1567456937865 -}, "rbs": {"s":-1,"t":-1,"d":[]}} From 6968ebe5214fc81c7ccc26bc25f7124d8cdde4ca Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Mon, 30 Jun 2025 12:39:05 -0300 Subject: [PATCH 58/62] Test passing --- .../Refresh/PeriodicSyncWorker.swift | 7 ++++- .../Refresh/RetryableSegmentsSyncWorker.swift | 7 ++++- .../Streaming/PushNotificationManager.swift | 9 +------ .../StreamingMySegmentsSyncTest.swift | 26 ++++++++++++++----- 4 files changed, 33 insertions(+), 16 deletions(-) diff --git a/Split/FetcherEngine/Refresh/PeriodicSyncWorker.swift b/Split/FetcherEngine/Refresh/PeriodicSyncWorker.swift index c25c73f2e..89f2c3657 100644 --- a/Split/FetcherEngine/Refresh/PeriodicSyncWorker.swift +++ b/Split/FetcherEngine/Refresh/PeriodicSyncWorker.swift @@ -225,7 +225,12 @@ class PeriodicMySegmentsSyncWorker: BasePeriodicSyncWorker { if result.success { if !result.msUpdated.isEmpty || !result.mlsUpdated.isEmpty { // For now is not necessary specify which entity was updated - notifyUpdate(.mySegmentsUpdated, metadata: EventMetadata(type: .SEGMENTS_UPDATED, data: result.msUpdated + result.mlsUpdated)) + if !result.msUpdated.isEmpty { + notifyUpdate(.mySegmentsUpdated, metadata: EventMetadata(type: .SEGMENTS_UPDATED, data: result.msUpdated)) + } + if !result.mlsUpdated.isEmpty { + notifyUpdate(.myLargeSegmentsUpdated, metadata: EventMetadata(type: .LARGE_SEGMENTS_UPDATED, data: result.msUpdated)) + } } } } catch { diff --git a/Split/FetcherEngine/Refresh/RetryableSegmentsSyncWorker.swift b/Split/FetcherEngine/Refresh/RetryableSegmentsSyncWorker.swift index 3881ea511..c9c605c8b 100644 --- a/Split/FetcherEngine/Refresh/RetryableSegmentsSyncWorker.swift +++ b/Split/FetcherEngine/Refresh/RetryableSegmentsSyncWorker.swift @@ -50,7 +50,12 @@ class RetryableMySegmentsSyncWorker: BaseRetryableSyncWorker { notifyUpdate(.myLargeSegmentsUpdated, metadata: EventMetadata(type: .LARGE_SEGMENTS_UPDATED, data: result.mlsUpdated)) } else if !result.msUpdated.isEmpty || !result.mlsUpdated.isEmpty { // For now is not necessary specify which entity was updated - notifyUpdate(.mySegmentsUpdated, metadata: EventMetadata(type: .SEGMENTS_UPDATED, data: result.msUpdated + result.mlsUpdated)) + if !result.msUpdated.isEmpty { + notifyUpdate(.mySegmentsUpdated, metadata: EventMetadata(type: .SEGMENTS_UPDATED, data: result.msUpdated)) + } + if !result.mlsUpdated.isEmpty { + notifyUpdate(.myLargeSegmentsUpdated, metadata: EventMetadata(type: .LARGE_SEGMENTS_UPDATED, data: result.mlsUpdated)) + } } return true } diff --git a/Split/Network/Streaming/PushNotificationManager.swift b/Split/Network/Streaming/PushNotificationManager.swift index 59e46de8e..38aa4f28f 100644 --- a/Split/Network/Streaming/PushNotificationManager.swift +++ b/Split/Network/Streaming/PushNotificationManager.swift @@ -134,14 +134,7 @@ class DefaultPushNotificationManager: PushNotificationManager { } Logger.d("Streaming authentication success") - var connectionDelay: Int64 = 0 - - #if DEBUG - connectionDelay = 1 - #else - connectionDelay = result.sseConnectionDelay - #endif - + let connectionDelay = result.sseConnectionDelay self.broadcasterChannel.push(event: .pushDelayReceived(delaySeconds: connectionDelay)) let lastId = lastConnId.value if connectionDelay > 0 { diff --git a/SplitTests/Integration/streaming/StreamingMySegmentsSyncTest.swift b/SplitTests/Integration/streaming/StreamingMySegmentsSyncTest.swift index 00d764b14..c3c6da64e 100644 --- a/SplitTests/Integration/streaming/StreamingMySegmentsSyncTest.swift +++ b/SplitTests/Integration/streaming/StreamingMySegmentsSyncTest.swift @@ -70,7 +70,7 @@ class StreamingMySegmentsSyncTest: XCTestCase { splitConfig.impressionRefreshRate = 999999 splitConfig.sdkReadyTimeOut = 60000 splitConfig.eventsPushRate = 999999 - splitConfig.logLevel = .info + splitConfig.logLevel = .error let key: Key = Key(matchingKey: userKey) let builder = DefaultSplitFactoryBuilder() @@ -94,12 +94,20 @@ class StreamingMySegmentsSyncTest: XCTestCase { sdkReadyExpectation.fulfill() } - client.on(event: .sdkUpdated) { metadata in - print("COOL ::: \(metadata?.type) \(metadata?.data)") - } + var segmentsMetadataExecuted = false + var largeSegmentsMetadataExecuted = false - client.on(event: .sdkUpdated) { - print("COOL :: ") + // Test with metadata + client.on(event: .sdkUpdated) { metadata in + if metadata!.type == .SEGMENTS_UPDATED { + XCTAssert(metadata!.data == ["new_segment"]) + segmentsMetadataExecuted = true + } else if metadata!.type == .LARGE_SEGMENTS_UPDATED { + if let dataArr = metadata?.data as? [String] { + XCTAssert(dataArr.allSatisfy { ["new_large_segment", "ls1", "ls2"].contains($0) }) + largeSegmentsMetadataExecuted = true + } + } } wait(for: [sdkReadyExpectation, sseExp], timeout: expTimeout) @@ -135,6 +143,12 @@ class StreamingMySegmentsSyncTest: XCTestCase { XCTAssertEqual(inResult, treatmentFirst) XCTAssertEqual("on", treatmentSec) XCTAssertEqual("on", treatmentOld) + + if type == .mySegmentsUpdate { + XCTAssertTrue(segmentsMetadataExecuted) + } else if type == .myLargeSegmentsUpdate { + XCTAssertTrue(largeSegmentsMetadataExecuted) + } let semaphore = DispatchSemaphore(value: 0) client.destroy(completion: { From b2a3985842ee0f3ab8770add0c6f2aaaf6eab28b Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Mon, 30 Jun 2025 13:23:07 -0300 Subject: [PATCH 59/62] Added test for built metadata passed to the EventsManager --- .../Fake/Streaming/FeatureFlagsSynchronizerStub.swift | 2 +- SplitTests/Streaming/FeatureFlagsSynchronizerTest.swift | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/SplitTests/Fake/Streaming/FeatureFlagsSynchronizerStub.swift b/SplitTests/Fake/Streaming/FeatureFlagsSynchronizerStub.swift index 43435555c..0b9e77b35 100644 --- a/SplitTests/Fake/Streaming/FeatureFlagsSynchronizerStub.swift +++ b/SplitTests/Fake/Streaming/FeatureFlagsSynchronizerStub.swift @@ -9,7 +9,7 @@ import Foundation @testable import Split class FeatureFlagsSynchronizerStub: FeatureFlagsSynchronizer { - var loadCalled = false + var loadCalled = false func load() { loadCalled = true } diff --git a/SplitTests/Streaming/FeatureFlagsSynchronizerTest.swift b/SplitTests/Streaming/FeatureFlagsSynchronizerTest.swift index efcfece3a..116663ba3 100644 --- a/SplitTests/Streaming/FeatureFlagsSynchronizerTest.swift +++ b/SplitTests/Streaming/FeatureFlagsSynchronizerTest.swift @@ -258,6 +258,13 @@ class FeatureFlagsSynchronizerTest: XCTestCase { XCTAssertTrue(persistentSplitsStorage.clearCalled) XCTAssertEqual(1, broadcasterChannel.pushedEvents.filter { $0 == .splitLoadedFromCache }.count) } + + func testMetadataOnFeatureFlagsSync() { + synchronizer.notifyUpdated(flagsList: ["Pepe2"]) + + XCTAssertEqual(eventsManager.metadata?.type, .FLAGS_UPDATED) + XCTAssertEqual(eventsManager.metadata?.data, ["Pepe2"]) + } func testSynchronizeSplitsWithChangeNumber() { From 4a4602511d87e1d21c01a8e8b0e18d077b00c971 Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Mon, 30 Jun 2025 13:33:14 -0300 Subject: [PATCH 60/62] Added test to check correct sending of metadata by key --- SplitTests/Streaming/SynchronizerTest.swift | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/SplitTests/Streaming/SynchronizerTest.swift b/SplitTests/Streaming/SynchronizerTest.swift index 63f65ca3b..9b1b4253a 100644 --- a/SplitTests/Streaming/SynchronizerTest.swift +++ b/SplitTests/Streaming/SynchronizerTest.swift @@ -223,6 +223,20 @@ class SynchronizerTest: XCTestCase { XCTAssertTrue(byKeyApiFacade.startSyncForKeyCalled[key] ?? false) } + + func testMetadataOnSegmentsByKeySync() { + let metadata = EventMetadata(type: .FLAGS_UPDATED, data: ["Carlos", "Juancho"]) + synchronizer.notifySegmentsUpdated(forKey: "4", metadata: metadata) + + XCTAssertEqual(byKeyApiFacade.updatedSegmentsMetadataForKey["4"], metadata) + } + + func testMetadataOnLargeSegmentsByKeySync() { + let metadata = EventMetadata(type: .FLAGS_UPDATED, data: ["Lagarto", "Juancho"]) + synchronizer.notifySegmentsUpdated(forKey: "5", metadata: metadata) + + XCTAssertEqual(byKeyApiFacade.updatedSegmentsMetadataForKey["5"], metadata) + } func testFlush() { From 191abca134ef030b45dfbf37719a325911c28f1f Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Wed, 2 Jul 2025 13:39:54 -0300 Subject: [PATCH 61/62] Metadata tested --- Split/Events/EventsManagerCoordinator.swift | 5 +++-- .../Api/RuleBasedSegmentsIntegrationTest.swift | 12 ++++++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/Split/Events/EventsManagerCoordinator.swift b/Split/Events/EventsManagerCoordinator.swift index 7a74ca7b0..537925600 100644 --- a/Split/Events/EventsManagerCoordinator.swift +++ b/Split/Events/EventsManagerCoordinator.swift @@ -20,8 +20,9 @@ class MainSplitEventsManager: SplitEventsManagerCoordinator { private let queue = DispatchQueue(label: "split-event-manager-coordinator") private let eventsToHandle: Set = Set( [.splitsLoadedFromCache, - .splitsUpdated, - .splitKilledNotification] + .splitsUpdated, + .splitKilledNotification, + .ruleBasedSegmentsUpdated] ) func notifyInternalEvent(_ event: SplitInternalEvent) { diff --git a/SplitTests/Integration/Api/RuleBasedSegmentsIntegrationTest.swift b/SplitTests/Integration/Api/RuleBasedSegmentsIntegrationTest.swift index 7a103fa2e..54d29c320 100644 --- a/SplitTests/Integration/Api/RuleBasedSegmentsIntegrationTest.swift +++ b/SplitTests/Integration/Api/RuleBasedSegmentsIntegrationTest.swift @@ -260,16 +260,24 @@ class RuleBasedSegmentsIntegrationTest: XCTestCase { expectedContents: String ) { let sdkUpdateExpectation = XCTestExpectation(description: "SDK_UPDATE received") + let sdkUpdateExpectationWithMetadata = XCTestExpectation(description: "SDK_UPDATE received with metadata") var sdkUpdatedTriggered = false - client.on(event: SplitEvent.sdkUpdated) { + client.on(event: .sdkUpdated) { sdkUpdatedTriggered = true sdkUpdateExpectation.fulfill() } + + // Test metadata + client.on(event: .sdkUpdated) { metadata in + XCTAssertEqual(metadata!.type, .RULE_BASED_SEGMENTS_UPDATED) + XCTAssertEqual(metadata!.data, ["rbs_test"]) + sdkUpdateExpectationWithMetadata.fulfill() + } streamingBinding?.push(message: "id:a62260de-13bb-11eb-adc1-0242ac120002") // send msg to confirm streaming connection ok streamingBinding?.push(message: change) - wait(for: [sdkUpdateExpectation], timeout: 10) + wait(for: [sdkUpdateExpectation, sdkUpdateExpectationWithMetadata], timeout: 10) let containsExpectedContents = testDatabase!.ruleBasedSegmentDao.getAll().contains { $0.name == expectedContents From 8cf43beaeb2e3110787d736e7c9cdf4d9c9a4e0e Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Thu, 3 Jul 2025 14:48:14 -0300 Subject: [PATCH 62/62] Merging conflict --- SplitTests/Streaming/FeatureFlagsSynchronizerTest.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SplitTests/Streaming/FeatureFlagsSynchronizerTest.swift b/SplitTests/Streaming/FeatureFlagsSynchronizerTest.swift index 116663ba3..142f0d890 100644 --- a/SplitTests/Streaming/FeatureFlagsSynchronizerTest.swift +++ b/SplitTests/Streaming/FeatureFlagsSynchronizerTest.swift @@ -260,7 +260,7 @@ class FeatureFlagsSynchronizerTest: XCTestCase { } func testMetadataOnFeatureFlagsSync() { - synchronizer.notifyUpdated(flagsList: ["Pepe2"]) + synchronizer.notifyUpdated(flags: ["Pepe2"]) XCTAssertEqual(eventsManager.metadata?.type, .FLAGS_UPDATED) XCTAssertEqual(eventsManager.metadata?.data, ["Pepe2"])