Skip to content

Add --show-progress and serialize worker output to fix interleaving#773

Open
MatthewMckee4 wants to merge 5 commits into
mainfrom
worktree-fluttering-growing-rose
Open

Add --show-progress and serialize worker output to fix interleaving#773
MatthewMckee4 wants to merge 5 commits into
mainfrom
worktree-fluttering-growing-rose

Conversation

@MatthewMckee4
Copy link
Copy Markdown
Owner

@MatthewMckee4 MatthewMckee4 commented May 6, 2026

Summary

Adds a live progress display while tests run, configurable via --show-progress (none | counter | bar) or [terminal] show-progress in karva.toml. The display is rendered on stderr by indicatif so it does not interfere with per-test result lines on stdout.

karva test --show-progress=bar
karva test --show-progress=counter

Closes #570.

In the course of wiring this up, the per-test result lines themselves had a longstanding bug: in parallel runs, multiple worker processes lock their own stdouts independently, which does not actually serialize their writes. Lines from different workers could splice mid-line, producing output like ... oktest tests.test_currency.... Closes #502.

This PR fixes the interleave at the source. Workers no longer write reporter lines directly to the inherited stdout — instead, each worker writes newline-delimited preformatted lines to a per-worker output file in its cache directory (via a new FileLineSink), and the orchestrator runs a background thread that drains those files, splits on \n, and prints whole lines to its own stdout. Single-writer per file plus newline-bounded reads means partial-line splices are impossible.

The same drain thread also drives the optional progress bar from the existing per-worker progress files. The bar is sized by test_count() (function definitions) and ticked once per test function — variants from @parametrize no longer each tick, so the bar reaches 100% exactly when the run finishes regardless of how many cases each function expanded into.

The bar style now matches ty's: {msg:8.dim} {bar:60.green/dim} {pos}/{len} tests, progress_chars("--"). Output writes use bar.suspend(|| writeln!(stdout, …)) so the bar's stderr redraws don't clash with stdout writes.

Test Plan

ci

@MatthewMckee4 MatthewMckee4 added diagnostics Related to diagnostics and output of Karva cli Related to the command-line interface configuration Related to configuring Karva labels May 6, 2026
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented May 6, 2026

Merging this PR will degrade performance by 12.3%

⚠️ Unknown Walltime execution environment detected

Using the Walltime instrument on standard Hosted Runners will lead to inconsistent data.

For the most accurate results, we recommend using CodSpeed Macro Runners: bare-metal machines fine-tuned for performance measurement consistency.

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

❌ 1 regressed benchmark

⚠️ Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Mode Benchmark BASE HEAD Efficiency
WallTime karva_benchmark 50.2 s 57.2 s -12.3%

Comparing worktree-fluttering-growing-rose (6fe0775) with main (8d4f87c)

Open in CodSpeed

@MatthewMckee4 MatthewMckee4 changed the title Add --show-progress for live progress display Add --show-progress and serialize worker output to fix interleaving May 6, 2026
Workers were spawned with Stdio::inherit(), so any byte they wrote
directly to FD 1 — Python print() in a user test, panic messages, etc.
— landed on the orchestrator's terminal without going through the
file-based output drain. When that hit FD 1 in the middle of an
orchestrator writeln!, the worker's PASS line came out spliced through
foreign bytes (issue #502 in another guise).

Spawn workers with Stdio::piped() for stdout, take the read end at
spawn time, and hand it to a per-worker reader thread inside
OutputDrain. The reader pushes whole lines through an mpsc channel
that drain_loop polls alongside the per-worker output files; both
sources emit through the same bar.suspend() path so nothing tears
against the bar either. Stderr stays inherited because the orchestrator
also writes tracing to stderr in real time and the drain's poll
interval would shuffle the relative order of those lines.

Two follow-on fixes were needed for ordering:

The drain now reads the pipe channel before polling the result files
in each iteration. A print() runs before the test returns and the
reporter writes its PASS line, so within one iteration the pipe line
should land in the emit batch ahead of the file line.

attach_with_output now reconfigures sys.stdout / sys.stderr to
line-buffer when show_output is true. Python defaults to block
buffering when its stdout isn't a TTY; with the pipe in place that
delayed every print() until the interpreter shut down, which is after
the reporter had already emitted PASS.
Worker stdout is now a pipe, so the worker's TTY-based auto-detection
always disables ANSI — colors got dropped on the way through the drain
even when the orchestrator's stdout was a terminal. Forward an explicit
--color=always or --color=never to every worker based on the
orchestrator's own should_colorize() check (or the user's --color
override if one was given). The drain pipes bytes through verbatim, so
the orchestrator-side TTY decision is the one that should drive
colorization.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cli Related to the command-line interface configuration Related to configuring Karva diagnostics Related to diagnostics and output of Karva

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Progress bar display mode Test logs on the same line

1 participant