Skip to content

Commit 52db918

Browse files
authored
Fix handling empty values of NO_COLOR and FORCE_COLOR (#11712)
* Fix handling empty values of NO_COLOR and FORCE_COLOR Fix handling NO_COLOR and FORCE_COLOR environment variables to correctly be ignored when they are set to an empty value, as defined in the specification: > Command-line software which adds ANSI color to its output by default > should check for a NO_COLOR environment variable that, when present > *and not an empty string* (regardless of its value), prevents > the addition of ANSI color. (emphasis mine, https://no-color.org/) The same is true of FORCE_COLOR, https://force-color.org/. * Streamline testing for FORCE_COLOR and NO_COLOR Streamline the tests for FORCE_COLOR and NO_COLOR variables, and cover all possible cases (unset, set to empty, set to "1"). Combine the two assert functions into one taking boolean parameters. Mock file.isatty in all circumstances to ensure that the environment variables take precedence over the fallback value resulting from isatty check (or that the fallback is actually used, in the case of both FORCE_COLOR and NO_COLOR being unset).
1 parent 54a0ee0 commit 52db918

File tree

5 files changed

+45
-28
lines changed

5 files changed

+45
-28
lines changed

AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,7 @@ Michael Goerz
266266
Michael Krebs
267267
Michael Seifert
268268
Michal Wajszczuk
269+
Michał Górny
269270
Michał Zięba
270271
Mickey Pashov
271272
Mihai Capotă

changelog/11712.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fixed handling ``NO_COLOR`` and ``FORCE_COLOR`` to ignore an empty value.

doc/en/reference/reference.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1146,13 +1146,13 @@ When set to ``0``, pytest will not use color.
11461146

11471147
.. envvar:: NO_COLOR
11481148

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

11531153
.. envvar:: FORCE_COLOR
11541154

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

11581158
Exceptions

src/_pytest/_io/terminalwriter.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@ def should_do_markup(file: TextIO) -> bool:
2929
return True
3030
if os.environ.get("PY_COLORS") == "0":
3131
return False
32-
if "NO_COLOR" in os.environ:
32+
if os.environ.get("NO_COLOR"):
3333
return False
34-
if "FORCE_COLOR" in os.environ:
34+
if os.environ.get("FORCE_COLOR"):
3535
return True
3636
return (
3737
hasattr(file, "isatty") and file.isatty() and os.environ.get("TERM") != "dumb"

testing/io/test_terminalwriter.py

Lines changed: 39 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import sys
66
from pathlib import Path
77
from typing import Generator
8+
from typing import Optional
89
from unittest import mock
910

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

166167

167-
def assert_color_set():
168+
def assert_color(expected: bool, default: Optional[bool] = None) -> None:
168169
file = io.StringIO()
169-
tw = terminalwriter.TerminalWriter(file)
170-
assert tw.hasmarkup
170+
if default is None:
171+
default = not expected
172+
file.isatty = lambda: default # type: ignore
173+
tw = terminalwriter.TerminalWriter(file=file)
174+
assert tw.hasmarkup is expected
171175
tw.line("hello", bold=True)
172176
s = file.getvalue()
173-
assert len(s) > len("hello\n")
174-
assert "\x1b[1m" in s
175-
assert "\x1b[0m" in s
176-
177-
178-
def assert_color_not_set():
179-
f = io.StringIO()
180-
f.isatty = lambda: True # type: ignore
181-
tw = terminalwriter.TerminalWriter(file=f)
182-
assert not tw.hasmarkup
183-
tw.line("hello", bold=True)
184-
s = f.getvalue()
185-
assert s == "hello\n"
177+
if expected:
178+
assert len(s) > len("hello\n")
179+
assert "\x1b[1m" in s
180+
assert "\x1b[0m" in s
181+
else:
182+
assert s == "hello\n"
186183

187184

188185
def test_should_do_markup_PY_COLORS_eq_1(monkeypatch: MonkeyPatch) -> None:
189186
monkeypatch.setitem(os.environ, "PY_COLORS", "1")
190-
assert_color_set()
187+
assert_color(True)
191188

192189

193190
def test_should_not_do_markup_PY_COLORS_eq_0(monkeypatch: MonkeyPatch) -> None:
194191
monkeypatch.setitem(os.environ, "PY_COLORS", "0")
195-
assert_color_not_set()
192+
assert_color(False)
196193

197194

198195
def test_should_not_do_markup_NO_COLOR(monkeypatch: MonkeyPatch) -> None:
199196
monkeypatch.setitem(os.environ, "NO_COLOR", "1")
200-
assert_color_not_set()
197+
assert_color(False)
201198

202199

203200
def test_should_do_markup_FORCE_COLOR(monkeypatch: MonkeyPatch) -> None:
204201
monkeypatch.setitem(os.environ, "FORCE_COLOR", "1")
205-
assert_color_set()
202+
assert_color(True)
206203

207204

208-
def test_should_not_do_markup_NO_COLOR_and_FORCE_COLOR(
205+
@pytest.mark.parametrize(
206+
["NO_COLOR", "FORCE_COLOR", "expected"],
207+
[
208+
("1", "1", False),
209+
("", "1", True),
210+
("1", "", False),
211+
],
212+
)
213+
def test_NO_COLOR_and_FORCE_COLOR(
209214
monkeypatch: MonkeyPatch,
215+
NO_COLOR: str,
216+
FORCE_COLOR: str,
217+
expected: bool,
210218
) -> None:
211-
monkeypatch.setitem(os.environ, "NO_COLOR", "1")
212-
monkeypatch.setitem(os.environ, "FORCE_COLOR", "1")
213-
assert_color_not_set()
219+
monkeypatch.setitem(os.environ, "NO_COLOR", NO_COLOR)
220+
monkeypatch.setitem(os.environ, "FORCE_COLOR", FORCE_COLOR)
221+
assert_color(expected)
222+
223+
224+
def test_empty_NO_COLOR_and_FORCE_COLOR_ignored(monkeypatch: MonkeyPatch) -> None:
225+
monkeypatch.setitem(os.environ, "NO_COLOR", "")
226+
monkeypatch.setitem(os.environ, "FORCE_COLOR", "")
227+
assert_color(True, True)
228+
assert_color(False, False)
214229

215230

216231
class TestTerminalWriterLineWidth:

0 commit comments

Comments
 (0)