Skip to content

Fix handling empty values of NO_COLOR and FORCE_COLOR #11712

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Dec 23, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
@@ -266,6 +266,7 @@ Michael Goerz
Michael Krebs
Michael Seifert
Michal Wajszczuk
Michał Górny
Michał Zięba
Mickey Pashov
Mihai Capotă
1 change: 1 addition & 0 deletions changelog/11712.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed handling ``NO_COLOR`` and ``FORCE_COLOR`` to ignore an empty value.
4 changes: 2 additions & 2 deletions doc/en/reference/reference.rst
Original file line number Diff line number Diff line change
@@ -1146,13 +1146,13 @@ When set to ``0``, pytest will not use color.

.. envvar:: NO_COLOR

When set (regardless of value), pytest will not use color in terminal output.
When set to a non-empty string (regardless of value), pytest will not use color in terminal output.
``PY_COLORS`` takes precedence over ``NO_COLOR``, which takes precedence over ``FORCE_COLOR``.
See `no-color.org <https://no-color.org/>`__ for other libraries supporting this community standard.

.. envvar:: FORCE_COLOR

When set (regardless of value), pytest will use color in terminal output.
When set to a non-empty string (regardless of value), pytest will use color in terminal output.
``PY_COLORS`` and ``NO_COLOR`` take precedence over ``FORCE_COLOR``.

Exceptions
4 changes: 2 additions & 2 deletions src/_pytest/_io/terminalwriter.py
Original file line number Diff line number Diff line change
@@ -29,9 +29,9 @@ def should_do_markup(file: TextIO) -> bool:
return True
if os.environ.get("PY_COLORS") == "0":
return False
if "NO_COLOR" in os.environ:
if os.environ.get("NO_COLOR"):
return False
if "FORCE_COLOR" in os.environ:
if os.environ.get("FORCE_COLOR"):
return True
return (
hasattr(file, "isatty") and file.isatty() and os.environ.get("TERM") != "dumb"
63 changes: 39 additions & 24 deletions testing/io/test_terminalwriter.py
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@
import sys
from pathlib import Path
from typing import Generator
from typing import Optional
from unittest import mock

import pytest
@@ -164,53 +165,67 @@ def test_attr_hasmarkup() -> None:
assert "\x1b[0m" in s


def assert_color_set():
def assert_color(expected: bool, default: Optional[bool] = None) -> None:
file = io.StringIO()
tw = terminalwriter.TerminalWriter(file)
assert tw.hasmarkup
if default is None:
default = not expected
file.isatty = lambda: default # type: ignore
tw = terminalwriter.TerminalWriter(file=file)
assert tw.hasmarkup is expected
tw.line("hello", bold=True)
s = file.getvalue()
assert len(s) > len("hello\n")
assert "\x1b[1m" in s
assert "\x1b[0m" in s


def assert_color_not_set():
f = io.StringIO()
f.isatty = lambda: True # type: ignore
tw = terminalwriter.TerminalWriter(file=f)
assert not tw.hasmarkup
tw.line("hello", bold=True)
s = f.getvalue()
assert s == "hello\n"
if expected:
assert len(s) > len("hello\n")
assert "\x1b[1m" in s
assert "\x1b[0m" in s
else:
assert s == "hello\n"


def test_should_do_markup_PY_COLORS_eq_1(monkeypatch: MonkeyPatch) -> None:
monkeypatch.setitem(os.environ, "PY_COLORS", "1")
assert_color_set()
assert_color(True)


def test_should_not_do_markup_PY_COLORS_eq_0(monkeypatch: MonkeyPatch) -> None:
monkeypatch.setitem(os.environ, "PY_COLORS", "0")
assert_color_not_set()
assert_color(False)


def test_should_not_do_markup_NO_COLOR(monkeypatch: MonkeyPatch) -> None:
monkeypatch.setitem(os.environ, "NO_COLOR", "1")
assert_color_not_set()
assert_color(False)


def test_should_do_markup_FORCE_COLOR(monkeypatch: MonkeyPatch) -> None:
monkeypatch.setitem(os.environ, "FORCE_COLOR", "1")
assert_color_set()
assert_color(True)


def test_should_not_do_markup_NO_COLOR_and_FORCE_COLOR(
@pytest.mark.parametrize(
["NO_COLOR", "FORCE_COLOR", "expected"],
[
("1", "1", False),
("", "1", True),
("1", "", False),
],
)
def test_NO_COLOR_and_FORCE_COLOR(
monkeypatch: MonkeyPatch,
NO_COLOR: str,
FORCE_COLOR: str,
expected: bool,
) -> None:
monkeypatch.setitem(os.environ, "NO_COLOR", "1")
monkeypatch.setitem(os.environ, "FORCE_COLOR", "1")
assert_color_not_set()
monkeypatch.setitem(os.environ, "NO_COLOR", NO_COLOR)
monkeypatch.setitem(os.environ, "FORCE_COLOR", FORCE_COLOR)
assert_color(expected)


def test_empty_NO_COLOR_and_FORCE_COLOR_ignored(monkeypatch: MonkeyPatch) -> None:
monkeypatch.setitem(os.environ, "NO_COLOR", "")
monkeypatch.setitem(os.environ, "FORCE_COLOR", "")
assert_color(True, True)
assert_color(False, False)


class TestTerminalWriterLineWidth: