From 080c5783a5ce91d8614caab8945b7a41b4d7804d Mon Sep 17 00:00:00 2001 From: Louis Shawn Date: Fri, 6 Feb 2026 08:56:17 +0800 Subject: [PATCH 1/2] fix: strip ANSI escape sequences from reports --- src/pytest_reportlog/plugin.py | 17 +++++++++++++++++ tests/test_reportlog.py | 31 +++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/src/pytest_reportlog/plugin.py b/src/pytest_reportlog/plugin.py index d44d56f..1f00a68 100644 --- a/src/pytest_reportlog/plugin.py +++ b/src/pytest_reportlog/plugin.py @@ -1,10 +1,25 @@ import json +import re from typing import Dict, Any, TextIO from _pytest.pathlib import Path import pytest +_ANSI_ESCAPE_RE = re.compile(r"\x1b(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])") + + +def _strip_ansi(obj: Any) -> Any: + if isinstance(obj, str): + return _ANSI_ESCAPE_RE.sub("", obj) + if isinstance(obj, dict): + return {_strip_ansi(k): _strip_ansi(v) for k, v in obj.items()} + if isinstance(obj, list): + return [_strip_ansi(v) for v in obj] + if isinstance(obj, tuple): + return tuple(_strip_ansi(v) for v in obj) + return obj + def pytest_addoption(parser): group = parser.getgroup("terminal reporting", "report-log plugin options") @@ -69,10 +84,12 @@ def close(self): self._file = None def _write_json_data(self, data): + data = _strip_ansi(data) try: json_data = json.dumps(data) except TypeError: data = cleanup_unserializable(data) + data = _strip_ansi(data) json_data = json.dumps(data) self._file.write(json_data + "\n") self._file.flush() diff --git a/tests/test_reportlog.py b/tests/test_reportlog.py index 032df8b..312b83c 100644 --- a/tests/test_reportlog.py +++ b/tests/test_reportlog.py @@ -184,6 +184,7 @@ def __str__(self): def test_subtest(pytester, tmp_path): """Regression test for #90.""" + pytest.importorskip("pytest_subtests") pytester.makepyfile(""" def test_foo(subtests): with subtests.test(): @@ -196,3 +197,33 @@ def test_foo(subtests): for line in lines: data = json.loads(line) assert "$report_type" in data + + +def _contains_ansi_escape(obj) -> bool: + if isinstance(obj, str): + return "\x1b" in obj + if isinstance(obj, dict): + return any( + _contains_ansi_escape(k) or _contains_ansi_escape(v) for k, v in obj.items() + ) + if isinstance(obj, (list, tuple)): + return any(_contains_ansi_escape(v) for v in obj) + return False + + +def test_strips_ansi_escape_sequences(testdir, tmp_path): + testdir.makepyfile( + r""" + import pytest + + def test_fail_with_ansi_message(): + pytest.fail("\x1b[31mRED\x1b[0m") + """ + ) + fn = tmp_path / "result.log" + result = testdir.runpytest(f"--report-log={fn}") + assert result.ret == pytest.ExitCode.TESTS_FAILED + + for line in fn.read_text("UTF-8").splitlines(): + data = json.loads(line) + assert not _contains_ansi_escape(data) From ba598739cb58ec061d3d41d2e973bb4a507f00ca Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 6 Feb 2026 00:56:49 +0000 Subject: [PATCH 2/2] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/test_reportlog.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/test_reportlog.py b/tests/test_reportlog.py index 312b83c..258877e 100644 --- a/tests/test_reportlog.py +++ b/tests/test_reportlog.py @@ -212,14 +212,12 @@ def _contains_ansi_escape(obj) -> bool: def test_strips_ansi_escape_sequences(testdir, tmp_path): - testdir.makepyfile( - r""" + testdir.makepyfile(r""" import pytest def test_fail_with_ansi_message(): pytest.fail("\x1b[31mRED\x1b[0m") - """ - ) + """) fn = tmp_path / "result.log" result = testdir.runpytest(f"--report-log={fn}") assert result.ret == pytest.ExitCode.TESTS_FAILED