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..258877e 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,31 @@ 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)