1+ //
2+ // Cornucopia – (C) Dr. Lauer Information Technology
3+ //
4+ import XCTest
5+ @testable import CornucopiaCore
6+
7+ final class ConcurrencyTests : XCTestCase {
8+
9+ func testAsyncWithTimeoutSuccess( ) async throws {
10+ let expectedResult = " Success "
11+
12+ let result = try await CC_asyncWithTimeout ( seconds: 2.0 ) {
13+ // Fast operation that completes well within timeout
14+ try await Task . sleep ( nanoseconds: 100_000_000 ) // 0.1 seconds
15+ return expectedResult
16+ }
17+
18+ XCTAssertEqual ( result, expectedResult)
19+ }
20+
21+ func testAsyncWithTimeoutFailure( ) async {
22+ let expectation = expectation ( description: " Timeout should occur " )
23+
24+ do {
25+ _ = try await CC_asyncWithTimeout ( seconds: 0.1 ) {
26+ // Slow operation that exceeds timeout
27+ try await Task . sleep ( nanoseconds: 500_000_000 ) // 0.5 seconds
28+ return " Should not reach here "
29+ }
30+ XCTFail ( " Should have thrown timeout error " )
31+ } catch {
32+ XCTAssertTrue ( error is Cornucopia . Core . AsyncWithTimeoutError )
33+ expectation. fulfill ( )
34+ }
35+
36+ await fulfillment ( of: [ expectation] , timeout: 1.0 )
37+ }
38+
39+ func testAsyncWithTimeoutErrorEquality( ) {
40+ let error1 = Cornucopia . Core. AsyncWithTimeoutError ( )
41+ let error2 = Cornucopia . Core. AsyncWithTimeoutError ( )
42+
43+ XCTAssertEqual ( error1, error2)
44+ }
45+
46+ func testAsyncWithTimeoutWithThrowingBody( ) async {
47+ enum TestError : Error {
48+ case customError
49+ }
50+
51+ do {
52+ _ = try await CC_asyncWithTimeout ( seconds: 1.0 ) {
53+ throw TestError . customError
54+ }
55+ XCTFail ( " Should have thrown custom error " )
56+ } catch {
57+ XCTAssertTrue ( error is TestError )
58+ if case TestError . customError = error {
59+ // Expected
60+ } else {
61+ XCTFail ( " Wrong error type " )
62+ }
63+ }
64+ }
65+
66+ func testAsyncWithTimeoutZeroTimeout( ) async {
67+ do {
68+ _ = try await CC_asyncWithTimeout ( seconds: 0.0 ) {
69+ // Even immediate return may not beat the timeout task
70+ return " immediate "
71+ }
72+ // Zero timeout might still allow immediate operations to complete
73+ // This behavior depends on task scheduling, so we don't fail if it succeeds
74+ } catch {
75+ XCTAssertTrue ( error is Cornucopia . Core . AsyncWithTimeoutError )
76+ }
77+ }
78+
79+ func testAsyncWithTimeoutNegativeTimeout( ) async {
80+ do {
81+ let result = try await CC_asyncWithTimeout ( seconds: - 1.0 ) {
82+ return " immediate "
83+ }
84+ // Negative timeout means immediate timeout, but immediate operations might still complete
85+ // depending on task scheduling
86+ } catch {
87+ XCTAssertTrue ( error is Cornucopia . Core . AsyncWithTimeoutError )
88+ }
89+ }
90+
91+ func testAsyncWithTimeoutMultipleResults( ) async throws {
92+ // Test that we only get the first result (either success or timeout)
93+ let results = await withTaskGroup ( of: Result< String, Error> . self ) { group in
94+ var results : [ Result < String , Error > ] = [ ]
95+
96+ for i in 0 ..< 5 {
97+ group. addTask {
98+ do {
99+ let result = try await CC_asyncWithTimeout ( seconds: 0.2 ) {
100+ try await Task . sleep ( nanoseconds: UInt64 ( i * 50_000_000 ) ) // 0, 0.05, 0.1, 0.15, 0.2 seconds
101+ return " Result \( i) "
102+ }
103+ return . success( result)
104+ } catch {
105+ return . failure( error)
106+ }
107+ }
108+ }
109+
110+ for await result in group {
111+ results. append ( result)
112+ }
113+ return results
114+ }
115+
116+ XCTAssertEqual ( results. count, 5 )
117+ // First few should succeed, later ones should timeout
118+ for (index, result) in results. enumerated ( ) {
119+ switch result {
120+ case . success( let value) :
121+ XCTAssertEqual ( value, " Result \( index) " )
122+ case . failure( let error) :
123+ XCTAssertTrue ( error is Cornucopia . Core . AsyncWithTimeoutError )
124+ }
125+ }
126+ }
127+
128+ func testAsyncWithDurationSuccess( ) async throws {
129+ let startTime = Date ( )
130+ let minDuration : TimeInterval = 0.2
131+
132+ let result = try await CC_asyncWithDuration ( seconds: minDuration) {
133+ // Fast operation
134+ try await Task . sleep ( nanoseconds: 50_000_000 ) // 0.05 seconds
135+ return " Quick result "
136+ }
137+
138+ let elapsed = Date ( ) . timeIntervalSince ( startTime)
139+
140+ XCTAssertEqual ( result, " Quick result " )
141+ XCTAssertGreaterThanOrEqual ( elapsed, minDuration - 0.01 ) // Allow small timing variance
142+ }
143+
144+ func testAsyncWithDurationSlowOperation( ) async throws {
145+ let startTime = Date ( )
146+ let minDuration : TimeInterval = 0.1
147+ let operationDuration : TimeInterval = 0.3
148+
149+ let result = try await CC_asyncWithDuration ( seconds: minDuration) {
150+ try await Task . sleep ( nanoseconds: UInt64 ( operationDuration * 1_000_000_000 ) )
151+ return " Slow result "
152+ }
153+
154+ let elapsed = Date ( ) . timeIntervalSince ( startTime)
155+
156+ XCTAssertEqual ( result, " Slow result " )
157+ // Should take at least the operation duration (since it's longer than min duration)
158+ XCTAssertGreaterThanOrEqual ( elapsed, operationDuration - 0.01 )
159+ }
160+
161+ func testAsyncWithDurationZeroDuration( ) async throws {
162+ let result = try await CC_asyncWithDuration ( seconds: 0.0 ) {
163+ return " Immediate "
164+ }
165+
166+ XCTAssertEqual ( result, " Immediate " )
167+ }
168+
169+ func testAsyncWithDurationWithThrowingBody( ) async {
170+ enum TestError : Error {
171+ case testFailure
172+ }
173+
174+ do {
175+ _ = try await CC_asyncWithDuration ( seconds: 0.1 ) {
176+ throw TestError . testFailure
177+ }
178+ XCTFail ( " Should have thrown error " )
179+ } catch {
180+ XCTAssertTrue ( error is TestError )
181+ }
182+ }
183+
184+ func testAsyncWithDurationTiming( ) async throws {
185+ let durations : [ TimeInterval ] = [ 0.1 , 0.2 , 0.3 ]
186+
187+ for duration in durations {
188+ let startTime = Date ( )
189+
190+ _ = try await CC_asyncWithDuration ( seconds: duration) {
191+ // Very fast operation
192+ return " Done "
193+ }
194+
195+ let elapsed = Date ( ) . timeIntervalSince ( startTime)
196+ XCTAssertGreaterThanOrEqual ( elapsed, duration - 0.02 , " Duration \( duration) not respected " )
197+ XCTAssertLessThan ( elapsed, duration + 0.05 , " Duration \( duration) too long " )
198+ }
199+ }
200+
201+ func testConcurrentAsyncWithTimeout( ) async {
202+ let results = await withTaskGroup ( of: Result< Int, Error> . self ) { group in
203+ var results : [ Result < Int , Error > ] = [ ]
204+
205+ // Launch multiple concurrent timeout operations
206+ for i in 0 ..< 3 {
207+ group. addTask {
208+ do {
209+ let result = try await CC_asyncWithTimeout ( seconds: 0.2 ) {
210+ try await Task . sleep ( nanoseconds: UInt64 ( i * 100_000_000 ) ) // 0, 0.1, 0.2 seconds
211+ return i * 10
212+ }
213+ return . success( result)
214+ } catch {
215+ return . failure( error)
216+ }
217+ }
218+ }
219+
220+ for await result in group {
221+ results. append ( result)
222+ }
223+ return results
224+ }
225+
226+ XCTAssertEqual ( results. count, 3 )
227+
228+ // Verify that fast operations succeed and slow ones timeout
229+ var successCount = 0
230+ var timeoutCount = 0
231+
232+ for result in results {
233+ switch result {
234+ case . success( _) :
235+ successCount += 1
236+ case . failure( let error) :
237+ XCTAssertTrue ( error is Cornucopia . Core . AsyncWithTimeoutError )
238+ timeoutCount += 1
239+ }
240+ }
241+
242+ XCTAssertGreaterThan ( successCount, 0 , " At least some operations should succeed " )
243+ }
244+ }
245+
246+ // MARK: - Task Sleep Extension Tests
247+ extension ConcurrencyTests {
248+
249+ func testTaskSleepSecondsDeprecated( ) async throws {
250+ let startTime = Date ( )
251+
252+ try await Task . CC_sleep ( seconds: 0.1 )
253+
254+ let elapsed = Date ( ) . timeIntervalSince ( startTime)
255+ XCTAssertGreaterThanOrEqual ( elapsed, 0.09 ) // Allow for small timing variance
256+ XCTAssertLessThan ( elapsed, 0.15 )
257+ }
258+
259+ func testTaskSleepMillisecondsDeprecated( ) async throws {
260+ let startTime = Date ( )
261+
262+ try await Task . CC_sleep ( milliseconds: 100 )
263+
264+ let elapsed = Date ( ) . timeIntervalSince ( startTime)
265+ XCTAssertGreaterThanOrEqual ( elapsed, 0.09 )
266+ XCTAssertLessThan ( elapsed, 0.15 )
267+ }
268+
269+ func testTaskSleepDispatchTimeInterval( ) async throws {
270+ let startTime = Date ( )
271+
272+ try await Task . CC_sleep ( for: . milliseconds( 150 ) )
273+
274+ let elapsed = Date ( ) . timeIntervalSince ( startTime)
275+ XCTAssertGreaterThanOrEqual ( elapsed, 0.14 )
276+ XCTAssertLessThan ( elapsed, 0.20 )
277+ }
278+
279+ func testTaskSleepZeroTime( ) async throws {
280+ // Should not throw and should complete quickly
281+ let startTime = Date ( )
282+
283+ try await Task . CC_sleep ( seconds: 0.0 )
284+
285+ let elapsed = Date ( ) . timeIntervalSince ( startTime)
286+ XCTAssertLessThan ( elapsed, 0.01 ) // Should be very fast
287+ }
288+ }
0 commit comments