Runtime error tests - #214
Merged
Merged
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Add dedicated tests for runtime error paths flagged in #141: - failing FCmd Future surfaces as TermFlowError (graceful, no crash) - a Sub throwing on its own thread does not kill the runtime - shutdown tears down cleanly with commands still pending in the bus - a command burst exceeding the per-frame coalescing cap (4096) is fully processed without loss Reuses the existing TuiRuntimeSpec harness; all deterministic, no real sleeps. Closes #141.
rorygraves
force-pushed
the
task/runtime-error-tests
branch
from
June 15, 2026 08:45
b0e4731 to
89da707
Compare
2 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Decisions — Test coverage for TuiRuntime error paths
What changed
Added four tests to
modules/termflow-app/src/test/scala/termflow/tui/TuiRuntimeSpec.scala,one per under-covered runtime error path. Two of the brief's five paths
(unhandled exception in
update→ overlay; throwingview) were already coveredby existing tests; the FCmd-failure path had an existing but convoluted test
(asserts via a renderer that throws
StopRuntime). New tests:a failing FCmd Future surfaces as a TermFlowError without crashing the loop— clean, graceful version of the FCmd-failure path. A failing
Futuremustpublish
TermFlowErrorCmd(Unexpected); a secondFCmdtied to aPromise(completed by the capturing renderer when the banner renders) drives Exit, so
the runtime exits normally instead of being torn down by a thrown
StopRuntime. Asserts the surfaced error isUnexpected("future boom", cause).a Sub raising an exception on its own thread does not kill the runtime— a registered
Subwhosestart()spawns a thread that throws. The threadpublishes the exit-driving command in a
finally(runs as the exceptionpropagates), proving the bus/loop survived. A
CountDownLatchconfirms thethrow actually happened; the runtime is asserted to shut down cleanly
(backend closed).
runtime shuts down cleanly with commands still pending in the bus— enqueues a burst, then
Exit, then a second burst.Exitis consumedbefore the trailing commands, so the runtime tears down (cancels subs,
restores terminal, closes backend) with work still queued. Asserts only the
pre-Exit burst ran (
updates == FirstBurst), subs cancelled, terminal restored.a command burst exceeding the per-frame coalescing cap is fully processed without loss— bursts 5000 commands (>MaxCoalescedCommandsPerFrame=4096). Each handled command returns a render-triggering
NoCmd, so the tailpiles up > 4096
NoCmds and overflows the per-frame drain cap into a laterframe. Asserts every command is handled (no loss) and the thousands of
commands coalesce into few rendered frames.
Key trade-offs / notes
TestTerminalBackend,TrackingTerminalBackend,NoopRenderer,CapturingRenderer,Console.withOutto mute summary output)rather than inventing scaffolding, per the brief.
CountDownLatch(with a 2s safety timeout on
await, not a fixed sleep) and queue ordering.The burst test relies on FIFO queue semantics and the documented 4096 cap.
Tried and rejected
RunnableandUncaughtExceptionHandler:the
Runnablebody ends inthrow(typeNothing), which the JVM lambdametafactory rejects (
LambdaConversionException: Nothing not convertible to void). Switched both to explicit anonymous classes withUnit-returningmethods. (See inline comment in the test.)
NoCmdburst to exercise the cap: rejected —NoCmdhas noupdatehook to count or terminate on, so it can't prove "no loss" or self-exit.
Used
GCmds (counted inupdate, each returning aNoCmd) instead.Risk
triggers because
Exitis processed during the drain (shouldExit short-circuitsthe wait), so wall-clock stays minimal.
MaxCoalescedCommandsPerFrame(4096) is private; the burst size (5000) ishard-coded with a comment. If that constant is lowered below ~5000 the test
still holds; if raised above 5000 the cap-overflow branch would no longer be
exercised (test would still pass but lose its intent).
Gate
sbt --batch ciCheck— green (311 tests, scalafmt clean).