Skip to content

Commit d317a81

Browse files
committed
tests++
1 parent 00801ff commit d317a81

File tree

6 files changed

+1234
-1
lines changed

6 files changed

+1234
-1
lines changed

.claude/settings.local.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,13 @@
22
"permissions": {
33
"allow": [
44
"Bash(swift test)",
5-
"Bash(swift test:*)"
5+
"Bash(swift test:*)",
6+
"Bash(grep:*)",
7+
"Bash(swift:*)",
8+
"Bash(rm:*)",
9+
"Bash(for i in {1..3})",
10+
"Bash(do echo \"=== Run $i ===\")",
11+
"Bash(done)"
612
],
713
"deny": []
814
}
Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
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

Comments
 (0)