Skip to content

Commit 9b1085c

Browse files
fix: support TerminalReporter.isatty being called
Fixes #13461.
1 parent b6e8144 commit 9b1085c

File tree

4 files changed

+31
-4
lines changed

4 files changed

+31
-4
lines changed

changelog/13461.bugfix.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Corrected ``_pytest.terminal.TerminalReporter.isatty`` to support
2+
being called as a method. Before it was just a boolean which could
3+
break correct code when using ``-o log_cli=true``).

src/_pytest/compat.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,3 +300,14 @@ def get_user_id() -> int | None:
300300
# This also work for Enums (if you use `is` to compare) and Literals.
301301
def assert_never(value: NoReturn) -> NoReturn:
302302
assert False, f"Unhandled value: {value} ({type(value).__name__})"
303+
304+
305+
class CallableBool:
306+
def __init__(self, value: bool) -> None:
307+
self._value = value
308+
309+
def __bool__(self) -> bool:
310+
return self._value
311+
312+
def __call__(self) -> bool:
313+
return self._value

src/_pytest/terminal.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131

3232
import pluggy
3333

34+
from _pytest import compat
3435
from _pytest import nodes
3536
from _pytest import timing
3637
from _pytest._code import ExceptionInfo
@@ -387,7 +388,9 @@ def __init__(self, config: Config, file: TextIO | None = None) -> None:
387388
self.reportchars = getreportopt(config)
388389
self.foldskipped = config.option.fold_skipped
389390
self.hasmarkup = self._tw.hasmarkup
390-
self.isatty = file.isatty()
391+
# isatty should be a method but was wrongly implemented as a boolean.
392+
# We use _CallableBool here to support both.
393+
self.isatty = compat.CallableBool(file.isatty())
391394
self._progress_nodeids_reported: set[str] = set()
392395
self._timing_nodeids_reported: set[str] = set()
393396
self._show_progress_info = self._determine_show_progress_info()
@@ -766,7 +769,7 @@ def _width_of_current_line(self) -> int:
766769
return self._tw.width_of_current_line
767770

768771
def pytest_collection(self) -> None:
769-
if self.isatty:
772+
if self.isatty():
770773
if self.config.option.verbose >= 0:
771774
self.write("collecting ... ", flush=True, bold=True)
772775
elif self.config.option.verbose >= 1:
@@ -779,7 +782,7 @@ def pytest_collectreport(self, report: CollectReport) -> None:
779782
self._add_stats("skipped", [report])
780783
items = [x for x in report.result if isinstance(x, Item)]
781784
self._numcollected += len(items)
782-
if self.isatty:
785+
if self.isatty():
783786
self.report_collect()
784787

785788
def report_collect(self, final: bool = False) -> None:
@@ -811,7 +814,7 @@ def report_collect(self, final: bool = False) -> None:
811814
line += f" / {skipped} skipped"
812815
if self._numcollected > selected:
813816
line += f" / {selected} selected"
814-
if self.isatty:
817+
if self.isatty():
815818
self.rewrite(line, bold=True, erase=True)
816819
if final:
817820
self.write("\n")

testing/test_terminal.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,16 @@ def test_long_xfail():
442442
]
443443
)
444444

445+
@pytest.mark.parametrize("isatty", [True, False])
446+
def test_isatty(self, pytester: Pytester, monkeypatch, isatty: bool) -> None:
447+
config = pytester.parseconfig()
448+
f = StringIO()
449+
monkeypatch.setattr(f, "isatty", lambda: isatty)
450+
tr = TerminalReporter(config, f)
451+
assert tr.isatty() == isatty
452+
# It was incorrectly implemented as a boolean so we still support using it as one.
453+
assert bool(tr.isatty) == isatty
454+
445455

446456
class TestCollectonly:
447457
def test_collectonly_basic(self, pytester: Pytester) -> None:

0 commit comments

Comments
 (0)