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:
parent
59e7d2bbc9
commit
5e1c3d2477
1
AUTHORS
1
AUTHORS
|
@ -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
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Added ``start`` and ``stop`` timestamps to ``TestReport`` objects.
|
|
@ -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,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -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"""
|
||||||
|
|
Loading…
Reference in New Issue