Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: tox-dev/tox
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 4.24.0
Choose a base ref
...
head repository: tox-dev/tox
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 4.24.1
Choose a head ref
  • 2 commits
  • 6 files changed
  • 2 contributors

Commits on Jan 21, 2025

  1. Adds ability to configure stderr output color (#3426)

    ssbarnea authored Jan 21, 2025

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    ee660b9 View commit details
  2. release 4.24.1

    gaborbernat committed Jan 21, 2025
    Copy the full SHA
    d4276dc View commit details
7 changes: 7 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
@@ -4,6 +4,13 @@ Release History

.. towncrier release notes start
v4.24.1 (2025-01-21)
--------------------

Misc - 4.24.1
~~~~~~~~~~~~~
- :issue:`3426`

v4.24.0 (2025-01-21)
--------------------

8 changes: 8 additions & 0 deletions src/tox/config/cli/parser.py
Original file line number Diff line number Diff line change
@@ -11,6 +11,8 @@
from pathlib import Path
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Literal, Optional, Sequence, Tuple, Type, TypeVar, cast

from colorama import Fore

from tox.config.loader.str_convert import StrConvert
from tox.plugin import NAME
from tox.util.ci import is_ci
@@ -366,6 +368,12 @@ def add_color_flags(parser: ArgumentParser) -> None:
choices=["yes", "no"],
help="should output be enriched with colors, default is yes unless TERM=dumb or NO_COLOR is defined.",
)
parser.add_argument(
"--stderr-color",
default="RED",
choices=[*Fore.__dict__.keys()],
help="color for stderr output, use RESET for terminal defaults.",
)


def add_exit_and_dump_after(parser: ArgumentParser) -> None:
19 changes: 10 additions & 9 deletions src/tox/execute/api.py
Original file line number Diff line number Diff line change
@@ -122,18 +122,19 @@ def call(
env: ToxEnv,
) -> Iterator[ExecuteStatus]:
start = time.monotonic()
stderr_color = None
if self._colored:
try:
cfg_color = env.conf._conf.options.stderr_color # noqa: SLF001
stderr_color = getattr(Fore, cfg_color)
except (AttributeError, KeyError, TypeError): # many tests have a mocked 'env'
stderr_color = Fore.RED
try:
# collector is what forwards the content from the file streams to the standard streams
out, err = out_err[0].buffer, out_err[1].buffer
out_sync = SyncWrite(
out.name,
out if show else None, # type: ignore[arg-type]
)
err_sync = SyncWrite(
err.name,
err if show else None, # type: ignore[arg-type]
Fore.RED if self._colored else None,
)
out_sync = SyncWrite(out.name, out if show else None) # type: ignore[arg-type]
err_sync = SyncWrite(err.name, err if show else None, stderr_color) # type: ignore[arg-type]

with out_sync, err_sync:
instance = self.build_instance(request, self._option_class(env), out_sync, err_sync)
with instance as status:
2 changes: 2 additions & 0 deletions tests/config/cli/test_cli_env_var.py
Original file line number Diff line number Diff line change
@@ -31,6 +31,7 @@ def test_verbose_no_test() -> None:
"verbose": 4,
"quiet": 0,
"colored": "no",
"stderr_color": "RED",
"work_dir": None,
"root_dir": None,
"config_file": None,
@@ -90,6 +91,7 @@ def test_env_var_exhaustive_parallel_values(
assert vars(options.parsed) == {
"always_copy": False,
"colored": "no",
"stderr_color": "RED",
"command": "legacy",
"default_runner": "virtualenv",
"develop": False,
2 changes: 2 additions & 0 deletions tests/config/cli/test_cli_ini.py
Original file line number Diff line number Diff line change
@@ -29,6 +29,7 @@
def default_options() -> dict[str, Any]:
return {
"colored": "no",
"stderr_color": "RED",
"command": "r",
"default_runner": "virtualenv",
"develop": False,
@@ -200,6 +201,7 @@ def test_ini_exhaustive_parallel_values(core_handlers: dict[str, Callable[[State
options = get_options("p")
assert vars(options.parsed) == {
"colored": "yes",
"stderr_color": "RED",
"command": "p",
"default_runner": "virtualenv",
"develop": False,
14 changes: 12 additions & 2 deletions tests/execute/local_subprocess/test_local_subprocess.py
Original file line number Diff line number Diff line change
@@ -48,20 +48,30 @@ def read_out_err(self) -> tuple[str, str]:
@pytest.mark.parametrize("color", [True, False], ids=["color", "no_color"])
@pytest.mark.parametrize(("out", "err"), [("out", "err"), ("", "")], ids=["simple", "nothing"])
@pytest.mark.parametrize("show", [True, False], ids=["show", "no_show"])
@pytest.mark.parametrize(
"stderr_color",
["RED", "YELLOW", "RESET"],
ids=["stderr_color_default", "stderr_color_yellow", "stderr_color_reset"],
)
def test_local_execute_basic_pass( # noqa: PLR0913
caplog: LogCaptureFixture,
os_env: dict[str, str],
out: str,
err: str,
show: bool,
color: bool,
stderr_color: str,
) -> None:
caplog.set_level(logging.NOTSET)
executor = LocalSubProcessExecutor(colored=color)

tox_env = MagicMock()
tox_env.conf._conf.options.stderr_color = stderr_color # noqa: SLF001
code = f"import sys; print({out!r}, end=''); print({err!r}, end='', file=sys.stderr)"
request = ExecuteRequest(cmd=[sys.executable, "-c", code], cwd=Path(), env=os_env, stdin=StdinSource.OFF, run_id="")
out_err = FakeOutErr()
with executor.call(request, show=show, out_err=out_err.out_err, env=MagicMock()) as status:

with executor.call(request, show=show, out_err=out_err.out_err, env=tox_env) as status:
while status.exit_code is None: # pragma: no branch
status.wait()
assert status.out == out.encode()
@@ -77,7 +87,7 @@ def test_local_execute_basic_pass( # noqa: PLR0913
out_got, err_got = out_err.read_out_err()
if show:
assert out_got == out
expected = (f"{Fore.RED}{err}{Fore.RESET}" if color else err) if err else ""
expected = f"{getattr(Fore, stderr_color)}{err}{Fore.RESET}" if color and err else err
assert err_got == expected
else:
assert not out_got