Merge pull request #7135 from pytest-dev/terminalwriter
This commit is contained in:
commit
6c2d358918
|
@ -0,0 +1,15 @@
|
|||
Pytest now uses its own ``TerminalWriter`` class instead of using the one from the ``py`` library.
|
||||
Plugins generally access this class through ``TerminalReporter.writer``, ``TerminalReporter.write()``
|
||||
(and similar methods), or ``_pytest.config.create_terminal_writer()``.
|
||||
|
||||
The following breaking changes were made:
|
||||
|
||||
- Output (``write()`` method and others) no longer flush implicitly; the flushing behavior
|
||||
of the underlying file is respected. To flush explicitly (for example, if you
|
||||
want output to be shown before an end-of-line is printed), use ``write(flush=True)`` or
|
||||
``terminal_writer.flush()``.
|
||||
- Explicit Windows console support was removed, delegated to the colorama library.
|
||||
- Support for writing ``bytes`` was removed.
|
||||
- The ``reline`` method and ``chars_on_current_line`` property were removed.
|
||||
- The ``stringio`` and ``encoding`` arguments was removed.
|
||||
- Support for passing a callable instead of a file was removed.
|
|
@ -1,39 +1,8 @@
|
|||
from typing import List
|
||||
from typing import Sequence
|
||||
|
||||
from py.io import TerminalWriter as BaseTerminalWriter # noqa: F401
|
||||
from .terminalwriter import get_terminal_width
|
||||
from .terminalwriter import TerminalWriter
|
||||
|
||||
|
||||
class TerminalWriter(BaseTerminalWriter):
|
||||
def _write_source(self, lines: List[str], indents: Sequence[str] = ()) -> None:
|
||||
"""Write lines of source code possibly highlighted.
|
||||
|
||||
Keeping this private for now because the API is clunky. We should discuss how
|
||||
to evolve the terminal writer so we can have more precise color support, for example
|
||||
being able to write part of a line in one color and the rest in another, and so on.
|
||||
"""
|
||||
if indents and len(indents) != len(lines):
|
||||
raise ValueError(
|
||||
"indents size ({}) should have same size as lines ({})".format(
|
||||
len(indents), len(lines)
|
||||
)
|
||||
)
|
||||
if not indents:
|
||||
indents = [""] * len(lines)
|
||||
source = "\n".join(lines)
|
||||
new_lines = self._highlight(source).splitlines()
|
||||
for indent, new_line in zip(indents, new_lines):
|
||||
self.line(indent + new_line)
|
||||
|
||||
def _highlight(self, source):
|
||||
"""Highlight the given source code if we have markup support"""
|
||||
if not self.hasmarkup:
|
||||
return source
|
||||
try:
|
||||
from pygments.formatters.terminal import TerminalFormatter
|
||||
from pygments.lexers.python import PythonLexer
|
||||
from pygments import highlight
|
||||
except ImportError:
|
||||
return source
|
||||
else:
|
||||
return highlight(source, PythonLexer(), TerminalFormatter(bg="dark"))
|
||||
__all__ = [
|
||||
"TerminalWriter",
|
||||
"get_terminal_width",
|
||||
]
|
||||
|
|
|
@ -0,0 +1,206 @@
|
|||
"""Helper functions for writing to terminals and files."""
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import unicodedata
|
||||
from functools import lru_cache
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
from typing import TextIO
|
||||
|
||||
|
||||
# This code was initially copied from py 1.8.1, file _io/terminalwriter.py.
|
||||
|
||||
|
||||
def get_terminal_width() -> int:
|
||||
width, _ = shutil.get_terminal_size(fallback=(80, 24))
|
||||
|
||||
# The Windows get_terminal_size may be bogus, let's sanify a bit.
|
||||
if width < 40:
|
||||
width = 80
|
||||
|
||||
return width
|
||||
|
||||
|
||||
@lru_cache(100)
|
||||
def char_width(c: str) -> int:
|
||||
# Fullwidth and Wide -> 2, all else (including Ambiguous) -> 1.
|
||||
return 2 if unicodedata.east_asian_width(c) in ("F", "W") else 1
|
||||
|
||||
|
||||
def get_line_width(text: str) -> int:
|
||||
text = unicodedata.normalize("NFC", text)
|
||||
return sum(char_width(c) for c in text)
|
||||
|
||||
|
||||
def should_do_markup(file: TextIO) -> bool:
|
||||
if os.environ.get("PY_COLORS") == "1":
|
||||
return True
|
||||
if os.environ.get("PY_COLORS") == "0":
|
||||
return False
|
||||
return (
|
||||
hasattr(file, "isatty")
|
||||
and file.isatty()
|
||||
and os.environ.get("TERM") != "dumb"
|
||||
and not (sys.platform.startswith("java") and os._name == "nt")
|
||||
)
|
||||
|
||||
|
||||
class TerminalWriter:
|
||||
_esctable = dict(
|
||||
black=30,
|
||||
red=31,
|
||||
green=32,
|
||||
yellow=33,
|
||||
blue=34,
|
||||
purple=35,
|
||||
cyan=36,
|
||||
white=37,
|
||||
Black=40,
|
||||
Red=41,
|
||||
Green=42,
|
||||
Yellow=43,
|
||||
Blue=44,
|
||||
Purple=45,
|
||||
Cyan=46,
|
||||
White=47,
|
||||
bold=1,
|
||||
light=2,
|
||||
blink=5,
|
||||
invert=7,
|
||||
)
|
||||
|
||||
def __init__(self, file: Optional[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.hasmarkup = should_do_markup(file)
|
||||
self._current_line = ""
|
||||
self._terminal_width = None # type: Optional[int]
|
||||
|
||||
@property
|
||||
def fullwidth(self) -> int:
|
||||
if self._terminal_width is not None:
|
||||
return self._terminal_width
|
||||
return get_terminal_width()
|
||||
|
||||
@fullwidth.setter
|
||||
def fullwidth(self, value: int) -> None:
|
||||
self._terminal_width = value
|
||||
|
||||
@property
|
||||
def width_of_current_line(self) -> int:
|
||||
"""Return an estimate of the width so far in the current line."""
|
||||
return get_line_width(self._current_line)
|
||||
|
||||
def markup(self, text: str, **markup: bool) -> str:
|
||||
for name in markup:
|
||||
if name not in self._esctable:
|
||||
raise ValueError("unknown markup: {!r}".format(name))
|
||||
if self.hasmarkup:
|
||||
esc = [self._esctable[name] for name, on in markup.items() if on]
|
||||
if esc:
|
||||
text = "".join("\x1b[%sm" % cod for cod in esc) + text + "\x1b[0m"
|
||||
return text
|
||||
|
||||
def sep(
|
||||
self,
|
||||
sepchar: str,
|
||||
title: Optional[str] = None,
|
||||
fullwidth: Optional[int] = None,
|
||||
**markup: bool
|
||||
) -> None:
|
||||
if fullwidth is None:
|
||||
fullwidth = self.fullwidth
|
||||
# the goal is to have the line be as long as possible
|
||||
# under the condition that len(line) <= fullwidth
|
||||
if sys.platform == "win32":
|
||||
# if we print in the last column on windows we are on a
|
||||
# new line but there is no way to verify/neutralize this
|
||||
# (we may not know the exact line width)
|
||||
# so let's be defensive to avoid empty lines in the output
|
||||
fullwidth -= 1
|
||||
if title is not None:
|
||||
# we want 2 + 2*len(fill) + len(title) <= fullwidth
|
||||
# i.e. 2 + 2*len(sepchar)*N + len(title) <= fullwidth
|
||||
# 2*len(sepchar)*N <= fullwidth - len(title) - 2
|
||||
# N <= (fullwidth - len(title) - 2) // (2*len(sepchar))
|
||||
N = max((fullwidth - len(title) - 2) // (2 * len(sepchar)), 1)
|
||||
fill = sepchar * N
|
||||
line = "{} {} {}".format(fill, title, fill)
|
||||
else:
|
||||
# we want len(sepchar)*N <= fullwidth
|
||||
# i.e. N <= fullwidth // len(sepchar)
|
||||
line = sepchar * (fullwidth // len(sepchar))
|
||||
# in some situations there is room for an extra sepchar at the right,
|
||||
# in particular if we consider that with a sepchar like "_ " the
|
||||
# trailing space is not important at the end of the line
|
||||
if len(line) + len(sepchar.rstrip()) <= fullwidth:
|
||||
line += sepchar.rstrip()
|
||||
|
||||
self.line(line, **markup)
|
||||
|
||||
def write(self, msg: str, *, flush: bool = False, **markup: bool) -> None:
|
||||
if msg:
|
||||
current_line = msg.rsplit("\n", 1)[-1]
|
||||
if "\n" in msg:
|
||||
self._current_line = current_line
|
||||
else:
|
||||
self._current_line += current_line
|
||||
|
||||
msg = self.markup(msg, **markup)
|
||||
|
||||
self._file.write(msg)
|
||||
if flush:
|
||||
self.flush()
|
||||
|
||||
def line(self, s: str = "", **markup: bool) -> None:
|
||||
self.write(s, **markup)
|
||||
self.write("\n")
|
||||
|
||||
def flush(self) -> None:
|
||||
self._file.flush()
|
||||
|
||||
def _write_source(self, lines: Sequence[str], indents: Sequence[str] = ()) -> None:
|
||||
"""Write lines of source code possibly highlighted.
|
||||
|
||||
Keeping this private for now because the API is clunky. We should discuss how
|
||||
to evolve the terminal writer so we can have more precise color support, for example
|
||||
being able to write part of a line in one color and the rest in another, and so on.
|
||||
"""
|
||||
if indents and len(indents) != len(lines):
|
||||
raise ValueError(
|
||||
"indents size ({}) should have same size as lines ({})".format(
|
||||
len(indents), len(lines)
|
||||
)
|
||||
)
|
||||
if not indents:
|
||||
indents = [""] * len(lines)
|
||||
source = "\n".join(lines)
|
||||
new_lines = self._highlight(source).splitlines()
|
||||
for indent, new_line in zip(indents, new_lines):
|
||||
self.line(indent + new_line)
|
||||
|
||||
def _highlight(self, source: str) -> str:
|
||||
"""Highlight the given source code if we have markup support."""
|
||||
if not self.hasmarkup:
|
||||
return source
|
||||
try:
|
||||
from pygments.formatters.terminal import TerminalFormatter
|
||||
from pygments.lexers.python import PythonLexer
|
||||
from pygments import highlight
|
||||
except ImportError:
|
||||
return source
|
||||
else:
|
||||
highlighted = highlight(
|
||||
source, PythonLexer(), TerminalFormatter(bg="dark")
|
||||
) # type: str
|
||||
return highlighted
|
|
@ -15,6 +15,7 @@ from typing import Union
|
|||
|
||||
import py
|
||||
|
||||
import _pytest._io
|
||||
from _pytest.compat import TYPE_CHECKING
|
||||
from _pytest.config.exceptions import UsageError
|
||||
|
||||
|
@ -466,7 +467,7 @@ class DropShorterLongHelpFormatter(argparse.HelpFormatter):
|
|||
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
||||
"""Use more accurate terminal width via pylib."""
|
||||
if "width" not in kwargs:
|
||||
kwargs["width"] = py.io.get_terminal_width()
|
||||
kwargs["width"] = _pytest._io.get_terminal_width()
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def _format_action_invocation(self, action: argparse.Action) -> str:
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
""" submit failure or test session information to a pastebin service. """
|
||||
import tempfile
|
||||
from io import StringIO
|
||||
from typing import IO
|
||||
|
||||
import pytest
|
||||
|
@ -99,11 +100,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(terminalreporter.config, 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))
|
||||
|
|
|
@ -1406,7 +1406,7 @@ def _showfixtures_main(config, session):
|
|||
|
||||
def write_docstring(tw: TerminalWriter, doc: str, indent: str = " ") -> None:
|
||||
for line in doc.split("\n"):
|
||||
tw.write(indent + line + "\n")
|
||||
tw.line(indent + line)
|
||||
|
||||
|
||||
class Function(PyobjMixin, nodes.Item):
|
||||
|
|
|
@ -82,10 +82,11 @@ class BaseReport:
|
|||
|
||||
.. 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
|
||||
|
|
|
@ -120,6 +120,7 @@ def show_test_item(item):
|
|||
used_fixtures = sorted(getattr(item, "fixturenames", []))
|
||||
if used_fixtures:
|
||||
tw.write(" (fixtures used: {})".format(", ".join(used_fixtures)))
|
||||
tw.flush()
|
||||
|
||||
|
||||
def pytest_runtest_setup(item):
|
||||
|
|
|
@ -68,6 +68,8 @@ def _show_fixture_action(fixturedef, msg):
|
|||
if hasattr(fixturedef, "cached_param"):
|
||||
tw.write("[{}]".format(fixturedef.cached_param))
|
||||
|
||||
tw.flush()
|
||||
|
||||
if capman:
|
||||
capman.resume_global_capture()
|
||||
|
||||
|
|
|
@ -344,7 +344,7 @@ class TerminalReporter:
|
|||
fspath = self.startdir.bestrelpath(fspath)
|
||||
self._tw.line()
|
||||
self._tw.write(fspath + " ")
|
||||
self._tw.write(res, **markup)
|
||||
self._tw.write(res, flush=True, **markup)
|
||||
|
||||
def write_ensure_prefix(self, prefix, extra="", **kwargs):
|
||||
if self.currentfspath != prefix:
|
||||
|
@ -360,8 +360,11 @@ class TerminalReporter:
|
|||
self._tw.line()
|
||||
self.currentfspath = None
|
||||
|
||||
def write(self, content, **markup):
|
||||
self._tw.write(content, **markup)
|
||||
def write(self, content: str, *, flush: bool = False, **markup: bool) -> None:
|
||||
self._tw.write(content, flush=flush, **markup)
|
||||
|
||||
def flush(self) -> None:
|
||||
self._tw.flush()
|
||||
|
||||
def write_line(self, line, **markup):
|
||||
if not isinstance(line, str):
|
||||
|
@ -438,9 +441,11 @@ class TerminalReporter:
|
|||
if self.showlongtestinfo:
|
||||
line = self._locationline(nodeid, *location)
|
||||
self.write_ensure_prefix(line, "")
|
||||
self.flush()
|
||||
elif self.showfspath:
|
||||
fsid = nodeid.split("::")[0]
|
||||
self.write_fspath_result(fsid, "")
|
||||
self.flush()
|
||||
|
||||
def pytest_runtest_logreport(self, report: TestReport) -> None:
|
||||
self._tests_ran = True
|
||||
|
@ -492,6 +497,7 @@ class TerminalReporter:
|
|||
self._tw.write(word, **markup)
|
||||
self._tw.write(" " + line)
|
||||
self.currentfspath = -2
|
||||
self.flush()
|
||||
|
||||
@property
|
||||
def _is_last_item(self):
|
||||
|
@ -540,24 +546,20 @@ class TerminalReporter:
|
|||
msg = self._get_progress_information_message()
|
||||
w = self._width_of_current_line
|
||||
fill = self._tw.fullwidth - w - 1
|
||||
self.write(msg.rjust(fill), **{color: True})
|
||||
self.write(msg.rjust(fill), flush=True, **{color: True})
|
||||
|
||||
@property
|
||||
def _width_of_current_line(self):
|
||||
"""Return the width of current line, using the superior implementation of py-1.6 when available"""
|
||||
try:
|
||||
return self._tw.width_of_current_line
|
||||
except AttributeError:
|
||||
# py < 1.6.0
|
||||
return self._tw.chars_on_current_line
|
||||
return self._tw.width_of_current_line
|
||||
|
||||
def pytest_collection(self) -> None:
|
||||
if self.isatty:
|
||||
if self.config.option.verbose >= 0:
|
||||
self.write("collecting ... ", bold=True)
|
||||
self.write("collecting ... ", flush=True, bold=True)
|
||||
self._collect_report_last_write = time.time()
|
||||
elif self.config.option.verbose >= 1:
|
||||
self.write("collecting ... ", bold=True)
|
||||
self.write("collecting ... ", flush=True, bold=True)
|
||||
|
||||
def pytest_collectreport(self, report: CollectReport) -> None:
|
||||
if report.failed:
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import io
|
||||
import operator
|
||||
import os
|
||||
import queue
|
||||
|
@ -1037,10 +1038,11 @@ raise ValueError()
|
|||
"""
|
||||
)
|
||||
excinfo = pytest.raises(ValueError, mod.f)
|
||||
tw = TerminalWriter(stringio=True)
|
||||
file = io.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(
|
||||
|
@ -1255,11 +1257,12 @@ raise ValueError()
|
|||
getattr(excinfo.value, attr).__traceback__ = None
|
||||
|
||||
r = excinfo.getrepr()
|
||||
tw = TerminalWriter(stringio=True)
|
||||
file = io.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",
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
import re
|
||||
from io import StringIO
|
||||
|
||||
import pytest
|
||||
from _pytest._io import TerminalWriter
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"has_markup, expected",
|
||||
[
|
||||
pytest.param(
|
||||
True, "{kw}assert{hl-reset} {number}0{hl-reset}\n", id="with markup"
|
||||
),
|
||||
pytest.param(False, "assert 0\n", id="no markup"),
|
||||
],
|
||||
)
|
||||
def test_code_highlight(has_markup, expected, color_mapping):
|
||||
f = StringIO()
|
||||
tw = TerminalWriter(f)
|
||||
tw.hasmarkup = has_markup
|
||||
tw._write_source(["assert 0"])
|
||||
assert f.getvalue().splitlines(keepends=True) == color_mapping.format([expected])
|
||||
|
||||
with pytest.raises(
|
||||
ValueError,
|
||||
match=re.escape("indents size (2) should have same size as lines (1)"),
|
||||
):
|
||||
tw._write_source(["assert 0"], [" ", " "])
|
|
@ -0,0 +1,235 @@
|
|||
import io
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
from typing import Generator
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
from _pytest._io import terminalwriter
|
||||
from _pytest.monkeypatch import MonkeyPatch
|
||||
|
||||
|
||||
# These tests were initially copied from py 1.8.1.
|
||||
|
||||
|
||||
def test_terminal_width_COLUMNS(monkeypatch: MonkeyPatch) -> None:
|
||||
monkeypatch.setenv("COLUMNS", "42")
|
||||
assert terminalwriter.get_terminal_width() == 42
|
||||
monkeypatch.delenv("COLUMNS", raising=False)
|
||||
|
||||
|
||||
def test_terminalwriter_width_bogus(monkeypatch: MonkeyPatch) -> None:
|
||||
monkeypatch.setattr(shutil, "get_terminal_size", mock.Mock(return_value=(10, 10)))
|
||||
monkeypatch.delenv("COLUMNS", raising=False)
|
||||
tw = terminalwriter.TerminalWriter()
|
||||
assert tw.fullwidth == 80
|
||||
|
||||
|
||||
def test_terminalwriter_computes_width(monkeypatch: MonkeyPatch) -> None:
|
||||
monkeypatch.setattr(terminalwriter, "get_terminal_width", lambda: 42)
|
||||
tw = terminalwriter.TerminalWriter()
|
||||
assert tw.fullwidth == 42
|
||||
|
||||
|
||||
def test_terminalwriter_dumb_term_no_markup(monkeypatch: MonkeyPatch) -> None:
|
||||
monkeypatch.setattr(os, "environ", {"TERM": "dumb", "PATH": ""})
|
||||
|
||||
class MyFile:
|
||||
closed = False
|
||||
|
||||
def isatty(self):
|
||||
return True
|
||||
|
||||
with monkeypatch.context() as m:
|
||||
m.setattr(sys, "stdout", MyFile())
|
||||
assert sys.stdout.isatty()
|
||||
tw = terminalwriter.TerminalWriter()
|
||||
assert not tw.hasmarkup
|
||||
|
||||
|
||||
win32 = int(sys.platform == "win32")
|
||||
|
||||
|
||||
class TestTerminalWriter:
|
||||
@pytest.fixture(params=["path", "stringio"])
|
||||
def tw(
|
||||
self, request, tmpdir
|
||||
) -> Generator[terminalwriter.TerminalWriter, None, None]:
|
||||
if request.param == "path":
|
||||
p = tmpdir.join("tmpfile")
|
||||
f = open(str(p), "w+", encoding="utf8")
|
||||
tw = terminalwriter.TerminalWriter(f)
|
||||
|
||||
def getlines():
|
||||
f.flush()
|
||||
with open(str(p), encoding="utf8") as fp:
|
||||
return fp.readlines()
|
||||
|
||||
elif request.param == "stringio":
|
||||
f = io.StringIO()
|
||||
tw = terminalwriter.TerminalWriter(f)
|
||||
|
||||
def getlines():
|
||||
f.seek(0)
|
||||
return f.readlines()
|
||||
|
||||
tw.getlines = getlines # type: ignore
|
||||
tw.getvalue = lambda: "".join(getlines()) # type: ignore
|
||||
|
||||
with f:
|
||||
yield tw
|
||||
|
||||
def test_line(self, tw) -> None:
|
||||
tw.line("hello")
|
||||
lines = tw.getlines()
|
||||
assert len(lines) == 1
|
||||
assert lines[0] == "hello\n"
|
||||
|
||||
def test_line_unicode(self, tw) -> None:
|
||||
msg = "b\u00f6y"
|
||||
tw.line(msg)
|
||||
lines = tw.getlines()
|
||||
assert lines[0] == msg + "\n"
|
||||
|
||||
def test_sep_no_title(self, tw) -> None:
|
||||
tw.sep("-", fullwidth=60)
|
||||
lines = tw.getlines()
|
||||
assert len(lines) == 1
|
||||
assert lines[0] == "-" * (60 - win32) + "\n"
|
||||
|
||||
def test_sep_with_title(self, tw) -> None:
|
||||
tw.sep("-", "hello", fullwidth=60)
|
||||
lines = tw.getlines()
|
||||
assert len(lines) == 1
|
||||
assert lines[0] == "-" * 26 + " hello " + "-" * (27 - win32) + "\n"
|
||||
|
||||
def test_sep_longer_than_width(self, tw) -> None:
|
||||
tw.sep("-", "a" * 10, fullwidth=5)
|
||||
(line,) = tw.getlines()
|
||||
# even though the string is wider than the line, still have a separator
|
||||
assert line == "- aaaaaaaaaa -\n"
|
||||
|
||||
@pytest.mark.skipif(sys.platform == "win32", reason="win32 has no native ansi")
|
||||
@pytest.mark.parametrize("bold", (True, False))
|
||||
@pytest.mark.parametrize("color", ("red", "green"))
|
||||
def test_markup(self, tw, bold: bool, color: str) -> None:
|
||||
text = tw.markup("hello", **{color: True, "bold": bold})
|
||||
assert "hello" in text
|
||||
|
||||
def test_markup_bad(self, tw) -> None:
|
||||
with pytest.raises(ValueError):
|
||||
tw.markup("x", wronkw=3)
|
||||
with pytest.raises(ValueError):
|
||||
tw.markup("x", wronkw=0)
|
||||
|
||||
def test_line_write_markup(self, tw) -> None:
|
||||
tw.hasmarkup = True
|
||||
tw.line("x", bold=True)
|
||||
tw.write("x\n", red=True)
|
||||
lines = tw.getlines()
|
||||
if sys.platform != "win32":
|
||||
assert len(lines[0]) >= 2, lines
|
||||
assert len(lines[1]) >= 2, lines
|
||||
|
||||
def test_attr_fullwidth(self, tw) -> None:
|
||||
tw.sep("-", "hello", fullwidth=70)
|
||||
tw.fullwidth = 70
|
||||
tw.sep("-", "hello")
|
||||
lines = tw.getlines()
|
||||
assert len(lines[0]) == len(lines[1])
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.platform == "win32", reason="win32 has no native ansi")
|
||||
def test_attr_hasmarkup() -> None:
|
||||
file = io.StringIO()
|
||||
tw = terminalwriter.TerminalWriter(file)
|
||||
assert not tw.hasmarkup
|
||||
tw.hasmarkup = True
|
||||
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 test_should_do_markup_PY_COLORS_eq_1(monkeypatch: MonkeyPatch) -> None:
|
||||
monkeypatch.setitem(os.environ, "PY_COLORS", "1")
|
||||
file = io.StringIO()
|
||||
tw = terminalwriter.TerminalWriter(file)
|
||||
assert tw.hasmarkup
|
||||
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 test_should_do_markup_PY_COLORS_eq_0(monkeypatch: MonkeyPatch) -> None:
|
||||
monkeypatch.setitem(os.environ, "PY_COLORS", "0")
|
||||
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"
|
||||
|
||||
|
||||
class TestTerminalWriterLineWidth:
|
||||
def test_init(self) -> None:
|
||||
tw = terminalwriter.TerminalWriter()
|
||||
assert tw.width_of_current_line == 0
|
||||
|
||||
def test_update(self) -> None:
|
||||
tw = terminalwriter.TerminalWriter()
|
||||
tw.write("hello world")
|
||||
assert tw.width_of_current_line == 11
|
||||
|
||||
def test_update_with_newline(self) -> None:
|
||||
tw = terminalwriter.TerminalWriter()
|
||||
tw.write("hello\nworld")
|
||||
assert tw.width_of_current_line == 5
|
||||
|
||||
def test_update_with_wide_text(self) -> None:
|
||||
tw = terminalwriter.TerminalWriter()
|
||||
tw.write("乇乂ㄒ尺卂 ㄒ卄丨匚匚")
|
||||
assert tw.width_of_current_line == 21 # 5*2 + 1 + 5*2
|
||||
|
||||
def test_composed(self) -> None:
|
||||
tw = terminalwriter.TerminalWriter()
|
||||
text = "café food"
|
||||
assert len(text) == 9
|
||||
tw.write(text)
|
||||
assert tw.width_of_current_line == 9
|
||||
|
||||
def test_combining(self) -> None:
|
||||
tw = terminalwriter.TerminalWriter()
|
||||
text = "café food"
|
||||
assert len(text) == 10
|
||||
tw.write(text)
|
||||
assert tw.width_of_current_line == 9
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"has_markup, expected",
|
||||
[
|
||||
pytest.param(
|
||||
True, "{kw}assert{hl-reset} {number}0{hl-reset}\n", id="with markup"
|
||||
),
|
||||
pytest.param(False, "assert 0\n", id="no markup"),
|
||||
],
|
||||
)
|
||||
def test_code_highlight(has_markup, expected, color_mapping):
|
||||
f = io.StringIO()
|
||||
tw = terminalwriter.TerminalWriter(f)
|
||||
tw.hasmarkup = has_markup
|
||||
tw._write_source(["assert 0"])
|
||||
assert f.getvalue().splitlines(keepends=True) == color_mapping.format([expected])
|
||||
|
||||
with pytest.raises(
|
||||
ValueError,
|
||||
match=re.escape("indents size (2) should have same size as lines (1)"),
|
||||
):
|
||||
tw._write_source(["assert 0"], [" ", " "])
|
|
@ -1255,7 +1255,7 @@ def test_help_formatter_uses_py_get_terminal_width(monkeypatch):
|
|||
formatter = DropShorterLongHelpFormatter("prog")
|
||||
assert formatter._width == 90
|
||||
|
||||
monkeypatch.setattr("py.io.get_terminal_width", lambda: 160)
|
||||
monkeypatch.setattr("_pytest._io.get_terminal_width", lambda: 160)
|
||||
formatter = DropShorterLongHelpFormatter("prog")
|
||||
assert formatter._width == 160
|
||||
|
||||
|
|
Loading…
Reference in New Issue