@@ -50,6 +50,29 @@ fileprivate struct NotificationTimeoutError: Error, CustomStringConvertible {
50
50
var description : String = " Failed to receive next notification within timeout "
51
51
}
52
52
53
+ /// A list of notifications that has been received by the SourceKit-LSP server but not handled from the test case yet.
54
+ ///
55
+ /// We can't use an `AsyncStream` for this because an `AsyncStream` is cancelled if a task that calls
56
+ /// `AsyncStream.Iterator.next` is cancelled and we want to be able to wait for new notifications even if waiting for a
57
+ /// a previous notification timed out.
58
+ actor PendingNotifications {
59
+ private var values : [ any NotificationType ] = [ ]
60
+
61
+ func add( _ value: any NotificationType ) {
62
+ values. insert ( value, at: 0 )
63
+ }
64
+
65
+ func next( timeout: Duration , pollingInterval: Duration = . milliseconds( 10 ) ) async throws -> any NotificationType {
66
+ for _ in 0 ..< Int ( timeout. seconds / pollingInterval. seconds) {
67
+ if let value = values. popLast ( ) {
68
+ return value
69
+ }
70
+ try await Task . sleep ( for: pollingInterval)
71
+ }
72
+ throw NotificationTimeoutError ( )
73
+ }
74
+ }
75
+
53
76
/// A mock SourceKit-LSP client (aka. a mock editor) that behaves like an editor
54
77
/// for testing purposes.
55
78
///
@@ -76,10 +99,7 @@ package final class TestSourceKitLSPClient: MessageHandler, Sendable {
76
99
private let serverToClientConnection : LocalConnection
77
100
78
101
/// Stream of the notifications that the server has sent to the client.
79
- private let notifications : AsyncStream < any NotificationType >
80
-
81
- /// Continuation to add a new notification from the ``server`` to the `notifications` stream.
82
- private let notificationYielder : AsyncStream < any NotificationType > . Continuation
102
+ private let notifications : PendingNotifications
83
103
84
104
/// The request handlers that have been set by `handleNextRequest`.
85
105
///
@@ -134,11 +154,7 @@ package final class TestSourceKitLSPClient: MessageHandler, Sendable {
134
154
options. sourcekitdRequestTimeout = defaultTimeout
135
155
}
136
156
137
- var notificationYielder: AsyncStream< any NotificationType> . Continuation!
138
- self . notifications = AsyncStream { continuation in
139
- notificationYielder = continuation
140
- }
141
- self. notificationYielder = notificationYielder
157
+ self . notifications = PendingNotifications ( )
142
158
143
159
let serverToClientConnection = LocalConnection ( receiverName: " client " )
144
160
self. serverToClientConnection = serverToClientConnection
@@ -248,26 +264,7 @@ package final class TestSourceKitLSPClient: MessageHandler, Sendable {
248
264
/// - Note: This also returns any notifications sent before the call to
249
265
/// `nextNotification`.
250
266
package func nextNotification( timeout: Duration = . seconds( defaultTimeout) ) async throws -> any NotificationType {
251
- // The task that gets the next notification from `self.notifications`.
252
- let notificationYielder = Task {
253
- for await notification in self . notifications {
254
- return notification
255
- }
256
- throw NotificationTimeoutError ( )
257
- }
258
- // After `timeout`, we tell `notificationYielder` that we are no longer interested in its result by cancelling it.
259
- // We wait for `notificationYielder` to accept this cancellation instead of returning immediately to avoid a
260
- // situation where `notificationYielder` continues running, eats the first notification but it then never gets
261
- // delivered to the test because we already delivered a timeout.
262
- let cancellationTask = Task {
263
- try await Task . sleep ( for: timeout)
264
- notificationYielder. cancel ( )
265
- }
266
- defer {
267
- // We have received a value and don't need the cancellationTask anymore
268
- cancellationTask. cancel ( )
269
- }
270
- return try await notificationYielder. value
267
+ return try await notifications. next ( timeout: timeout)
271
268
}
272
269
273
270
/// Await the next diagnostic notification sent to the client.
@@ -340,8 +337,10 @@ package final class TestSourceKitLSPClient: MessageHandler, Sendable {
340
337
// MARK: - Conformance to MessageHandler
341
338
342
339
/// - Important: Implementation detail of `TestSourceKitLSPServer`. Do not call from tests.
343
- package func handle( _ params: some NotificationType ) {
344
- notificationYielder. yield ( params)
340
+ package func handle( _ notification: some NotificationType ) {
341
+ Task {
342
+ await notifications. add ( notification)
343
+ }
345
344
}
346
345
347
346
/// - Important: Implementation detail of `TestSourceKitLSPClient`. Do not call from tests.
0 commit comments