From 880c8b72b3f6141d5d022b741d0cf7eef8268bad Mon Sep 17 00:00:00 2001 From: Tsvika Shapira Date: Sun, 16 Feb 2025 17:27:19 +0200 Subject: [PATCH 01/10] switch line to _ --- src/pytest_cov/engine.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pytest_cov/engine.py b/src/pytest_cov/engine.py index 650dbcc9..f7896faa 100644 --- a/src/pytest_cov/engine.py +++ b/src/pytest_cov/engine.py @@ -155,15 +155,15 @@ def summary(self, stream): # Output coverage section header. if len(self.node_descs) == 1: - self.sep(stream, '-', f"coverage: {''.join(self.node_descs)}") + self.sep(stream, '_', f"coverage: {''.join(self.node_descs)}") else: - self.sep(stream, '-', 'coverage') + self.sep(stream, '_', 'coverage') for node_desc in sorted(self.node_descs): self.sep(stream, ' ', f'{node_desc}') # Report on any failed workers. if self.failed_workers: - self.sep(stream, '-', 'coverage: failed workers') + self.sep(stream, '_', 'coverage: failed workers') stream.write('The following workers failed to return coverage data, ensure that pytest-cov is installed on these workers.\n') for node in self.failed_workers: stream.write(f'{node.gateway.id}\n') From 507c4e6ee77bcbf4579464d88a4497c064ddc5de Mon Sep 17 00:00:00 2001 From: Tsvika Shapira Date: Sun, 16 Feb 2025 16:35:50 +0200 Subject: [PATCH 02/10] add heading to coverage reports --- src/pytest_cov/plugin.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pytest_cov/plugin.py b/src/pytest_cov/plugin.py index 916efc8e..2fbbfd48 100644 --- a/src/pytest_cov/plugin.py +++ b/src/pytest_cov/plugin.py @@ -333,6 +333,7 @@ def pytest_runtestloop(self, session): # it for unit tests that don't need it from coverage.misc import CoverageException + self.cov_controller.sep(self.cov_report, '=', 'coverage report') try: self.cov_total = self.cov_controller.summary(self.cov_report) except CoverageException as exc: From 3bb24fcd5099589a964238944614801cac528bcf Mon Sep 17 00:00:00 2001 From: Tsvika Shapira Date: Sun, 16 Feb 2025 17:41:16 +0200 Subject: [PATCH 03/10] use TerminalReporter for first heading --- src/pytest_cov/plugin.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/pytest_cov/plugin.py b/src/pytest_cov/plugin.py index 2fbbfd48..69a9ecb1 100644 --- a/src/pytest_cov/plugin.py +++ b/src/pytest_cov/plugin.py @@ -11,6 +11,11 @@ from coverage.results import display_covered from coverage.results import should_fail_under +try: + from pytest import TerminalReporter # noqa: PT013 +except ImportError: + from _pytest.terminal import TerminalReporter + from . import CovDisabledWarning from . import CovFailUnderWarning from . import CovReportWarning @@ -333,7 +338,8 @@ def pytest_runtestloop(self, session): # it for unit tests that don't need it from coverage.misc import CoverageException - self.cov_controller.sep(self.cov_report, '=', 'coverage report') + tr = TerminalReporter(session.config, self.cov_report) + tr.write_sep('=', 'coverage report') try: self.cov_total = self.cov_controller.summary(self.cov_report) except CoverageException as exc: From 9c05143ea2379ad9a95c9d1ce9455637d4fc467c Mon Sep 17 00:00:00 2001 From: Tsvika Shapira Date: Sun, 16 Feb 2025 17:29:46 +0200 Subject: [PATCH 04/10] use correct width --- src/pytest_cov/engine.py | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/src/pytest_cov/engine.py b/src/pytest_cov/engine.py index f7896faa..03350f70 100644 --- a/src/pytest_cov/engine.py +++ b/src/pytest_cov/engine.py @@ -5,6 +5,7 @@ import functools import os import random +import shutil import socket import sys import warnings @@ -134,15 +135,36 @@ def get_node_desc(platform, version_info): return 'platform {}, python {}'.format(platform, '{}.{}.{}-{}-{}'.format(*version_info[:5])) @staticmethod - def sep(stream, s, txt): + def get_width(): + 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 + # 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. + width -= 1 + return width + + def sep(self, stream, s, txt): if hasattr(stream, 'sep'): stream.sep(s, txt) else: - sep_total = max((70 - 2 - len(txt)), 2) - sep_len = sep_total // 2 - sep_extra = sep_total % 2 - out = f'{s * sep_len} {txt} {s * (sep_len + sep_extra)}\n' - stream.write(out) + fullwidth = self.get_width() + N = max((fullwidth - len(txt) - 2) // (2 * len(s)), 1) + fill = s * N + line = f'{fill} {txt} {fill}' + # 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(s.rstrip()) <= fullwidth: + line += s.rstrip() + line += '\n' + stream.write(line) @_ensure_topdir def summary(self, stream): From fa529a1bac2cc8b3ede65a6d0a5333ad9fc40180 Mon Sep 17 00:00:00 2001 From: Tsvika Shapira Date: Sun, 16 Feb 2025 18:06:02 +0200 Subject: [PATCH 05/10] fix tests for _ --- tests/test_pytest_cov.py | 74 ++++++++++++++++++++-------------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/tests/test_pytest_cov.py b/tests/test_pytest_cov.py index 8aa2a339..7f3ade38 100644 --- a/tests/test_pytest_cov.py +++ b/tests/test_pytest_cov.py @@ -207,7 +207,7 @@ def test_central(pytester, testdir, prop): result = testdir.runpytest('-v', f'--cov={script.dirpath()}', '--cov-report=term-missing', script, *prop.args) - result.stdout.fnmatch_lines(['*- coverage: platform *, python * -*', f'test_central* {prop.result} *', '*10 passed*']) + result.stdout.fnmatch_lines(['*_ coverage: platform *, python * _*', f'test_central* {prop.result} *', '*10 passed*']) assert result.ret == 0 @@ -218,7 +218,7 @@ def test_annotate(testdir): result.stdout.fnmatch_lines( [ - '*- coverage: platform *, python * -*', + '*_ coverage: platform *, python * _*', 'Coverage annotated source written next to source', '*10 passed*', ] @@ -233,7 +233,7 @@ def test_annotate_output_dir(testdir): result.stdout.fnmatch_lines( [ - '*- coverage: platform *, python * -*', + '*_ coverage: platform *, python * _*', 'Coverage annotated source written to dir ' + DEST_DIR, '*10 passed*', ] @@ -251,7 +251,7 @@ def test_html(testdir): result.stdout.fnmatch_lines( [ - '*- coverage: platform *, python * -*', + '*_ coverage: platform *, python * _*', 'Coverage HTML written to dir htmlcov', '*10 passed*', ] @@ -269,7 +269,7 @@ def test_html_output_dir(testdir): result.stdout.fnmatch_lines( [ - '*- coverage: platform *, python * -*', + '*_ coverage: platform *, python * _*', 'Coverage HTML written to dir ' + DEST_DIR, '*10 passed*', ] @@ -289,7 +289,7 @@ def test_term_report_does_not_interact_with_html_output(testdir): result.stdout.fnmatch_lines( [ - '*- coverage: platform *, python * -*', + '*_ coverage: platform *, python * _*', 'Coverage HTML written to dir ' + DEST_DIR, '*1 passed*', ] @@ -317,7 +317,7 @@ def test_html_configured_output_dir(testdir): result.stdout.fnmatch_lines( [ - '*- coverage: platform *, python * -*', + '*_ coverage: platform *, python * _*', 'Coverage HTML written to dir somewhere', '*10 passed*', ] @@ -335,7 +335,7 @@ def test_xml_output_dir(testdir): result.stdout.fnmatch_lines( [ - '*- coverage: platform *, python * -*', + '*_ coverage: platform *, python * _*', 'Coverage XML written to file ' + XML_REPORT_NAME, '*10 passed*', ] @@ -351,7 +351,7 @@ def test_json_output_dir(testdir): result.stdout.fnmatch_lines( [ - '*- coverage: platform *, python * -*', + '*_ coverage: platform *, python * _*', 'Coverage JSON written to file ' + JSON_REPORT_NAME, '*10 passed*', ] @@ -368,7 +368,7 @@ def test_lcov_output_dir(testdir): result.stdout.fnmatch_lines( [ - '*- coverage: platform *, python * -*', + '*_ coverage: platform *, python * _*', 'Coverage LCOV written to file ' + LCOV_REPORT_NAME, '*10 passed*', ] @@ -490,7 +490,7 @@ def test_central_nonspecific(pytester, testdir, prop): testdir.tmpdir.join('.coveragerc').write(prop.fullconf) result = testdir.runpytest('-v', '--cov', '--cov-report=term-missing', script, *prop.args) - result.stdout.fnmatch_lines(['*- coverage: platform *, python * -*', f'test_central_nonspecific* {prop.result} *', '*10 passed*']) + result.stdout.fnmatch_lines(['*_ coverage: platform *, python * _*', f'test_central_nonspecific* {prop.result} *', '*10 passed*']) # multi-module coverage report assert any(line.startswith('TOTAL ') for line in result.stdout.lines) @@ -520,7 +520,7 @@ def test_central_coveragerc(pytester, testdir, prop): result.stdout.fnmatch_lines( [ - '*- coverage: platform *, python * -*', + '*_ coverage: platform *, python * _*', f'test_central_coveragerc* {prop.result} *', '*10 passed*', ] @@ -557,7 +557,7 @@ def test_central_with_path_aliasing(pytester, testdir, monkeypatch, opts, prop): result.stdout.fnmatch_lines( [ - '*- coverage: platform *, python * -*', + '*_ coverage: platform *, python * _*', f'src[\\/]mod* {prop.result} *', '*10 passed*', ] @@ -599,7 +599,7 @@ def test_foobar(bad): result.stdout.fnmatch_lines( [ - '*- coverage: platform *, python * -*', + '*_ coverage: platform *, python * _*', '*mod* 100%', '*1 passed*', ] @@ -636,7 +636,7 @@ def test_subprocess_with_path_aliasing(pytester, testdir, monkeypatch): result.stdout.fnmatch_lines( [ - '*- coverage: platform *, python * -*', + '*_ coverage: platform *, python * _*', f'src[\\/]child_script* {CHILD_SCRIPT_RESULT}*', f'src[\\/]parent_script* {PARENT_SCRIPT_RESULT}*', ] @@ -661,7 +661,7 @@ def test_show_missing_coveragerc(pytester, testdir, prop): result.stdout.fnmatch_lines( [ - '*- coverage: platform *, python * -*', + '*_ coverage: platform *, python * _*', 'Name * Stmts * Miss * Cover * Missing', f'test_show_missing_coveragerc* {prop.result} * 11*', '*10 passed*', @@ -767,7 +767,7 @@ def test_dist_collocated(pytester, testdir, prop): *prop.args, ) - result.stdout.fnmatch_lines(['*- coverage: platform *, python * -*', f'test_dist_collocated* {prop.result} *', '*10 passed*']) + result.stdout.fnmatch_lines(['*_ coverage: platform *, python * _*', f'test_dist_collocated* {prop.result} *', '*10 passed*']) assert result.ret == 0 @@ -802,7 +802,7 @@ def test_dist_not_collocated(pytester, testdir, prop): *prop.args, ) - result.stdout.fnmatch_lines(['*- coverage: platform *, python * -*', f'test_dist_not_collocated* {prop.result} *', '*10 passed*']) + result.stdout.fnmatch_lines(['*_ coverage: platform *, python * _*', f'test_dist_not_collocated* {prop.result} *', '*10 passed*']) assert result.ret == 0 @@ -838,7 +838,7 @@ def test_dist_not_collocated_coveragerc_source(pytester, testdir, prop): *prop.args, ) - result.stdout.fnmatch_lines(['*- coverage: platform *, python * -*', f'test_dist_not_collocated* {prop.result} *', '*10 passed*']) + result.stdout.fnmatch_lines(['*_ coverage: platform *, python * _*', f'test_dist_not_collocated* {prop.result} *', '*10 passed*']) assert result.ret == 0 @@ -850,7 +850,7 @@ def test_central_subprocess(testdir): result.stdout.fnmatch_lines( [ - '*- coverage: platform *, python * -*', + '*_ coverage: platform *, python * _*', f'child_script* {CHILD_SCRIPT_RESULT}*', f'parent_script* {PARENT_SCRIPT_RESULT}*', ] @@ -876,7 +876,7 @@ def test_central_subprocess_change_cwd(testdir): result.stdout.fnmatch_lines( [ - '*- coverage: platform *, python * -*', + '*_ coverage: platform *, python * _*', f'*child_script* {CHILD_SCRIPT_RESULT}*', '*parent_script* 100%*', ] @@ -904,7 +904,7 @@ def test_central_subprocess_change_cwd_with_pythonpath(pytester, testdir, monkey result.stdout.fnmatch_lines( [ - '*- coverage: platform *, python * -*', + '*_ coverage: platform *, python * _*', f'*child_script* {CHILD_SCRIPT_RESULT}*', ] ) @@ -930,7 +930,7 @@ def test_foo(): result = testdir.runpytest('-v', '--cov-config=coveragerc', f'--cov={script.dirpath()}', '--cov-branch', script) result.stdout.fnmatch_lines( [ - '*- coverage: platform *, python * -*', + '*_ coverage: platform *, python * _*', 'test_central_subprocess_no_subscript* * 3 * 0 * 100%*', ] ) @@ -948,7 +948,7 @@ def test_dist_subprocess_collocated(testdir): result.stdout.fnmatch_lines( [ - '*- coverage: platform *, python * -*', + '*_ coverage: platform *, python * _*', f'child_script* {CHILD_SCRIPT_RESULT}*', f'parent_script* {PARENT_SCRIPT_RESULT}*', ] @@ -988,7 +988,7 @@ def test_dist_subprocess_not_collocated(pytester, testdir, tmpdir): result.stdout.fnmatch_lines( [ - '*- coverage: platform *, python * -*', + '*_ coverage: platform *, python * _*', f'child_script* {CHILD_SCRIPT_RESULT}*', f'parent_script* {PARENT_SCRIPT_RESULT}*', ] @@ -1061,7 +1061,7 @@ def test_funcarg(testdir): result = testdir.runpytest('-v', f'--cov={script.dirpath()}', '--cov-report=term-missing', script) - result.stdout.fnmatch_lines(['*- coverage: platform *, python * -*', 'test_funcarg* 3 * 100%*', '*1 passed*']) + result.stdout.fnmatch_lines(['*_ coverage: platform *, python * _*', 'test_funcarg* 3 * 100%*', '*1 passed*']) assert result.ret == 0 @@ -1111,7 +1111,7 @@ def test_run(): result = testdir.runpytest('-vv', f'--cov={script.dirpath()}', '--cov-report=term-missing', script) - result.stdout.fnmatch_lines(['*- coverage: platform *, python * -*', 'test_cleanup_on_sigterm* 26-27', '*1 passed*']) + result.stdout.fnmatch_lines(['*_ coverage: platform *, python * _*', 'test_cleanup_on_sigterm* 26-27', '*1 passed*']) assert result.ret == 0 @@ -1157,7 +1157,7 @@ def test_run(): result = testdir.runpytest('-vv', f'--cov={script.dirpath()}', '--cov-report=term-missing', script) - result.stdout.fnmatch_lines(['*- coverage: platform *, python * -*', f'test_cleanup_on_sigterm* {setup[1]}', '*1 passed*']) + result.stdout.fnmatch_lines(['*_ coverage: platform *, python * _*', f'test_cleanup_on_sigterm* {setup[1]}', '*1 passed*']) assert result.ret == 0 @@ -1201,7 +1201,7 @@ def test_run(): result = testdir.runpytest('-vv', '--assert=plain', f'--cov={script.dirpath()}', '--cov-report=term-missing', script) - result.stdout.fnmatch_lines(['*- coverage: platform *, python * -*', f'test_cleanup_on_sigterm* {setup[1]}', '*1 passed*']) + result.stdout.fnmatch_lines(['*_ coverage: platform *, python * _*', f'test_cleanup_on_sigterm* {setup[1]}', '*1 passed*']) assert result.ret == 0 @@ -1236,7 +1236,7 @@ def test_run(): result = testdir.runpytest('-vv', '--assert=plain', f'--cov={script.dirpath()}', '--cov-report=term-missing', script) - result.stdout.fnmatch_lines(['*- coverage: platform *, python * -*', 'test_cleanup_on_sigterm* 88% 19-20', '*1 passed*']) + result.stdout.fnmatch_lines(['*_ coverage: platform *, python * _*', 'test_cleanup_on_sigterm* 88% 19-20', '*1 passed*']) assert result.ret == 0 @@ -1273,7 +1273,7 @@ def test_run(): result = testdir.runpytest('-vv', '--assert=plain', f'--cov={script.dirpath()}', '--cov-report=term-missing', script) - result.stdout.fnmatch_lines(['*- coverage: platform *, python * -*', 'test_cleanup_on_sigterm* 89% 22-23', '*1 passed*']) + result.stdout.fnmatch_lines(['*_ coverage: platform *, python * _*', 'test_cleanup_on_sigterm* 89% 22-23', '*1 passed*']) assert result.ret == 0 @@ -1505,7 +1505,7 @@ def test_dist_boxed(testdir): result = testdir.runpytest('-v', '--assert=plain', f'--cov={script.dirpath()}', '--boxed', script) - result.stdout.fnmatch_lines(['*- coverage: platform *, python * -*', f'test_dist_boxed* {SCRIPT_SIMPLE_RESULT}*', '*1 passed*']) + result.stdout.fnmatch_lines(['*_ coverage: platform *, python * _*', f'test_dist_boxed* {SCRIPT_SIMPLE_RESULT}*', '*1 passed*']) assert result.ret == 0 @@ -1516,7 +1516,7 @@ def test_dist_bare_cov(testdir): result = testdir.runpytest('-v', '--cov', '-n', '1', script) - result.stdout.fnmatch_lines(['*- coverage: platform *, python * -*', f'test_dist_bare_cov* {SCRIPT_SIMPLE_RESULT}*', '*1 passed*']) + result.stdout.fnmatch_lines(['*_ coverage: platform *, python * _*', f'test_dist_bare_cov* {SCRIPT_SIMPLE_RESULT}*', '*1 passed*']) assert result.ret == 0 @@ -1747,7 +1747,7 @@ def test_append_coverage_subprocess(testdir): result.stdout.fnmatch_lines( [ - '*- coverage: platform *, python * -*', + '*_ coverage: platform *, python * _*', f'child_script* {CHILD_SCRIPT_RESULT}*', f'parent_script* {PARENT_SCRIPT_RESULT}*', ] @@ -1781,7 +1781,7 @@ def test_double_cov(testdir): script = testdir.makepyfile(SCRIPT_SIMPLE) result = testdir.runpytest('-v', '--assert=plain', '--cov', f'--cov={script.dirpath()}', script) - result.stdout.fnmatch_lines(['*- coverage: platform *, python * -*', f'test_double_cov* {SCRIPT_SIMPLE_RESULT}*', '*1 passed*']) + result.stdout.fnmatch_lines(['*_ coverage: platform *, python * _*', f'test_double_cov* {SCRIPT_SIMPLE_RESULT}*', '*1 passed*']) assert result.ret == 0 @@ -1789,7 +1789,7 @@ def test_double_cov2(testdir): script = testdir.makepyfile(SCRIPT_SIMPLE) result = testdir.runpytest('-v', '--assert=plain', '--cov', '--cov', script) - result.stdout.fnmatch_lines(['*- coverage: platform *, python * -*', f'test_double_cov2* {SCRIPT_SIMPLE_RESULT}*', '*1 passed*']) + result.stdout.fnmatch_lines(['*_ coverage: platform *, python * _*', f'test_double_cov2* {SCRIPT_SIMPLE_RESULT}*', '*1 passed*']) assert result.ret == 0 @@ -1804,7 +1804,7 @@ def test_cov_reset_then_set(testdir): script = testdir.makepyfile(SCRIPT_SIMPLE) result = testdir.runpytest('-v', '--assert=plain', f'--cov={script.dirpath()}', '--cov-reset', f'--cov={script.dirpath()}', script) - result.stdout.fnmatch_lines(['*- coverage: platform *, python * -*', f'test_cov_reset_then_set* {SCRIPT_SIMPLE_RESULT}*', '*1 passed*']) + result.stdout.fnmatch_lines(['*_ coverage: platform *, python * _*', f'test_cov_reset_then_set* {SCRIPT_SIMPLE_RESULT}*', '*1 passed*']) @pytest.mark.skipif('sys.platform == "win32" and platform.python_implementation() == "PyPy"') From 5f379a7cdac145c1d7da8bc7534aee5b0d4b50b1 Mon Sep 17 00:00:00 2001 From: Tsvika Shapira Date: Thu, 20 Feb 2025 14:36:44 +0200 Subject: [PATCH 06/10] move heading to pytest_terminal_summary --- src/pytest_cov/plugin.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/pytest_cov/plugin.py b/src/pytest_cov/plugin.py index 69a9ecb1..393ef3ae 100644 --- a/src/pytest_cov/plugin.py +++ b/src/pytest_cov/plugin.py @@ -11,11 +11,6 @@ from coverage.results import display_covered from coverage.results import should_fail_under -try: - from pytest import TerminalReporter # noqa: PT013 -except ImportError: - from _pytest.terminal import TerminalReporter - from . import CovDisabledWarning from . import CovFailUnderWarning from . import CovReportWarning @@ -222,6 +217,7 @@ def __init__(self, options, pluginmanager, start=True, no_cov_should_warn=False) self._start_path = None self._disabled = False self.options = options + self.wrote_heading = False is_dist = getattr(options, 'numprocesses', False) or getattr(options, 'distload', False) or getattr(options, 'dist', 'no') != 'no' if getattr(options, 'no_cov', False): @@ -338,8 +334,6 @@ def pytest_runtestloop(self, session): # it for unit tests that don't need it from coverage.misc import CoverageException - tr = TerminalReporter(session.config, self.cov_report) - tr.write_sep('=', 'coverage report') try: self.cov_total = self.cov_controller.summary(self.cov_report) except CoverageException as exc: @@ -363,9 +357,15 @@ def pytest_runtestloop(self, session): # make sure we get the EXIT_TESTSFAILED exit code compat_session.testsfailed += 1 + def write_heading(self, terminalreporter): + if not self.wrote_heading: + terminalreporter.write_sep('=', 'tests coverage') + self.wrote_heading = True + def pytest_terminal_summary(self, terminalreporter): if self._disabled: if self.options.no_cov_should_warn: + self.write_heading(terminalreporter) message = 'Coverage disabled via --no-cov switch!' terminalreporter.write(f'WARNING: {message}\n', red=True, bold=True) warnings.warn(CovDisabledWarning(message), stacklevel=1) @@ -381,9 +381,11 @@ def pytest_terminal_summary(self, terminalreporter): # Avoid undesirable new lines when output is disabled with "--cov-report=". if report: + self.write_heading(terminalreporter) terminalreporter.write('\n' + report + '\n') if self.options.cov_fail_under is not None and self.options.cov_fail_under > 0: + self.write_heading(terminalreporter) failed = self.cov_total < self.options.cov_fail_under markup = {'red': True, 'bold': True} if failed else {'green': True} message = '{fail}Required test coverage of {required}% {reached}. ' 'Total coverage: {actual:.2f}%\n'.format( From beb8542f748a4ff1a04f339449e57c7a1d170569 Mon Sep 17 00:00:00 2001 From: Tsvika Shapira Date: Thu, 20 Feb 2025 14:36:44 +0200 Subject: [PATCH 07/10] convert newlines to conform with the rest of pytest --- src/pytest_cov/engine.py | 2 +- src/pytest_cov/plugin.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/pytest_cov/engine.py b/src/pytest_cov/engine.py index 03350f70..512e5d88 100644 --- a/src/pytest_cov/engine.py +++ b/src/pytest_cov/engine.py @@ -163,7 +163,7 @@ def sep(self, stream, s, txt): # trailing space is not important at the end of the line. if len(line) + len(s.rstrip()) <= fullwidth: line += s.rstrip() - line += '\n' + line += '\n\n' stream.write(line) @_ensure_topdir diff --git a/src/pytest_cov/plugin.py b/src/pytest_cov/plugin.py index 393ef3ae..f38ade51 100644 --- a/src/pytest_cov/plugin.py +++ b/src/pytest_cov/plugin.py @@ -379,10 +379,9 @@ def pytest_terminal_summary(self, terminalreporter): report = self.cov_report.getvalue() - # Avoid undesirable new lines when output is disabled with "--cov-report=". if report: self.write_heading(terminalreporter) - terminalreporter.write('\n' + report + '\n') + terminalreporter.write(report) if self.options.cov_fail_under is not None and self.options.cov_fail_under > 0: self.write_heading(terminalreporter) From dd404a96c2f87f86e3c3afc0f27371f526fbd57d Mon Sep 17 00:00:00 2001 From: Tsvika Shapira Date: Thu, 6 Mar 2025 14:46:05 +0200 Subject: [PATCH 08/10] made wrote_headings private --- src/pytest_cov/plugin.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pytest_cov/plugin.py b/src/pytest_cov/plugin.py index f38ade51..011ef704 100644 --- a/src/pytest_cov/plugin.py +++ b/src/pytest_cov/plugin.py @@ -217,7 +217,7 @@ def __init__(self, options, pluginmanager, start=True, no_cov_should_warn=False) self._start_path = None self._disabled = False self.options = options - self.wrote_heading = False + self._wrote_heading = False is_dist = getattr(options, 'numprocesses', False) or getattr(options, 'distload', False) or getattr(options, 'dist', 'no') != 'no' if getattr(options, 'no_cov', False): @@ -358,9 +358,9 @@ def pytest_runtestloop(self, session): compat_session.testsfailed += 1 def write_heading(self, terminalreporter): - if not self.wrote_heading: + if not self._wrote_heading: terminalreporter.write_sep('=', 'tests coverage') - self.wrote_heading = True + self._wrote_heading = True def pytest_terminal_summary(self, terminalreporter): if self._disabled: From d1eb861fc27c8b15bc9e2396f0d5b3613b0a26f7 Mon Sep 17 00:00:00 2001 From: Tsvika Shapira Date: Thu, 6 Mar 2025 15:13:11 +0200 Subject: [PATCH 09/10] move section between functions --- src/pytest_cov/engine.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/pytest_cov/engine.py b/src/pytest_cov/engine.py index 512e5d88..bd2eded2 100644 --- a/src/pytest_cov/engine.py +++ b/src/pytest_cov/engine.py @@ -140,14 +140,6 @@ def get_width(): # The Windows get_terminal_size may be bogus, let's sanify a bit. if width < 40: width = 80 - # 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. - width -= 1 return width def sep(self, stream, s, txt): @@ -155,6 +147,14 @@ def sep(self, stream, s, txt): stream.sep(s, txt) else: fullwidth = self.get_width() + # 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 N = max((fullwidth - len(txt) - 2) // (2 * len(s)), 1) fill = s * N line = f'{fill} {txt} {fill}' From 27db5e84210f26d91768ded725910252b0ccb427 Mon Sep 17 00:00:00 2001 From: Tsvika Shapira Date: Thu, 6 Mar 2025 15:14:00 +0200 Subject: [PATCH 10/10] add reference to code source --- src/pytest_cov/engine.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/pytest_cov/engine.py b/src/pytest_cov/engine.py index bd2eded2..009b96f6 100644 --- a/src/pytest_cov/engine.py +++ b/src/pytest_cov/engine.py @@ -136,6 +136,7 @@ def get_node_desc(platform, version_info): @staticmethod def get_width(): + # taken from https://github.com/pytest-dev/pytest/blob/33c7b05a/src/_pytest/_io/terminalwriter.py#L26 width, _ = shutil.get_terminal_size(fallback=(80, 24)) # The Windows get_terminal_size may be bogus, let's sanify a bit. if width < 40: @@ -147,6 +148,7 @@ def sep(self, stream, s, txt): stream.sep(s, txt) else: fullwidth = self.get_width() + # taken from https://github.com/pytest-dev/pytest/blob/33c7b05a/src/_pytest/_io/terminalwriter.py#L126 # The goal is to have the line be as long as possible # under the condition that len(line) <= fullwidth. if sys.platform == 'win32': @@ -163,6 +165,7 @@ def sep(self, stream, s, txt): # trailing space is not important at the end of the line. if len(line) + len(s.rstrip()) <= fullwidth: line += s.rstrip() + # (end of terminalwriter borrowed code) line += '\n\n' stream.write(line)