[rp-reporter] again fix downloading and checking files
Make it thread safe
Related-prod: PRODX-48948
Change-Id: I4a08c19665c2ce6dcf18ade8e4bbf9832f0f9315
diff --git a/rp_reporter/rp_reporter/batch_reporter.py b/rp_reporter/rp_reporter/batch_reporter.py
index 0ee2664..a38e1b7 100755
--- a/rp_reporter/rp_reporter/batch_reporter.py
+++ b/rp_reporter/rp_reporter/batch_reporter.py
@@ -1,18 +1,11 @@
import click
-
-import ipdb
+import logging
import itertools
-import sys, os
import jenkins_jinny.main as jj
from copy import deepcopy
-from pathlib import Path
+from .report_from_xml import timestamp, Reporter
-from .report_from_xml import report_xml, timestamp
-from .report_from_xml import client as rp_client
-import logging
-
-logging.basicConfig(level=logging.DEBUG)
LOG = logging.getLogger("rp_reporter")
def upload_job(job:str, suite_per_job=False, tags=None):
@@ -40,8 +33,9 @@
# for file_location in deploy_job.get_artifacts("deployed.yaml"):
# osdpl_content = yaml.safe_load_all(open(file_location))
# yaml.safe_load_all
- # tags["rockoon"] =
-
+ # tags["rockoon"] =
+ reporter = Reporter()
+ rp_client = reporter.client
launch_id = None
if suite_per_job:
tags["jenkins_job"] = job.number
@@ -52,8 +46,15 @@
description=f"Deploy job {job.url}"
)
print(f"(^-^)_日 report will be here {rp_client.get_launch_ui_url()}")
-
+ rp_client.log(time=timestamp(),
+ message=f"Job status: {job.status}",
+ # item_id=launch_id
+ )
for child in itertools.chain([job], job.heirs):
+ rp_client.log(time=timestamp(),
+ message=f"{child} {child.status} {child.url}",
+ # item_id=launch_id
+ )
print(f"⫍⌕⫎ tests in {child}")
test_tags = deepcopy(tags)
test_results_files = None
@@ -99,6 +100,9 @@
if not test_results_files:
# We can iterate by child jobs which don't contain any reports
continue
+ rp_client.log(time=timestamp(),
+ message=f"Found file to upload: {test_results_files}",
+ item_id=launch_id)
report_path = test_results_files[0]
LOG.info("=== report_xml {kwargs}".format(
kwargs = dict(report_path=report_path,
@@ -109,7 +113,7 @@
)
print(f"(っ・-・)っ Uploading {report_path}")
- report_xml(
+ reporter.report_xml(
report_path=report_path,
title = title,
attributes=test_tags,
@@ -120,6 +124,10 @@
# if suite_per_job:
#
# LOG.info(rp_client.get_launch_info())
+ rp_client.log(time=timestamp(),
+ message="Reporting completed",
+ # item_id=launch_id
+ )
if suite_per_job:
report_url = rp_client.get_launch_ui_url()
rp_client.finish_launch(end_time=timestamp())
@@ -160,7 +168,7 @@
:param report_path: Url or file location of xunit report
"""
title = report_path.split("/")[-1]
- report_xml(
+ Reporter().report_xml(
report_path=report_path,
title=title,
)
diff --git a/rp_reporter/rp_reporter/report_from_xml.py b/rp_reporter/rp_reporter/report_from_xml.py
index 75bc891..d55171e 100644
--- a/rp_reporter/rp_reporter/report_from_xml.py
+++ b/rp_reporter/rp_reporter/report_from_xml.py
@@ -9,6 +9,8 @@
import re
import wget
import time
+import uuid
+from typing import Optional
from xml.etree import ElementTree
from reportportal_client import RPClient
@@ -17,219 +19,246 @@
from .settings import RP_ENDPOINT, RP_APIKEY, RP_PROJECT
LOG = logging.getLogger("rp_reporter")
-scheduled = []
-uuid_by_subfolder = dict()
-client = RPClient(endpoint=RP_ENDPOINT,
- project=RP_PROJECT,
- api_key=RP_APIKEY,
- is_skipped_an_issue=False,
- truncate_attributes=False
- )
-def schedule_finishing(item):
- scheduled.append(item)
-
-def finish_all():
- while scheduled:
- finishing_item = scheduled.pop()
- client.finish_test_item(item_id=finishing_item,
- end_time=timestamp()
- )
-
-def create_subfolders(list_of_subfolders:list, root_folder=None) -> str:
- parent_folder = root_folder
- for number, subfolder in enumerate(list_of_subfolders):
- subfolder_fullpath = list_of_subfolders[:number]
- subfolder_fullpath.append(subfolder)
- fullpath = ".".join(subfolder_fullpath)
- if fullpath not in uuid_by_subfolder.keys():
- created_folder = client.start_test_item(
- name=subfolder,
- start_time=timestamp(),
- item_type="suite",
- parent_item_id=parent_folder
- )
- LOG.debug(f"start_test_item {subfolder=} "
- f"item_type=suite "
- f"name={subfolder} "
- f"parent_item_id={parent_folder}")
-
- LOG.info(f"Started suite: uuid={created_folder} for suite {subfolder=} in {fullpath=} with {parent_folder=}")
- schedule_finishing(created_folder)
- uuid_by_subfolder[fullpath] = created_folder
- folder_uuid = uuid_by_subfolder.get(fullpath)
- parent_folder = folder_uuid
- return parent_folder
-
-def report_xml(report_path, title,
- attributes=None, link=None, to_specific_launch=None):
- ts: xunitparser.TestSuite
- tr: xunitparser.TestResult
- tc: xunitparser.TestCase
- if not attributes:
- attributes = dict()
- # FIXME rewrite it to not use globals
- global uuid_by_subfolder
- uuid_by_subfolder = dict()
- global scheduled
- scheduled = list()
- _report_path = str(report_path)
+def obtain_file(report_path:str) -> Optional[str]:
+ """
+ Returns file's location in file system
+ If report_path is an URL script will download it to /tmp folder
+ :param report_path: str - file system or network location
+ :return:
+ """
if report_path.startswith("http"):
try:
- report_path = wget.download(report_path, f"/tmp/")
+ downloaded_file = wget.download(report_path,
+ f"/tmp/{uuid.uuid4()}.xml")
+ return downloaded_file
except urllib.error.HTTPError:
print("File is absent")
- return
- try:
- ElementTree.fromstring(open(report_path).read())
- except ElementTree.ParseError:
- print("File is not in XML format")
- return
+ return None
if pathlib.Path(report_path).exists():
- report_file = report_path
+ return report_path
else:
raise FileNotFoundError(report_path)
- ts, tr = xunitparser.parse(open(report_file))
- # ipdb.set_trace()
- # xunitparser can't provide start_time from xml
- # so we can use only now()
- # if tr.timestamp:
- # start_time = datetime.datetime.fromisoformat(tr.timestamp)
- # ipdb.set_trace()
- # if tr.time
- start_time = int(os.stat(report_file).st_birthtime * 1000) # to cut float part
- attributes.update(ts.properties)
- last_subfolder = None
- if to_specific_launch:
- test_suite = client.start_test_item(
- name=title,
- start_time=timestamp(),
- attributes=attributes or None,
- item_type="suite",
- description=f"{link} \n uploaded from report {_report_path} ",
- )
- LOG.debug(f"start_test_item {test_suite=} item_type=suite name={title}")
- schedule_finishing(item=test_suite)
- root_folder = test_suite
- else:
- launch_id = client.start_launch(name=title,
- start_time=timestamp(),
- attributes=attributes or None,
- description=f"{link} \n uploaded from report {_report_path} "
- )
- LOG.debug(f"start_launch {launch_id=} name={title} ")
- print(f"(^-^)_日 report will be here {client.get_launch_ui_url()}")
- root_folder = None
- if not launch_id:
- # ipdb.set_trace()
- raise Exception(f"Launch {title} is not published ")
+def is_xml(file_path:str) -> bool:
+ try:
+ ElementTree.fromstring(open(file_path).read())
+ return True
+ except ElementTree.ParseError:
+ print("File is not in XML format")
+ return False
- LOG.info(f"Sending to RP")
- ts_with_progress_bar = click.progressbar(ts,
- label='Sending to RP',
- # item_show_func=lambda a: a.methodname if a is not None,
- length=tr.testsRun,
- show_percent=True,
- bar_template='[%(bar)s] %(info)s %(label)s'
- )
- with ts_with_progress_bar:
- started_at = time.time()
+class Reporter:
+ def __init__(self, client=None):
+ if not client:
+ self.client = RPClient(endpoint=RP_ENDPOINT,
+ project=RP_PROJECT,
+ api_key=RP_APIKEY,
+ is_skipped_an_issue=False,
+ truncate_attributes=False
+ )
+ self.scheduled = list()
+ self.uuid_by_subfolder = dict()
+ self.attributes = dict()
- for tc in ts_with_progress_bar:
- if tc.classname:
- last_subfolder = create_subfolders(
- list_of_subfolders=tc.classname.split("."),
- root_folder=root_folder
- )
- elif "setup" in tc.methodname.lower() or "teardown" in tc.methodname.lower():
- # setup and teardown don't have classname but have path in their name like
- # in tempest:
- # setUpClass (tempest.api.compute.admin.test_create_server.WindowsServers11Test)
- found_text: list = re.findall(r"\([\w.]+\)", tc.methodname)
- if found_text:
- found_text: str = found_text[-1]
- if found_text:
- found_text: str = found_text.strip("()")
- last_subfolder = create_subfolders(
- list_of_subfolders=found_text.split("."),
- root_folder=root_folder)
- # name = f"{tc.classname}.{tc.methodname}"
- # else:
- name = tc.methodname
- elapsed = time.time() - started_at
- ts_with_progress_bar.label = f"{elapsed:.2f}s {name}".replace("\n", " ")
+ def schedule_finishing(self, item) -> None:
+ self.scheduled.append(item)
- # It's a bad way to detect setup\teardown because every testing
- # framework has his own way to log setup\teardown
- # Also test name can contain setup
- # if "setup" in tc.methodname.lower():
- # item_type="setup"
- # elif "teardown" in tc.methodname.lower():
- # item_type="teardown"
- # else:
- # item_type = "STEP"
- item_type = "STEP"
-
- # ipdb.set_trace()
- test_started_at = timestamp()
- item_id = client.start_test_item(
- name=name,
- start_time=test_started_at,
- item_type=item_type,
- description=f"{tc.classname}.{tc.methodname}",
- parent_item_id=last_subfolder
- )
- LOG.debug(f"start_test_item {item_id=} "
- f"{name=} "
- f"{item_type=} "
- f"parent_item_id={last_subfolder}")
- if not item_id:
- raise Exception(f"Failed to start test {name}")
- match tc.result:
- case "success":
- status = "PASSED"
- case "skipped":
- status = "SKIPPED"
- case "failure":
- status = "FAILED"
- case "error":
- status = "FAILED"
- case _:
- raise BaseException(f"Unknown {tc.result=} in xml")
-
- # LOG.debug(f"Logging {tc.trace}")
- # ipdb.set_trace()
- if tc.message:
- client.log(time=timestamp(),
- message=tc.message,
- item_id=item_id
- )
- if tc.trace:
- client.log(time=timestamp(),
- message=tc.trace,
- item_id=item_id
- )
-
- # timestamp() 1739905243451 in milliseconds
- # tc.time datetime.timedelta(microseconds=259000)
- end_time_with_duration = datetime.datetime.fromtimestamp(
- int(test_started_at) / 1000) + tc.time
- end_time_in_milliseconds = int(
- end_time_with_duration.timestamp() * 1000)
-
- client.finish_test_item(item_id=item_id,
- end_time=str(end_time_in_milliseconds),
- status=status,
+ def finish_all(self) -> None:
+ while self.scheduled:
+ finishing_item = self.scheduled.pop()
+ self.client.finish_test_item(item_id=finishing_item,
+ end_time=timestamp()
)
- finish_all()
+ self.reset_cache()
- if not to_specific_launch:
- client.finish_launch(end_time=timestamp())
- LOG.info(client.get_launch_info())
+ def reset_cache(self) -> None:
+ self.scheduled = list()
+ self.uuid_by_subfolder = dict()
+
+ def create_subfolders(self,
+ list_of_subfolders: list, root_folder=None) -> str:
+ parent_folder = root_folder
+ for number, subfolder in enumerate(list_of_subfolders):
+ subfolder_fullpath = list_of_subfolders[:number]
+ subfolder_fullpath.append(subfolder)
+ fullpath = ".".join(subfolder_fullpath)
+ if fullpath not in self.uuid_by_subfolder.keys():
+ created_folder = self.client.start_test_item(
+ name=subfolder,
+ start_time=timestamp(),
+ item_type="suite",
+ parent_item_id=parent_folder
+ )
+ LOG.debug(f"start_test_item {subfolder=} "
+ f"item_type=suite "
+ f"name={subfolder} "
+ f"parent_item_id={parent_folder}")
+
+ LOG.info(
+ f"Started suite: uuid={created_folder} for suite "
+ f"{subfolder=} in {fullpath=} with {parent_folder=}")
+ self.schedule_finishing(created_folder)
+ self.uuid_by_subfolder[fullpath] = created_folder
+ folder_uuid = self.uuid_by_subfolder.get(fullpath)
+ parent_folder = folder_uuid
+ return parent_folder
+
+ def report_xml(self, report_path, title, attributes=None,
+ link=None, to_specific_launch=None):
+ ts: xunitparser.TestSuite
+ tr: xunitparser.TestResult
+ tc: xunitparser.TestCase
+ report_file = obtain_file(report_path)
+ if not report_file:
+ print("Error occurred with file. Interrupting reporting")
+ return
+ ts, tr = xunitparser.parse(open(report_file))
+ if not ts:
+ print(f"{ts=} is empty in {report_file=}. Interrupting reporting")
+ return
+
+ # xunitparser can't provide start_time from xml
+ # so we can use only now()
+ # if tr.timestamp:
+ # start_time = datetime.datetime.fromisoformat(tr.timestamp)
+ # ipdb.set_trace()
+ # if tr.time
+ # start_time = int(os.stat(report_file).st_birthtime * 1000) # to cut float part
+ attributes.update(ts.properties)
+
+ last_subfolder = None
+ if to_specific_launch:
+ test_suite = self.client.start_test_item(
+ name=title,
+ start_time=timestamp(),
+ attributes=attributes or None,
+ item_type="suite",
+ description=f"{link} \n uploaded from report {report_path} ",
+ )
+ LOG.debug(
+ f"start_test_item {test_suite=} item_type=suite name={title}")
+ self.schedule_finishing(item=test_suite)
+ root_folder = test_suite
+ else:
+ launch_id = self.client.start_launch(name=title,
+ start_time=timestamp(),
+ attributes=attributes or None,
+ description=f"{link} \n uploaded from report {report_path} "
+ )
+ LOG.debug(f"start_launch {launch_id=} name={title} ")
+ print(f"(^-^)_日 report will be here {self.client.get_launch_ui_url()}")
+ root_folder = None
+ if not launch_id:
+ # ipdb.set_trace()
+ raise Exception(f"Launch {title} is not published ")
+
+ LOG.info(f"Sending to RP")
+
+ ts_with_progress_bar = click.progressbar(ts,
+ label='Sending to RP',
+ # item_show_func=lambda a: a.methodname if a is not None,
+ length=tr.testsRun,
+ show_percent=True,
+ bar_template='[%(bar)s] %(info)s %(label)s'
+ )
+ with ts_with_progress_bar:
+ started_at = time.time()
+
+ for tc in ts_with_progress_bar:
+ if tc.classname:
+ last_subfolder = self.create_subfolders(
+ list_of_subfolders=tc.classname.split("."),
+ root_folder=root_folder
+ )
+ elif "setup" in tc.methodname.lower() or "teardown" in tc.methodname.lower():
+ # setup and teardown don't have classname but have path in their name like
+ # in tempest:
+ # setUpClass (tempest.api.compute.admin.test_create_server.WindowsServers11Test)
+ found_text: list = re.findall(r"\([\w.]+\)", tc.methodname)
+ if found_text:
+ found_text: str = found_text[-1]
+ if found_text:
+ found_text: str = found_text.strip("()")
+ last_subfolder = self.create_subfolders(
+ list_of_subfolders=found_text.split("."),
+ root_folder=root_folder)
+ # name = f"{tc.classname}.{tc.methodname}"
+ # else:
+ name = tc.methodname
+ elapsed = time.time() - started_at
+ ts_with_progress_bar.label = f"{elapsed:.2f}s {name}".replace(
+ "\n", " ")
+
+ # It's a bad way to detect setup\teardown because every testing
+ # framework has his own way to log setup\teardown
+ # Also test name can contain setup
+ # if "setup" in tc.methodname.lower():
+ # item_type="setup"
+ # elif "teardown" in tc.methodname.lower():
+ # item_type="teardown"
+ # else:
+ # item_type = "STEP"
+ item_type = "STEP"
+
+ test_started_at = timestamp()
+ item_id = self.client.start_test_item(
+ name=name,
+ start_time=test_started_at,
+ item_type=item_type,
+ description=f"{tc.classname}.{tc.methodname}",
+ parent_item_id=last_subfolder
+ )
+ LOG.debug(f"start_test_item {item_id=} "
+ f"{name=} "
+ f"{item_type=} "
+ f"parent_item_id={last_subfolder}")
+ if not item_id:
+ raise Exception(f"Failed to start test {name}")
+ match tc.result:
+ case "success":
+ status = "PASSED"
+ case "skipped":
+ status = "SKIPPED"
+ case "failure":
+ status = "FAILED"
+ case "error":
+ status = "FAILED"
+ case _:
+ raise BaseException(f"Unknown {tc.result=} in xml")
+
+ # LOG.debug(f"Logging {tc.trace}")
+ # ipdb.set_trace()
+ if tc.message:
+ self.client.log(time=timestamp(),
+ message=tc.message,
+ item_id=item_id
+ )
+ if tc.trace:
+ self.client.log(time=timestamp(),
+ message=tc.trace,
+ item_id=item_id
+ )
+
+ # timestamp() 1739905243451 in milliseconds
+ # tc.time datetime.timedelta(microseconds=259000)
+ end_time_with_duration = datetime.datetime.fromtimestamp(
+ int(test_started_at) / 1000) + tc.time
+ end_time_in_milliseconds = int(
+ end_time_with_duration.timestamp() * 1000)
+
+ self.client.finish_test_item(item_id=item_id,
+ end_time=str(end_time_in_milliseconds),
+ status=status,
+ )
+ self.finish_all()
+
+ if not to_specific_launch:
+ self.client.finish_launch(end_time=timestamp())
+ LOG.info(self.client.get_launch_info())
if __name__ == "__main__":
@@ -242,11 +271,10 @@
}
link = ""
-
- report_xml(
+ reporter = Reporter()
+ reporter.report_xml(
report_path=report_path,
title = title,
- attributes=attributes,
link=link,
# to_specific_launch="432ce97b-2727-4e4c-8303-9f1d966a184e"
)
diff --git a/rp_reporter/rp_reporter/settings.py b/rp_reporter/rp_reporter/settings.py
index 773e6bc..3ca05bf 100644
--- a/rp_reporter/rp_reporter/settings.py
+++ b/rp_reporter/rp_reporter/settings.py
@@ -3,7 +3,6 @@
from pathlib import Path
import logging
-
LOG = logging.getLogger("rp_reporter")
RP_CONFIG_FILE = environ.get("RP_CONFIG_FILE") or (Path.home() / ".reportportal_config")
@@ -29,12 +28,12 @@
RP_PROJECT = environ.get('RP_PROJECT') or from_conf('RP_PROJECT') or call_error("RP_PROJECT")
RP_LOG_FILE = environ.get('RP_LOG_FILE') or from_conf('RP_LOG_FILE') or (Path.cwd() / "report.log")
-RP_LOGGING = environ.get('RP_LOGGING') or from_conf('RP_LOGGING') or logging.INFO
+RP_LOGGING = environ.get('RP_LOGGING') or from_conf('RP_LOGGING') or logging.WARNING
logging.basicConfig(level=RP_LOGGING,
format='%(asctime)s %(levelname)s - %(filename)s:%(lineno)d (%(funcName)s) - %(message)s',
- filename=RP_LOG_FILE,
- filemode='w'
+ # filename=RP_LOG_FILE,
+ # filemode='w'
)
if __name__ == "__main__":