@@ -136,4 +136,133 @@ final class EvalContextTests: XCTestCase {
136136
137137 XCTAssertEqual ( ctx. asObjectMap ( ) , expected)
138138 }
139+
140+ func testContextDeepCopyCreatesIndependentCopy( ) {
141+ // Create original context with various data types
142+ let originalContext = MutableContext ( targetingKey: " original-key " )
143+ originalContext. add ( key: " string " , value: . string( " original-value " ) )
144+ originalContext. add ( key: " integer " , value: . integer( 42 ) )
145+ originalContext. add ( key: " boolean " , value: . boolean( true ) )
146+ originalContext. add ( key: " list " , value: . list( [ . string( " item1 " ) , . integer( 100 ) ] ) )
147+ originalContext. add ( key: " structure " , value: . structure( [
148+ " nested-string " : . string( " nested-value " ) ,
149+ " nested-int " : . integer( 200 ) ,
150+ ] ) )
151+
152+ guard let copiedContext = originalContext. deepCopy ( ) as? MutableContext else {
153+ XCTFail ( " Failed to cast to MutableContext " )
154+ return
155+ }
156+
157+ XCTAssertEqual ( copiedContext. getTargetingKey ( ) , " original-key " )
158+ XCTAssertEqual ( copiedContext. getValue ( key: " string " ) ? . asString ( ) , " original-value " )
159+ XCTAssertEqual ( copiedContext. getValue ( key: " integer " ) ? . asInteger ( ) , 42 )
160+ XCTAssertEqual ( copiedContext. getValue ( key: " boolean " ) ? . asBoolean ( ) , true )
161+ XCTAssertEqual ( copiedContext. getValue ( key: " list " ) ? . asList ( ) ? [ 0 ] . asString ( ) , " item1 " )
162+ XCTAssertEqual ( copiedContext. getValue ( key: " list " ) ? . asList ( ) ? [ 1 ] . asInteger ( ) , 100 )
163+ XCTAssertEqual (
164+ copiedContext. getValue ( key: " structure " ) ? . asStructure ( ) ? [ " nested-string " ] ? . asString ( ) ,
165+ " nested-value "
166+ )
167+ XCTAssertEqual ( copiedContext. getValue ( key: " structure " ) ? . asStructure ( ) ? [ " nested-int " ] ? . asInteger ( ) , 200 )
168+
169+ originalContext. setTargetingKey ( targetingKey: " modified-key " )
170+ originalContext. add ( key: " string " , value: . string( " modified-value " ) )
171+ originalContext. add ( key: " new-key " , value: . string( " new-value " ) )
172+
173+ XCTAssertEqual ( copiedContext. getTargetingKey ( ) , " original-key " )
174+ XCTAssertEqual ( copiedContext. getValue ( key: " string " ) ? . asString ( ) , " original-value " )
175+ XCTAssertNil ( copiedContext. getValue ( key: " new-key " ) )
176+ XCTAssertEqual ( originalContext. getTargetingKey ( ) , " modified-key " )
177+ XCTAssertEqual ( originalContext. getValue ( key: " string " ) ? . asString ( ) , " modified-value " )
178+ XCTAssertEqual ( originalContext. getValue ( key: " new-key " ) ? . asString ( ) , " new-value " )
179+ }
180+
181+ func testContextDeepCopyWithEmptyContext( ) {
182+ let emptyContext = MutableContext ( )
183+ guard let copiedContext = emptyContext. deepCopy ( ) as? MutableContext else {
184+ XCTFail ( " Failed to cast to MutableContext " )
185+ return
186+ }
187+
188+ XCTAssertEqual ( emptyContext. getTargetingKey ( ) , " " )
189+ XCTAssertEqual ( copiedContext. getTargetingKey ( ) , " " )
190+ XCTAssertTrue ( emptyContext. keySet ( ) . isEmpty)
191+ XCTAssertTrue ( copiedContext. keySet ( ) . isEmpty)
192+
193+ emptyContext. setTargetingKey ( targetingKey: " test " )
194+ emptyContext. add ( key: " key " , value: . string( " value " ) )
195+
196+ XCTAssertEqual ( copiedContext. getTargetingKey ( ) , " " )
197+ XCTAssertTrue ( copiedContext. keySet ( ) . isEmpty)
198+ }
199+
200+ func testContextDeepCopyPreservesAllValueTypes( ) {
201+ let date = Date ( )
202+ let originalContext = MutableContext ( targetingKey: " test-key " )
203+ originalContext. add ( key: " null " , value: . null)
204+ originalContext. add ( key: " string " , value: . string( " test-string " ) )
205+ originalContext. add ( key: " boolean " , value: . boolean( false ) )
206+ originalContext. add ( key: " integer " , value: . integer( 12345 ) )
207+ originalContext. add ( key: " double " , value: . double( 3.14159 ) )
208+ originalContext. add ( key: " date " , value: . date( date) )
209+ originalContext. add ( key: " list " , value: . list( [ . string( " list-item " ) , . integer( 999 ) ] ) )
210+ originalContext. add ( key: " structure " , value: . structure( [
211+ " struct-key " : . string( " struct-value " ) ,
212+ " struct-number " : . integer( 777 ) ,
213+ ] ) )
214+
215+ guard let copiedContext = originalContext. deepCopy ( ) as? MutableContext else {
216+ XCTFail ( " Failed to cast to MutableContext " )
217+ return
218+ }
219+
220+ XCTAssertTrue ( copiedContext. getValue ( key: " null " ) ? . isNull ( ) ?? false )
221+ XCTAssertEqual ( copiedContext. getValue ( key: " string " ) ? . asString ( ) , " test-string " )
222+ XCTAssertEqual ( copiedContext. getValue ( key: " boolean " ) ? . asBoolean ( ) , false )
223+ XCTAssertEqual ( copiedContext. getValue ( key: " integer " ) ? . asInteger ( ) , 12345 )
224+ XCTAssertEqual ( copiedContext. getValue ( key: " double " ) ? . asDouble ( ) , 3.14159 )
225+ XCTAssertEqual ( copiedContext. getValue ( key: " date " ) ? . asDate ( ) , date)
226+ XCTAssertEqual ( copiedContext. getValue ( key: " list " ) ? . asList ( ) ? [ 0 ] . asString ( ) , " list-item " )
227+ XCTAssertEqual ( copiedContext. getValue ( key: " list " ) ? . asList ( ) ? [ 1 ] . asInteger ( ) , 999 )
228+ XCTAssertEqual (
229+ copiedContext. getValue ( key: " structure " ) ? . asStructure ( ) ? [ " struct-key " ] ? . asString ( ) ,
230+ " struct-value "
231+ )
232+ XCTAssertEqual ( copiedContext. getValue ( key: " structure " ) ? . asStructure ( ) ? [ " struct-number " ] ? . asInteger ( ) , 777 )
233+ }
234+
235+ func testContextDeepCopyIsThreadSafe( ) {
236+ let context = MutableContext ( targetingKey: " initial-key " )
237+ context. add ( key: " initial " , value: . string( " initial-value " ) )
238+
239+ let expectation = XCTestExpectation ( description: " Concurrent deep copy operations " )
240+ let concurrentQueue = DispatchQueue ( label: " test.concurrent " , attributes: . concurrent)
241+ let group = DispatchGroup ( )
242+
243+ // Perform multiple concurrent operations
244+ for i in 0 ..< 100 {
245+ group. enter ( )
246+ concurrentQueue. async {
247+ // Modify the context
248+ context. setTargetingKey ( targetingKey: " modified- \( i) " )
249+ context. add ( key: " key- \( i) " , value: . integer( Int64 ( i) ) )
250+
251+ // Perform deep copy
252+ let copiedContext = context. deepCopy ( )
253+
254+ // Verify the copy is independent
255+ XCTAssertNotEqual ( copiedContext. getTargetingKey ( ) , " initial-key " )
256+ XCTAssertNotNil ( copiedContext. getValue ( key: " initial " ) )
257+
258+ group. leave ( )
259+ }
260+ }
261+
262+ group. notify ( queue: . main) {
263+ expectation. fulfill ( )
264+ }
265+
266+ wait ( for: [ expectation] , timeout: 5.0 )
267+ }
139268}
0 commit comments