Use same code for setting up cli/non-cli formatter
Showing 2 of 3 files from the diff.
testing/logging/test_reporting.py
changed.
src/_pytest/logging.py
changed.
Other files ignored by Codecov
changelog/5311.feature.rst
is new.
@@ -1084,3 +1084,48 @@
Loading
1084 | 1084 | with open(os.path.join(report_dir_base, "test_second"), "r") as rfh: |
|
1085 | 1085 | content = rfh.read() |
|
1086 | 1086 | assert "message from test 2" in content |
|
1087 | + | ||
1088 | + | ||
1089 | + | def test_colored_captured_log(testdir): |
|
1090 | + | """ |
|
1091 | + | Test that the level names of captured log messages of a failing test are |
|
1092 | + | colored. |
|
1093 | + | """ |
|
1094 | + | testdir.makepyfile( |
|
1095 | + | """ |
|
1096 | + | import logging |
|
1097 | + | ||
1098 | + | logger = logging.getLogger(__name__) |
|
1099 | + | ||
1100 | + | def test_foo(): |
|
1101 | + | logger.info('text going to logger from call') |
|
1102 | + | assert False |
|
1103 | + | """ |
|
1104 | + | ) |
|
1105 | + | result = testdir.runpytest("--log-level=INFO", "--color=yes") |
|
1106 | + | assert result.ret == 1 |
|
1107 | + | result.stdout.fnmatch_lines( |
|
1108 | + | [ |
|
1109 | + | "*-- Captured log call --*", |
|
1110 | + | "\x1b[32mINFO \x1b[0m*text going to logger from call", |
|
1111 | + | ] |
|
1112 | + | ) |
|
1113 | + | ||
1114 | + | ||
1115 | + | def test_colored_ansi_esc_caplogtext(testdir): |
|
1116 | + | """ |
|
1117 | + | Make sure that caplog.text does not contain ANSI escape sequences. |
|
1118 | + | """ |
|
1119 | + | testdir.makepyfile( |
|
1120 | + | """ |
|
1121 | + | import logging |
|
1122 | + | ||
1123 | + | logger = logging.getLogger(__name__) |
|
1124 | + | ||
1125 | + | def test_foo(caplog): |
|
1126 | + | logger.info('text going to logger from call') |
|
1127 | + | assert '\x1b' not in caplog.text |
|
1128 | + | """ |
|
1129 | + | ) |
|
1130 | + | result = testdir.runpytest("--log-level=INFO", "--color=yes") |
|
1131 | + | assert result.ret == 0 |
@@ -18,6 +18,11 @@
Loading
18 | 18 | ||
19 | 19 | DEFAULT_LOG_FORMAT = "%(levelname)-8s %(name)s:%(filename)s:%(lineno)d %(message)s" |
|
20 | 20 | DEFAULT_LOG_DATE_FORMAT = "%H:%M:%S" |
|
21 | + | _ANSI_ESCAPE_SEQ = re.compile(r"\x1b\[[\d;]+m") |
|
22 | + | ||
23 | + | ||
24 | + | def _remove_ansi_escape_sequences(text): |
|
25 | + | return _ANSI_ESCAPE_SEQ.sub("", text) |
|
21 | 26 | ||
22 | 27 | ||
23 | 28 | class ColoredLevelFormatter(logging.Formatter): |
@@ -257,8 +262,8 @@
Loading
257 | 262 | ||
258 | 263 | @property |
|
259 | 264 | def text(self): |
|
260 | - | """Returns the log text.""" |
|
261 | - | return self.handler.stream.getvalue() |
|
265 | + | """Returns the formatted log text.""" |
|
266 | + | return _remove_ansi_escape_sequences(self.handler.stream.getvalue()) |
|
262 | 267 | ||
263 | 268 | @property |
|
264 | 269 | def records(self): |
@@ -394,7 +399,7 @@
Loading
394 | 399 | config.option.verbose = 1 |
|
395 | 400 | ||
396 | 401 | self.print_logs = get_option_ini(config, "log_print") |
|
397 | - | self.formatter = logging.Formatter( |
|
402 | + | self.formatter = self._create_formatter( |
|
398 | 403 | get_option_ini(config, "log_format"), |
|
399 | 404 | get_option_ini(config, "log_date_format"), |
|
400 | 405 | ) |
@@ -428,6 +433,19 @@
Loading
428 | 433 | if self._log_cli_enabled(): |
|
429 | 434 | self._setup_cli_logging() |
|
430 | 435 | ||
436 | + | def _create_formatter(self, log_format, log_date_format): |
|
437 | + | # color option doesn't exist if terminal plugin is disabled |
|
438 | + | color = getattr(self._config.option, "color", "no") |
|
439 | + | if color != "no" and ColoredLevelFormatter.LEVELNAME_FMT_REGEX.search( |
|
440 | + | log_format |
|
441 | + | ): |
|
442 | + | formatter = ColoredLevelFormatter( |
|
443 | + | create_terminal_writer(self._config), log_format, log_date_format |
|
444 | + | ) |
|
445 | + | else: |
|
446 | + | formatter = logging.Formatter(log_format, log_date_format) |
|
447 | + | return formatter |
|
448 | + | ||
431 | 449 | def _setup_cli_logging(self): |
|
432 | 450 | config = self._config |
|
433 | 451 | terminal_reporter = config.pluginmanager.get_plugin("terminalreporter") |
@@ -438,23 +456,12 @@
Loading
438 | 456 | capture_manager = config.pluginmanager.get_plugin("capturemanager") |
|
439 | 457 | # if capturemanager plugin is disabled, live logging still works. |
|
440 | 458 | log_cli_handler = _LiveLoggingStreamHandler(terminal_reporter, capture_manager) |
|
441 | - | log_cli_format = get_option_ini(config, "log_cli_format", "log_format") |
|
442 | - | log_cli_date_format = get_option_ini( |
|
443 | - | config, "log_cli_date_format", "log_date_format" |
|
459 | + | ||
460 | + | log_cli_formatter = self._create_formatter( |
|
461 | + | get_option_ini(config, "log_cli_format", "log_format"), |
|
462 | + | get_option_ini(config, "log_cli_date_format", "log_date_format"), |
|
444 | 463 | ) |
|
445 | - | if ( |
|
446 | - | config.option.color != "no" |
|
447 | - | and ColoredLevelFormatter.LEVELNAME_FMT_REGEX.search(log_cli_format) |
|
448 | - | ): |
|
449 | - | log_cli_formatter = ColoredLevelFormatter( |
|
450 | - | create_terminal_writer(config), |
|
451 | - | log_cli_format, |
|
452 | - | datefmt=log_cli_date_format, |
|
453 | - | ) |
|
454 | - | else: |
|
455 | - | log_cli_formatter = logging.Formatter( |
|
456 | - | log_cli_format, datefmt=log_cli_date_format |
|
457 | - | ) |
|
464 | + | ||
458 | 465 | log_cli_level = get_actual_log_level(config, "log_cli_level", "log_level") |
|
459 | 466 | self.log_cli_handler = log_cli_handler |
|
460 | 467 | self.live_logs_context = lambda: catching_logs( |
py27-pluggymaster-xdist
py27-pexpect,py27-twisted-linux
TRAVIS_PYTHON_VERSION=2.7 TRAVIS_OS_NAME=linux
py34-xdist
py35-xdist
py37
py27-lsof-nobyte-numpy
py27-xdist-osx
TRAVIS_OS_NAME=osx
linting,docs,doctesting-linux
TRAVIS_PYTHON_VERSION=3.7 TRAVIS_OS_NAME=linux
py37-lsof-numpy-xdist-linux
TRAVIS_PYTHON_VERSION=3.7 TRAVIS_OS_NAME=linux
py37-pexpect,py37-twisted-linux
TRAVIS_PYTHON_VERSION=3.7 TRAVIS_OS_NAME=linux
Sunburst
The inner-most circle is the entire project, moving away from the center are folders then, finally, a single file.
The size and color of each slice is representing the number of statements and the coverage, respectively.
Icicle
The top section represents the entire project. Proceeding with folders and finally individual files.
The size and color of each slice is representing the number of statements and the coverage, respectively.