From a6da49f0d399355220ed8c6713253b9e564c29a4 Mon Sep 17 00:00:00 2001 From: Otto Sabart Date: Wed, 30 Oct 2024 15:35:58 +0100 Subject: [PATCH] Import tmt SubResults into ReportPortal --- tmt/steps/report/reportportal.py | 64 ++++++++++++++++++++++++++------ 1 file changed, 53 insertions(+), 11 deletions(-) diff --git a/tmt/steps/report/reportportal.py b/tmt/steps/report/reportportal.py index 636e2205c9..5ac248f836 100644 --- a/tmt/steps/report/reportportal.py +++ b/tmt/steps/report/reportportal.py @@ -1,7 +1,7 @@ import dataclasses +import datetime import os import re -from time import time from typing import TYPE_CHECKING, Any, Optional, overload import requests @@ -12,7 +12,6 @@ from tmt.result import ResultOutcome from tmt.utils import field, yaml_to_dict - if TYPE_CHECKING: from tmt._compat.typing import TypeAlias @@ -187,7 +186,7 @@ class ReportReportPortalData(tmt.steps.report.ReportStepData): @tmt.steps.provides_method("reportportal") class ReportReportPortal(tmt.steps.report.ReportPlugin[ReportReportPortalData]): """ - Report test results to a ReportPortal instance via API. + Report test results and their subresults to a ReportPortal instance via API. For communication with Report Portal API is necessary to provide following options: @@ -281,8 +280,8 @@ def check_options(self) -> None: "may cause an unexpected behaviour with launch-per-plan structure") @property - def time(self) -> str: - return str(int(time() * 1000)) + def datetime(self) -> str: + return datetime.datetime.now(datetime.timezone.utc).isoformat() @property def headers(self) -> dict[str, str]: @@ -396,13 +395,14 @@ def go(self, *, logger: Optional[tmt.log.Logger] = None) -> None: self.warn("SSL verification is disabled for all requests being made to ReportPortal " f"instance ({self.data.url}).") - launch_time = self.time + launch_time = self.datetime # Support for idle tests executed = bool(self.step.plan.execute.results()) if executed: # launch time should be the earliest start time of all plans - launch_time = min([r.start_time or self.time + # TODO: Does the 'min' work with datetime isoformat correctly? + launch_time = min([r.start_time or self.datetime for r in self.step.plan.execute.results()]) # Create launch, suites (if "--suite_per_plan") and tests; @@ -533,7 +533,7 @@ def go(self, *, logger: Optional[tmt.log.Logger] = None) -> None: for result, test in self.step.plan.execute.results_for_tests( self.step.plan.discover.tests()): - test_time = self.time + test_time = self.datetime test_name = None test_description = '' test_link = None @@ -544,7 +544,7 @@ def go(self, *, logger: Optional[tmt.log.Logger] = None) -> None: if result: serial_number = result.serial_number test_name = result.name - test_time = result.start_time or self.time + test_time = result.start_time or self.datetime # for guests, save their primary address if result.guest.primary_address: item_attributes.append({ @@ -595,16 +595,19 @@ def go(self, *, logger: Optional[tmt.log.Logger] = None) -> None: "type": "step", "testCaseId": test_id, "startTime": test_time}) + item_uuid = yaml_to_dict(response.text).get("id") assert item_uuid is not None self.verbose("uuid", item_uuid, "yellow", shift=1) self.data.test_uuids[serial_number] = item_uuid else: item_uuid = self.data.test_uuids[serial_number] + # Support for idle tests status = "SKIPPED" if result: # For each log + # TODO: Try to save logs for subresults? for index, log_path in enumerate(result.log): try: log = self.step.plan.execute.read(log_path) @@ -636,9 +639,47 @@ def go(self, *, logger: Optional[tmt.log.Logger] = None) -> None: "level": "ERROR", "time": result.end_time}) - test_time = result.end_time or self.time + test_time = result.end_time or self.datetime - # Finish the test item + # Also create (and *finish*) the child test item for every tmt subresult and + # map it under parent test item + for subresult in result.subresult: + # Create a child item + response = self.rp_api_post( + session=session, + path=f"item/{item_uuid}", + json={ + "name": subresult.name, + # TODO: Decide which attributes we want to set for subresults + # "description": test_description, + # "attributes": item_attributes, + # "parameters": env_vars, + # "codeRef": test_link, + "launchUuid": launch_uuid, + "type": "step", + # "testCaseId": test_id, + "startTime": subresult.start_time or self.datetime}) + + child_item_uuid = yaml_to_dict(response.text).get("id") + assert child_item_uuid is not None + + # Finish the child item + response = self.rp_api_put( + session=session, + path=f"item/{item_uuid}", + json={ + "launchUuid": launch_uuid, + "status": self.TMT_TO_RP_RESULT_STATUS[subresult.result], + # TODO: Workaround: The problem is, that the `start-time` for + # subresults is not set by `tmt-report-result` and the `end-time` + # **is** and it's **lower** than autogenerated `start-time` set by + # `self.datetime`. + # "endTime": subresult.end_time, + "endTime": self.datetime}) + + self.verbose("child uuid", child_item_uuid, "yellow", shift=2) + + # Finish the parent test item response = self.rp_api_put( session=session, path=f"item/{item_uuid}", @@ -648,6 +689,7 @@ def go(self, *, logger: Optional[tmt.log.Logger] = None) -> None: "status": status, "issue": { "issueType": self.get_defect_type_locator(session, defect_type)}}) + launch_time = test_time if create_suite: