@@ -2,7 +2,7 @@ import JavaScriptKit
2
2
import XCTest
3
3
4
4
final class StressTests : XCTestCase {
5
-
5
+
6
6
func testJSObjectMemoryExhaustion( ) async throws {
7
7
guard let gc = JSObject . global. gc. function else {
8
8
throw XCTSkip ( " Missing --expose-gc flag " )
@@ -13,22 +13,22 @@ final class StressTests: XCTestCase {
13
13
let maxIterations = 25_000
14
14
var objects : [ JSObject ] = [ ]
15
15
var lastSuccessfulCount = 0
16
-
16
+
17
17
do {
18
18
for i in 0 ..< maxIterations {
19
19
let obj = JSObject ( )
20
20
// Add properties to increase memory pressure
21
21
obj [ " index " ] = JSValue . number ( Double ( i) )
22
- obj [ " data " ] = JSValue . string ( String ( repeating: " x " , count: 1000 ) ) // 1KB string per object
23
-
22
+ obj [ " data " ] = JSValue . string ( String ( repeating: " x " , count: 1000 ) ) // 1KB string per object
23
+
24
24
// Create nested objects to stress the reference graph
25
25
let nested = JSObject ( )
26
- nested [ " parent_ref " ] = obj. jsValue // Circular reference
26
+ nested [ " parent_ref " ] = obj. jsValue // Circular reference
27
27
obj [ " nested " ] = nested. jsValue
28
-
28
+
29
29
objects. append ( obj)
30
30
lastSuccessfulCount = i
31
-
31
+
32
32
// Aggressive GC every 1000 objects to test cleanup under pressure
33
33
if i % 1000 == 0 {
34
34
gc ( )
@@ -39,33 +39,33 @@ final class StressTests: XCTestCase {
39
39
// Expected to eventually fail due to memory pressure
40
40
print ( " JSObject stress test stopped at \( lastSuccessfulCount) objects: \( error) " )
41
41
}
42
-
42
+
43
43
// Verify objects are still accessible after memory pressure
44
44
let sampleCount = min ( 1000 , objects. count)
45
45
for i in 0 ..< sampleCount {
46
46
XCTAssertEqual ( objects [ i] [ " index " ] , JSValue . number ( Double ( i) ) )
47
47
XCTAssertNotNil ( objects [ i] [ " nested " ] . object)
48
48
}
49
-
49
+
50
50
// Force cleanup
51
51
objects. removeAll ( )
52
52
for _ in 0 ..< 20 {
53
53
gc ( )
54
54
try await Task . sleep ( for: . milliseconds( 10 ) )
55
55
}
56
56
}
57
-
57
+
58
58
func testJSClosureMemoryPressureWithoutFinalizationRegistry( ) async throws {
59
59
guard let gc = JSObject . global. gc. function else {
60
60
throw XCTSkip ( " Missing --expose-gc flag " )
61
61
}
62
62
63
- // Test heavy closure allocation to stress Swift heap management
63
+ // Test heavy closure allocation to stress Swift heap management
64
64
// Focus on scenarios where FinalizationRegistry is not used
65
65
let maxClosures = 15_000
66
66
var closures : [ JSClosure ] = [ ]
67
67
var successCount = 0
68
-
68
+
69
69
do {
70
70
for i in 0 ..< maxClosures {
71
71
// Create closures that capture significant data
@@ -75,14 +75,14 @@ final class StressTests: XCTestCase {
75
75
let result = capturedData. count + Int( arguments. first? . number ?? 0 )
76
76
return JSValue . number ( Double ( result) )
77
77
}
78
-
78
+
79
79
closures. append ( closure)
80
80
successCount = i + 1
81
-
81
+
82
82
// Test closure immediately to ensure it works under memory pressure
83
83
let result = closure ( [ JSValue . number ( 10 ) ] )
84
- XCTAssertEqual ( result. number, 110.0 ) // 100 (capturedData.count) + 10
85
-
84
+ XCTAssertEqual ( result. number, 110.0 ) // 100 (capturedData.count) + 10
85
+
86
86
// More frequent GC to stress the system
87
87
if i % 500 == 0 {
88
88
gc ( )
@@ -92,27 +92,27 @@ final class StressTests: XCTestCase {
92
92
} catch {
93
93
print ( " JSClosure stress test stopped at \( successCount) closures: \( error) " )
94
94
}
95
-
95
+
96
96
// Test random closures still work after extreme memory pressure
97
97
for _ in 0 ..< min ( 100 , closures. count) {
98
98
let randomIndex = Int . random ( in: 0 ..< closures. count)
99
99
let result = closures [ randomIndex] ( [ JSValue . number ( 5 ) ] )
100
- XCTAssertTrue ( result. number! > 5 ) // Should be 5 + capturedData.count (100+)
100
+ XCTAssertTrue ( result. number! > 5 ) // Should be 5 + capturedData.count (100+)
101
101
}
102
-
102
+
103
103
#if JAVASCRIPTKIT_WITHOUT_WEAKREFS
104
104
for closure in closures {
105
105
closure. release ( )
106
106
}
107
107
#endif
108
-
108
+
109
109
closures. removeAll ( )
110
110
for _ in 0 ..< 20 {
111
111
gc ( )
112
112
try await Task . sleep ( for: . milliseconds( 10 ) )
113
113
}
114
114
}
115
-
115
+
116
116
func testMixedAllocationMemoryBoundaries( ) async throws {
117
117
guard let gc = JSObject . global. gc. function else {
118
118
throw XCTSkip ( " Missing --expose-gc flag " )
@@ -122,33 +122,35 @@ final class StressTests: XCTestCase {
122
122
let cycles = 200
123
123
var totalObjects = 0
124
124
var totalClosures = 0
125
-
125
+
126
126
for cycle in 0 ..< cycles {
127
127
var cycleObjects : [ JSObject ] = [ ]
128
128
var cycleClosure : [ JSClosure ] = [ ]
129
-
129
+
130
130
// Exponentially increase allocation pressure each cycle
131
131
let objectsThisCycle = min ( 100 + cycle, 1000 )
132
132
let closuresThisCycle = min ( 50 + cycle / 2 , 500 )
133
-
133
+
134
134
do {
135
135
// Allocate objects
136
136
for i in 0 ..< objectsThisCycle {
137
137
let obj = JSObject ( )
138
138
// Create memory-intensive properties
139
- obj [ " large_array " ] = JSObject . global. Array. function!. from!(
140
- ( 0 ..< 1000 ) . map { JSValue . number ( Double ( $0) ) } . jsValue
141
- ) . jsValue
142
- obj [ " metadata " ] = [
143
- " cycle " : cycle,
144
- " index " : i,
145
- " timestamp " : Int ( Date ( ) . timeIntervalSince1970)
146
- ] . jsValue
147
-
139
+ obj [ " large_array " ] =
140
+ JSObject . global. Array. function!. from!(
141
+ ( 0 ..< 1000 ) . map { JSValue . number ( Double ( $0) ) } . jsValue
142
+ ) . jsValue
143
+ obj [ " metadata " ] =
144
+ [
145
+ " cycle " : cycle,
146
+ " index " : i,
147
+ " timestamp " : Int ( Date ( ) . timeIntervalSince1970) ,
148
+ ] . jsValue
149
+
148
150
cycleObjects. append ( obj)
149
151
totalObjects += 1
150
152
}
151
-
153
+
152
154
// Allocate closures with increasing complexity
153
155
for i in 0 ..< closuresThisCycle {
154
156
let heavyData = String ( repeating: " data " , count: cycle + 100 )
@@ -159,13 +161,13 @@ final class StressTests: XCTestCase {
159
161
cycleClosure. append ( closure)
160
162
totalClosures += 1
161
163
}
162
-
164
+
163
165
} catch {
164
166
print ( " Memory boundary reached at cycle \( cycle) : \( error) " )
165
167
print ( " Total objects created: \( totalObjects) , closures: \( totalClosures) " )
166
168
break
167
169
}
168
-
170
+
169
171
// Test system still works under extreme pressure
170
172
if !cycleObjects. isEmpty {
171
173
XCTAssertNotNil ( cycleObjects [ 0 ] [ " large_array " ] . object)
@@ -174,16 +176,16 @@ final class StressTests: XCTestCase {
174
176
let result = cycleClosure [ 0 ] ( arguments: [ ] )
175
177
XCTAssertNotNil ( result. string)
176
178
}
177
-
179
+
178
180
#if JAVASCRIPTKIT_WITHOUT_WEAKREFS
179
181
for closure in cycleClosure {
180
182
closure. release ( )
181
183
}
182
184
#endif
183
-
185
+
184
186
cycleObjects. removeAll ( )
185
187
cycleClosure. removeAll ( )
186
-
188
+
187
189
// Aggressive cleanup every 10 cycles
188
190
if cycle % 10 == 0 {
189
191
for _ in 0 ..< 10 {
@@ -192,10 +194,10 @@ final class StressTests: XCTestCase {
192
194
}
193
195
}
194
196
}
195
-
197
+
196
198
print ( " Stress test completed: \( totalObjects) objects, \( totalClosures) closures allocated " )
197
199
}
198
-
200
+
199
201
func testHeapFragmentationRecovery( ) async throws {
200
202
guard let gc = JSObject . global. gc. function else {
201
203
throw XCTSkip ( " Missing --expose-gc flag " )
@@ -204,16 +206,16 @@ final class StressTests: XCTestCase {
204
206
// Test system recovery from heap fragmentation by creating/destroying
205
207
// patterns that stress the memory allocator
206
208
let fragmentationCycles = 100
207
-
209
+
208
210
for cycle in 0 ..< fragmentationCycles {
209
211
var shortLivedObjects : [ JSObject ] = [ ]
210
212
var longLivedObjects : [ JSObject ] = [ ]
211
-
213
+
212
214
// Create fragmentation pattern: many short-lived, few long-lived
213
215
for i in 0 ..< 1000 {
214
216
let obj = JSObject ( )
215
217
obj [ " data " ] = JSValue . string ( String ( repeating: " fragment " , count: 100 ) )
216
-
218
+
217
219
if i % 10 == 0 {
218
220
// Long-lived objects
219
221
longLivedObjects. append ( obj)
@@ -222,32 +224,32 @@ final class StressTests: XCTestCase {
222
224
shortLivedObjects. append ( obj)
223
225
}
224
226
}
225
-
227
+
226
228
// Immediately release short-lived objects to create fragmentation
227
229
shortLivedObjects. removeAll ( )
228
-
230
+
229
231
// Force GC to reclaim fragmented memory
230
232
for _ in 0 ..< 5 {
231
233
gc ( )
232
234
try await Task . sleep ( for: . milliseconds( 1 ) )
233
235
}
234
-
236
+
235
237
// Test system can still allocate efficiently after fragmentation
236
238
var recoveryTest : [ JSObject ] = [ ]
237
239
for i in 0 ..< 500 {
238
240
let obj = JSObject ( )
239
241
obj [ " recovery_test " ] = JSValue . number ( Double ( i) )
240
242
recoveryTest. append ( obj)
241
243
}
242
-
244
+
243
245
// Verify recovery objects work correctly
244
246
for (i, obj) in recoveryTest. enumerated ( ) {
245
247
XCTAssertEqual ( obj [ " recovery_test " ] , JSValue . number ( Double ( i) ) )
246
248
}
247
-
249
+
248
250
recoveryTest. removeAll ( )
249
251
longLivedObjects. removeAll ( )
250
-
252
+
251
253
if cycle % 20 == 0 {
252
254
for _ in 0 ..< 10 {
253
255
gc ( )
@@ -256,4 +258,4 @@ final class StressTests: XCTestCase {
256
258
}
257
259
}
258
260
}
259
- }
261
+ }
0 commit comments