From 72ba487e0bab45ab450294dbdde0620bd810d446 Mon Sep 17 00:00:00 2001 From: Mahesh Date: Tue, 10 Feb 2026 13:11:44 +0530 Subject: [PATCH 1/2] AIENG-304-elapsed-timestamp --- launchable/test_runners/pytest.py | 38 ++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/launchable/test_runners/pytest.py b/launchable/test_runners/pytest.py index 3341628ff..3ca1eafdc 100644 --- a/launchable/test_runners/pytest.py +++ b/launchable/test_runners/pytest.py @@ -3,7 +3,8 @@ import os import pathlib import subprocess -from typing import Generator, List +from datetime import datetime, timezone +from typing import Generator, List, Optional import click from junitparser import Properties, TestCase # type: ignore @@ -13,7 +14,6 @@ from . import launchable - # Please specify junit_family=legacy for pytest report format. if using pytest version 6 or higher. # - pytest has changed its default test report format from xunit1 to xunit2 since version 6. # - https://docs.pytest.org/en/latest/deprecations.html#junit-family-default-value-change-to-xunit2 @@ -36,6 +36,15 @@ # # + + +def _ts_to_iso(ts: Optional[float]) -> Optional[str]: + # convert to ISO-8601 formatted date + if ts is None: + return None + return datetime.fromtimestamp(ts, tz=timezone.utc).isoformat() + + @click.argument('source_roots', required=False, nargs=-1) @launchable.subset def subset(client, source_roots: List[str]): @@ -326,15 +335,38 @@ def parse_func( else: props = None + # extract raw timestamps + start_ts = data.get("start") + stop_ts = data.get("stop") + + # convert to ISO-8601 + start_iso = _ts_to_iso(start_ts) + stop_iso = _ts_to_iso(stop_ts) + + print( + f"DEBUG pytest timing start={start_iso}, stop={stop_iso}" + ) + + event_data = {} + if start_iso: + event_data["start_timestamp"] = start_iso + if stop_iso: + event_data["stop_timestamp"] = stop_iso + test_path = _parse_pytest_nodeid(nodeid) for path in test_path: if path.get("type") == "file": path["name"] = pathlib.Path(path["name"]).as_posix() + data_payload = event_data if event_data else None + + # stop_iso is being passed as timestamp as it reflects event finalization (start + duration = stop) + # sending both start and stop time in the event data field yield CaseEvent.create( test_path=test_path, duration_secs=data.get("duration", 0), status=status, stdout=stdout, stderr=stderr, - data=props) + timestamp=stop_iso, + data=data_payload) From a3216420cca39401f5a174c014ed8ede20013961 Mon Sep 17 00:00:00 2001 From: Mahesh Date: Mon, 16 Feb 2026 16:01:30 +0530 Subject: [PATCH 2/2] Wording changes --- launchable/test_runners/pytest.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/launchable/test_runners/pytest.py b/launchable/test_runners/pytest.py index 3ca1eafdc..ff3d3dd2f 100644 --- a/launchable/test_runners/pytest.py +++ b/launchable/test_runners/pytest.py @@ -38,7 +38,7 @@ # -def _ts_to_iso(ts: Optional[float]) -> Optional[str]: +def _timestamp_to_iso(ts: Optional[float]) -> Optional[str]: # convert to ISO-8601 formatted date if ts is None: return None @@ -336,22 +336,22 @@ def parse_func( props = None # extract raw timestamps - start_ts = data.get("start") - stop_ts = data.get("stop") + start_timestamp = data.get("start") + stop_timestamp = data.get("stop") # convert to ISO-8601 - start_iso = _ts_to_iso(start_ts) - stop_iso = _ts_to_iso(stop_ts) + start_timestamp_iso_format = _timestamp_to_iso(start_timestamp) + end_timestamp_iso_format = _timestamp_to_iso(stop_timestamp) print( - f"DEBUG pytest timing start={start_iso}, stop={stop_iso}" + f"DEBUG pytest timing start={start_timestamp_iso_format}, stop={end_timestamp_iso_format}" ) event_data = {} - if start_iso: - event_data["start_timestamp"] = start_iso - if stop_iso: - event_data["stop_timestamp"] = stop_iso + if start_timestamp_iso_format: + event_data["start_timestamp"] = start_timestamp_iso_format + if end_timestamp_iso_format: + event_data["stop_timestamp"] = end_timestamp_iso_format test_path = _parse_pytest_nodeid(nodeid) for path in test_path: @@ -360,7 +360,8 @@ def parse_func( data_payload = event_data if event_data else None - # stop_iso is being passed as timestamp as it reflects event finalization (start + duration = stop) + # end_timestamp_iso_format is being passed as timestamp as it reflects event finalization + # start + duration = stop # sending both start and stop time in the event data field yield CaseEvent.create( test_path=test_path, @@ -368,5 +369,5 @@ def parse_func( status=status, stdout=stdout, stderr=stderr, - timestamp=stop_iso, + timestamp=end_timestamp_iso_format, data=data_payload)