@@ -136,4 +136,133 @@ final class EvalContextTests: XCTestCase {
136
136
137
137
XCTAssertEqual ( ctx. asObjectMap ( ) , expected)
138
138
}
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
+ }
139
268
}
0 commit comments