Skip to content
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

[WIP] TerminalWriter: adopt some more for using colors on CI #427

Open
wants to merge 5 commits into
base: my-master
Choose a base branch
from
Open
Show file tree
Hide file tree
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
65 changes: 65 additions & 0 deletions src/_pytest/_io/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,71 @@
import os
import sys
from typing import List
from typing import Sequence

import py.io

from _pytest.compat import TYPE_CHECKING

if TYPE_CHECKING:
from typing import TextIO


def use_markup(file: "TextIO") -> bool:
# Backward compatibility with pylib: handle PY_COLORS={0,1} only.
val = os.getenv("PY_COLORS")
if val in ("0", "1"):
return val == "1"

# TODO
# # PYTEST_FORCE_COLOR: handled as boolean.
# val = os.getenv("PYTEST_FORCE_COLOR")
# if val is not None:
# from _pytest.config import _strtobool
#
# return _strtobool(val)

# NO_COLOR: disable markup with any value (https://no-color.org/).
if "NO_COLOR" in os.environ:
return False

# TODO
# if _running_on_ci():
# return True

return file.isatty() if hasattr(file, "isatty") else False


class TerminalWriter(py.io.TerminalWriter): # noqa: pygrep-py
def __init__(self, file: "TextIO" = None) -> None:
if file is None:
file = sys.stdout
if hasattr(file, "isatty") and file.isatty() and sys.platform == "win32":
try:
import colorama
except ImportError:
pass
else:
file = colorama.AnsiToWin32(file).stream
assert file is not None
self._file = file
self._lastlen = 0
self._chars_on_current_line = 0
self._width_of_current_line = 0
self.hasmarkup = use_markup(self._file)

def write(self, msg: str, **markup: bool) -> int: # type: ignore[override]
if not msg:
return 0
self._update_chars_on_current_line(msg) # type: ignore[attr-defined]
if self.hasmarkup and markup:
markupmsg = self.markup(msg, **markup)
else:
markupmsg = msg
ret = self._file.write(markupmsg)
self._file.flush()
return ret

@property
def fullwidth(self):
if hasattr(self, "_terminal_width"):
Expand Down Expand Up @@ -50,3 +111,7 @@ def _highlight(self, source):
return source
else:
return highlight(source, PythonLexer(), TerminalFormatter(bg="dark"))


def _running_on_ci():
return os.environ.get("CI", "").lower() == "true" or "BUILD_NUMBER" in os.environ
8 changes: 6 additions & 2 deletions src/_pytest/assertion/truncate.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
~8 terminal lines, unless running in "-vv" mode or running on CI.
"""
from ..compat import TYPE_CHECKING
from _pytest.assertion.util import _running_on_ci

if TYPE_CHECKING:
from typing import List
Expand All @@ -28,7 +27,12 @@ def _should_truncate(config: "Config") -> bool:
level = config.getini("assert_truncate_level") # type: str
verbose = config.option.verbose # type: int
if level == "auto":
return verbose < 2 and not _running_on_ci()
if verbose >= 2:
return False

from _pytest._io import _running_on_ci

return not _running_on_ci()
return int(level) > verbose


Expand Down
5 changes: 0 additions & 5 deletions src/_pytest/assertion/util.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"""Utilities for assertion debugging"""
import collections.abc
import itertools
import os
import pprint
import re
from typing import AbstractSet
Expand Down Expand Up @@ -534,7 +533,3 @@ def _notin_text(term: str, text: str, verbose: int = 0) -> List[str]:
"? " + indent + marker,
]
return newdiff


def _running_on_ci():
return os.environ.get("CI", "").lower() == "true" or "BUILD_NUMBER" in os.environ
8 changes: 4 additions & 4 deletions src/_pytest/pastebin.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
""" submit failure or test session information to a pastebin service. """
import tempfile
from io import StringIO

import pytest

Expand Down Expand Up @@ -93,11 +94,10 @@ def pytest_terminal_summary(terminalreporter):
msg = rep.longrepr.reprtraceback.reprentries[-1].reprfileloc
except AttributeError:
msg = tr._getfailureheadline(rep)
tw = _pytest.config.create_terminal_writer(
terminalreporter.config, stringio=True
)
file = StringIO()
tw = _pytest.config.create_terminal_writer(tr.config, file=file)
rep.toterminal(tw)
s = tw.stringio.getvalue()
s = file.getvalue()
assert len(s)
pastebinurl = create_new_paste(s)
tr.write_line("{} --> {}".format(msg, pastebinurl))
7 changes: 4 additions & 3 deletions src/_pytest/reports.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,17 +75,18 @@ def get_sections(self, prefix):
yield prefix, content

@property
def longreprtext(self):
def longreprtext(self) -> str:
"""
Read-only property that returns the full string representation
of ``longrepr``.

.. versionadded:: 3.0
"""
tw = TerminalWriter(stringio=True)
file = StringIO()
tw = TerminalWriter(file)
tw.hasmarkup = False
self.toterminal(tw)
exc = tw.stringio.getvalue()
exc = file.getvalue()
return exc.strip()

@property
Expand Down
10 changes: 7 additions & 3 deletions src/_pytest/terminal.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
from _pytest import nodes
from _pytest._code.code import ExceptionInfo
from _pytest._code.code import ReprFileLocation
from _pytest.assertion.util import _running_on_ci
from _pytest.compat import order_preserving_dict
from _pytest.compat import shell_quote
from _pytest.compat import TYPE_CHECKING
Expand Down Expand Up @@ -1214,10 +1213,15 @@ def short_test_summary(self) -> None:
if not self.reportchars:
return

if not self.isatty or _running_on_ci():
if not self.isatty:
termwidth = None
else:
termwidth = self._tw.fullwidth
from _pytest._io import _running_on_ci

if _running_on_ci():
termwidth = None
else:
termwidth = self._tw.fullwidth

def show_simple(stat, lines: List[str]) -> None:
failed = self.stats.get(stat, [])
Expand Down
11 changes: 7 additions & 4 deletions testing/code/test_excinfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import queue
import sys
import textwrap
from io import StringIO
from typing import Union

import py.path
Expand Down Expand Up @@ -1037,10 +1038,11 @@ def f():
"""
)
excinfo = pytest.raises(ValueError, mod.f)
tw = TerminalWriter(stringio=True)
file = StringIO()
tw = TerminalWriter(file=file)
repr = excinfo.getrepr(**reproptions)
repr.toterminal(tw)
assert tw.stringio.getvalue()
assert file.getvalue()

def test_traceback_repr_style(self, importasmod, tw_mock):
mod = importasmod(
Expand Down Expand Up @@ -1256,11 +1258,12 @@ def g():
getattr(excinfo.value, attr).__traceback__ = None

r = excinfo.getrepr()
tw = TerminalWriter(stringio=True)
file = StringIO()
tw = TerminalWriter(file=file)
tw.hasmarkup = False
r.toterminal(tw)

matcher = LineMatcher(tw.stringio.getvalue().splitlines())
matcher = LineMatcher(file.getvalue().splitlines())
matcher.fnmatch_lines(
[
"ValueError: invalid value",
Expand Down