Skip to content

Make three timing-dependent engine tests deterministic - #814

Merged
Sev7eNup merged 1 commit into
mainfrom
test/deterministic-timing-tests
Nov 6, 2026
Merged

Sev7eNup merged 1 commit into
mainfrom
test/deterministic-timing-tests

Conversation

@Sev7eNup

@Sev7eNup Sev7eNup commented Nov 6, 2026

Copy link
Copy Markdown
Owner

Three backend tests failed on busy CI runners in the last day, each on a wall-clock assumption.

  • ExecuteAsync_WaitAnyJunction_FiresAfterFirstBranchCompletes (measured 1604 ms against a 1500 ms bound): the slow branch now waits forever, so the run can only finish if the waitAny junction fires on the fast branch and stands the slow one down. The test asserts exactly that, with branchSlow ending Cancelled. A 30 s WaitAsync turns a regression into a failure instead of a hang.
  • ExecuteAsync_CancelledStepWriteFailsWithNonOutageError_DoesNotFailTheWinningRun (the loser finished inside its 3 s window, so no Cancelled write happened): same approach, the loser can only end through the stand-down.
  • FileWatcherSnapshotDeadlineTests (StartAsync reported the temp directory as unreachable within 1 s): the cause is in the product code. RunBoundedAsync started the blocking filesystem call with Task.Run, so the hard deadline also counted the wait for a pool thread. On a busy host a healthy directory could look unreachable, and that applies to production as well. The call now runs on a dedicated thread (TaskCreationOptions.LongRunning), which also keeps a hung SMB call off the pool. It runs once per registration, health probe or reconcile, so the extra thread is cheap. RunBoundedAsync_RunsWorkOffTheThreadPool pins this, and the CHANGELOG has a line under Fixed.

Tests run locally (Engine.Tests):

  • WorkflowEngineTests, WorkflowEngineUnpersistedFailureTests, FileWatcherTriggerSourceTests and FileWatcherSnapshotDeadlineTests (122 tests)
  • the NodePilot.Engine.Tests.Triggers namespace and TriggerOrchestrator (235 tests)
  • the four targeted tests, 5 runs each

The rest is covered by CI.

🤖 Generated with Claude Code

- waitAny: the slow branch never finishes on its own, so the run can only
  complete if the junction fires on the fast branch and stands the slow one
  down. That replaces the wall-clock bound.
- Cancelled write: the losing branch always loses for the same reason, so it
  always attempts its Cancelled write.
- FileWatcher: RunBoundedAsync started the blocking filesystem call with
  Task.Run, so the hard deadline also counted the wait for a pool thread. On
  a busy host that made a healthy directory look unreachable, in tests and in
  production. The call now runs on a dedicated thread, which also keeps a
  hung SMB call off the pool.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Sev7eNup
Sev7eNup merged commit 8de0baa into main Nov 6, 2026
17 checks passed
@Sev7eNup
Sev7eNup deleted the test/deterministic-timing-tests branch November 6, 2026 07:23
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.

1 participant