Explorar o código

test(lsp): synchronize idle server eviction

Tianyi Cui hai 2 meses
pai
achega
3fa5795bf4
Modificáronse 1 ficheiros con 36 adicións e 9 borrados
  1. 36 9
      packages/lsp/lsp-local/tests/lifecycle.spec.ts

+ 36 - 9
packages/lsp/lsp-local/tests/lifecycle.spec.ts

@@ -1,11 +1,11 @@
-import { afterEach, beforeEach, describe, expect, it } from 'vitest'
+import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
 import { mkdtemp, mkdir, readFile, rm, writeFile } from 'node:fs/promises'
 import { realpath } from 'node:fs/promises'
 import { tmpdir } from 'node:os'
 import { join } from 'node:path'
 import { pathToFileURL, fileURLToPath } from 'node:url'
 import { Context } from 'cordis'
-import Lsp, { type LspQueryRequest, type LspQueryResult } from '@deepseek-ai/dsh-lsp'
+import Lsp, { type LspProvider, type LspQueryRequest, type LspQueryResult } from '@deepseek-ai/dsh-lsp'
 import { deadline } from '@deepseek-ai/dsh-timeout'
 import * as LspLocal from '@deepseek-ai/dsh-lsp-local'
 import type { LspLocalServerConfig } from '@deepseek-ai/dsh-lsp-local'
@@ -38,12 +38,27 @@ function fakeServer(fakeEnv: Record<string, string> = {}, overrides: Partial<Lsp
 }
 
 /** Mount the real seam + lsp-local plugin driving one fake server. */
-async function mount(fakeEnv: Record<string, string> = {}, overrides: Partial<LspLocalServerConfig> = {}): Promise<Context> {
+async function mount(
+  fakeEnv: Record<string, string> = {},
+  overrides: Partial<LspLocalServerConfig> = {},
+  captureProvider?: (provider: LspProvider) => void,
+): Promise<Context> {
   const ctx = new Context()
   await ctx.plugin(Lsp)
-  await ctx.plugin(LspLocal, {
-    servers: { fake: fakeServer(fakeEnv, overrides) },
-  })
+  const register = ctx.lsp.registerProvider.bind(ctx.lsp)
+  const registrationSpy = captureProvider === undefined
+    ? undefined
+    : vi.spyOn(ctx.lsp, 'registerProvider').mockImplementation((provider) => {
+      captureProvider(provider)
+      return register(provider)
+    })
+  try {
+    await ctx.plugin(LspLocal, {
+      servers: { fake: fakeServer(fakeEnv, overrides) },
+    })
+  } finally {
+    registrationSpy?.mockRestore()
+  }
   return ctx
 }
 
@@ -241,10 +256,22 @@ describe('lsp-local end to end over a fake server', () => {
     // The first query succeeds, then the server exits before the second arrives, leaving a dead
     // instance in the pool. The next query must evict-and-replace it and still succeed, rather than
     // failing once on the closed connection first.
-    const ctx = await mount({ LSP_FAKE_EXIT_AFTER_REPLY: '1', LSP_FAKE_DEF: JSON.stringify(locationJson(0)) })
+    let provider: LspProvider | undefined
+    const ctx = await mount(
+      { LSP_FAKE_EXIT_AFTER_REPLY: '1', LSP_FAKE_DEF: JSON.stringify(locationJson(0)) },
+      {},
+      (registered) => { provider = registered },
+    )
     expect(await ctx.lsp.query(query('goToDefinition'))).toMatchObject({ kind: 'locations' })
-    // Wait past the fixture's post-reply exit so the pooled instance is observably dead.
-    await new Promise(resolve => setTimeout(resolve, 60))
+    if (provider === undefined) throw new Error('expected lsp-local to register a provider')
+    // This implementation-local test reaches the private pool only to synchronize with its actual
+    // close state. A fixed wall-clock sleep can expire before a CPU-starved child runs its exit timer.
+    const instances = (provider as unknown as {
+      readonly instances: ReadonlyMap<string, { readonly dead: boolean }>
+    }).instances
+    const instance = [...instances.values()][0]
+    if (instance === undefined) throw new Error('expected one pooled LSP instance')
+    await waitFor(async () => instance.dead)
     expect(await ctx.lsp.query(query('goToDefinition'))).toMatchObject({ kind: 'locations' })
     await ctx.fiber.dispose()
   })