Explorar o código

test(core): speed up shell tool suite (#41831)

Kit Langton hai 5 días
pai
achega
baa54a5150
Modificáronse 1 ficheiros con 85 adicións e 63 borrados
  1. 85 63
      packages/core/test/tool-shell.test.ts

+ 85 - 63
packages/core/test/tool-shell.test.ts

@@ -3,17 +3,20 @@ import { realpathSync } from "node:fs"
 import os from "os"
 import path from "path"
 import { describe, expect } from "bun:test"
-import { DateTime, Deferred, Duration, Effect, Fiber, Layer, Scope, Stream } from "effect"
+import { Deferred, Duration, Effect, Fiber, Layer, Scope, Stream } from "effect"
 import { Money } from "@opencode-ai/schema/money"
 import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
 import { LayerNode } from "@opencode-ai/util/effect/layer-node"
-import { makeGlobalNode } from "@opencode-ai/util/effect/app-node"
+import { makeGlobalNode, makeLocationNode } from "@opencode-ai/util/effect/app-node"
 import { filesystem } from "@opencode-ai/util/effect/app-node-platform"
 import { Database } from "@opencode-ai/core/database/database"
 import { Bus } from "@opencode-ai/core/bus"
+import { Config } from "@opencode-ai/core/config"
+import { Environment } from "@opencode-ai/core/environment"
 import { FSUtil } from "@opencode-ai/util/fs-util"
 import { Global } from "@opencode-ai/util/global"
 import { Location } from "@opencode-ai/core/location"
+import { LocationMutation } from "@opencode-ai/core/location-mutation"
 import { LocationServiceMap } from "@opencode-ai/core/location-service-map"
 import { Model } from "@opencode-ai/core/model"
 import { Provider } from "@opencode-ai/core/provider"
@@ -36,7 +39,7 @@ import { Tool } from "@opencode-ai/core/tool"
 import { tmpdir } from "./fixture/tmpdir"
 import { tempGlobalLayer } from "./fixture/global"
 import { testEffect } from "./lib/effect"
-import { toolIdentity, executeTool, toolDefinitions } from "./lib/tool"
+import { toolIdentity, executeTool, registerToolPlugin, toolDefinitions } from "./lib/tool"
 
 const sessionID = Session.ID.make("ses_shell_tool_test")
 const sessionModel = Model.Ref.make({ id: Model.ID.make("test"), providerID: Provider.ID.make("test") })
@@ -124,27 +127,42 @@ const executionNode = makeGlobalNode({
   deps: [Bus.node, SessionStore.node],
 })
 
-const layer = AppNodeBuilder.build(
-  LayerNode.group([
-    Database.node,
-    Bus.node,
-    Job.node,
-    Session.node,
-    SessionExecution.node,
-    PluginRuntime.providerNode,
-    LocationServiceMap.node,
-    filesystem,
-    FSUtil.node,
-    Global.node,
-  ]),
-  [
-    [SessionExecution.node, executionNode],
-    [Permission.node, permission],
-    [Global.node, tempGlobalLayer],
+const shellPluginSupervisor = makeLocationNode({
+  service: PluginSupervisor.Service,
+  layer: Layer.effect(
+    PluginSupervisor.Service,
+    registerToolPlugin(ShellTool.Plugin).pipe(Effect.as(PluginSupervisor.Service.of({ flush: Effect.void }))),
+  ),
+  deps: [
+    Config.node,
+    Environment.node,
+    LocationMutation.node,
+    Permission.node,
+    PluginRuntime.node,
+    Shell.node,
+    Tool.node,
   ],
-)
+})
 
-const it = testEffect(layer)
+const nodes = LayerNode.group([
+  Database.node,
+  Bus.node,
+  Job.node,
+  Session.node,
+  SessionExecution.node,
+  PluginRuntime.providerNode,
+  LocationServiceMap.node,
+  filesystem,
+  FSUtil.node,
+  Global.node,
+])
+const replacements = [
+  [SessionExecution.node, executionNode],
+  [Permission.node, permission],
+  [Global.node, tempGlobalLayer],
+] satisfies LayerNode.Replacements
+const productionIt = testEffect(AppNodeBuilder.build(nodes, replacements))
+const it = testEffect(AppNodeBuilder.build(nodes, [...replacements, [PluginSupervisor.node, shellPluginSupervisor]]))
 
 const call = (input: typeof ShellTool.Input.Type, id = "call-shell") => ({
   sessionID,
@@ -165,9 +183,6 @@ const idleCommand = isWindows ? "Start-Sleep -Seconds 60" : "sleep 60"
 const timeoutOutputCommand = isWindows
   ? "[Console]::Out.Write('before timeout'); Start-Sleep -Seconds 60"
   : "printf 'before timeout'; sleep 60"
-const steadyProgressCommand = isWindows
-  ? "[Console]::Out.Write('steady'); Start-Sleep -Milliseconds 3400"
-  : "printf steady; sleep 3.4"
 const bodyExitCommand = isWindows
   ? "[Console]::Out.Write('body'); Start-Sleep -Milliseconds 100; exit 7"
   : "printf body && exit 7"
@@ -203,42 +218,49 @@ const withSession = <A, E, R>(directory: string, body: (registry: Tool.Interface
   })
 
 describe("ShellTool", () => {
-  it.live("registers and returns real successful output from the active Location", () =>
-    Effect.acquireUseRelease(
-      Effect.promise(() => tmpdir()),
-      (tmp) => {
-        reset()
-        return withSession(tmp.path, (registry) =>
-          Effect.gen(function* () {
-            const definitions = yield* toolDefinitions(registry)
-            const definition = definitions.find((tool) => tool.name === "shell")
-            expect(definition?.description).toStartWith("Execute a shell command and return its output.")
-            expect(definition?.inputSchema).not.toHaveProperty("properties.timeout.maximum")
-            // Code Mode receives the declared output schema, including the command output text.
-            expect(definition?.outputSchema).toHaveProperty("properties.output")
-            expect(
-              (yield* toolDefinitions(registry, [{ action: "shell", resource: "*", effect: "deny" }])).map(
-                (tool) => tool.name,
-              ),
-            ).not.toContain("shell")
-
-            const settled = yield* executeTool(registry, call({ command: helloCommand }))
-            expect(settled.status).toBe("completed")
-            expect(settled.metadata).toMatchObject({ exit: 0, truncated: false })
-            expect(settled.content?.[0]).toEqual({ type: "text", text: "hello" })
-            expect(settled.content?.[1]).toMatchObject({
-              type: "text",
-              text: expect.stringContaining("Command exited with code 0."),
-            })
-            expect(assertions).toMatchObject([
-              { sessionID, action: "shell", resources: [isWindows ? "Start-Sleep -Milliseconds 100" : helloCommand] },
-            ])
-            expect(assertions[0]?.save).toEqual([isWindows ? "Start-Sleep *" : "printf *"])
-          }),
-        )
-      },
-      (tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]().then(() => undefined)),
-    ),
+  productionIt.live(
+    "registers and returns real successful output from the active Location",
+    () =>
+      Effect.acquireUseRelease(
+        Effect.promise(() => tmpdir()),
+        (tmp) => {
+          reset()
+          return withSession(tmp.path, (registry) =>
+            Effect.gen(function* () {
+              const definitions = yield* toolDefinitions(registry)
+              const definition = definitions.find((tool) => tool.name === "shell")
+              expect(definition?.description).toStartWith("Execute a shell command and return its output.")
+              expect(definition?.inputSchema).not.toHaveProperty("properties.timeout.maximum")
+              // Code Mode receives the declared output schema, including the command output text.
+              expect(definition?.outputSchema).toHaveProperty("properties.output")
+              expect(
+                (yield* toolDefinitions(registry, [{ action: "shell", resource: "*", effect: "deny" }])).map(
+                  (tool) => tool.name,
+                ),
+              ).not.toContain("shell")
+
+              const settled = yield* executeTool(registry, call({ command: helloCommand }))
+              expect(settled.status).toBe("completed")
+              expect(settled.metadata).toMatchObject({ exit: 0, truncated: false })
+              expect(settled.content?.[0]).toEqual({ type: "text", text: "hello" })
+              expect(settled.content?.[1]).toMatchObject({
+                type: "text",
+                text: expect.stringContaining("Command exited with code 0."),
+              })
+              expect(assertions).toMatchObject([
+                {
+                  sessionID,
+                  action: "shell",
+                  resources: [isWindows ? "Start-Sleep -Milliseconds 100" : helloCommand],
+                },
+              ])
+              expect(assertions[0]?.save).toEqual([isWindows ? "Start-Sleep *" : "printf *"])
+            }),
+          )
+        },
+        (tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]().then(() => undefined)),
+      ),
+    { timeout: 15_000 },
   )
 
   it.live("resolves a relative workdir from the active Location", () =>
@@ -577,7 +599,7 @@ describe("ShellTool", () => {
   )
 
   it.live(
-    "does not repeat shell ID progress",
+    "reports shell ID progress once",
     () =>
       Effect.acquireUseRelease(
         Effect.promise(() => tmpdir()),
@@ -587,7 +609,7 @@ describe("ShellTool", () => {
             Effect.gen(function* () {
               const updates: Tool.Metadata[] = []
               yield* executeTool(registry, {
-                ...call({ command: steadyProgressCommand }, "call-steady-progress"),
+                ...call({ command: helloCommand }, "call-shell-id-progress"),
                 progress: (update) => Effect.sync(() => updates.push(update)),
               })
               expect(updates).toHaveLength(1)