From 4ea71a186b4c63e21c9b4c1503884c37c9e65be8 Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Fri, 25 Sep 2026 01:02:24 -0700 Subject: [PATCH] test(signals): assert retention on bookkeeping, not a GC heap count Co-Authored-By: Claude Opus 5.5 --- js/signals/src/index.test.ts | 44 ++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/js/signals/src/index.test.ts b/js/signals/src/index.test.ts index 8491741c57..71c5b190d6 100644 --- a/js/signals/src/index.test.ts +++ b/js/signals/src/index.test.ts @@ -1,4 +1,3 @@ -import { heapStats } from "bun:jsc"; import { describe, expect, spyOn, test } from "bun:test"; import { Computed, type Dispose, Effect, type GetPromise, Once, race, Signal } from "./index.ts"; @@ -1135,17 +1134,21 @@ describe("race", () => { expect(closed.reactions).toBe(1); }); - test("many races against a long-lived promise keep the heap flat", async () => { + test("many races against a long-lived promise leave it no listeners", async () => { const closed = new Promise(() => {}); - const measure = () => { - Bun.gc(true); - return heapStats().objectCount; - }; + const add = spyOn(Set.prototype, "add"); + let sets: Set[]; + try { + for (let i = 0; i < 1000; i++) await race([Promise.resolve(i), closed]); + sets = [...add.mock.contexts] as Set[]; + } finally { + add.mockRestore(); + } - for (let i = 0; i < 1000; i++) await race([Promise.resolve(i), closed]); - const before = measure(); - for (let i = 0; i < 10000; i++) await race([Promise.resolve(i), closed]); - expect(measure() - before).toBeLessThan(1000); + // `closed` is the last value each race listens to, so the last set added to is its listeners. + const listeners = sets.at(-1); + expect(sets.filter((set) => set === listeners).length).toBe(1000); + expect(sets.every((set) => set.size === 0)).toBe(true); }); }); @@ -1204,15 +1207,22 @@ describe("effect.race", () => { describe("spawn retention", () => { test("an effect that never reruns drops settled tasks", async () => { const effect = new Effect(); - const promises = () => { - Bun.gc(true); - return heapStats().objectTypeCounts.Promise ?? 0; - }; + const add = spyOn(Set.prototype, "add"); + let sets: Set[]; + try { + for (let i = 0; i < 100; i++) effect.spawn(async () => {}); + sets = [...add.mock.contexts] as Set[]; + } finally { + add.mockRestore(); + } + + // The effect's own task set, found by what spawn added to it. + const tasks = sets[0]; + expect(sets.every((set) => set === tasks)).toBe(true); + expect(tasks?.size).toBe(100); - const before = promises(); - for (let i = 0; i < 10000; i++) effect.spawn(async () => {}); await settle(); - expect(promises() - before).toBeLessThan(100); + expect(tasks?.size).toBe(0); effect.close(); });