Propagate timestamps from CallInfo to TestReport objects (#10711)

This makes it possible to correlate pytest stages with external events, and also makes it readable when TestReports are exported externall (for example with pytest-reportlog).

Closes #10710
This commit is contained in:
HTRafal 2023-02-10 21:52:54 +01:00 committed by GitHub
parent 59e7d2bbc9
commit 5e1c3d2477
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 34 additions and 0 deletions

View File

@ -297,6 +297,7 @@ Ram Rachum
Ran Benita Ran Benita
Raphael Castaneda Raphael Castaneda
Raphael Pierzina Raphael Pierzina
Rafal Semik
Raquel Alegre Raquel Alegre
Ravi Chandra Ravi Chandra
Robert Holt Robert Holt

View File

@ -0,0 +1 @@
Added ``start`` and ``stop`` timestamps to ``TestReport`` objects.

View File

@ -262,6 +262,8 @@ class TestReport(BaseReport):
when: "Literal['setup', 'call', 'teardown']", when: "Literal['setup', 'call', 'teardown']",
sections: Iterable[Tuple[str, str]] = (), sections: Iterable[Tuple[str, str]] = (),
duration: float = 0, duration: float = 0,
start: float = 0,
stop: float = 0,
user_properties: Optional[Iterable[Tuple[str, object]]] = None, user_properties: Optional[Iterable[Tuple[str, object]]] = None,
**extra, **extra,
) -> None: ) -> None:
@ -299,6 +301,11 @@ class TestReport(BaseReport):
#: Time it took to run just the test. #: Time it took to run just the test.
self.duration: float = duration self.duration: float = duration
#: The system time when the call started, in seconds since the epoch.
self.start: float = start
#: The system time when the call ended, in seconds since the epoch.
self.stop: float = stop
self.__dict__.update(extra) self.__dict__.update(extra)
def __repr__(self) -> str: def __repr__(self) -> str:
@ -317,6 +324,8 @@ class TestReport(BaseReport):
# Remove "collect" from the Literal type -- only for collection calls. # Remove "collect" from the Literal type -- only for collection calls.
assert when != "collect" assert when != "collect"
duration = call.duration duration = call.duration
start = call.start
stop = call.stop
keywords = {x: 1 for x in item.keywords} keywords = {x: 1 for x in item.keywords}
excinfo = call.excinfo excinfo = call.excinfo
sections = [] sections = []
@ -361,6 +370,8 @@ class TestReport(BaseReport):
when, when,
sections, sections,
duration, duration,
start,
stop,
user_properties=item.user_properties, user_properties=item.user_properties,
) )

View File

@ -6,6 +6,7 @@ from _pytest._code.code import ExceptionChainRepr
from _pytest._code.code import ExceptionRepr from _pytest._code.code import ExceptionRepr
from _pytest.config import Config from _pytest.config import Config
from _pytest.pytester import Pytester from _pytest.pytester import Pytester
from _pytest.python_api import approx
from _pytest.reports import CollectReport from _pytest.reports import CollectReport
from _pytest.reports import TestReport from _pytest.reports import TestReport
@ -415,6 +416,26 @@ class TestReportSerialization:
result.stdout.fnmatch_lines(["E *Error: No module named 'unknown'"]) result.stdout.fnmatch_lines(["E *Error: No module named 'unknown'"])
result.stdout.no_fnmatch_line("ERROR - *ConftestImportFailure*") result.stdout.no_fnmatch_line("ERROR - *ConftestImportFailure*")
def test_report_timestamps_match_duration(self, pytester: Pytester, mock_timing):
reprec = pytester.inline_runsource(
"""
import pytest
from _pytest import timing
@pytest.fixture
def fixture_():
timing.sleep(5)
yield
timing.sleep(5)
def test_1(fixture_): timing.sleep(10)
"""
)
reports = reprec.getreports("pytest_runtest_logreport")
assert len(reports) == 3
for report in reports:
data = report._to_json()
loaded_report = TestReport._from_json(data)
assert loaded_report.stop - loaded_report.start == approx(report.duration)
class TestHooks: class TestHooks:
"""Test that the hooks are working correctly for plugins""" """Test that the hooks are working correctly for plugins"""