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