uttrflow / uttrflow/uttrflow-swift
Test polling helpers give up after 30 s of wall-clock time and fail CI when the runner stalls, while five other loops have no limit and hang instead
- Dominant language
- Swift
- Stars
- 4
- Forks
- 17
- Avg merge
- 3h 32m
- Merged PRs (30d)
- 277
Description
## What happens
Tests that wait for work running beside them each carry a private copy of a polling loop, and the copies disagree about how to stop.
**A 30-second wall-clock ceiling**, then a recorded failure:
- `Tests/UttrflowPipelineTests/DictationPipelineTurnTests.swift:144` (`eventually`, failure at line 150)
- `Tests/UttrflowPipelineTests/DictationControllerTests.swift:678`
- `Tests/UttrflowAudioTests/AVAudioCaptureEngineTests.swift:191` (`settle`)
- `Tests/UttrflowAudioTests/InputDeviceSessionTests.swift:193` (`untilSettled`)
- `Tests/UttrflowTests/DiagnosticsEngineProbeTests.swift:13` (`settled`)
On a loaded runner the whole test process can stall for about 30 seconds. When it does, the ceiling expires although nothing is wrong. `DictationPipelineTurnTests.swift:150` failed four to seven tests at once in CI runs 34815318669 and 34796754962 on `main` and 34792317387 on a branch, all on 2026-09-14; in the same runs dozens of unrelated one-millisecond tests reported "passed after ~30 seconds".
**No limit at all**, in suites that have no `.timeLimit` either, so a regression hangs the run rather than failing it (the #395 class):
- `Tests/UttrflowPipelineTests/DictationPipelineSettingsTests.swift:69`, `:221`, `:224`
- `Tests/UttrflowPredictTests/IdleReleaseInterleavingTests.swift:95` (`until`)
- `Tests/UttrflowLocalModelTests/ReloadableWeightsTests.swift:94`
`Tests/UttrflowSpeechTests/BackedSpeechEngineTurnTests.swift:76` already does the right thing: it yields without a clock and relies on the suite's `.timeLimit(.minutes(1))` to end a wait that never finishes.
## Why it matters
A contributor whose pull request touches nothing near dictation can get a red CI run from these, and the ones without a limit leave a local `swift test` sitting silently until killed. Seven copies of the same loop also mean each new test writes an eighth.
## Acceptance criteria
- One helper in `Sources/UttrflowTestSupport/` (for example `Eventually.swift`, `public func eventually(_ condition: () async -> Bool) async`), which polls by yielding or a short sleep and has **no** wall-clock ceiling of its own.
- Every file listed above uses it instead of its private loop.
- Every suite that uses it has `.timeLimit(.minutes(1))` (or longer where a test genuinely needs it), so a condition that never holds fails the test instead of hanging the run.
- No test's pass or fail depends on how many seconds have elapsed.
- `make verify` passes.
## Where to start
- `Sources/UttrflowTestSupport/ManualClock.swift` shows the style of the shared test helpers.
- `Tests/UttrflowSpeechTests/BackedSpeechEngineTurnTests.swift:71-83` is the pattern to copy.
- To check a changed wait still fails when it should, temporarily break the condition (e.g. `eventually { false }`) and confirm the time limit reports it.
- Read [CONTRIBUTING.md](https://github.com/uttrflow/uttrflow-swift/blob/main/CONTRIBUTING.md) first, and say on this issue that you are taking it.
- Before pushing, run `make verify` (with `DEVELOPER_DIR` pointing at Xcode 26.6 or later). It is the same command CI runs.
**Size:** S to M, about 2–3 hours.
Contributor guide
Research direction
Read CONTRIBUTING.md, Sources/UttrflowTestSupport/ManualClock.swift, and Tests/UttrflowSpeechTests/BackedSpeechEngineTurnTests.swift:71-83 first. Replace the private polling loops in the listed test files with one shared helper, add the required suite time limits, and run make verify; temporarily using eventually { false } should confirm that a stalled condition fails through the suite limit.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- swift
- Domain
- testing
- Issue type
- Refactor
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100