8c94e9005f
Co-authored-by: Brendan Allan <14191578+Brendonovich@users.noreply.github.com> Co-authored-by: Kit Langton <kit.langton@gmail.com> Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com> Co-authored-by: Affan Ali <93028901+affanali2k3@users.noreply.github.com> Co-authored-by: affanali2k3 <affanalikhanxx@gmail.com> Co-authored-by: Frank <frank@anoma.ly> Co-authored-by: opencode-agent[bot] <219766164+opencode-agent[bot]@users.noreply.github.com> Co-authored-by: 𝓛𝓲𝓽𝓽𝓵𝓮 𝓕𝓻𝓪𝓷𝓴 <little-frank@opencord.local> Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Co-authored-by: Jay V <air@live.ca> Co-authored-by: Dax Raad <d@ironbay.co> Co-authored-by: Aarav Sareen <96787824+arvsrn@users.noreply.github.com> Co-authored-by: OpeOginni <107570612+OpeOginni@users.noreply.github.com> Co-authored-by: Luke Parker <10430890+Hona@users.noreply.github.com> Co-authored-by: Ben Guthrie <benjee.012@gmail.com> Co-authored-by: Dax <mail@thdxr.com> Co-authored-by: Filip <34747899+neriousy@users.noreply.github.com> Co-authored-by: Max Anderson <max.a.anderson95@gmail.com> Co-authored-by: Brendan Allan <git@brendonovich.dev> Co-authored-by: Jack <jack@anoma.ly> Co-authored-by: Shoubhit Dash <shoubhit2005@gmail.com> Co-authored-by: Dustin Deus <deusdustin@gmail.com> Co-authored-by: starptech <starptech@starptechs-MBP.fritz.box> Co-authored-by: Aiden Cline <aidenpcline@gmail.com> Co-authored-by: usrnk1 <7547651+usrnk1@users.noreply.github.com> Co-authored-by: Jay <53023+jayair@users.noreply.github.com> Co-authored-by: runvip <164729189+runvip@users.noreply.github.com> Co-authored-by: opencode <opencode@sst.dev> Co-authored-by: Julian Coy <julian@ex-machina.co> Co-authored-by: Vladimir Glafirov <vglafirov@gitlab.com>
133 lines
4.4 KiB
TypeScript
133 lines
4.4 KiB
TypeScript
import { describe, expect } from "bun:test"
|
|
import { Deferred, Effect, Fiber } from "effect"
|
|
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
|
import { Job } from "@/job"
|
|
import { SessionSchema } from "@opencode-ai/core/session/schema"
|
|
import { testEffect } from "./lib/effect"
|
|
|
|
const it = testEffect(LayerNode.compile(Job.node))
|
|
|
|
describe("job", () => {
|
|
it.instance("tracks started jobs through completion", () =>
|
|
Effect.gen(function* () {
|
|
const jobs = yield* Job.Service
|
|
const latch = yield* Deferred.make<void>()
|
|
const job = yield* jobs.start({
|
|
type: "test",
|
|
title: "test job",
|
|
run: Deferred.await(latch).pipe(Effect.as("done")),
|
|
})
|
|
|
|
expect(job.id.startsWith("job_")).toBe(true)
|
|
expect(job.status).toBe("running")
|
|
expect(job.title).toBe("test job")
|
|
|
|
yield* Deferred.succeed(latch, undefined)
|
|
const done = yield* jobs.wait({ id: job.id })
|
|
|
|
expect(done.timedOut).toBe(false)
|
|
expect(done.info?.status).toBe("completed")
|
|
expect(done.info?.output).toBe("done")
|
|
expect((yield* jobs.list()).map((item) => item.id)).toEqual([job.id])
|
|
}),
|
|
)
|
|
|
|
it.instance("returns a running snapshot when wait times out", () =>
|
|
Effect.gen(function* () {
|
|
const jobs = yield* Job.Service
|
|
const job = yield* jobs.start({ type: "test", run: Effect.never })
|
|
|
|
const result = yield* jobs.wait({ id: job.id, timeout: 1 })
|
|
|
|
expect(result.timedOut).toBe(true)
|
|
expect(result.info?.status).toBe("running")
|
|
}),
|
|
)
|
|
|
|
it.instance("deduplicates concurrent starts for a running id", () =>
|
|
Effect.gen(function* () {
|
|
const jobs = yield* Job.Service
|
|
const started = yield* Deferred.make<void>()
|
|
const id = "job_test"
|
|
const [first, second] = yield* Effect.all(
|
|
[
|
|
jobs.start({
|
|
id,
|
|
type: "test",
|
|
run: Deferred.succeed(started, undefined).pipe(Effect.andThen(Effect.never)),
|
|
}),
|
|
jobs.start({ id, type: "test", run: Effect.fail(new Error("duplicate started")) }),
|
|
],
|
|
{ concurrency: "unbounded" },
|
|
)
|
|
|
|
yield* Deferred.await(started)
|
|
|
|
expect(first.id).toBe(id)
|
|
expect(second.id).toBe(id)
|
|
expect(first.status).toBe("running")
|
|
expect(second.status).toBe("running")
|
|
expect((yield* jobs.list()).map((item) => item.id)).toEqual([id])
|
|
|
|
yield* jobs.cancel(id)
|
|
}),
|
|
)
|
|
|
|
it.instance("records failed jobs", () =>
|
|
Effect.gen(function* () {
|
|
const jobs = yield* Job.Service
|
|
const job = yield* jobs.start({ type: "test", run: Effect.fail(new Error("boom")) })
|
|
|
|
const result = yield* jobs.wait({ id: job.id })
|
|
|
|
expect(result.info?.status).toBe("error")
|
|
expect(result.info?.error).toBe("boom")
|
|
}),
|
|
)
|
|
|
|
it.instance("can cancel running jobs", () =>
|
|
Effect.gen(function* () {
|
|
const jobs = yield* Job.Service
|
|
const interrupted = yield* Deferred.make<void>()
|
|
const job = yield* jobs.start({
|
|
type: "test",
|
|
run: Effect.never.pipe(Effect.ensuring(Deferred.succeed(interrupted, undefined))),
|
|
})
|
|
|
|
const cancelled = yield* jobs.cancel(job.id)
|
|
|
|
expect(cancelled?.status).toBe("cancelled")
|
|
yield* Deferred.await(interrupted).pipe(Effect.timeout("1 second"))
|
|
expect((yield* jobs.get(job.id))?.status).toBe("cancelled")
|
|
}),
|
|
)
|
|
|
|
it.instance("releases blocking waits when backgrounded", () =>
|
|
Effect.gen(function* () {
|
|
const jobs = yield* Job.Service
|
|
const latch = yield* Deferred.make<void>()
|
|
const job = yield* jobs.start({ type: "test", run: Deferred.await(latch).pipe(Effect.as("done")) })
|
|
const waiting = yield* jobs
|
|
.block({ id: job.id, sessionID: SessionSchema.ID.make("ses_parent") })
|
|
.pipe(Effect.forkChild)
|
|
|
|
expect(yield* jobs.background(job.id)).toMatchObject({ id: job.id, status: "running" })
|
|
expect(yield* Fiber.join(waiting)).toMatchObject({ type: "backgrounded", info: { id: job.id } })
|
|
|
|
yield* Deferred.succeed(latch, undefined)
|
|
expect((yield* jobs.wait({ id: job.id })).info?.output).toBe("done")
|
|
}),
|
|
)
|
|
|
|
it.instance("returns immutable snapshots", () =>
|
|
Effect.gen(function* () {
|
|
const jobs = yield* Job.Service
|
|
const job = yield* jobs.start({ type: "test", metadata: { value: "initial" }, run: Effect.succeed("done") })
|
|
|
|
if (job.metadata) job.metadata.value = "changed"
|
|
|
|
expect((yield* jobs.get(job.id))?.metadata?.value).toBe("initial")
|
|
}),
|
|
)
|
|
})
|