Skip to content

Commit 63a6d7d

Browse files
committed
add batch mode tests
1 parent 227d5d1 commit 63a6d7d

File tree

1 file changed

+220
-0
lines changed

1 file changed

+220
-0
lines changed
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
//
2+
// Copyright (c) 2023 PADL Software Pty Ltd
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the License);
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an 'AS IS' BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
//
16+
17+
@testable import IORing
18+
import SystemPackage
19+
import XCTest
20+
21+
final class IORingBatchingTests: XCTestCase {
22+
func testBatchingConfiguration() throws {
23+
// Test default batching configuration
24+
_ = try IORing()
25+
// Can't directly access private properties, but we can test behavior
26+
27+
// Test custom batching configuration
28+
_ = try IORing(batchSize: 16, batchTimeout: .milliseconds(5))
29+
// Again, behavior testing rather than direct property access
30+
}
31+
32+
func testBatchingSizeThreshold() async throws {
33+
let batchSize = 4
34+
let ring = try IORing(batchSize: batchSize, batchTimeout: .seconds(10)) // Long timeout
35+
36+
var completedOperations: [String] = []
37+
38+
// Track submissions by creating multiple async operations
39+
await withTaskGroup(of: Void.self) { group in
40+
for i in 0..<batchSize {
41+
group.addTask {
42+
do {
43+
let data = "test\(i)".data(using: .utf8)!
44+
_ = try await ring.write(
45+
[UInt8](data),
46+
to: FileDescriptor.standardOutput
47+
)
48+
completedOperations.append("write\(i)")
49+
} catch {
50+
// Ignore write errors for testing purposes
51+
completedOperations.append("write\(i)") // Still count as attempted
52+
}
53+
}
54+
}
55+
}
56+
57+
// All operations should complete when batch size is reached
58+
XCTAssertEqual(completedOperations.count, batchSize)
59+
}
60+
61+
func testBatchingTimeout() async throws {
62+
let ring = try IORing(batchSize: 100, batchTimeout: .milliseconds(50)) // Small timeout
63+
64+
let startTime = ContinuousClock.now
65+
66+
// Submit a single operation that should be forced by timeout
67+
let data = "timeout test".data(using: .utf8)!
68+
do {
69+
_ = try await ring.write([UInt8](data), to: FileDescriptor.standardOutput)
70+
} catch {
71+
// Ignore write errors for testing purposes
72+
}
73+
74+
let elapsed = ContinuousClock.now - startTime
75+
76+
// Should complete within timeout window (with some tolerance)
77+
XCTAssertLessThan(elapsed, .milliseconds(100))
78+
XCTAssertGreaterThan(elapsed, .milliseconds(40)) // Should take at least timeout duration
79+
}
80+
81+
func testSubmissionGroupBatching() async throws {
82+
let ring = try IORing(batchSize: 2, batchTimeout: .seconds(1))
83+
84+
// Test that submission groups work with batching
85+
// We can't easily test the internal group mechanics without access to internal APIs,
86+
// but we can test that the batching system doesn't interfere with normal operation
87+
88+
let startTime = ContinuousClock.now
89+
for i in 0..<3 {
90+
let data = "group\(i)".data(using: .utf8)!
91+
do {
92+
_ = try await ring.write(
93+
[UInt8](data),
94+
to: FileDescriptor.standardOutput
95+
)
96+
} catch {
97+
// Ignore write errors - we're testing batching behavior
98+
}
99+
}
100+
let elapsed = ContinuousClock.now - startTime
101+
102+
// Should complete reasonably quickly
103+
XCTAssertLessThan(elapsed, .seconds(1))
104+
}
105+
106+
func testForcedSubmission() async throws {
107+
let ring = try IORing(batchSize: 10, batchTimeout: .seconds(10)) // High thresholds
108+
109+
// Test that operations complete even with high batch thresholds
110+
// by relying on forced submission in critical paths
111+
let data = "forced test".data(using: .utf8)!
112+
113+
let startTime = ContinuousClock.now
114+
do {
115+
_ = try await ring.write([UInt8](data), to: FileDescriptor.standardOutput)
116+
} catch {
117+
// Ignore write errors
118+
}
119+
let elapsed = ContinuousClock.now - startTime
120+
121+
// Should complete reasonably quickly even with high batch settings
122+
XCTAssertLessThan(elapsed, .seconds(1))
123+
}
124+
125+
func testNoBatchingMode() async throws {
126+
// Test with batching effectively disabled
127+
let ring = try IORing(batchSize: 1, batchTimeout: .microseconds(1))
128+
129+
let operations = 5
130+
var completionTimes: [ContinuousClock.Instant] = []
131+
132+
await withTaskGroup(of: Void.self) { group in
133+
for i in 0..<operations {
134+
group.addTask {
135+
do {
136+
let data = "nobatch\(i)".data(using: .utf8)!
137+
_ = try await ring.write([UInt8](data), to: FileDescriptor.standardOutput)
138+
completionTimes.append(ContinuousClock.now)
139+
} catch {
140+
// Ignore write errors
141+
}
142+
}
143+
}
144+
}
145+
146+
// All operations should complete
147+
XCTAssertEqual(completionTimes.count, operations)
148+
}
149+
150+
func testConcurrentBatching() async throws {
151+
let ring = try IORing(batchSize: 3, batchTimeout: .milliseconds(100))
152+
153+
let concurrentOperations = 10
154+
var results: [Bool] = []
155+
156+
await withTaskGroup(of: Bool.self) { group in
157+
for i in 0..<concurrentOperations {
158+
group.addTask {
159+
do {
160+
let data = "concurrent\(i)".data(using: .utf8)!
161+
_ = try await ring.write([UInt8](data), to: FileDescriptor.standardOutput)
162+
return true
163+
} catch {
164+
return false // Operation failed, but that's okay for this test
165+
}
166+
}
167+
}
168+
169+
for await result in group {
170+
results.append(result)
171+
}
172+
}
173+
174+
// Most operations should succeed (allowing for some failures due to fd limits)
175+
let successCount = results.filter { $0 }.count
176+
XCTAssertGreaterThan(successCount, concurrentOperations / 2)
177+
}
178+
179+
func testBatchingMemoryEfficiency() async throws {
180+
// Test that batching doesn't cause memory issues
181+
let ring = try IORing(batchSize: 5, batchTimeout: .milliseconds(50))
182+
183+
// Perform many operations to test memory management
184+
for batch in 0..<20 {
185+
await withTaskGroup(of: Void.self) { group in
186+
for i in 0..<10 {
187+
group.addTask {
188+
do {
189+
let data = "memory\(batch)-\(i)".data(using: .utf8)!
190+
_ = try await ring.write([UInt8](data), to: FileDescriptor.standardOutput)
191+
} catch {
192+
// Ignore write errors
193+
}
194+
}
195+
}
196+
}
197+
}
198+
199+
// If we get here without crashing or hanging, memory management is working
200+
XCTAssertTrue(true)
201+
}
202+
203+
func testAPITransparency() async throws {
204+
// Test that existing API continues to work unchanged
205+
let oldStyleRing = try IORing() // Should use default batching
206+
let newStyleRing = try IORing(batchSize: 4, batchTimeout: .milliseconds(10))
207+
208+
// Both should support the same API
209+
let data = "transparency".data(using: .utf8)!
210+
211+
do {
212+
_ = try await oldStyleRing.write([UInt8](data), to: FileDescriptor.standardOutput)
213+
_ = try await newStyleRing.write([UInt8](data), to: FileDescriptor.standardOutput)
214+
} catch {
215+
// Ignore write errors - we're testing API compatibility
216+
}
217+
218+
XCTAssertTrue(true) // If we get here, API is compatible
219+
}
220+
}

0 commit comments

Comments
 (0)