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