mirror of
https://github.com/anomalyco/opencode.git
synced 2026-10-21 20:40:00 +00:00
test(core): bound advance by real time instead of steps (#57119)
This commit is contained in:
parent
ce15cd8821
commit
367c8fbec7
2 changed files with 9 additions and 3 deletions
|
|
@ -33,7 +33,7 @@ Sibling skills: `tui-tests` (OpenTUI renderer tests in tui and cli), `app-e2e-te
|
|||
|
||||
- **Effect code.** `it.effect` from `packages/core/test/lib/effect.ts` runs on `TestClock`; `testEffect(layer)` adds your layer. server and cli import it from `../../core/test/lib/effect`; ai has `packages/ai/test/lib/effect.ts`.
|
||||
- **Fork, then adjust.** Fork with `Effect.forkChild({ startImmediately: true })` (`packages/ai/test/http-timeout.test.ts:58`), or await a Deferred the code completes and then `Effect.yieldNow` (`:80-81`). Otherwise `TestClock.adjust` can run before the sleep is registered, and the join hangs until Bun's timeout.
|
||||
- **Debounced streams and watchers.** `advance(condition)` and `drain` in `packages/core/test/lib/clock.ts` alternate one real 1ms macrotask with 500ms adjusts, because debounces register sleeps across fiber hops. Use `drain` (4 such steps) before a "nothing happened" assertion instead of `Bun.sleep(25)`. `advance`'s 100-step bound is right under TestClock, but a condition waiting on slow real work (a child, a large write) can run out of steps under load.
|
||||
- **Debounced streams and watchers.** `advance(condition)` and `drain` in `packages/core/test/lib/clock.ts` alternate one real 1ms macrotask with 500ms adjusts, because debounces register sleeps across fiber hops. Use `drain` (4 such steps) before a "nothing happened" assertion instead of `Bun.sleep(25)`. `advance` is bounded by 15s of real time, not a step count, because its condition can also wait on slow real work (a plugin import, a child, a large write) that took more than 100 steps on a loaded Linux runner (`supervisor-reload.test.ts` "retains a discovered plugin change during initial activation").
|
||||
- **Real I/O between virtual steps.** `TestClock.withLive(Effect.sleep("10 millis"))`. `registered()` at `packages/cli/test/service.test.ts:740` tests the 5s registration watch with real file I/O under `it.effect`. This departs from the effect skill's "filesystem → `it.live`" on purpose: the timer is the contract.
|
||||
- **Real events need `it.live`.** Under `it.effect` the clock moves only when the test adjusts it, so an Effect timeout on a path driven by real processes, sockets or file events never fires and a missing event hangs instead of failing. The AppProcess tests (`packages/core/test/process/process.test.ts:13-14`) use `it.live` because the spawner's 1-second post-exit capture deadline (`packages/util/src/cross-spawn-spawner.ts:322`) never fired under TestClock.
|
||||
- **Give a racing component its own clock.** When a real-time deadline races slow setup, provide that component a `Clock` that only the test moves past the deadline. `packages/core/test/browser-idle.test.ts:24-38` wraps the live clock for `LocationActivity` (`:49-51`) and adds the TTL at `:84`, once the browser state is attached. Before, it waited out a real 2s TTL and failed on Windows CI with "RPC is unavailable: experimental.browser" when plugin activation was slow. Shortening a real TTL makes that race flakier.
|
||||
|
|
|
|||
|
|
@ -16,13 +16,19 @@ const tick = Effect.gen(function* () {
|
|||
* their sleeps from separate fiber hops that a single adjust can miss, so the
|
||||
* loop alternates real-macrotask settles with adjusts until the condition
|
||||
* holds. Extra adjusts are harmless when nothing is pending.
|
||||
*
|
||||
* Bounded by real time, not a step count: the condition can also wait on real
|
||||
* work such as a plugin import, which needs many more steps on a loaded CI
|
||||
* runner than locally.
|
||||
*/
|
||||
export const advance = Effect.fnUntraced(function* (condition: () => boolean) {
|
||||
for (let attempt = 0; attempt < 100; attempt++) {
|
||||
const deadline = performance.now() + 15_000
|
||||
while (performance.now() < deadline) {
|
||||
if (condition()) return
|
||||
yield* tick
|
||||
}
|
||||
return yield* Effect.die(new Error("condition never became true after 100 advances"))
|
||||
if (condition()) return
|
||||
return yield* Effect.die(new Error("condition never became true within 15s"))
|
||||
})
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue