Skip to content

Runtime error tests - #214

Merged
rorygraves merged 1 commit into
mainfrom
task/runtime-error-tests
Jun 15, 2026
Merged

Runtime error tests#214
rorygraves merged 1 commit into
mainfrom
task/runtime-error-tests

Conversation

@rorygraves

Copy link
Copy Markdown
Contributor

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; throwing view) were already covered
by existing tests; the FCmd-failure path had an existing but convoluted test
(asserts via a renderer that throws StopRuntime). New tests:

  1. a failing FCmd Future surfaces as a TermFlowError without crashing the loop
    — clean, graceful version of the FCmd-failure path. A failing Future must
    publish TermFlowErrorCmd(Unexpected); a second FCmd tied to a Promise
    (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 is Unexpected("future boom", cause).

  2. a Sub raising an exception on its own thread does not kill the runtime
    — a registered Sub whose start() spawns a thread that throws. The thread
    publishes the exit-driving command in a finally (runs as the exception
    propagates), proving the bus/loop survived. A CountDownLatch confirms the
    throw actually happened; the runtime is asserted to shut down cleanly
    (backend closed).

  3. runtime shuts down cleanly with commands still pending in the bus
    — enqueues a burst, then Exit, then a second burst. Exit is consumed
    before 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.

  4. 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 tail
    piles up > 4096 NoCmds and overflows the per-frame drain cap into a later
    frame. Asserts every command is handled (no loss) and the thousands of
    commands coalesce into few rendered frames.

Key trade-offs / notes

  • Reused existing harness (TestTerminalBackend, TrackingTerminalBackend,
    NoopRenderer, CapturingRenderer, Console.withOut to mute summary output)
    rather than inventing scaffolding, per the brief.
  • All deterministic — no real sleeps. Synchronisation via 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

  • SAM lambdas for the Sub test's Runnable and UncaughtExceptionHandler:
    the Runnable body ends in throw (type Nothing), which the JVM lambda
    metafactory rejects (LambdaConversionException: Nothing not convertible to void). Switched both to explicit anonymous classes with Unit-returning
    methods. (See inline comment in the test.)
  • A pure NoCmd burst to exercise the cap: rejected — NoCmd has no update
    hook to count or terminate on, so it can't prove "no loss" or self-exit.
    Used GCmds (counted in update, each returning a NoCmd) instead.

Risk

  • Low. Test-only change. The burst test's single ~16ms frame throttle never
    triggers because Exit is processed during the drain (shouldExit short-circuits
    the wait), so wall-clock stays minimal.
  • MaxCoalescedCommandsPerFrame (4096) is private; the burst size (5000) is
    hard-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).

@codecov

codecov Bot commented Jun 15, 2026

Copy link
Copy Markdown

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
rorygraves force-pushed the task/runtime-error-tests branch from b0e4731 to 89da707 Compare June 15, 2026 08:45
@rorygraves
rorygraves merged commit 140f81f into main Jun 15, 2026
2 checks passed
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