Files
hermes-agent/tests-js/bootstrap-installer-stage-timer.test.ts
brooklyn! d753957e8a fix(install): Windows setup no longer hangs forever on Node.js dependencies (#85529)
* fix(install): time-box the Windows node-deps stage so a stalled npm or Playwright install can't hang setup forever

scripts/install.sh has bounded this same work with run_with_timeout
"$NODE_DEPS_TIMEOUT" (600s default) since #39219, but install.ps1 never got
the guard: Install-NodeDeps ran both `npm install` and `npx playwright
install chromium` unbounded. A stalled registry fetch or a wedged Chromium
archive extraction (#76222, #84614) froze the installer indefinitely -- one
user left it running 12+ hours overnight before asking for help.

Route both invocations through _Invoke-NativeWithTimeout: cmd.exe launches
the native command with its output merged to a log, the parent polls with a
wall-clock deadline and tails new log lines to the console each tick (the
live progress that makes a 3-minute download distinguishable from a hang),
and on timeout taskkill /T /F kills the real process tree and returns 124 --
the same convention as coreutils timeout and bash's run_with_timeout.
Wait-Job was rejected for this: jobs swallow live output and Stop-Job leaves
the npm child running. Windows PowerShell 5.1-safe throughout.

Timeouts surface as a warning with the log path, a note that re-running the
installer resumes (stages are idempotent), and the NODE_DEPS_TIMEOUT env
override for slow links -- mirroring bash.

Fixes #76222.
Closes #84614.
Supersedes #76303.

Co-authored-by: JonthanaHanh <JonthanaHanh@users.noreply.github.com>

* fix(installer): roll stage timers over to hours so an overnight stall doesn't read as "744 hours"

formatElapsed rendered a running stage as m:ss with unbounded minutes: a
node-deps stage left hanging overnight showed "744:38", which the user who
reported the hang understandably read as 744 hours. formatDuration
(completed stages) had the same unbounded-minutes shape.

Move both formatters into src/lib/format.ts (pure, no React) and add the
hour rollover: h:mm:ss live, "Xh Ym" completed. tests-js pins the shapes,
including 744m38s -> 12:24:38.

---------

Co-authored-by: JonthanaHanh <JonthanaHanh@users.noreply.github.com>
2026-08-13 13:38:20 -05:00

57 lines
1.9 KiB
TypeScript

/**
* Regression for the bootstrap installer's stage timers (issue report:
* "it says 744 hours or minutes — I don't know how to adjust the counter").
*
* ``formatElapsed`` rendered a running stage as ``m:ss`` with unbounded
* minutes: a node-deps stage left hanging overnight showed ``744:38`` (12h24m
* of minutes), which the user read as 744 hours. ``formatDuration`` (completed
* stages) had the same unbounded-minutes shape. These pin the hour rollover.
*/
import { describe, expect, it } from 'vitest'
import {
formatDuration,
formatElapsed,
} from '../apps/bootstrap-installer/src/lib/format'
const SEC = 1000
const MIN = 60 * SEC
const HOUR = 60 * MIN
describe('formatElapsed (live stage timer)', () => {
it('renders bare seconds under a minute', () => {
expect(formatElapsed(0)).toBe('0s')
expect(formatElapsed(59 * SEC)).toBe('59s')
})
it('renders m:ss between a minute and an hour', () => {
expect(formatElapsed(MIN)).toBe('1:00')
expect(formatElapsed(12 * MIN + 38 * SEC)).toBe('12:38')
expect(formatElapsed(59 * MIN + 59 * SEC)).toBe('59:59')
})
it('rolls over to h:mm:ss past an hour', () => {
expect(formatElapsed(HOUR)).toBe('1:00:00')
// The overnight-hang report: 744 minutes 38 seconds must NOT render as
// "744:38".
expect(formatElapsed(744 * MIN + 38 * SEC)).toBe('12:24:38')
})
it('clamps negative input (clock skew) to zero', () => {
expect(formatElapsed(-5 * SEC)).toBe('0s')
})
})
describe('formatDuration (completed stage)', () => {
it('keeps the sub-hour shapes', () => {
expect(formatDuration(999)).toBe('999ms')
expect(formatDuration(1500)).toBe('1.5s')
expect(formatDuration(2 * MIN + 5 * SEC)).toBe('2m 5s')
})
it('rolls over to hours past 60 minutes', () => {
expect(formatDuration(HOUR)).toBe('1h 0m')
expect(formatDuration(12 * HOUR + 24 * MIN)).toBe('12h 24m')
})
})