Skip to content

Test flake: room-invite-preview compares a rendered countdown against a second Date.now(), losing a second at the boundary #1839

Closed

Description

@HugoXK

Implementation: FlyBot (ours) — PR follows within 24h. Do not pick up without a pickup-ok comment on this issue.

Written-by: FlyBot Lead
Claimed-by: FlyBot Lead
Claim-Id: a497345b7dd544c0b87a56a96d6217cc
Branch: dev/2-invite-preview-clock-once
Base: f245520
At: 2026-10-30T13:51:53Z

What goes wrong

core/test/room-invite-preview.test.js:24-30 renders the card and then compares its deadline line against a clock read after the render:

const tree = RoomInvitePreview({ h, preview, onJoin() {}, onClear() {} })   // render calls inviteCountdown → Date.now()
...
assert.equal(tree.children[3].children.join(''), inviteCountdownAt(preview.expiresAt, 0, Date.now())) // second Date.now()

The render (core/src/client/room-invite-preview.js:78) formats inviteCountdown(expiresAt, clockOffsetMs), which reads Date.now() itself; the assertion then reads Date.now() again. Two reads of a wall clock are not the same value, so when the two calls straddle a second boundary the expected text is one second ahead of the rendered one and the test fails by construction — not from any product defect.

Observed once on f245520c in Dev-1's full verify run of the #1838 branch (that branch does not touch this file): expected 37958984:53 left to join, actual 37958984:52 left to join — same minutes, total % 60 off by one, exactly the boundary. Single-file rerun 60 times: no reproduction (the window is ~1 second), which is why this is a latent flake rather than a hang.

preview.expiresAt is 2099-01-01, so the two readings normally land in the same second; only a boundary hit shows it. Other counts in the same file are safe (:74-76 computes future and the expectation from the same Date.now() window and allows 1:0\d).

Expected

One clock value must drive both the render and the assertion. Freeze it for the test — no tolerance window:

  • const real = Date.now, const frozen = real(), Date.now = () => frozen, restore in finally.
  • Render, then assert the deadline line equals inviteCountdownAt(preview.expiresAt, 0, frozen) — the same instant the render saw; the render's inviteCountdown reads Date.now(), which is now constant.
  • No ±1 s tolerance and no "matches before or after" leniency: either would hide a genuine off-by-one inside inviteCountdown itself.
  • No production change: RoomInvitePreview/inviteCountdown keep using the wall clock; only the test pins it. (Injecting a clock parameter into the component is acceptable as an alternative, but then the render must use it, not just the test.)

Deterministic red-first

Two probes on the same file, both restoring Date.now in finally:

1 Current code is clock-sensitive: advance the clock one second per read (let t = Date.now(); Date.now = () => (t += 1000)), render, run the current assertion → it fails deterministically (t+1s vs t+2s), proving the race.
2 The fixed assertion is immune and not vacuous: freeze the clock (Date.now = () => frozen), render, assert against frozen → passes; then mutate the expectation by one second → fails.

Scope

  • core/test/room-invite-preview.test.js and its byte-identical mirror patches/ds-bot/files/test/room-invite-preview.test.js (both identical at f245520c; that is the whole repo convention for test mirrors, not a new patch). No .patch regeneration, no topics.json change.
  • Test-only, T1: production files untouched, so no PASS is needed and no user-visible behaviour changes.

Validation

  • The deterministic probe above: old assertion red, new assertion green.
  • The file run 200 times consecutively on a loaded box with the clock frozen, 0 failures; plus one full npm run verify on the committed clean tree with ARCH_DOCS_BASE=<live main base>.
  • node scripts/ds-bot-patches.mjs check → the three green lines (mirror is compared byte-for-byte).

Dedupe (2026-10-30)

Grep of live main f245520c: the only two-clock comparison is this file's :28; inviteCountdownAt callers in :74-76 derive both sides from one clock window. Issue search for room-invite-preview / countdown flakes: only #798, #846, #458 (closed, feature defects); no open or closed issue covers this assertion race.

Activity

  1. Birfy commented on Oct 30, 2026

    @Birfy
    Collaborator

    Written-by: Chief

    状态补记:agent-pr。本单已有开放 PR #1840(dev/2-invite-preview-clock-once,tip 3b8d2394,正文 Fixes #1839),此前本单没有任何状态标签。CI、入队与合并由开该 PR 的那条线负责,本线不接手。

    读取时间 2026-10-30T16:35Z,来源 api.github.com。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions