Skip to content

Add a parallel tox runner with per-environment progress bars#14690

Closed
laurac8r wants to merge 12 commits into
pytest-dev:mainfrom
laurac8r:enhance/tox-multi-branch
Closed

Add a parallel tox runner with per-environment progress bars#14690
laurac8r wants to merge 12 commits into
pytest-dev:mainfrom
laurac8r:enhance/tox-multi-branch

Conversation

@laurac8r

@laurac8r laurac8r commented Jul 7, 2026

Copy link
Copy Markdown

Adds scripts/tox_progress.py, a contributor helper that runs multiple tox
environments concurrently and renders a live per-environment progress bar
(collection count, pass/fail/skip tallies, elapsed time) in place of tox's
serial, interleaved output. Handy for local pre-push sweeps across
py310py314, linting, and friends.

This is contributor tooling under scripts/ — not part of the shipped pytest
package, and it changes no runtime behavior.

What it does

  • Runs each env as uvx tox run -e <env>, in parallel threads (all at once, or
    capped with -p N).
  • Parses pytest output live to drive a progress bar per env; non-pytest envs
    (linting, docs, …) render a spinner.
  • Gates pytest-xdist's -n auto on the xdist env-name factor, matching how
    tox.ini conditions the pytest-xdist dependency — plain pyXY envs run
    sequentially, *-xdist envs run parallel.
  • Ctrl-C terminates all running tox subprocesses and exits 130; otherwise
    exits 1 if any env failed, else 0. Failed envs get their last 30 output
    lines echoed after the final render.

Usage

$ uv run scripts/tox_progress.py -e py314
$ uv run scripts/tox_progress.py -e linting,py310,py311,py312,py313,py314
$ uv run scripts/tox_progress.py -e py312,py313 -p 2   # cap concurrency at 2

Testing

24 unit tests in scripts/test_tox_progress.py cover ANSI stripping, summary
parsing, the build_cmd factor-gating contract, the short-test-summary
double-count guard, and terminate_all teardown semantics.

$ uv run --with pytest pytest scripts/test_tox_progress.py -q
24 passed

pre-commit run --all-files passes clean.

Checklist

  • Documentation included (module docstring documents usage, the xdist
    factor rule, Ctrl-C behavior, and exit codes).
  • Tests included (scripts/test_tox_progress.py, 24 tests).
  • Added myself to AUTHORS.
  • Changelog newsfragment added (changelog/<PR#>.contrib.rst).
  • AI agents credited in Co-authored-by: commit trailers.
  • Allow maintainers to push and squash when merging.

laurac8r added 11 commits March 28, 2026 17:01
run_env created the Popen locally but never assigned it to state.proc,
leaving TestSubprocessCleanup.test_proc_stored_on_state_for_cleanup red.
Assign immediately after creation so callers can terminate a running env.

Co-authored-by: Claude Code - Fable 5, Opus 4.8, Sonnet 5
Plain pyXY envs have no pytest-xdist (tox.ini installs it only for the
xdist factor), so unconditionally passing '-n auto' failed them with
'unrecognized arguments: -n' (exit 4). build_cmd() now adds '-n auto'
only when 'xdist' is a dash-token of the env name — and still re-feeds
it to xdist envs, since explicit posargs replace the env's defaults.

Drop '-q': it suppresses the 'collected N items' line and the
'='-delimited summary, so status never left 'collecting' and every
count stayed 0. Without '-q', pytest's end-of-run 'short test summary
info' recap re-lists failures; a guard stops progress-char counting
once that section starts to avoid double-counting.

Co-authored-by: Claude Code - Fable 5, Opus 4.8, Sonnet 5
terminate_all() sends terminate() to each still-running tracked proc
(skipping finished and never-started ones, swallowing the poll/exit
race), and main() now catches KeyboardInterrupt to invoke it and
return 130. Cursor restore still runs via the existing finally.

Co-authored-by: Claude Code - Fable 5, Opus 4.8, Sonnet 5
Cover uv-run usage and the -p concurrency cap, the xdist factor rule
(-n auto only for *-xdist envs, re-supplied because posargs replace tox
defaults), why -q must never be added, non-pytest spinner envs, Ctrl-C
teardown (exit 130), and exit-code semantics.

Co-authored-by: Claude Code - Fable 5, Opus 4.8, Sonnet 5
Co-authored-by: Claude Code - Fable 5, Opus 4.8, Sonnet 5
Copilot AI review requested due to automatic review settings July 7, 2026 15:40
Co-authored-by: Claude Code - Fable 5, Opus 4.8, Sonnet 5
@psf-chronographer psf-chronographer Bot added the bot:chronographer:provided (automation) changelog entry is part of PR label Jul 7, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new contributor-facing helper under scripts/ to run multiple tox environments concurrently and render a live, per-environment progress display (bars for pytest envs, spinner for non-pytest envs), with unit tests and minor repo hygiene updates.

Changes:

  • Add scripts/tox_progress.py parallel tox runner with live progress rendering and Ctrl-C termination behavior.
  • Add scripts/test_tox_progress.py unit tests covering parsing/command construction/cleanup behavior.
  • Update repo metadata files (AUTHORS, .gitignore).

Reviewed changes

Copilot reviewed 4 out of 5 changed files in this pull request and generated 2 comments.

File Description
scripts/tox_progress.py New parallel tox runner with output parsing + progress UI.
scripts/test_tox_progress.py New unit tests for tox_progress helper.
AUTHORS Adds contributor name.
.gitignore Removes an ignore entry (unrelated to tox runner).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +5 to +15
from unittest.mock import MagicMock
from unittest.mock import patch

from tox_progress import build_cmd
from tox_progress import EnvState
from tox_progress import parse_summary_line
from tox_progress import run_env
from tox_progress import strip_ansi
from tox_progress import terminate_all

import pytest
Comment thread scripts/tox_progress.py
Comment on lines +220 to +226
for raw_line in proc.stdout:
line = raw_line.rstrip("\n")
state.output_lines.append(line)
state.elapsed = time.time() - state.start_time

# xdist: detect worker startup
clean_check = strip_ansi(line)
@The-Compiler

The-Compiler commented Jul 7, 2026

Copy link
Copy Markdown
Member

tox already has a parallel mode. The output improvements are nice, but I'm -1 on this for a variety of reasons.

All in all, I don't think some fancy progress bars are worth having some 500 lines of rather complex/hacky code (e.g. parsing output instead of integrating as a pytest plugin, etc.). Feel free to maintain this as a separate project of course (I suppose it maybe could be generalized to work outside of pytest's repo, or maybe even be a tox plugin or something?), but I don't think this should live in the pytest repo.

@nicoddemus

Copy link
Copy Markdown
Member

All in all, I don't think some fancy progress bars are worth having some 500 lines of rather complex/hacky code

Definitely agree.

Thanks anyway for the contribution.

@nicoddemus nicoddemus closed this Jul 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bot:chronographer:provided (automation) changelog entry is part of PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants