From 7b9a41452476132edac9d142a9165dfd4f75a762 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 20 Mar 2019 20:45:15 -0300 Subject: [PATCH] Add pytest_report_serialize and pytest_report_unserialize hooks These hooks will be used by pytest-xdist and pytest-subtests to serialize and customize reports. --- src/_pytest/config/__init__.py | 1 + src/_pytest/hookspec.py | 35 ++++++++++++++++++++++++ src/_pytest/reports.py | 16 +++++++++++ testing/test_reports.py | 50 ++++++++++++++++++++++++++++++++++ 4 files changed, 102 insertions(+) diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index ca2bebabc..4ed9deac4 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -140,6 +140,7 @@ default_plugins = ( "stepwise", "warnings", "logging", + "reports", ) diff --git a/src/_pytest/hookspec.py b/src/_pytest/hookspec.py index 0641e3bc5..a465923f9 100644 --- a/src/_pytest/hookspec.py +++ b/src/_pytest/hookspec.py @@ -375,6 +375,41 @@ def pytest_runtest_logreport(report): the respective phase of executing a test. """ +@hookspec(firstresult=True) +def pytest_report_serialize(config, report): + """ + .. warning:: + This hook is experimental and subject to change between pytest releases, even + bug fixes. + + The intent is for this to be used by plugins maintained by the core-devs, such + as ``pytest-xdist``, ``pytest-subtests``, and as a replacement for the internal + 'resultlog' plugin. + + In the future it might become part of the public hook API. + + Serializes the given report object into a data structure suitable for sending + over the wire, or converted to JSON. + """ + + +@hookspec(firstresult=True) +def pytest_report_unserialize(config, data): + """ + .. warning:: + This hook is experimental and subject to change between pytest releases, even + bug fixes. + + The intent is for this to be used by plugins maintained by the core-devs, such + as ``pytest-xdist``, ``pytest-subtests``, and as a replacement for the internal + 'resultlog' plugin. + + In the future it might become part of the public hook API. + + Restores a report object previously serialized with pytest_report_serialize().; + """ + + # ------------------------------------------------------------------------- # Fixture related hooks # ------------------------------------------------------------------------- diff --git a/src/_pytest/reports.py b/src/_pytest/reports.py index 5f0777375..b4160eb96 100644 --- a/src/_pytest/reports.py +++ b/src/_pytest/reports.py @@ -404,3 +404,19 @@ class CollectErrorRepr(TerminalRepr): def toterminal(self, out): out.line(self.longrepr, red=True) + + +def pytest_report_serialize(report): + if isinstance(report, (TestReport, CollectReport)): + data = report._to_json() + data["_report_type"] = report.__class__.__name__ + return data + + +def pytest_report_unserialize(data): + if "_report_type" in data: + if data["_report_type"] == "TestReport": + return TestReport._from_json(data) + elif data["_report_type"] == "CollectReport": + return CollectReport._from_json(data) + assert "Unknown report_type unserialize data: {}".format(data["_report_type"]) diff --git a/testing/test_reports.py b/testing/test_reports.py index 322995326..3413a805b 100644 --- a/testing/test_reports.py +++ b/testing/test_reports.py @@ -181,3 +181,53 @@ class TestReportSerialization(object): assert newrep.skipped == rep.skipped if rep.failed: assert newrep.longrepr == str(rep.longrepr) + + +class TestHooks: + """Test that the hooks are working correctly for plugins""" + + def test_test_report(self, testdir, pytestconfig): + testdir.makepyfile( + """ + import os + def test_a(): assert False + def test_b(): pass + """ + ) + reprec = testdir.inline_run() + reports = reprec.getreports("pytest_runtest_logreport") + assert len(reports) == 6 + for rep in reports: + data = pytestconfig.hook.pytest_report_serialize( + config=pytestconfig, report=rep + ) + assert data["_report_type"] == "TestReport" + new_rep = pytestconfig.hook.pytest_report_unserialize( + config=pytestconfig, data=data + ) + assert new_rep.nodeid == rep.nodeid + assert new_rep.when == rep.when + assert new_rep.outcome == rep.outcome + + def test_collect_report(self, testdir, pytestconfig): + testdir.makepyfile( + """ + import os + def test_a(): assert False + def test_b(): pass + """ + ) + reprec = testdir.inline_run() + reports = reprec.getreports("pytest_collectreport") + assert len(reports) == 2 + for rep in reports: + data = pytestconfig.hook.pytest_report_serialize( + config=pytestconfig, report=rep + ) + assert data["_report_type"] == "CollectReport" + new_rep = pytestconfig.hook.pytest_report_unserialize( + config=pytestconfig, data=data + ) + assert new_rep.nodeid == rep.nodeid + assert new_rep.when == "collect" + assert new_rep.outcome == rep.outcome