Skip to content

Commit

Permalink
Use same code for setting up cli/non-cli formatter
Browse files Browse the repository at this point in the history
A method _create_formatter was introduced that is used for both the
log_cli_formatter and the log_formatter.

Consequences of this commit are:
* Captured logs that are output for each failing test are formatted
  using the ColoredLevelFromatter.
* The formatter used for writing to a file still uses the non-colored
  logging.Formatter class.
  • Loading branch information
twmr committed May 28, 2019
1 parent 10ca84f commit a91f174
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 19 deletions.
3 changes: 3 additions & 0 deletions changelog/5311.feature.rst
@@ -0,0 +1,3 @@
Captured logs that are output for each failing test are formatted using the
ColoredLevelFromatter. As a consequence caplog.text contains the ANSI
escape sequences used for coloring the level names now.
45 changes: 26 additions & 19 deletions src/_pytest/logging.py
Expand Up @@ -17,6 +17,11 @@

DEFAULT_LOG_FORMAT = "%(levelname)-8s %(name)s:%(filename)s:%(lineno)d %(message)s"
DEFAULT_LOG_DATE_FORMAT = "%H:%M:%S"
_ANSI_ESCAPE_SEQ = re.compile(r"\x1b\[[\d;]+m")


def _remove_ansi_escape_sequences(text):
return _ANSI_ESCAPE_SEQ.sub("", text)


class ColoredLevelFormatter(logging.Formatter):
Expand Down Expand Up @@ -256,8 +261,8 @@ def get_records(self, when):

@property
def text(self):
"""Returns the log text."""
return self.handler.stream.getvalue()
"""Returns the formatted log text."""
return _remove_ansi_escape_sequences(self.handler.stream.getvalue())

@property
def records(self):
Expand Down Expand Up @@ -393,7 +398,7 @@ def __init__(self, config):
config.option.verbose = 1

self.print_logs = get_option_ini(config, "log_print")
self.formatter = logging.Formatter(
self.formatter = self._create_formatter(
get_option_ini(config, "log_format"),
get_option_ini(config, "log_date_format"),
)
Expand Down Expand Up @@ -427,6 +432,19 @@ def __init__(self, config):
if self._log_cli_enabled():
self._setup_cli_logging()

def _create_formatter(self, log_format, log_date_format):
# color option doesn't exist if terminal plugin is disabled
color = getattr(self._config.option, "color", "no")
if color != "no" and ColoredLevelFormatter.LEVELNAME_FMT_REGEX.search(
log_format
):
formatter = ColoredLevelFormatter(
create_terminal_writer(self._config), log_format, log_date_format
)
else:
formatter = logging.Formatter(log_format, log_date_format)
return formatter

def _setup_cli_logging(self):
config = self._config
terminal_reporter = config.pluginmanager.get_plugin("terminalreporter")
Expand All @@ -437,23 +455,12 @@ def _setup_cli_logging(self):
capture_manager = config.pluginmanager.get_plugin("capturemanager")
# if capturemanager plugin is disabled, live logging still works.
log_cli_handler = _LiveLoggingStreamHandler(terminal_reporter, capture_manager)
log_cli_format = get_option_ini(config, "log_cli_format", "log_format")
log_cli_date_format = get_option_ini(
config, "log_cli_date_format", "log_date_format"

log_cli_formatter = self._create_formatter(
get_option_ini(config, "log_cli_format", "log_format"),
get_option_ini(config, "log_cli_date_format", "log_date_format"),
)
if (
config.option.color != "no"
and ColoredLevelFormatter.LEVELNAME_FMT_REGEX.search(log_cli_format)
):
log_cli_formatter = ColoredLevelFormatter(
create_terminal_writer(config),
log_cli_format,
datefmt=log_cli_date_format,
)
else:
log_cli_formatter = logging.Formatter(
log_cli_format, datefmt=log_cli_date_format
)

log_cli_level = get_actual_log_level(config, "log_cli_level", "log_level")
self.log_cli_handler = log_cli_handler
self.live_logs_context = lambda: catching_logs(
Expand Down
45 changes: 45 additions & 0 deletions testing/logging/test_reporting.py
Expand Up @@ -1084,3 +1084,48 @@ def test_second():
with open(os.path.join(report_dir_base, "test_second"), "r") as rfh:
content = rfh.read()
assert "message from test 2" in content


def test_colored_captured_log(testdir):
"""
Test that the level names of captured log messages of a failing test are
colored.
"""
testdir.makepyfile(
"""
import logging
logger = logging.getLogger(__name__)
def test_foo():
logger.info('text going to logger from call')
assert False
"""
)
result = testdir.runpytest("--log-level=INFO", "--color=yes")
assert result.ret == 1
result.stdout.fnmatch_lines(
[
"*-- Captured log call --*",
"\x1b[32mINFO \x1b[0m*text going to logger from call",
]
)


def test_colored_ansi_esc_caplogtext(testdir):
"""
Make sure that caplog.text does not contain ANSI escape sequences.
"""
testdir.makepyfile(
"""
import logging
logger = logging.getLogger(__name__)
def test_foo(caplog):
logger.info('text going to logger from call')
assert '\x1b' not in caplog.text
"""
)
result = testdir.runpytest("--log-level=INFO", "--color=yes")
assert result.ret == 0

0 comments on commit a91f174

Please sign in to comment.