def test_chained_exceptions(
        self, pytester: Pytester, tw_mock, report_class
    ) -> None:
        """Check serialization/deserialization of report objects containing chained exceptions (#5786)"""
        pytester.makepyfile(
            f"""
            def foo():
                raise ValueError('value error')
            def test_a():
                try:
                    foo()
                except ValueError as e:
                    raise RuntimeError('runtime error') from e
            if {report_class is CollectReport}:
                test_a()
        """
        )

        reprec = pytester.inline_run()
        if report_class is TestReport:
            reports: Sequence[TestReport] | Sequence[CollectReport] = reprec.getreports(
                "pytest_runtest_logreport"
            )
            # we have 3 reports: setup/call/teardown
            assert len(reports) == 3
            # get the call report
            report = reports[1]
        else:
            assert report_class is CollectReport
            # three collection reports: session, test file, directory
            reports = reprec.getreports("pytest_collectreport")
            assert len(reports) == 3
            report = reports[1]

        def check_longrepr(longrepr: ExceptionChainRepr) -> None:
            """Check the attributes of the given longrepr object according to the test file.

            We can get away with testing both CollectReport and TestReport with this function because
            the longrepr objects are very similar.
            """
            assert isinstance(longrepr, ExceptionChainRepr)
            assert longrepr.sections == [("title", "contents", "=")]
            assert len(longrepr.chain) == 2
            entry1, entry2 = longrepr.chain
            tb1, fileloc1, desc1 = entry1
            tb2, fileloc2, desc2 = entry2

            assert "ValueError('value error')" in str(tb1)
            assert "RuntimeError('runtime error')" in str(tb2)

            assert (
                desc1
                == "The above exception was the direct cause of the following exception:"
            )
            assert desc2 is None

        assert report.failed
        assert len(report.sections) == 0
        assert isinstance(report.longrepr, ExceptionChainRepr)
        report.longrepr.addsection("title", "contents", "=")
        check_longrepr(report.longrepr)

        data = report._to_json()
        loaded_report = report_class._from_json(data)

        assert loaded_report.failed
        check_longrepr(loaded_report.longrepr)

        # make sure we don't blow up on ``toterminal`` call; we don't test the actual output because it is very
        # brittle and hard to maintain, but we can assume it is correct because ``toterminal`` is already tested
        # elsewhere and we do check the contents of the longrepr object after loading it.
        loaded_report.longrepr.toterminal(tw_mock)
Check serialization/deserialization of report objects containing chained exceptions (#5786)
testing/test_reports.py