From 49397cf2b4bb03fe573bf72c460d05014848363a Mon Sep 17 00:00:00 2001 From: teknium1 <127238744+teknium1@users.noreply.github.com> Date: Fri, 18 Sep 2026 03:32:47 -0700 Subject: [PATCH] fix(tests): parallel runner reports a known flag's missing value as usage, not per file The bare-flag check asked pytest's parser which tokens it does not know, but wrapped parse_known_args in the same blanket except that guards parser construction. A known flag with a missing value (`--tb` alone) raises pytest.UsageError there, which the except turned into "nothing unknown", so discovery ran and every per-file pytest died with "argument --tb: expected one argument". Keep the fallback around building the parser only; let parse_known_args run outside it and surface UsageError (and the unknown-token list) as this runner's own usage error before discovery. One invariant test. --- scripts/run_tests_parallel.py | 32 +++++++++++++++--------- tests/scripts/test_run_tests_parallel.py | 14 +++++++++++ 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/scripts/run_tests_parallel.py b/scripts/run_tests_parallel.py index 8438833132..9c461f8882 100755 --- a/scripts/run_tests_parallel.py +++ b/scripts/run_tests_parallel.py @@ -56,7 +56,7 @@ import threading import time from concurrent.futures import ThreadPoolExecutor, Future from pathlib import Path -from typing import Dict, List, Tuple +from typing import Dict, List, Optional, Tuple # Default test discovery roots. @@ -824,8 +824,8 @@ def _make_stdio_glyph_safe() -> None: pass -def _unknown_pytest_flags(tokens: List[str]) -> List[str]: - """Return the bare passthrough tokens pytest itself would reject. +def _pytest_flag_error(tokens: List[str]) -> Optional[str]: + """Return pytest's own complaint about the bare passthrough tokens, if any. A mistyped flag (``--jbs``) that is not one of OUR options used to be forwarded to every per-file pytest, so the run discovered the whole suite @@ -833,19 +833,27 @@ def _unknown_pytest_flags(tokens: List[str]) -> List[str]: learn about a typo. Ask pytest's own argparse parser (with the installed plugins loaded, so ``-n``/``--timeout`` count) which tokens it does not know; argparse handles the attached-value (``-rA``), combined-flag - (``-xvs``) and ``-k expr`` forms for us. If the parser cannot be built - the check is skipped and tokens are forwarded as before. + (``-xvs``) and ``-k expr`` forms for us. A known flag with a bad or + missing value (``--tb`` alone) makes that parser raise ``UsageError``; + it is reported the same way instead of once per discovered file. Only + if the parser cannot be built is the check skipped and tokens forwarded + as before. """ try: - from _pytest.config import get_config + from _pytest.config import UsageError, get_config config = get_config() config.pluginmanager.load_setuptools_entrypoints("pytest11") parser = config._parser.optparser - _, unknown = parser.parse_known_args(tokens) except Exception: - return [] - return [tok for tok in unknown if tok.startswith("-")] + return None + try: + _, unknown = parser.parse_known_args(tokens) + except UsageError as exc: + # "usage: ...\n: error: argument --tb: expected one argument" + return str(exc).rsplit("error: ", 1)[-1].strip() + unknown = [tok for tok in unknown if tok.startswith("-")] + return f"unrecognized arguments: {' '.join(unknown)}" if unknown else None def main() -> int: @@ -1008,9 +1016,9 @@ def main() -> int: # here with usage instead of once per discovered file. Anything after a # literal ``--`` is the caller's explicit choice and is forwarded as-is. if bare_passthrough: - unknown = _unknown_pytest_flags(bare_passthrough) - if unknown: - parser.error(f"unrecognized arguments: {' '.join(unknown)}") + flag_error = _pytest_flag_error(bare_passthrough) + if flag_error: + parser.error(flag_error) # ── Node-id selectors → file + ``-k`` filter ──────────────────────────── # This runner is FILE-granular: it spawns one ``pytest `` per test diff --git a/tests/scripts/test_run_tests_parallel.py b/tests/scripts/test_run_tests_parallel.py index 4f30d3ade8..14450ecfbe 100644 --- a/tests/scripts/test_run_tests_parallel.py +++ b/tests/scripts/test_run_tests_parallel.py @@ -312,6 +312,20 @@ def test_unknown_bare_flag_errors_with_usage_instead_of_sweeping(tmp_path: Path) assert "2✓" in proc.stdout or "2 passed" in proc.stdout, proc.stdout +def test_known_flag_missing_value_errors_with_usage_instead_of_sweeping( + tmp_path: Path, +) -> None: + """``--tb`` with no value is a pytest UsageError, not a per-file sweep. + + The flag itself is known, so an unknown-token check alone lets it through; + pytest's own parser must be allowed to reject it up front. + """ + probe_dir = _make_probe_dir(tmp_path) + + proc = _run_runner(probe_dir, "--tb") + assert proc.returncode == 2, proc.stdout + assert "usage:" in proc.stdout and "--tb: expected one argument" in proc.stdout + assert "Discovered" not in proc.stdout def test_bare_value_flag_keeps_its_value(tmp_path: Path) -> None: