Stabilize watcher test readiness (#28194)

This commit is contained in:
Kit Langton 2026-05-18 12:51:46 -04:00 committed by GitHub
parent a2e6bd503b
commit 3ab67f3280
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 54 additions and 22 deletions

View file

@ -1,7 +1,8 @@
import { describe, expect } from "bun:test"
import path from "path"
import { realpath } from "fs/promises"
import { AppFileSystem } from "@opencode-ai/core/filesystem"
import { ConfigProvider, Deferred, Effect, Layer, Option } from "effect"
import { ConfigProvider, Deferred, Duration, Effect, Layer, Option } from "effect"
import { TestInstance, provideInstance } from "../fixture/fixture"
import { testEffect } from "../lib/effect"
import { GlobalBus, type GlobalEvent } from "../../src/bus/global"
@ -78,23 +79,49 @@ function wait(directory: string, check: (evt: WatcherEvent) => boolean) {
})
}
function nextUpdate<E>(directory: string, check: (evt: WatcherEvent) => boolean, trigger: Effect.Effect<void, E>) {
function maybeNextUpdate<E>(
directory: string,
check: (evt: WatcherEvent) => boolean,
trigger: Effect.Effect<void, E>,
timeout: Duration.Input = "5 seconds",
) {
return Effect.acquireUseRelease(
wait(directory, check),
({ deferred }) =>
Effect.gen(function* () {
yield* trigger
return yield* Deferred.await(deferred).pipe(
Effect.timeoutOrElse({
duration: "5 seconds",
orElse: () => Effect.fail(new Error("timed out waiting for file watcher update")),
}),
)
return yield* Deferred.await(deferred).pipe(Effect.timeoutOption(timeout))
}),
({ cleanup }) => Effect.sync(cleanup),
)
}
function nextUpdate<E>(directory: string, check: (evt: WatcherEvent) => boolean, trigger: Effect.Effect<void, E>) {
return Effect.gen(function* () {
const result = yield* maybeNextUpdate(directory, check, trigger)
if (Option.isSome(result)) return result.value
return yield* Effect.fail(new Error("timed out waiting for file watcher update"))
})
}
function eventuallyUpdate<E>(
directory: string,
check: (evt: WatcherEvent) => boolean,
trigger: () => Effect.Effect<void, E>,
) {
return Effect.gen(function* () {
while (true) {
const result = yield* maybeNextUpdate(directory, check, trigger(), "250 millis")
if (Option.isSome(result)) return result.value
}
}).pipe(
Effect.timeoutOrElse({
duration: "5 seconds",
orElse: () => Effect.fail(new Error("timed out waiting for file watcher readiness")),
}),
)
}
/** Effect that asserts no matching event arrives within `ms`. */
function noUpdate<E>(
directory: string,
@ -125,22 +152,25 @@ function ready(directory: string) {
const fs = yield* AppFileSystem.Service
const git = yield* Git.Service
yield* nextUpdate(
yield* eventuallyUpdate(
directory,
(evt) => evt.file === file && evt.event === "add",
fs.writeFileString(file, "ready"),
(evt) => evt.file === file,
() => fs.writeFileString(file, `ready-${Math.random()}`),
).pipe(Effect.ensuring(fs.remove(file, { force: true }).pipe(Effect.ignore)), Effect.asVoid)
if (!(yield* fs.existsSafe(head))) return
const branch = `watch-${Math.random().toString(36).slice(2)}`
const realHead = yield* Effect.promise(() => realpath(head).catch(() => head))
const hash = (yield* git.run(["rev-parse", "HEAD"], { cwd: directory })).text()
yield* nextUpdate(
yield* eventuallyUpdate(
directory,
(evt) => evt.file === head && evt.event !== "unlink",
fs
.writeFileString(path.join(directory, ".git", "refs", "heads", branch), hash.trim() + "\n")
.pipe(Effect.andThen(fs.writeFileString(head, `ref: refs/heads/${branch}\n`))),
(evt) => (evt.file === head || evt.file === realHead) && evt.event !== "unlink",
() => {
const branch = `watch-${Math.random().toString(36).slice(2)}`
return fs
.writeFileString(path.join(directory, ".git", "refs", "heads", branch), hash.trim() + "\n")
.pipe(Effect.andThen(fs.writeFileString(head, `ref: refs/heads/${branch}\n`)))
},
).pipe(Effect.asVoid)
})
}

View file

@ -67,6 +67,7 @@ Repeated setup work, long sleeps/timeouts, serial integration tests, filesystem/
| Remaining prompt behavior tests mostly do not require repository state | Removed git setup from safe loop/reference/error fixtures; restored shell queue/cancel cases | 23.400s | 19.610s | keep | Safety review found shell runner readiness depends on git-backed setup in several tests; current single rerun passes. |
| Session processor effect tests do not require repository state | Removed git setup from all processor-effect temp server fixtures | 12.500s | 9.230s | keep | Two targeted reruns passed after the change: 9.61s, 9.23s. |
| HTTP listen PTY ticket tests restart the same listener topology twice | Folded directory-scoped ticket regression into the broader unsafe-ticket test | 7.051s | 6.170s | keep | Two targeted reruns passed after the change: 6.76s, 6.17s; still covers mint failure and successful same-directory upgrade. |
| File watcher readiness can write before async native subscriptions are active | Retried short readiness writes and accepted symlink-realpath HEAD events | failed | 4.62s | keep | Three sequential focused watcher runs passed: 4.62s, 4.57s, 4.64s; full suite no longer failed in `watcher.test.ts`. |
## Profiling Results
@ -107,11 +108,12 @@ Targeted 3-run baselines:
Full-suite sanity checks:
| Command | Result | Notes |
| -------------------- | -------: | -------------------------------------------------------------------- |
| `bun run bench:test` | 225.069s | Before continuing prompt/session work. |
| `bun run bench:test` | 186.729s | After prompt, processor, and PTY wins before safety review restores. |
| `bun run bench:test` | 202.317s | After restoring prompt shell coverage and SDK VCS parity coverage. |
| Command | Result | Notes |
| -------------------- | -------: | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `bun run bench:test` | 225.069s | Before continuing prompt/session work. |
| `bun run bench:test` | 186.729s | After prompt, processor, and PTY wins before safety review restores. |
| `bun run bench:test` | 202.317s | After restoring prompt shell coverage and SDK VCS parity coverage. |
| `bun run bench:test` | failed | Watcher blocker cleared; current run later failed in focused-passing `tool/skill.test.ts` and prompt shell timeout cases under full-suite load. |
## Dead Ends