Skip to content

Commit bd8975b

Browse files
pblazejclaude
andcommitted
Test: unit-level coverage for DataChannelPair pre-flight + drain paths
Adds four `@Suite(.tags(.dataChannel))` unit tests that exercise the parts of `DataChannelPair` reachable without an actual `LKRTCDataChannel`: * `openCompleter` times out with `LiveKitError(.timedOut)` when channels never arrive (smoke test for the 7s→15s timeout bump in this PR). * `reset(throwing: someError)` resumes parked sends with that exact error. * `reset(throwing: nil)` resumes parked sends with `LiveKitError(.cancelled)`. * `openCompleter.wait()` honors caller-Task cancellation and throws `LiveKitError(.cancelled)` — the cancellation propagation we relied on for the combined `async let` gate in `Room.ensurePublisherConnected`. Whole suite runs in ~100ms (no E2E setup). Anything that needs real `sendData` dispatch or `bufferedAmount` drains is still covered by `RealiableDataChannelTests` / `EncryptedDataChannelTests`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 9d0e2f9 commit bd8975b

1 file changed

Lines changed: 90 additions & 0 deletions

File tree

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*
2+
* Copyright 2026 LiveKit
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+
import Foundation
18+
@testable import LiveKit
19+
import Testing
20+
21+
/// Unit-level coverage for the parts of `DataChannelPair` that don't need a
22+
/// real `LKRTCDataChannel`: the pre-flight `openCompleter` semantics and the
23+
/// `.drain` path that fails parked sends after `reset(throwing:)`. Anything
24+
/// that needs real `sendData` dispatch or `bufferedAmount` drains is exercised
25+
/// by `RealiableDataChannelTests` / `EncryptedDataChannelTests` end-to-end.
26+
@Suite(.tags(.dataChannel))
27+
struct DataChannelPairTests {
28+
@Test func openCompleterTimesOutWhenChannelsNeverArrive() async {
29+
let pair = DataChannelPair()
30+
do {
31+
try await pair.openCompleter.wait(timeout: 0.1)
32+
Issue.record("Expected openCompleter to time out")
33+
} catch let error as LiveKitError {
34+
#expect(error.type == .timedOut)
35+
} catch {
36+
Issue.record("Expected LiveKitError, got \(error)")
37+
}
38+
}
39+
40+
@Test func resetFailsParkedSendsWithProvidedError() async throws {
41+
let pair = DataChannelPair()
42+
43+
// No channels are set, so the send enqueues and parks.
44+
let sendTask = Task {
45+
try await pair.send(userPacket: Livekit_UserPacket(), kind: .reliable)
46+
}
47+
try await Task.sleep(nanoseconds: 50_000_000)
48+
49+
pair.reset(throwing: LiveKitError(.invalidState, message: "custom"))
50+
await expectLiveKitError(.invalidState, from: sendTask)
51+
}
52+
53+
@Test func resetWithNilErrorFailsParkedSendsAsCancelled() async throws {
54+
let pair = DataChannelPair()
55+
56+
let sendTask = Task {
57+
try await pair.send(userPacket: Livekit_UserPacket(), kind: .reliable)
58+
}
59+
try await Task.sleep(nanoseconds: 50_000_000)
60+
61+
pair.reset(throwing: nil)
62+
await expectLiveKitError(.cancelled, from: sendTask)
63+
}
64+
65+
@Test func openCompleterWaitHonorsTaskCancellation() async {
66+
let pair = DataChannelPair()
67+
let waitTask = Task { try await pair.openCompleter.wait() }
68+
await waitForRegistration(of: pair.openCompleter)
69+
70+
waitTask.cancel()
71+
await expectLiveKitError(.cancelled, from: waitTask)
72+
}
73+
}
74+
75+
private func waitForRegistration(of completer: AsyncCompleter<some Any>) async {
76+
while completer.waiterCount == 0 {
77+
await Task.yield()
78+
}
79+
}
80+
81+
private func expectLiveKitError(_ expected: LiveKitErrorType, from task: Task<some Sendable, Error>) async {
82+
do {
83+
_ = try await task.value
84+
Issue.record("Expected LiveKitError(.\(expected)) to be thrown")
85+
} catch let error as LiveKitError {
86+
#expect(error.type == expected)
87+
} catch {
88+
Issue.record("Expected LiveKitError, got \(error)")
89+
}
90+
}

0 commit comments

Comments
 (0)