Skip to content

Commit 040dfae

Browse files
committed
feat: Add testing utilities that make a number of nio-related tests much easier to run.
1 parent 033ecf7 commit 040dfae

File tree

1 file changed

+103
-0
lines changed

1 file changed

+103
-0
lines changed
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Copyright (c) 2025 PassiveLogic, Inc.
4+
// Licensed under Apache License v2.0
5+
//
6+
// See LICENSE.txt for license information
7+
//
8+
// SPDX-License-Identifier: Apache-2.0
9+
//
10+
//===----------------------------------------------------------------------===//
11+
12+
// swift-format-ignore: AmbiguousTrailingClosureOverload
13+
14+
import NIOConcurrencyHelpers
15+
import XCTest
16+
17+
@testable import NIOCore
18+
19+
func assertNoThrowWithValue<T>(
20+
_ body: @autoclosure () throws -> T,
21+
defaultValue: T? = nil,
22+
message: String? = nil,
23+
file: StaticString = #filePath,
24+
line: UInt = #line
25+
) throws -> T {
26+
do {
27+
return try body()
28+
} catch {
29+
XCTFail(
30+
"\(message.map { $0 + ": " } ?? "")unexpected error \(error) thrown", file: (file), line: line
31+
)
32+
if let defaultValue = defaultValue {
33+
return defaultValue
34+
} else {
35+
throw error
36+
}
37+
}
38+
}
39+
40+
func assert(
41+
_ condition: @autoclosure () -> Bool,
42+
within time: TimeAmount,
43+
testInterval: TimeAmount? = nil,
44+
_ message: String = "condition not satisfied in time",
45+
file: StaticString = #filePath,
46+
line: UInt = #line
47+
) {
48+
let testInterval = testInterval ?? TimeAmount.nanoseconds(time.nanoseconds / 5)
49+
let endTime = NIODeadline.now() + time
50+
51+
repeat {
52+
if condition() { return }
53+
usleep(UInt32(testInterval.nanoseconds / 1000))
54+
} while NIODeadline.now() < endTime
55+
56+
if !condition() {
57+
XCTFail(message, file: (file), line: line)
58+
}
59+
}
60+
61+
func assertSuccess<Value>(
62+
_ result: Result<Value, Error>, file: StaticString = #filePath, line: UInt = #line
63+
) {
64+
guard case .success = result else {
65+
return XCTFail("Expected result to be successful", file: (file), line: line)
66+
}
67+
}
68+
69+
func assertFailure<Value>(
70+
_ result: Result<Value, Error>, file: StaticString = #filePath, line: UInt = #line
71+
) {
72+
guard case .failure = result else {
73+
return XCTFail("Expected result to be a failure", file: (file), line: line)
74+
}
75+
}
76+
77+
extension EventLoopFuture {
78+
var isFulfilled: Bool {
79+
if self.eventLoop.inEventLoop {
80+
// Easy, we're on the EventLoop. Let's just use our knowledge that we run completed future callbacks
81+
// immediately.
82+
var fulfilled = false
83+
self.assumeIsolated().whenComplete { _ in
84+
fulfilled = true
85+
}
86+
return fulfilled
87+
} else {
88+
let fulfilledBox = NIOLockedValueBox(false)
89+
let group = DispatchGroup()
90+
91+
group.enter()
92+
self.eventLoop.execute {
93+
let isFulfilled = self.isFulfilled // This will now enter the above branch.
94+
fulfilledBox.withLockedValue {
95+
$0 = isFulfilled
96+
}
97+
group.leave()
98+
}
99+
group.wait() // this is very nasty but this is for tests only, so...
100+
return fulfilledBox.withLockedValue { $0 }
101+
}
102+
}
103+
}

0 commit comments

Comments
 (0)