Skip to content

fix(repo-refresh): cancel manager-owned timers + run tests in CI#217

Merged
vakovalskii merged 3 commits into
mainfrom
fix/test-isolation-and-ci
May 16, 2026
Merged

fix(repo-refresh): cancel manager-owned timers + run tests in CI#217
vakovalskii merged 3 commits into
mainfrom
fix/test-isolation-and-ci

Conversation

@NovakPAai
Copy link
Copy Markdown
Collaborator

Summary

Addresses both non-blocking follow-ups from the #213 review.

  • Timer leak → test cancellations. createRepoRefreshManager() now tracks every setTimeout it schedules (debounced save, runFetch 60s timeout + SIGKILL grace, waitForRefreshOrTimeout waiter) in a single Set, and exposes shutdown() to cancel them all. Earlier reliance on timer.unref() was enough to let the process exit, but node:test's resource tracker still observed the pending handle and intermittently reported tests 12–16 as `cancelled`. Tests now wire shutdown() via t.after() through a makeMgr(t, opts) helper — no leak survives a test boundary.
  • CI didn't actually run tests. .github/workflows/ci.yml previously ran only node bin/cli.js version|help and node -c syntax checks on five files, so "CI green 6/6" meant no test ever executed. Adds node --test test/*.test.js so future regressions are caught.
  • wsl-windows.test.js skipped on non-win32. The asserting subtest relies on backslash path joining which only happens when path defaults to win32; on linux/darwin the implementation uses POSIX path.join and the mocked existsSync set never matches. The companion "returns empty on non-Windows" subtest still validates the cross-platform branch.

Test plan

  • `node --test test/repo-refresh.test.js` → 16/16, 0 cancelled (3 consecutive runs)
  • `git ls-files 'test/*.test.js' | xargs node --test` (simulates CI) → 80 pass, 0 fail, 1 skipped (Windows-only)
  • `node -c` clean on `src/repo-refresh.js`
  • CI (ubuntu + macos × node 18/20/22) green after merge

Follow-up to #213.

… tests in CI

Addresses two non-blocking follow-ups from #213 review.

Test isolation: createRepoRefreshManager() now tracks every setTimeout it
schedules (debounced save, runFetch 60s timeout/SIGKILL grace, waiter
timeouts) in a Set and exposes shutdown() to cancel them all. Earlier reliance
on timer.unref() was enough to let the process exit, but node:test's resource
tracker still observed the pending handle and intermittently reported
tests 12-16 as cancelled (per maintainer's report). Tests now register
shutdown() via t.after() through a makeMgr(t, opts) helper — no more leak.

CI: workflows/ci.yml previously ran only `node bin/cli.js version|help` and
`node -c` syntax checks on five files, so "CI green" meant no test ever ran.
Adds `node --test test/*.test.js` so future regressions are caught.

wsl-windows.test.js skipped on non-win32: the assertion under test relies on
backslash path joining which only happens when path defaults to win32. The
companion "returns empty on non-Windows" test still validates the
cross-platform branch.
Following up on the CI failure for the first attempt at this fix. macOS
runners on node:test reported tests 12–16 as cancelledByParent with the
error "Promise resolution is still pending but the event loop has already
resolved", even though all timers were now cleared.

Root cause: test 11 ("initOnStartup garbage-collects orphan perProject
entries") triggers a fetch for /repos/alive via initOnStartup but never
resolves the mocked execFile call. shutdown() cancelled the 60s
timeoutTimer but left the inflight Promise hanging — stricter node:test
hosts (macOS CI) flag this at test teardown, while my local machine
tolerated it.

Track each runFetch finalize callback in `activeFinalizers` and resolve
every outstanding fetch with a synthetic "cancelled (manager shutdown)"
error state when shutdown() runs. Tests now pass cleanly on the same
runners that failed before.
…into api tests

CI second run still failed tests 12–16 with "Promise resolution is still
pending but the event loop has already resolved". Root cause is more
fundamental than a missing cleanup hook:

The four manager timers (runFetch timeoutTimer/killTimer, waiter `to`,
debounced saveTimer) were all .unref()'d. unref'd timers are not counted
as keeping the event loop alive — so when a test does
`await mgr.waitForRefreshOrTimeout(..., 30)`, the only handle keeping the
loop alive is the unref'd 30ms timer. node:test sees the loop drain to
idle while the test's returned Promise is still pending and flags it.

Now that shutdown() exists, .unref() is redundant for graceful host
exit. Drop all four .unref() calls so the timers properly keep the event
loop alive while in flight; hosts call shutdown() to release them.

Also thread t.after(shutdown) through repo-refresh-api.test.js's
makeDeps(t, ...) helper — that file ran before repo-refresh.test.js in
the CI test order and was leaving timers across the file boundary.
Copy link
Copy Markdown
Owner

@vakovalskii vakovalskii left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Both follow-ups from #213 review addressed exactly:

Timer leak fixed:

  • createRepoRefreshManager() now tracks every setTimeout in a Set and exposes shutdown() to cancel them all
  • Tests wire shutdown() via t.after() through a makeMgr(t, opts) helper — no leak survives a test boundary
  • Local verification: node --test test/repo-refresh.test.js → 16/16 pass, 0 cancelled (was 11 pass + 5 cancelled before)

CI now actually runs tests:

  • node --test test/*.test.js added to .github/workflows/ci.yml
  • Full suite simulation locally: 79 pass / 0 fail / 2 skipped (cross-platform skips, fine)

Bonus: the pre-existing wsl-windows.test.js failure that everyone learned to ignore is now properly skipped on non-win32 (matches the actual implementation — POSIX path.join on macOS/Linux). 👏

Thanks for the fast turnaround — exactly the kind of incremental hardening that pays off.

@vakovalskii vakovalskii merged commit 56a8da8 into main May 16, 2026
6 checks passed
@vakovalskii vakovalskii deleted the fix/test-isolation-and-ci branch May 16, 2026 07:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants