diff --git a/launchable/test_runners/pytest.py b/launchable/test_runners/pytest.py index 2919767f0..3ecbbd94d 100644 --- a/launchable/test_runners/pytest.py +++ b/launchable/test_runners/pytest.py @@ -264,27 +264,41 @@ def parse_func( stderr = "" longrepr = data.get("longrepr", None) if longrepr: - message = None - reprcrash = longrepr.get("reprcrash", None) - if reprcrash: - message = reprcrash.get("message", None) - - text = None - reprtraceback = longrepr.get("reprtraceback", None) - if reprtraceback: - reprentries = reprtraceback.get("reprentries", None) - if reprentries: - for r in reprentries: - d = r.get("data", None) - if d: - text = "\n".join(d.get("lines", [])) - - if message and text: - stderr = message + "\n" + text - elif message: - stderr = stderr + message - elif text: - stderr = stderr + text + # https://github.com/pytest-dev/pytest/blob/1d7d63555e431d4562bcacbdc97038b0613d20ba/src/_pytest/reports.py#L60 + if isinstance(longrepr, dict): + # https://github.com/pytest-dev/pytest/blob/1d7d63555e431d4562bcacbdc97038b0613d20ba/src/_pytest/reports.py#L361 + message = None + reprcrash = longrepr.get("reprcrash", None) + if reprcrash: + message = reprcrash.get("message", None) + + text = None + reprtraceback = longrepr.get("reprtraceback", None) + if reprtraceback: + reprentries = reprtraceback.get("reprentries", None) + if reprentries: + for r in reprentries: + d = r.get("data", None) + if d: + text = "\n".join(d.get("lines", [])) + + if message and text: + stderr = message + "\n" + text + elif message: + stderr = stderr + message + elif text: + stderr = stderr + text + elif isinstance(longrepr, list): + # [path, lineno, messge] + # https://github.com/pytest-dev/pytest/blob/1d7d63555e431d4562bcacbdc97038b0613d20ba/src/_pytest/reports.py#L371 + if len(longrepr) == 3: + stderr = longrepr[2] + + elif isinstance(longrepr, str): + # When longrepr is a string, it is the same as the stderr. + # https://github.com/pytest-dev/pytest/blob/1d7d63555e431d4562bcacbdc97038b0613d20ba/src/_pytest/reports.py#L377 + # https://github.com/pytest-dev/pytest/blob/1d7d63555e431d4562bcacbdc97038b0613d20ba/src/_pytest/nodes.py#L470 + stderr = longrepr test_path = _parse_pytest_nodeid(nodeid) for path in test_path: diff --git a/tests/test_runners/test_pytest.py b/tests/test_runners/test_pytest.py index 092fd3f31..5cfe90790 100644 --- a/tests/test_runners/test_pytest.py +++ b/tests/test_runners/test_pytest.py @@ -1,11 +1,12 @@ import gzip import json import os -from unittest import mock +import tempfile +from unittest import TestCase, mock import responses # type: ignore -from launchable.test_runners.pytest import _parse_pytest_nodeid +from launchable.test_runners.pytest import PytestJSONReportParser, _parse_pytest_nodeid from tests.cli_test_case import CliTestCase @@ -77,3 +78,63 @@ def test_parse_pytest_nodeid(self): {"type": "class", "name": "tests.fooo.func4_test"}, {"type": "testcase", "name": "test_func6"}, ]) + + +class PytestJSONReportParserLongreprTest(TestCase): + class DummyClient: + pass + + def setUp(self): + self.parser = PytestJSONReportParser(self.DummyClient()) + + def _parse_line(self, data): + with tempfile.NamedTemporaryFile(mode="w+", delete=False) as f: + f.write(json.dumps(data) + "\n") + f.flush() + results = list(self.parser.parse_func(f.name)) + return results + + def _make_event_data(self, longrepr): + return { + "nodeid": "tests/test_sample.py::test_fail", + "when": "call", + "outcome": "failed", + "longrepr": longrepr, + } + + def _assert_stderr(self, events, expected_stderr): + self.assertEqual(events[0]["stderr"], expected_stderr) + + def test_longrepr_dict_message_and_text(self): + data = self._make_event_data( + { + "reprcrash": {"message": "AssertionError: fail"}, + "reprtraceback": { + "reprentries": [{"data": {"lines": ["line1", "line2"]}}] + }, + } + ) + events = self._parse_line(data) + self._assert_stderr(events, "AssertionError: fail\nline1\nline2") + + def test_longrepr_dict_only_message(self): + data = self._make_event_data({"reprcrash": {"message": "Only message"}}) + events = self._parse_line(data) + self._assert_stderr(events, "Only message") + + def test_longrepr_dict_only_text(self): + data = self._make_event_data( + {"reprtraceback": {"reprentries": [{"data": {"lines": ["text only"]}}]}} + ) + events = self._parse_line(data) + self._assert_stderr(events, "text only") + + def test_longrepr_list(self): + data = self._make_event_data(["file.py", 10, "list message"]) + events = self._parse_line(data) + self._assert_stderr(events, "list message") + + def test_longrepr_str(self): + data = self._make_event_data("string message") + events = self._parse_line(data) + self._assert_stderr(events, "string message")