Skip to content

Commit ac2ab34

Browse files
authored
cancellation test
1 parent 1b22127 commit ac2ab34

3 files changed

Lines changed: 81 additions & 56 deletions

File tree

modules/app/src/main/scala/LSPCancelRequest.scala

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,10 @@ package langoustine.lsp.app
1919
import jsonrpclib.CallId
2020
import jsonrpclib.fs2.CancelTemplate
2121

22-
private case class LSPCancelRequest(id: CallId) derives io.circe.Codec.AsObject
23-
24-
object LSPCancelRequest:
25-
// import com.github.plokhotnyuk.jsoniter_scala.macros.*
26-
// given JsonValueCodec[LSPCancelRequest] = JsonCodecMaker.make[LSPCancelRequest]
27-
// given Codec[LSPCancelRequest] = Codec.fromJsonCodec
22+
private[langoustine] case class LSPCancelRequest(id: CallId)
23+
derives io.circe.Codec.AsObject
2824

25+
private[langoustine] object LSPCancelRequest:
2926
val cancelTemplate: CancelTemplate = CancelTemplate
3027
.make[LSPCancelRequest](
3128
"$/cancelRequest",

modules/tests/src/test/scala/LSPTests.scala

Lines changed: 53 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,16 @@ package tests.core
1818

1919
import scala.concurrent.duration.*
2020

21+
import langoustine.testkit.*
22+
2123
import cats.effect.IO
2224
import jsonrpclib.fs2.catsMonadic
2325
import jsonrpclib.{fs2 as _, *}
2426
import langoustine.lsp.*
2527
import langoustine.lsp.all.*
2628
import langoustine.lsp.structures.InitializeParams.ClientInfo
2729
import langoustine.lsp.structures.InitializeResult.ServerInfo
30+
import cats.syntax.all.*
2831

2932
object LSPTests extends weaver.SimpleIOSuite:
3033

@@ -138,6 +141,56 @@ object LSPTests extends weaver.SimpleIOSuite:
138141

139142
}
140143

144+
serverTest("cancellation") { log =>
145+
import requests.*
146+
147+
val NumParallelCalls = 5
148+
149+
IO.ref(Set.empty[CallId.StringId])
150+
.parProduct(cats.effect.std.CountDownLatch[IO](NumParallelCalls))
151+
.flatMap: (cancelled, latch) =>
152+
val server = basicServer.handleRequest(textDocument.documentSymbol) {
153+
in =>
154+
log.info(s"in handler for ${in.params.textDocument.uri}") *>
155+
IO.never
156+
.onCancel(
157+
log.info(
158+
s"Cancelled ${in.params.textDocument.uri}"
159+
) *>
160+
cancelled.update(
161+
_ + CallId.StringId(in.params.textDocument.uri.value)
162+
) *>
163+
latch.release
164+
)
165+
.as(None)
166+
}
167+
168+
Probe
169+
.create(server, log)
170+
.use: probe =>
171+
val requests = List.tabulate(NumParallelCalls): i =>
172+
val callID = CallId.StringId(s"document $i")
173+
probe
174+
.requestAndForget(
175+
callID,
176+
textDocument.documentSymbol,
177+
DocumentSymbolParams(
178+
TextDocumentIdentifier(
179+
uri = DocumentUri(callID.string)
180+
)
181+
)
182+
)
183+
.as(callID)
184+
185+
for
186+
callIds <- requests.parSequence
187+
_ <- callIds.parTraverse(probe.cancel)
188+
_ <- latch.await.timeout(5.seconds)
189+
allCancelled <- cancelled.get
190+
yield expect.same(callIds.toSet, allCancelled)
191+
end for
192+
}
193+
141194
serverTest("documentSymbol") { log =>
142195
val symbols: Option[Vector[DocumentSymbol]] =
143196
Option(
@@ -190,51 +243,3 @@ end LSPTests
190243

191244
def basicServer =
192245
LSPBuilder.create[IO]
193-
194-
// test("textDocument/documentSymbol") {
195-
// import requests.*
196-
197-
// val symbols: Option[Vector[DocumentSymbol]] =
198-
// Opt(
199-
// Vector(
200-
// DocumentSymbol(
201-
// name = "root",
202-
// kind = SymbolKind.Array,
203-
// range = Range(Position(0, 0), Position(0, 5)),
204-
// selectionRange = Range(Position(0, 0), Position(0, 5)),
205-
// children = Opt(
206-
// Vector(
207-
// DocumentSymbol(
208-
// name = "child",
209-
// kind = SymbolKind.String,
210-
// range = Range(Position(1, 0), Position(1, 5)),
211-
// selectionRange = Range(Position(1, 0), Position(1, 5))
212-
// )
213-
// )
214-
// )
215-
// )
216-
// )
217-
// )
218-
219-
// val server = basicServer[Try].handleRequest(textDocument.documentSymbol) {
220-
// in =>
221-
// Try {
222-
// symbols
223-
// }
224-
// }
225-
226-
// val (response, _) = request(
227-
// server,
228-
// CallId.StringId("resp1"),
229-
// textDocument.documentSymbol,
230-
// DocumentSymbolParams(
231-
// TextDocumentIdentifier(DocumentUri("/home/bla.txt"))
232-
// )
233-
// ).get
234-
235-
// expect.same(
236-
// response,
237-
// symbols
238-
// )
239-
// }
240-
// end LSPTests

modules/tests/src/test/scala/Probe.scala

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
package tests.core
17+
package langoustine.testkit
1818

1919
import scala.concurrent.duration.*
2020

@@ -31,6 +31,8 @@ import jsonrpclib.fs2.catsMonadic
3131
import jsonrpclib.{fs2 as _, *}
3232
import langoustine.lsp.*
3333
import langoustine.lsp.all.*
34+
import _root_.io.circe.syntax.*
35+
import langoustine.lsp.app.LSPCancelRequest
3436

3537
class Probe(
3638
messagesFromServer: SignallingRef[IO, Vector[Message]],
@@ -40,6 +42,13 @@ class Probe(
4042
timeouts: Timeouts
4143
):
4244

45+
def cancel(callId: CallId) =
46+
val msg = InputMessage.NotificationMessage(
47+
"$/cancelRequest",
48+
Some(Payload(LSPCancelRequest(callId).asJson))
49+
)
50+
send(msg)
51+
4352
def notification[T <: LSPNotification](t: T, params: t.In): IO[Unit] =
4453
val msg = InputMessage.NotificationMessage(
4554
method = t.notificationMethod,
@@ -88,6 +97,20 @@ class Probe(
8897
.timeout(timeouts.waitForNotifications)
8998
end waitForNotifications
9099

100+
def requestAndForget[T <: LSPRequest](
101+
callId: CallId,
102+
t: T,
103+
params: t.In
104+
): IO[Unit] =
105+
val msg = InputMessage.RequestMessage(
106+
method = t.requestMethod,
107+
callId = callId,
108+
params = Some(Payload(t.inputToJson.apply(params)))
109+
)
110+
111+
send(msg)
112+
end requestAndForget
113+
91114
def request[T <: LSPRequest](callId: CallId, t: T, params: t.In): IO[t.Out] =
92115
val msg = InputMessage.RequestMessage(
93116
method = t.requestMethod,
@@ -136,7 +159,7 @@ object Probe:
136159
) =
137160
for
138161
ch <- FS2Channel
139-
.resource[IO]()
162+
.resource[IO](cancelTemplate = Some(LSPCancelRequest.cancelTemplate))
140163
.flatMap(ch => ch.withEndpoints(builder.build(Communicate.channel(ch))))
141164
_ <- log.info("wut").toResource
142165
input <- fs2.concurrent.Channel.synchronous[IO, Message].toResource

0 commit comments

Comments
 (0)