From 7de8f2a8aa6bae76b950d77db1b8fc5523b327f8 Mon Sep 17 00:00:00 2001 From: Natalia Bubakova Date: Wed, 20 Sep 2023 20:51:58 +0200 Subject: [PATCH] Rewriting ReportPortal plugin extension [WIP]: * mapping according to options --launch-per-plan and --suite-per-plan * uploading to existing launch/suite with options --upload-to-launch LAUNCH_ID, --upload-to-suite SUITE_ID * option --launch-description and preparation for --launch-attributes (for suite-per-plan mapping) * trial option --launch-rerun * trial option --defect-type --- tmt/steps/report/reportportal.py | 180 ++++++++++++++++++++----------- 1 file changed, 119 insertions(+), 61 deletions(-) diff --git a/tmt/steps/report/reportportal.py b/tmt/steps/report/reportportal.py index 19af38d6ae..a292cc1392 100644 --- a/tmt/steps/report/reportportal.py +++ b/tmt/steps/report/reportportal.py @@ -32,32 +32,65 @@ class ReportReportPortalData(tmt.steps.report.ReportStepData): metavar="LAUNCH_NAME", default=os.getenv('TMT_PLUGIN_REPORT_REPORTPORTAL_LAUNCH'), help="The launch name (name of plan per launch is used by default).") + launch_description: Optional[str] = field( + option="--launch-description", + metavar="LAUNCH_DESCRIPTION", + default=os.getenv('TMT_PLUGIN_REPORT_REPORTPORTAL_LAUNCH_DESCRIPTION'), + help="Pass the description for ReportPortal launch, especially with '--suite-per-plan' " + "option (Otherwise Summary from plan fmf data per each launch is used by default).") + ## launch_attributes: List[str] = field( + ## default_factory=list, + ## multiple=True, + ## normalize=tmt.utils.normalize_string_list, + ## option="--launch-attributes", + ## metavar="KEY:VALUE", + ## help="Additional attributes to be reported to ReportPortal," + ## "especially launch attributes for merge option.") + launch_per_plan: bool = field( + option="--launch-per-plan", + default=False, + is_flag=True, + help="Mapping launch per plan, creating one or more launches with no suite structure.") + suite_per_plan: bool = field( + option="--suite-per-plan", + default=False, + is_flag=True, + help="Mapping suite per plan, creating one launch and continous uploading suites into it. " + "Can be used with '--upload-to-launch' option to avoid creating a new launch.") + upload_to_launch: Optional[str] = field( + option="--upload-to-launch", + metavar="LAUNCH_ID", + default=None, + help="Pass the launch ID for an additional upload to an existing launch. " + "ID can be found in the launch URL.") + upload_to_suite: Optional[str] = field( + option="--upload-to-suite", + metavar="LAUNCH_SUITE", + default=None, + help="Pass the suite ID for an additional upload to an existing launch. " + "ID can be found in the suite URL.") + launch_rerun: bool = field( + option="--launch-rerun", + default=False, + is_flag=True, + help="Rerun the launch and create Retry version per each test. Mapping is based on unique " + "suite/test names and works with '--suite-per-plan' option only.") + defect_type: Optional[str] = field( + option="--defect-type", + metavar="DEFECT_NAME", + default=None, + help="Pass the defect type to be used for failed test " + "('To Investigate' is used by default).") exclude_variables: str = field( option="--exclude-variables", metavar="PATTERN", default="^TMT_.*", help="Regular expression for excluding environment variables " "from reporting to ReportPortal ('^TMT_.*' used by default).") - attributes: List[str] = field( - default_factory=list, - multiple=True, - normalize=tmt.utils.normalize_string_list, - option="--attributes", - metavar="KEY:VALUE", - help="Additional attributes to be reported to ReportPortal," - "especially launch attributes for merge option.") - merge: bool = field( - option="--merge", - default=False, - is_flag=True, - help="Report suite per plan and merge them into one launch.") - uuid: Optional[str] = field( - option="--uuid", - metavar="LAUNCH_UUID", - default=None, - help="The launch uuid for additional merging to an existing launch.") launch_url: str = "" launch_uuid: str = "" + suite_uuid: str = "" + test_uuids: List[str] = [] @tmt.steps.provides_method("reportportal") @@ -149,60 +182,74 @@ def go(self) -> None: "Content-Type": "application/json"} launch_time = self.step.plan.execute.results()[0].starttime - merge_bool = self.get("merge") - rerun_bool = False - # TODO: implement rerun/retry - - create_launch = True - launch_uuid = "" - suite_uuid = "" + # create launch, suites if "suite_per_plan" and tests; or report to + # existing launch/suite if its id is given + launch_uuid = self.get("upload_to_launch") or self.get( + "launch_uuid") or self.step.plan.my_run.rp_uuid + suite_uuid = self.get("upload_to_suite") or self.get("suite_uuid") + suite_per_plan = self.get("suite_per_plan") + launch_per_plan = self.get("launch_per_plan") + if launch_per_plan and suite_per_plan: + # write warning that only one of the options can be used + launch_per_plan = False + elif launch_per_plan and suite_per_plan is False: + launch_per_plan = True # default + create_launch = ((launch_per_plan or (suite_per_plan and not launch_uuid)) + and not suite_uuid) + + # if suite_per_plan and suite_uuid == True: + # write warning that combinations of these options is not supported and + # test will be uploaded to the given suite without additional suite creation + create_suite = suite_per_plan and not suite_uuid + + ## create_launch = True + ## launch_uuid = "" + ## suite_uuid = "" launch_url = "" launch_name = "" + launch_rerun = self.get("launch_rerun") envar_pattern = self.get("exclude-variables") or "$^" - extra_attributes = self.get("attributes") - launch_attributes = [ - {'key': attribute.split(':', 2)[0], 'value': attribute.split(':', 2)[1]} - for attribute in extra_attributes] or [] + defect_type = self.get("defect_type") + ## extra_attributes = self.get("attributes") + ## launch_attributes = [ + ## {'key': attribute.split(':', 2)[0], 'value': attribute.split(':', 2)[1]} + ## for attribute in extra_attributes] or [] attributes = [ {'key': key, 'value': value[0]} for key, value in self.step.plan._fmf_context.items()] - for attr in launch_attributes: - if attr not in attributes: - attributes.append(attr) + ## for attr in launch_attributes: + ## if attr not in attributes: + ## attributes.append(attr) + if suite_per_plan: + launch_attributes = "" + # TODO: + # get common attributes from all plans + else: + launch_attributes = attributes.copy() + + launch_description = self.get("launch_description") or self.step.plan.summary # Communication with RP instance with tmt.utils.retry_session() as session: # get defect type locator - response = session.get( - url=f"{url}/settings", - headers=headers) - self.handle_response(response) - defect_types = yaml_to_dict(response.text).get("subTypes") - dt_tmp = [dt['locator'] - for dt in defect_types['TO_INVESTIGATE'] if dt['longName'] == 'Idle'] - dt_locator = dt_tmp[0] if dt_tmp else None dt_locator = None - # TODO: - # implement 'idle - update' - # cover cases when there is no Idle defect type defined - - stored_launch_uuid = self.get("uuid") or self.step.plan.my_run.rp_uuid - if merge_bool and stored_launch_uuid: - create_launch = False - launch_uuid = stored_launch_uuid + if defect_type: response = session.get( - url=f"{url}/launch/uuid/{launch_uuid}", + url=f"{url}/settings", headers=headers) self.handle_response(response) - launch_name = yaml_to_dict(response.text).get("name") - self.verbose("launch", launch_name, color="yellow") - launch_id = yaml_to_dict(response.text).get("id") - launch_url = f"{endpoint}/ui/#{project}/launches/all/{launch_id}" - else: + defect_types = yaml_to_dict(response.text).get("subTypes") + dt_tmp = [dt['locator'] + for dt in defect_types['TO_INVESTIGATE'] if dt['longName'] == defect_type] + dt_locator = dt_tmp[0] if dt_tmp else None + # TODO: + # cover cases when there the given defect type is not defined + + if create_launch: # create_launch = True launch_name = self.get("launch") or self.step.plan.name @@ -213,20 +260,29 @@ def go(self) -> None: headers=headers, json={ "name": launch_name, - "description": "" if merge_bool else self.step.plan.summary, - "attributes": launch_attributes if merge_bool else attributes, + "description": launch_description, + "attributes": launch_attributes, "startTime": launch_time, - "rerun": rerun_bool}) + "rerun": launch_rerun}) self.handle_response(response) launch_uuid = yaml_to_dict(response.text).get("id") assert launch_uuid is not None - if merge_bool: + if suite_per_plan: self.step.plan.my_run.rp_uuid = launch_uuid + else: + response = session.get( + url=f"{url}/launch/uuid/{launch_uuid}", + headers=headers) + self.handle_response(response) + launch_name = yaml_to_dict(response.text).get("name") + self.verbose("launch", launch_name, color="yellow") + launch_id = yaml_to_dict(response.text).get("id") + launch_url = f"{endpoint}/ui/#{project}/launches/all/{launch_id}" self.verbose("uuid", launch_uuid, "yellow", shift=1) self.data.launch_uuid = launch_uuid - if merge_bool: + if create_suite: # Create a suite suite_name = self.step.plan.name self.info("suite", suite_name, color="cyan") @@ -262,7 +318,7 @@ def go(self) -> None: # Create a test item self.info("test", result.name, color="cyan") response = session.post( - url=f"{url}/item{f'/{suite_uuid}' if merge_bool else ''}", + url=f"{url}/item{f'/{suite_uuid}' if suite_uuid else ''}", headers=headers, json={ "name": result.name, @@ -327,7 +383,7 @@ def go(self) -> None: self.handle_response(response) launch_time = result.endtime - if merge_bool: + if create_suite: # Finish the test suite response = session.put( url=f"{url}/item/{suite_uuid}", @@ -337,6 +393,8 @@ def go(self) -> None: "endTime": launch_time}) self.handle_response(response) + # TODO: + # If suite-per-plan, finish only when it is the last plan if create_launch: # Finish the launch response = session.put(