feat(core): add embedded v2 session runtime and tool foundation (#30632)
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
import { describe, expect } from "bun:test"
|
||||
import fs from "fs/promises"
|
||||
import { realpathSync } from "node:fs"
|
||||
import { tmpdir } from "node:os"
|
||||
import { Effect, Exit, Stream } from "effect"
|
||||
import path from "node:path"
|
||||
import { Effect, Exit, Fiber, Stream } from "effect"
|
||||
import { ChildProcess } from "effect/unstable/process"
|
||||
import { AppProcess } from "@opencode-ai/core/process"
|
||||
import { testEffect } from "../lib/effect"
|
||||
@@ -11,6 +13,18 @@ const it = testEffect(AppProcess.defaultLayer)
|
||||
const NODE = process.execPath
|
||||
const cmd = (...args: string[]) => ChildProcess.make(NODE, args)
|
||||
|
||||
const waitForFile = (file: string) =>
|
||||
Effect.promise(async () => {
|
||||
while (true) {
|
||||
try {
|
||||
return await fs.readFile(file, "utf8")
|
||||
} catch (error) {
|
||||
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error
|
||||
await new Promise<void>((resolve) => setTimeout(resolve, 10))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
describe("AppProcess", () => {
|
||||
describe("run", () => {
|
||||
it.effect(
|
||||
@@ -118,6 +132,50 @@ describe("AppProcess", () => {
|
||||
expect(result.command).toBe(`${NODE} -e process.stdout.write('hi')`)
|
||||
}),
|
||||
)
|
||||
|
||||
if (process.platform !== "win32") {
|
||||
it.live(
|
||||
"timeout cleans up the scoped child process",
|
||||
Effect.acquireUseRelease(
|
||||
Effect.promise(() => fs.mkdtemp(path.join(tmpdir(), "opencode-process-timeout-"))),
|
||||
(directory) => {
|
||||
const ready = path.join(directory, "ready")
|
||||
const settled = path.join(directory, "settled")
|
||||
const script = `const fs=require('fs');fs.writeFileSync(${JSON.stringify(ready)},String(process.pid));process.on('SIGTERM',()=>{fs.writeFileSync(${JSON.stringify(settled)},'settled');process.exit(0)});setInterval(()=>{},60000)`
|
||||
return Effect.gen(function* () {
|
||||
const svc = yield* AppProcess.Service
|
||||
const exit = yield* Effect.exit(svc.run(cmd("-e", script), { timeout: "1 second" }))
|
||||
expect(Exit.isFailure(exit)).toBe(true)
|
||||
expect(yield* waitForFile(ready)).toMatch(/^\d+$/)
|
||||
expect(yield* waitForFile(settled)).toBe("settled")
|
||||
})
|
||||
},
|
||||
(directory) => Effect.promise(() => fs.rm(directory, { recursive: true, force: true })),
|
||||
),
|
||||
5_000,
|
||||
)
|
||||
|
||||
it.live(
|
||||
"fiber interruption cleans up the scoped child process after readiness",
|
||||
Effect.acquireUseRelease(
|
||||
Effect.promise(() => fs.mkdtemp(path.join(tmpdir(), "opencode-process-interrupt-"))),
|
||||
(directory) => {
|
||||
const ready = path.join(directory, "ready")
|
||||
const settled = path.join(directory, "settled")
|
||||
const script = `const fs=require('fs');fs.writeFileSync(${JSON.stringify(ready)},String(process.pid));process.on('SIGTERM',()=>{fs.writeFileSync(${JSON.stringify(settled)},'settled');process.exit(0)});setInterval(()=>{},60000)`
|
||||
return Effect.gen(function* () {
|
||||
const svc = yield* AppProcess.Service
|
||||
const fiber = yield* svc.run(cmd("-e", script)).pipe(Effect.forkChild)
|
||||
expect(yield* waitForFile(ready)).toMatch(/^\d+$/)
|
||||
yield* Fiber.interrupt(fiber)
|
||||
expect(yield* waitForFile(settled)).toBe("settled")
|
||||
})
|
||||
},
|
||||
(directory) => Effect.promise(() => fs.rm(directory, { recursive: true, force: true })),
|
||||
),
|
||||
5_000,
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
describe("inherited platform methods", () => {
|
||||
|
||||
Reference in New Issue
Block a user