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