Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions packages/testing/contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
UnknownIdentity,
} from '@commy/core/ports'
import { Duration, Effect, Exit, Option, Queue, type Scope, Stream } from 'effect'
import { REALM_HOOK_TIMEOUT_MS } from './realm-hooks.ts'

export interface ContractEnv {
/**
Expand Down Expand Up @@ -110,9 +111,9 @@ export const runAgentCommsContract = (label: string, factory: ContractFactory):

beforeEach(async () => {
env = await factory()
})
}, REALM_HOOK_TIMEOUT_MS)

afterEach(() => Effect.runPromise(env.dispose()))
afterEach(() => Effect.runPromise(env.dispose()), REALM_HOOK_TIMEOUT_MS)

test('publisher.post returns a MessageRef whose channel matches the destination', () =>
Effect.runPromise(
Expand Down
39 changes: 39 additions & 0 deletions packages/testing/realm-hooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { afterEach, beforeEach } from 'bun:test'

/**
* bun:test lifecycle hooks keep their own 5s timeout, independent of any
* per-test timeout. In isolation a real in-process realm starts and stops in a
* few ms, but under a full-parallel `bun run check` the starved event loop can
* stretch that setup/teardown past 5s — and then the HOOK, not the test body,
* times out, landing the failure on whichever test the hook happened to be
* wrapping (comms-xwqm). 30s gives that contention wide headroom while still
* failing if a hook genuinely wedges.
*
* This is headroom, not a cure-all: the long-poll teardown tests (the
* gap-replay / scope-close-interrupt cases that drive an infinite server
* handler and rely on AbortSignal unwinding a forked drain fiber) can still
* exceed even this under aggressive contention. That residual is a fiber/scope
* lifecycle problem, not a too-small-number problem, and is tracked for the
* Effect-Scope-based realm rework (comms-4lz5 / comms-30hq) — running the whole
* test, realm acquisition included, inside one scope with guaranteed finalizers
* removes the leftover-fiber-starves-teardown failure mode structurally.
*/
export const REALM_HOOK_TIMEOUT_MS = 30_000

/**
* Register per-test realm lifecycle on the bun:test hooks — a fresh instance
* before each test, stopped after — with the contention-proof timeout above.
* The caller keeps its own directly-referenced binding via `assign`, so
* existing `realm.` / `fixture.` call sites stay untouched.
*/
export const registerRealmHooks = <T extends { readonly stop: () => Promise<void> }>(
start: () => T,
assign: (instance: T) => void,
): void => {
let instance: T
beforeEach(() => {
instance = start()
assign(instance)
}, REALM_HOOK_TIMEOUT_MS)
afterEach(() => instance.stop(), REALM_HOOK_TIMEOUT_MS)
}
11 changes: 4 additions & 7 deletions packages/zulip/adapter.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { afterEach, beforeEach, expect, test } from 'bun:test'
import { expect, test } from 'bun:test'
import type { ChannelRef, Identity, InboundEvent, MessageRef } from '@commy/core/ports'
import {
DirectoryError,
Expand All @@ -17,6 +17,7 @@ import {
UnknownChannel,
type UnknownIdentity,
} from '@commy/core/ports'
import { registerRealmHooks } from '@commy/testing/realm-hooks'
import { FetchHttpClient, HttpClient } from '@effect/platform'
import { Cause, Duration, Effect, Exit, Option, Queue, Redacted, type Scope, Stream } from 'effect'
import type { ZulipAdapter, ZulipAdapterConfig } from './adapter.ts'
Expand All @@ -27,12 +28,8 @@ import { startTestRealm } from './test-server.ts'

let realm: TestRealm

beforeEach(() => {
realm = startTestRealm()
})

afterEach(async () => {
await realm.stop()
registerRealmHooks(startTestRealm, (next) => {
realm = next
})

const HERMES = {
Expand Down
11 changes: 4 additions & 7 deletions packages/zulip/bot-dm-guard.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { afterEach, beforeEach, expect, test } from 'bun:test'
import { expect, test } from 'bun:test'
import { registerRealmHooks } from '@commy/testing/realm-hooks'
import { FetchHttpClient, HttpClient } from '@effect/platform'
import { Cause, Effect, Exit, Option, Schema } from 'effect'
import { BotToBotDirectMessageError, type RecipientDirectory, wrapBotHttp } from './bot-dm-guard.ts'
Expand All @@ -9,12 +10,8 @@ import { ZulipUserRef } from './user-ref.ts'

let realm: TestRealm

beforeEach(() => {
realm = startTestRealm()
})

afterEach(async () => {
await realm.stop()
registerRealmHooks(startTestRealm, (next) => {
realm = next
})

const SELF_ID = ZulipUserRef(100)
Expand Down
11 changes: 4 additions & 7 deletions packages/zulip/http.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { afterEach, beforeEach, expect, test } from 'bun:test'
import { expect, test } from 'bun:test'
import { registerRealmHooks } from '@commy/testing/realm-hooks'
import { FetchHttpClient, HttpClient } from '@effect/platform'
import {
Cause,
Expand Down Expand Up @@ -115,12 +116,8 @@ const startFixture = (): Fixture => {

let fixture: Fixture

beforeEach(() => {
fixture = startFixture()
})

afterEach(async () => {
await fixture.stop()
registerRealmHooks(startFixture, (next) => {
fixture = next
})

const successSchema = Schema.Struct({ result: Schema.Literal('success') })
Expand Down