Procházet zdrojové kódy

test(lsp): cover diagnostics wait and spawn dedupe

Kit Langton před 1 dnem
rodič
revize
83f83ef29b

+ 44 - 27
packages/opencode/test/lsp/client.test.ts

@@ -1,6 +1,7 @@
 import { describe, expect, test, beforeEach } from "bun:test"
 import path from "path"
 import { Effect } from "effect"
+import { Bus } from "../../src/bus"
 import { LSPClient } from "../../src/lsp"
 import { LSPServer } from "../../src/lsp"
 import { Log } from "../../src/util"
@@ -17,21 +18,27 @@ function spawnFakeServer() {
   }
 }
 
+async function createClient() {
+  const handle = spawnFakeServer() as any
+  const cwd = process.cwd()
+  const client = await Effect.runPromise(
+    LSPClient.create({
+      serverID: "fake",
+      server: handle as unknown as LSPServer.Handle,
+      root: cwd,
+    }).pipe(provideInstance(cwd)),
+  )
+
+  return { client, cwd }
+}
+
 describe("LSPClient interop", () => {
   beforeEach(async () => {
     await Log.init({ print: true })
   })
 
   test("handles workspace/workspaceFolders request", async () => {
-    const handle = spawnFakeServer() as any
-
-    const client = await Effect.runPromise(
-      LSPClient.create({
-        serverID: "fake",
-        server: handle as unknown as LSPServer.Handle,
-        root: process.cwd(),
-      }).pipe(provideInstance(process.cwd())),
-    )
+    const { client } = await createClient()
 
     await client.connection.sendNotification("test/trigger", {
       method: "workspace/workspaceFolders",
@@ -45,15 +52,7 @@ describe("LSPClient interop", () => {
   })
 
   test("handles client/registerCapability request", async () => {
-    const handle = spawnFakeServer() as any
-
-    const client = await Effect.runPromise(
-      LSPClient.create({
-        serverID: "fake",
-        server: handle as unknown as LSPServer.Handle,
-        root: process.cwd(),
-      }).pipe(provideInstance(process.cwd())),
-    )
+    const { client } = await createClient()
 
     await client.connection.sendNotification("test/trigger", {
       method: "client/registerCapability",
@@ -67,15 +66,7 @@ describe("LSPClient interop", () => {
   })
 
   test("handles client/unregisterCapability request", async () => {
-    const handle = spawnFakeServer() as any
-
-    const client = await Effect.runPromise(
-      LSPClient.create({
-        serverID: "fake",
-        server: handle as unknown as LSPServer.Handle,
-        root: process.cwd(),
-      }).pipe(provideInstance(process.cwd())),
-    )
+    const { client } = await createClient()
 
     await client.connection.sendNotification("test/trigger", {
       method: "client/unregisterCapability",
@@ -87,4 +78,30 @@ describe("LSPClient interop", () => {
 
     await Effect.runPromise(client.shutdown())
   })
+
+  test("waitForDiagnostics() resolves when a matching diagnostic event is published", async () => {
+    const { client, cwd } = await createClient()
+    const file = path.join(cwd, "fixture.ts")
+
+    const waiting = Effect.runPromise(client.waitForDiagnostics({ path: file }).pipe(provideInstance(cwd)))
+
+    await Effect.runPromise(Effect.sleep(20))
+    await Effect.runPromise(Effect.promise(() => Bus.publish(LSPClient.Event.Diagnostics, { path: file, serverID: "fake" })).pipe(provideInstance(cwd)))
+    await waiting
+
+    await Effect.runPromise(client.shutdown())
+  })
+
+  test("waitForDiagnostics() times out without throwing when no event arrives", async () => {
+    const { client, cwd } = await createClient()
+    const started = Date.now()
+
+    await Effect.runPromise(client.waitForDiagnostics({ path: path.join(cwd, "never.ts") }).pipe(provideInstance(cwd)))
+
+    const elapsed = Date.now() - started
+    expect(elapsed).toBeGreaterThanOrEqual(2900)
+    expect(elapsed).toBeLessThan(5000)
+
+    await Effect.runPromise(client.shutdown())
+  })
 })

+ 30 - 1
packages/opencode/test/lsp/lifecycle.test.ts

@@ -1,6 +1,6 @@
 import { afterEach, beforeEach, describe, expect, spyOn, test } from "bun:test"
 import path from "path"
-import { Effect, Layer } from "effect"
+import { Effect, Fiber, Layer, Scope } from "effect"
 import { LSP } from "../../src/lsp"
 import { LSPServer } from "../../src/lsp"
 import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner"
@@ -153,6 +153,35 @@ describe("LSP service lifecycle", () => {
       ),
     ),
   )
+
+  it.live("touchFile() dedupes concurrent spawn attempts for the same file", () =>
+    provideTmpdirInstance(
+      (dir) =>
+        LSP.Service.use((lsp) =>
+          Effect.gen(function* () {
+            const gate = Promise.withResolvers<void>()
+            const scope = yield* Scope.Scope
+            const file = path.join(dir, "src", "inside.ts")
+
+            spawnSpy.mockImplementation(async () => {
+              await gate.promise
+              return undefined
+            })
+
+            const fiber = yield* Effect.all([lsp.touchFile(file, false), lsp.touchFile(file, false)], {
+              concurrency: "unbounded",
+            }).pipe(Effect.forkIn(scope))
+
+            yield* Effect.sleep(20)
+            expect(spawnSpy).toHaveBeenCalledTimes(1)
+
+            gate.resolve()
+            yield* Fiber.join(fiber)
+          }),
+        ),
+      { config: { lsp: true } },
+    ),
+  )
 })
 
 describe("LSP.Diagnostic", () => {