Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 35 additions & 9 deletions smart_tests/test_runners/robot.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from datetime import datetime
from typing import Annotated, List
from typing import Annotated, List, Optional
from xml.etree import ElementTree as ET

from junitparser import JUnitXml # type: ignore
Expand All @@ -13,10 +13,31 @@


def parse_func(p: str) -> ET.ElementTree:

def parse_suite(suite: ET.Element):
DATETIME_FORMAT = '%Y%m%d %H:%M:%S.%f'

DATETIME_FORMAT = '%Y%m%d %H:%M:%S.%f'

def suite_timestamp(suite: ET.Element) -> Optional[str]:
"""The suite's start time as an ISO-8601 naive string, or None if absent.

Robot records a naive, local wall-clock start (e.g. '20210319 11:34:50.085',
no timezone). The server owns timezone resolution (naive -> agent-local);
the CLI only normalizes the shape to ISO-8601.
"""
status = suite.find('status')
if status is None:
return None
# RF 7.0+: already-ISO 'start' attribute; hand it over untouched.
iso_start = status.get('start')
if iso_start:
return iso_start
raw = status.get('starttime')
if not raw:
return None
try:
return datetime.strptime(raw, DATETIME_FORMAT).isoformat()
except ValueError:
return None

def parse_suite(suite: ET.Element, testsuite: ET.Element):
suite_name = suite.get('name')
for test in suite.iter("test"):
test_name = test.get('name')
Expand Down Expand Up @@ -72,14 +93,19 @@ def find_leaf_suites(suite: ET.Element):
return leaves

original_tree = ET.parse(p)
testsuite = ET.Element("testsuite", {"name": "robot"})
testsuites = ET.Element("testsuites")

SUITE_TAG_NAME = "suite"
for top_suite in original_tree.findall(SUITE_TAG_NAME):
for leaf_suite in find_leaf_suites(top_suite):
parse_suite(leaf_suite)

return ET.ElementTree(testsuite)
attribs = {"name": str(leaf_suite.get('name'))}
timestamp = suite_timestamp(leaf_suite)
if timestamp:
attribs["timestamp"] = timestamp
testsuite = ET.SubElement(testsuites, "testsuite", attribs)
parse_suite(leaf_suite, testsuite)

return ET.ElementTree(testsuites)


@smart_tests.record.tests
Expand Down
13 changes: 13 additions & 0 deletions tests/test_runners/test_robot.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,23 @@

import responses # type: ignore

from smart_tests.test_runners.robot import parse_func
from tests.cli_test_case import CliTestCase


class RobotTest(CliTestCase):
def test_suite_start_timestamp(self):
# Each leaf suite carries its own start as an ISO-8601 naive string for the server to
# resolve. RF < 7.0 'starttime' (naive, space-separated) is normalized to ISO.
legacy = parse_func(str(self.test_files_dir) + "/output_legacy.xml").getroot()
self.assertEqual(
[s.get("timestamp") for s in legacy.findall("testsuite")],
["2021-03-19T11:34:50.080000", "2021-03-19T11:34:50.092000"],
)
# RF 7.0+ 'start' is already ISO and passes through; every suite must be tagged.
modern = parse_func(str(self.test_files_dir) + "/output.xml").getroot()
self.assertTrue(all(s.get("timestamp") for s in modern.findall("testsuite")))

@responses.activate
@mock.patch.dict(os.environ, {"SMART_TESTS_TOKEN": CliTestCase.smart_tests_token})
def test_subset(self):
Expand Down
Loading