Skip to content

Commit 1a701b5

Browse files
committed
Refactor test cleanup to properly handle async shutdown operations
- Add dedicated async and background shutdown helper functions - Replace defer blocks with InBackground variants for fire-and-forget cleanup - Use explicit await for async shutdown in MemoryAdapterAsyncTests - Prevents test hanging issues by properly managing EventLoopGroup and NIOThreadPool lifecycle
1 parent 3a8867d commit 1a701b5

File tree

5 files changed

+49
-13
lines changed

5 files changed

+49
-13
lines changed

Tests/CasbinTests/ConfigTests.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ struct ConfigTests {
99
let filePath = #file.components(separatedBy: "ConfigTests.swift")[0] + "examples/testini.ini"
1010
let pool = NIOThreadPool(numberOfThreads: 1)
1111
pool.start()
12-
defer { shutdownThreadPoolAsync(pool) }
12+
defer { shutdownThreadPoolInBackground(pool) }
1313
let elg = MultiThreadedEventLoopGroup(numberOfThreads: 1)
14-
defer { shutdownEventLoopGroupAsync(elg) }
14+
defer { shutdownEventLoopGroupInBackground(elg) }
1515
let fileIo = NonBlockingFileIO(threadPool: pool)
1616

1717
var config = try Config.from(file: filePath, fileIo: fileIo, on: elg.next()).wait()
@@ -36,7 +36,7 @@ struct ConfigTests {
3636
@Test("load from text and get/set")
3737
func testFromText() throws {
3838
let elg = MultiThreadedEventLoopGroup(numberOfThreads: 1)
39-
defer { shutdownEventLoopGroupAsync(elg) }
39+
defer { shutdownEventLoopGroupInBackground(elg) }
4040
let text = #"""
4141
# test config
4242
debug = true

Tests/CasbinTests/EnforcerTests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ struct EnforcerTests {
1212
pool.start()
1313
let elg = MultiThreadedEventLoopGroup(numberOfThreads: 1)
1414
defer {
15-
shutdownEventLoopGroupAsync(elg)
16-
shutdownThreadPoolAsync(pool)
15+
shutdownEventLoopGroupInBackground(elg)
16+
shutdownThreadPoolInBackground(pool)
1717
}
1818
return try body(pool, elg)
1919
}

Tests/CasbinTests/MemoryAdapterAsyncTests.swift

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,18 @@ import NIO
66
struct MemoryAdapterAsyncTests {
77

88
// Each test must shut down its EventLoopGroup or swift test may hang.
9-
// Provide a helper that manages lifecycle for us.
9+
// Provide a helper that manages lifecycle and properly awaits shutdown completion.
1010
private func withAdapter<R>(_ body: (MemoryAdapter) async throws -> R) async throws -> R {
1111
let elg = MultiThreadedEventLoopGroup(numberOfThreads: 1)
12-
defer { shutdownEventLoopGroupAsync(elg) }
1312
let adapter = MemoryAdapter(on: elg.next())
14-
return try await body(adapter)
13+
do {
14+
let result = try await body(adapter)
15+
try await shutdownEventLoopGroupAsync(elg)
16+
return result
17+
} catch {
18+
try? await shutdownEventLoopGroupAsync(elg)
19+
throw error
20+
}
1521
}
1622

1723
private func makeModel() -> DefaultModel {

Tests/CasbinTests/RbacApiTests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ struct RbacApiTests {
99
pool.start()
1010
let elg = MultiThreadedEventLoopGroup(numberOfThreads: 1)
1111
defer {
12-
shutdownEventLoopGroupAsync(elg)
13-
shutdownThreadPoolAsync(pool)
12+
shutdownEventLoopGroupInBackground(elg)
13+
shutdownThreadPoolInBackground(pool)
1414
}
1515
let fileIo = NonBlockingFileIO(threadPool: pool)
1616
let m = try DefaultModel.from(file: TestsfilePath + mfile, fileIo: fileIo, on: elg.next()).wait()

Tests/CasbinTests/TestSupport.swift

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,41 @@ public let TestsfilePath = #file.components(separatedBy: "TestSupport.swift")[0]
77
@inline(__always)
88
func tryBool(_ body: () throws -> Bool) -> Bool { (try? body()) ?? false }
99

10-
// Non-blocking shutdown helpers so cooperative test executors don't hang.
11-
func shutdownEventLoopGroupAsync(_ group: EventLoopGroup) {
10+
// MARK: - Awaitable async shutdown helpers
11+
// Use these in async test contexts where you can await completion.
12+
13+
func shutdownEventLoopGroupAsync(_ group: EventLoopGroup) async throws {
14+
try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Void, Error>) in
15+
group.shutdownGracefully(queue: .global()) { error in
16+
if let error = error {
17+
continuation.resume(throwing: error)
18+
} else {
19+
continuation.resume()
20+
}
21+
}
22+
}
23+
}
24+
25+
func shutdownThreadPoolAsync(_ pool: NIOThreadPool) async throws {
26+
try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Void, Error>) in
27+
pool.shutdownGracefully(queue: .global()) { error in
28+
if let error = error {
29+
continuation.resume(throwing: error)
30+
} else {
31+
continuation.resume()
32+
}
33+
}
34+
}
35+
}
36+
37+
// MARK: - Fire-and-forget background shutdown helpers
38+
// Use these in synchronous test contexts where blocking would cause hangs.
39+
// These initiate shutdown but return immediately without waiting for completion.
40+
41+
func shutdownEventLoopGroupInBackground(_ group: EventLoopGroup) {
1242
group.shutdownGracefully(queue: .global()) { _ in }
1343
}
1444

15-
func shutdownThreadPoolAsync(_ pool: NIOThreadPool) {
45+
func shutdownThreadPoolInBackground(_ pool: NIOThreadPool) {
1646
pool.shutdownGracefully(queue: .global()) { _ in }
1747
}

0 commit comments

Comments
 (0)