mirror of
https://github.com/anomalyco/opencode.git
synced 2026-10-29 10:17:00 +00:00
122 lines
4.9 KiB
TypeScript
122 lines
4.9 KiB
TypeScript
import { describe, expect, test } from "bun:test"
|
|
import { Effect, Fiber } from "effect"
|
|
import { CodeMode } from "../src/index.js"
|
|
import { MAX_ARRAY_LENGTH, MAX_PENDING_PROMISES, MAX_STRING_LENGTH } from "../src/interpreter/limits.js"
|
|
|
|
const runtime = CodeMode.make({ tools: {} })
|
|
|
|
const value = async (code: string) => {
|
|
const result = await Effect.runPromise(runtime.execute(code))
|
|
if (!result.ok) throw new Error(`expected success, got ${result.error.kind}: ${result.error.message}`)
|
|
return result.value
|
|
}
|
|
|
|
const failure = async (code: string) => {
|
|
const result = await Effect.runPromise(runtime.execute(code))
|
|
if (result.ok) throw new Error(`expected failure, got value ${JSON.stringify(result.value)}`)
|
|
return result.error.message
|
|
}
|
|
|
|
const S = MAX_STRING_LENGTH
|
|
const A = MAX_ARRAY_LENGTH
|
|
|
|
describe("one built-in cannot build an unbounded value", () => {
|
|
test("strings: repeat, pad, concat, join, +, template, JSON.stringify", async () => {
|
|
expect(await value(`return "x".repeat(${S}).length`)).toBe(S)
|
|
for (const code of [
|
|
`"x".repeat(${S + 1})`,
|
|
`"x".padStart(${S + 1})`,
|
|
`"x".padEnd(${S + 1}, "y")`,
|
|
`"x".repeat(${S}).concat("y")`,
|
|
`["x".repeat(${S}), "y"].join("")`,
|
|
`"x".repeat(${S}) + "y"`,
|
|
`\`\${"x".repeat(${S})}y\``,
|
|
`JSON.stringify("x".repeat(${S}))`,
|
|
]) {
|
|
expect(await failure(`try { ${code} } catch (e) { throw Error(e.name + ": " + e.message) }`)).toContain(
|
|
"RangeError: Invalid string length",
|
|
)
|
|
}
|
|
})
|
|
|
|
// split and matchAll share the same result check but must build ten million items first to reach it,
|
|
// which is too slow for CI.
|
|
test("arrays: constructor, length, Array.from, concat, flat", async () => {
|
|
expect(await value(`return Array(${A}).length`)).toBe(A)
|
|
for (const code of [
|
|
`Array(${A + 1})`,
|
|
`const a = []; a.length = ${A + 1}`,
|
|
`Array.from({ length: ${A + 1} })`,
|
|
`Array(${A}).concat([1])`,
|
|
`[Array(${A}).fill(0), [1]].flat()`,
|
|
]) {
|
|
expect(await failure(`try { ${code} } catch (e) { throw Error(e.name + ": " + e.message) }`)).toContain(
|
|
"RangeError: Invalid array length",
|
|
)
|
|
}
|
|
})
|
|
|
|
test("promises: too many pending at once, while settled ones do not count", async () => {
|
|
const n = MAX_PENDING_PROMISES
|
|
expect(await value(`for (let i = 0; i < ${n * 2}; i++) Promise.resolve(i); return 1`)).toBe(1)
|
|
expect(
|
|
await failure(
|
|
`try { for (let i = 0; i <= ${n}; i++) new Promise(() => {}) } catch (e) { throw Error(e.name + ": " + e.message) }`,
|
|
),
|
|
).toContain("RangeError: Too many pending promises")
|
|
expect(await failure(`await Promise.all(Array(${n + 1}).fill(0).map(() => new Promise(() => {})))`)).toContain(
|
|
"Too many pending promises",
|
|
)
|
|
}, 10_000)
|
|
})
|
|
|
|
describe("the host stays in control", () => {
|
|
test("other host work keeps running while one long job runs", async () => {
|
|
let ticks = 0
|
|
const result = await Effect.runPromise(
|
|
Effect.gen(function* () {
|
|
const counter = yield* Effect.forkChild(
|
|
Effect.forever(
|
|
Effect.andThen(
|
|
Effect.yieldNow,
|
|
Effect.sync(() => {
|
|
ticks += 1
|
|
}),
|
|
),
|
|
),
|
|
)
|
|
const result = yield* runtime.execute(`let t = 0; for (let i = 0; i < 1_000_000; i++) t += i % 3; return t`)
|
|
yield* Fiber.interrupt(counter)
|
|
return result
|
|
}),
|
|
)
|
|
expect(result).toMatchObject({ ok: true, value: 999_999 })
|
|
// The loop takes well over 10 ms anywhere, and the runner yields to the host every millisecond; a job that kept
|
|
// the thread for its whole run would let the counter tick at most a couple of times around it.
|
|
expect(ticks).toBeGreaterThan(10)
|
|
})
|
|
|
|
// Each case would run for seconds or forever; the timeout must still stop it promptly.
|
|
test.each([
|
|
["a loop of only built-in calls", `return Iterator.from({ next: Object }).drop(Infinity).next()`],
|
|
[
|
|
"serializing shared references",
|
|
`let a = [0]; for (let i = 0; i < 24; i++) a = [a, a]; return JSON.stringify(a).length`,
|
|
],
|
|
["copying out shared references", `let a = [0]; for (let i = 0; i < 24; i++) a = [a, a]; return a`],
|
|
[
|
|
"reporting many unhandled rejections",
|
|
`const a = Array(10_000).fill(0); for (let i = 0; i < 20_000; i++) Promise.reject(a); return 1`,
|
|
],
|
|
])("%s still times out", async (_, code) => {
|
|
const result = await Effect.runPromise(CodeMode.make({ limits: { timeoutMs: 100 } }).execute(code))
|
|
expect(result).toMatchObject({ ok: false, error: { kind: "TimeoutExceeded" } })
|
|
})
|
|
|
|
test("nesting deeper than the host stack fails cleanly, and the next run is unaffected", async () => {
|
|
expect(await failure(`const f = () => f; f${"()".repeat(100_000)}; return 1`)).toBe(
|
|
"Execution exceeded the maximum nesting depth.",
|
|
)
|
|
expect(await value(`const f = () => f; f${"()".repeat(500)}; return 1`)).toBe(1)
|
|
})
|
|
})
|