1-
21import XCTest
32import Combine
43@testable import OpenFeature
54
65class ConcurrencyRaceConditionTests : XCTestCase {
7-
86 override func setUp( ) {
97 super. setUp ( )
108 OpenFeatureAPI . shared. clearProvider ( )
@@ -23,41 +21,39 @@ class ConcurrencyRaceConditionTests: XCTestCase {
2321
2422 let concurrentOperations = 100
2523 let expectedTargetingKeys = Set ( ( 0 ..< concurrentOperations) . map { " user \( $0) " } )
26-
24+
2725 await withTaskGroup ( of: Void . self) { group in
2826 for i in 0 ..< concurrentOperations {
2927 group. addTask {
3028 let ctx = ImmutableContext (
3129 targetingKey: " user \( i) " ,
3230 structure: ImmutableStructure ( attributes: [
3331 " id " : . integer( Int64 ( i) ) ,
34- " timestamp " : . string( " \( Date ( ) . timeIntervalSince1970) " )
32+ " timestamp " : . string( " \( Date ( ) . timeIntervalSince1970) " ) ,
3533 ] )
3634 )
37-
38- // This should trigger the race condition in updateContext
35+
3936 await OpenFeatureAPI . shared. setEvaluationContextAndWait ( evaluationContext: ctx)
4037 }
4138 }
4239 }
43-
40+
4441 cancellable. cancel ( )
45-
46- // Verify final state is consistent and correct
42+
4743 let finalContext = OpenFeatureAPI . shared. getEvaluationContext ( )
4844 XCTAssertNotNil ( finalContext, " Final evaluation context should not be nil after concurrent operations " )
49-
45+
5046 if let context = finalContext {
5147 let targetingKey = context. getTargetingKey ( )
5248 XCTAssertTrue (
5349 expectedTargetingKeys. contains ( targetingKey) ,
5450 " Final targeting key ' \( targetingKey) ' should be one of the expected keys from concurrent operations "
5551 )
56-
52+
5753 let contextMap = context. asObjectMap ( )
5854 XCTAssertTrue ( contextMap. keys. contains ( " id " ) , " Context should contain 'id' attribute " )
5955 XCTAssertTrue ( contextMap. keys. contains ( " timestamp " ) , " Context should contain 'timestamp' attribute " )
60-
56+
6157 if let idValue = contextMap [ " id " ] as? Int64 {
6258 let expectedId = Int64 ( targetingKey. replacingOccurrences ( of: " user " , with: " " ) ) !
6359 XCTAssertEqual ( idValue, expectedId, " Context 'id' should match the targeting key number " )
@@ -67,15 +63,11 @@ class ConcurrencyRaceConditionTests: XCTestCase {
6763 }
6864 }
6965
70- /// Test the specific race condition between setProvider and setEvaluationContext
71- /// This was the main issue identified by the external reviewer
7266 func testSetProviderVsSetEvaluationContextRaceCondition( ) async throws {
7367 let concurrentOperations = 50
7468
7569 await withTaskGroup ( of: Void . self) { group in
76- // Concurrently set providers and evaluation contexts
7770 for i in 0 ..< concurrentOperations {
78- // Set provider operations
7971 group. addTask {
8072 let provider = MockProvider ( )
8173 let ctx = ImmutableContext (
@@ -85,7 +77,6 @@ class ConcurrencyRaceConditionTests: XCTestCase {
8577 await OpenFeatureAPI . shared. setProviderAndWait ( provider: provider, initialContext: ctx)
8678 }
8779
88- // Set evaluation context operations
8980 group. addTask {
9081 let ctx = ImmutableContext (
9182 targetingKey: " context-user \( i) " ,
@@ -96,20 +87,18 @@ class ConcurrencyRaceConditionTests: XCTestCase {
9687 }
9788 }
9889
99- // Verify the API is in a consistent state
10090 let finalState = OpenFeatureAPI . shared. getState ( )
10191 XCTAssertNotNil ( finalState. provider, " Provider should not be nil after concurrent operations " )
10292 XCTAssertNotNil ( finalState. evaluationContext, " Evaluation context should not be nil after concurrent operations " )
103- XCTAssertTrue ( [ . ready, . error, . fatal] . contains ( finalState. providerStatus) , " Provider status should be in a valid final state " )
104-
105- // Verify the final context has expected structure from one of the operations
93+ XCTAssertTrue ( [ . ready] . contains ( finalState. providerStatus) , " Provider status should be in a valid final state " )
94+
10695 if let context = finalState. evaluationContext {
10796 let targetingKey = context. getTargetingKey ( )
10897 XCTAssertTrue (
10998 targetingKey. hasPrefix ( " provider-user " ) || targetingKey. hasPrefix ( " context-user " ) ,
11099 " Final targeting key ' \( targetingKey) ' should be from one of the concurrent operations "
111100 )
112-
101+
113102 let contextMap = context. asObjectMap ( )
114103 let hasProviderAttribute = contextMap. keys. contains ( " provider " )
115104 let hasContextAttribute = contextMap. keys. contains ( " context " )
@@ -120,62 +109,21 @@ class ConcurrencyRaceConditionTests: XCTestCase {
120109 }
121110 }
122111
123- /// Test the race condition between provider initialization and context updates
124- func testProviderInitializationVsContextUpdateRaceCondition( ) async throws {
125- let concurrentOperations = 30
126-
127- await withTaskGroup ( of: Void . self) { group in
128- for i in 0 ..< concurrentOperations {
129- // Provider initialization with context
130- group. addTask {
131- let provider = MockProvider ( )
132- let initialCtx = ImmutableContext (
133- targetingKey: " init-user \( i) " ,
134- structure: ImmutableStructure ( attributes: [ " init " : . integer( Int64 ( i) ) ] )
135- )
136- await OpenFeatureAPI . shared. setProviderAndWait ( provider: provider, initialContext: initialCtx)
137- }
138-
139- // Immediate context updates
140- group. addTask {
141- let updateCtx = ImmutableContext (
142- targetingKey: " update-user \( i) " ,
143- structure: ImmutableStructure ( attributes: [ " update " : . integer( Int64 ( i) ) ] )
144- )
145- await OpenFeatureAPI . shared. setEvaluationContextAndWait ( evaluationContext: updateCtx)
146- }
147-
148- // Clear provider operations
149- group. addTask {
150- OpenFeatureAPI . shared. clearProvider ( )
151- }
152- }
153- }
154-
155- // Verify the API ends in a consistent state
156- let finalState = OpenFeatureAPI . shared. getState ( )
157- XCTAssertTrue ( [ . notReady, . ready, . error, . fatal] . contains ( finalState. providerStatus) )
158- }
159-
160- /// Test high-frequency state changes to stress test synchronization
161112 func testHighFrequencyStateChangesRaceCondition( ) async throws {
162113 let highFrequencyOperations = 200
163114 let startTime = Date ( )
164115
165116 await withTaskGroup ( of: Void . self) { group in
166117 for i in 0 ..< highFrequencyOperations {
167118 group. addTask {
168- // Rapid fire operations
169119 let provider = MockProvider ( )
170120 let ctx = ImmutableContext (
171121 targetingKey: " rapid-user \( i) " ,
172122 structure: ImmutableStructure ( attributes: [
173123 " iteration " : . integer( Int64 ( i) ) ,
174- " timestamp " : . string( " \( Date ( ) . timeIntervalSince1970) " )
124+ " timestamp " : . string( " \( Date ( ) . timeIntervalSince1970) " ) ,
175125 ] )
176126 )
177-
178- // Alternate between different operations
179127 switch i % 4 {
180128 case 0 :
181129 await OpenFeatureAPI . shared. setProviderAndWait ( provider: provider)
@@ -198,26 +146,25 @@ class ConcurrencyRaceConditionTests: XCTestCase {
198146 // Verify operations completed in reasonable time (no deadlocks)
199147 XCTAssertLessThan ( duration, 10.0 , " Operations took too long, possible deadlock " )
200148
201- // Verify final state is consistent
202149 let finalState = OpenFeatureAPI . shared. getState ( )
203150 XCTAssertTrue (
204- [ ProviderStatus . notReady , . ready, . error , . fatal ] . contains ( finalState. providerStatus) ,
151+ [ ProviderStatus . ready ] . contains ( finalState. providerStatus) ,
205152 " Provider status ' \( finalState. providerStatus) ' should be in a valid state after high-frequency operations "
206153 )
207-
208- // If we have a provider and context, verify they're consistent
209154 if finalState. provider != nil && finalState. evaluationContext != nil {
210155 let context = finalState. evaluationContext!
211156 let targetingKey = context. getTargetingKey ( )
212157 XCTAssertTrue (
213158 targetingKey. hasPrefix ( " rapid-user " ) ,
214159 " Final targeting key ' \( targetingKey) ' should be from the rapid operations if context exists "
215160 )
216-
161+
217162 let contextMap = context. asObjectMap ( )
218163 if contextMap. keys. contains ( " iteration " ) {
219164 XCTAssertTrue ( contextMap. keys. contains ( " timestamp " ) , " Context with iteration should also have timestamp " )
220165 }
166+ } else {
167+ XCTFail ( )
221168 }
222169 }
223170}
0 commit comments