Skip to content

Commit

Permalink
Add more exception logic and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
HardNorth committed Jan 8, 2025
1 parent 158e2c2 commit 9d478ca
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 73 deletions.
79 changes: 40 additions & 39 deletions robotframework_reportportal/listener.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
from reportportal_client.helpers import LifoQueue, guess_content_type_from_bytes, is_binary

from robotframework_reportportal.helpers import _unescape, match_pattern, translate_glob_to_regex
from robotframework_reportportal.model import Keyword, Launch, LogMessage, Suite, Test
from robotframework_reportportal.model import Keyword, Launch, LogMessage, Suite, Test, Entity
from robotframework_reportportal.service import RobotService
from robotframework_reportportal.static import MAIN_SUITE_ID, PABOT_WITHOUT_LAUNCH_ID_MSG
from robotframework_reportportal.variables import Variables
Expand Down Expand Up @@ -150,8 +150,7 @@ def _build_msg_struct(self, message: Dict[str, Any]) -> LogMessage:
else:
msg = LogMessage(message["message"])
msg.level = message["level"]
if not msg.launch_log:
msg.item_id = getattr(self.current_item, "rp_item_id", None)
msg.item_id = self.current_item.rp_item_id

message_str = msg.message
if is_binary(message_str):
Expand Down Expand Up @@ -218,42 +217,52 @@ def __post_log_message(self, message: LogMessage) -> None:
logger.debug(f"ReportPortal - Log Message: {message}")
self.service.log(message=message)

def __post_skipped_keyword(self, kwd: Keyword) -> None:
def __post_skipped_keyword(self, kwd: Keyword, clean_data_remove: bool) -> None:
self._do_start_keyword(kwd)
if clean_data_remove:
kwd.remove_data = False
for log_message in kwd.skipped_logs:
self.__post_log_message(log_message)
skipped_kwds = kwd.skipped_keywords
skipped_keywords = kwd.skipped_keywords
kwd.skipped_keywords = []
for skipped_kwd in skipped_kwds:
self.__post_skipped_keyword(skipped_kwd)
self._do_end_keyword(kwd)
for skipped_kwd in skipped_keywords:
self.__post_skipped_keyword(skipped_kwd, clean_data_remove)
if kwd.status != "NOT SET":
self._do_end_keyword(kwd)

def _post_skipped_keywords(self, to_post: Optional[Any]) -> None:
def _post_skipped_keywords(self, to_post: Optional[Any], clean_data_remove: bool = False) -> None:
if not to_post:
return
if isinstance(to_post, Keyword):
if not to_post.posted:
self._do_start_keyword(to_post)
if clean_data_remove:
to_post.remove_data = False
log_messages = to_post.skipped_logs
to_post.skipped_logs = []
for log_message in log_messages:
self.__post_log_message(log_message)
skipped_kwds = to_post.skipped_keywords
if skipped_kwds:
skipped_keywords = to_post.skipped_keywords
if skipped_keywords:
to_post.skipped_keywords = []
for skipped_kwd in skipped_kwds:
for skipped_kwd in skipped_keywords:
if skipped_kwd.posted:
log_messages = skipped_kwd.skipped_logs
skipped_kwd.skipped_logs = []
for log_message in log_messages:
self.__post_log_message(log_message)
skipped_child_kwds = skipped_kwd.skipped_keywords
for skipped_child_kwd in skipped_child_kwds:
skipped_child_keywords = skipped_kwd.skipped_keywords
for skipped_child_kwd in skipped_child_keywords:
if skipped_child_kwd.posted:
continue
self.__post_skipped_keyword(skipped_child_kwd)
self.__post_skipped_keyword(skipped_child_kwd, clean_data_remove)
continue
self.__post_skipped_keyword(skipped_kwd)
self.__post_skipped_keyword(skipped_kwd, clean_data_remove)

def __find_root_keyword_with_removed_data(self, keyword: Entity) -> Entity:
if keyword.parent.remove_data and keyword.parent.type == "KEYWORD":
return self.__find_root_keyword_with_removed_data(keyword.parent)
return keyword

def _log_message(self, message: LogMessage) -> None:
"""Send log message to the Report Portal.
Expand All @@ -274,10 +283,8 @@ def _log_message(self, message: LogMessage) -> None:
else:
if not self._remove_all_keyword_content:
# Post everything skipped by '--removekeywords' option
self._post_skipped_keywords(current_item)
self._post_skipped_keywords(self.__find_root_keyword_with_removed_data(current_item), True)
self.__post_log_message(message)
else:
self.current_item.skipped_logs.append(message)

@check_rp_enabled
def log_message(self, message: Dict) -> None:
Expand All @@ -304,11 +311,6 @@ def log_message_with_image(self, msg: Dict, image: str):
}
self._log_message(mes)

@property
def parent_id(self) -> Optional[str]:
"""Get rp_item_id attribute of the current item."""
return getattr(self.current_item, "rp_item_id", None)

@property
def service(self) -> RobotService:
"""Initialize instance of the RobotService."""
Expand Down Expand Up @@ -409,8 +411,7 @@ def start_suite(self, name: str, attributes: Dict, ts: Optional[Any] = None) ->
logger.debug(f"ReportPortal - Create global Suite: {attributes}")
else:
logger.debug(f"ReportPortal - Start Suite: {attributes}")
suite = Suite(name, attributes)
suite.rp_parent_item_id = self.parent_id
suite = Suite(name, attributes, self.current_item)
suite.rp_item_id = self.service.start_suite(suite=suite, ts=ts)
self._add_current_item(suite)

Expand Down Expand Up @@ -440,9 +441,8 @@ def start_test(self, name: str, attributes: Dict, ts: Optional[Any] = None) -> N
# no 'source' parameter at this level for Robot versions < 4
attributes = attributes.copy()
attributes["source"] = getattr(self.current_item, "source", None)
test = Test(name=name, robot_attributes=attributes, test_attributes=self.variables.test_attributes)
test = Test(name, attributes, self.variables.test_attributes, self.current_item)
logger.debug(f"ReportPortal - Start Test: {attributes}")
test.rp_parent_item_id = self.parent_id
test.rp_item_id = self.service.start_test(test=test, ts=ts)
self._add_current_item(test)

Expand Down Expand Up @@ -489,9 +489,8 @@ def start_keyword(self, name: str, attributes: Dict, ts: Optional[Any] = None) -
:param attributes: Dictionary passed by the Robot Framework
:param ts: Timestamp(used by the ResultVisitor)
"""
kwd = Keyword(name=name, parent_type=self.current_item.type, robot_attributes=attributes)
kwd = Keyword(name, attributes, self.current_item)
parent = self.current_item
kwd.rp_parent_item_id = parent.rp_item_id
skip_kwd = parent.remove_data
skip_data = self._remove_all_keyword_content or self._remove_data_passed_tests
kwd.remove_data = skip_kwd or skip_data
Expand Down Expand Up @@ -535,31 +534,33 @@ def end_keyword(self, _: Optional[str], attributes: Dict, ts: Optional[Any] = No
kwd = self.current_item.update(attributes)

if kwd.matched_filter is WUKS_KEYWORD_MATCH and kwd.skip_origin is kwd:
skipped_kwds = kwd.skipped_keywords
skipped_kwds_num = len(skipped_kwds)
if skipped_kwds_num > 2:
skipped_keywords = kwd.skipped_keywords
skipped_keywords_num = len(skipped_keywords)
if skipped_keywords_num > 2:
if kwd.status == "FAIL":
message = REMOVED_WUKS_KEYWORD_LOG.format(number=len(kwd.skipped_keywords) - 1)
else:
message = REMOVED_WUKS_KEYWORD_LOG.format(number=len(kwd.skipped_keywords) - 2)
self._log_data_removed(kwd.rp_item_id, kwd.start_time, message)
if skipped_kwds_num > 1 and kwd.status != "FAIL":
if skipped_keywords_num > 1 and kwd.status != "FAIL":
first_iteration = kwd.skipped_keywords[0]
self._post_skipped_keywords(first_iteration)
self._do_end_keyword(first_iteration)
if skipped_kwds_num > 0:
if skipped_keywords_num > 0:
last_iteration = kwd.skipped_keywords[-1]
self._post_skipped_keywords(last_iteration)
self._do_end_keyword(last_iteration, ts)

elif (
(kwd.matched_filter is FOR_KEYWORD_MATCH) or (kwd.matched_filter is WHILE_KEYWORD_NAME)
) and kwd.skip_origin is kwd:
skipped_kwds = kwd.skipped_keywords
skipped_kwds_num = len(skipped_kwds)
if skipped_kwds_num > 1:
skipped_keywords = kwd.skipped_keywords
skipped_keywords_num = len(skipped_keywords)
if skipped_keywords_num > 1:
self._log_data_removed(
kwd.rp_item_id, kwd.start_time, REMOVED_FOR_WHILE_KEYWORD_LOG.format(number=skipped_kwds_num - 1)
kwd.rp_item_id,
kwd.start_time,
REMOVED_FOR_WHILE_KEYWORD_LOG.format(number=skipped_keywords_num - 1),
)
last_iteration = kwd.skipped_keywords[-1]
self._post_skipped_keywords(last_iteration)
Expand Down
70 changes: 39 additions & 31 deletions robotframework_reportportal/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,31 @@
TEST_CASE_ID_SIGN = "test_case_id:"


class Entity:
"""Base class for all test items."""

type: str
remove_data: bool
rp_item_id: Optional[str]
parent: Optional["Entity"]

def __init__(self, entity_type: str, parent: Optional["Entity"]):
"""Initialize required attributes.
:param entity_type: Type of the entity
:param parent: Parent entity
"""
self.type = entity_type
self.parent = parent
self.rp_item_id = None
self.remove_data = False

@property
def rp_parent_item_id(self):
"""Get parent item ID."""
return getattr(self.parent, "rp_item_id", None)


class LogMessage(str):
"""Class represents Robot Framework messages."""

Expand All @@ -44,7 +69,7 @@ def __init__(self, message: str):
self.timestamp = None


class Keyword:
class Keyword(Entity):
"""Class represents Robot Framework keyword."""

robot_attributes: Dict[str, Any]
Expand All @@ -56,9 +81,6 @@ class Keyword:
keyword_type: str
libname: str
name: str
rp_item_id: Optional[str]
rp_parent_item_id: Optional[str]
parent_type: str
start_time: str
status: str
tags: List[str]
Expand All @@ -70,13 +92,14 @@ class Keyword:
matched_filter: Optional[Any]
skip_origin: Optional[Any]

def __init__(self, name: str, robot_attributes: Dict[str, Any], parent_type: Optional[str] = None):
def __init__(self, name: str, robot_attributes: Dict[str, Any], parent: Entity):
"""Initialize required attributes.
:param name: Name of the keyword
:param robot_attributes: Attributes passed through the listener
:param parent_type: Type of the parent test item
:param parent: Parent entity
"""
super().__init__("KEYWORD", parent)
self.robot_attributes = robot_attributes
self.args = robot_attributes["args"]
self.assign = robot_attributes["assign"]
Expand All @@ -86,17 +109,13 @@ def __init__(self, name: str, robot_attributes: Dict[str, Any], parent_type: Opt
self.keyword_type = robot_attributes["type"]
self.libname = robot_attributes["libname"]
self.name = name
self.rp_item_id = None
self.rp_parent_item_id = None
self.parent_type = parent_type
self.start_time = robot_attributes["starttime"]
self.status = robot_attributes.get("status")
self.tags = robot_attributes["tags"]
self.type = "KEYWORD"
self.skipped_keywords = []
self.skipped_logs = []
self.posted = True
self.remove_data = False
self.matched_filter = None
self.skip_origin = None

Expand All @@ -111,12 +130,12 @@ def get_name(self) -> str:
def get_type(self) -> str:
"""Get keyword type."""
if self.keyword_type.lower() in ("setup", "teardown"):
if self.parent_type.lower() == "keyword":
if self.parent.type.lower() == "keyword":
return "STEP"
if self.keyword_type.lower() == "setup":
return "BEFORE_{0}".format(self.parent_type.upper())
return "BEFORE_{0}".format(self.parent.type.upper())
if self.keyword_type.lower() == "teardown":
return "AFTER_{0}".format(self.parent_type.upper())
return "AFTER_{0}".format(self.parent.type.upper())
else:
return "STEP"

Expand All @@ -130,7 +149,7 @@ def update(self, attributes: Dict[str, Any]) -> "Keyword":
return self


class Suite:
class Suite(Entity):
"""Class represents Robot Framework test suite."""

robot_attributes: Union[List[str], Dict[str, Any]]
Expand All @@ -141,23 +160,21 @@ class Suite:
metadata: Dict[str, str]
name: str
robot_id: str
rp_item_id: Optional[str]
rp_parent_item_id: Optional[str]
start_time: Optional[str]
statistics: str
status: str
suites: List[str]
tests: List[str]
total_tests: int
type: str = "SUITE"
remove_data: bool = False

def __init__(self, name: str, robot_attributes: Dict[str, Any]):
def __init__(self, name: str, robot_attributes: Dict[str, Any], parent: Optional[Entity] = None):
"""Initialize required attributes.
:param name: Suite name
:param robot_attributes: Suite attributes passed through the listener
:param parent: Parent entity
"""
super().__init__("SUITE", parent)
self.robot_attributes = robot_attributes
self.doc = robot_markup_to_markdown(robot_attributes["doc"])
self.end_time = robot_attributes.get("endtime", "")
Expand All @@ -166,15 +183,12 @@ def __init__(self, name: str, robot_attributes: Dict[str, Any]):
self.metadata = robot_attributes["metadata"]
self.name = name
self.robot_id = robot_attributes["id"]
self.rp_item_id = None
self.rp_parent_item_id = None
self.start_time = robot_attributes.get("starttime")
self.statistics = robot_attributes.get("statistics")
self.status = robot_attributes.get("status")
self.suites = robot_attributes["suites"]
self.tests = robot_attributes["tests"]
self.total_tests = robot_attributes["totaltests"]
self.type = "SUITE"

@property
def attributes(self) -> Optional[List[Dict[str, str]]]:
Expand Down Expand Up @@ -224,7 +238,7 @@ def attributes(self) -> Optional[List[Dict[str, str]]]:
return self.launch_attributes


class Test:
class Test(Entity):
"""Class represents Robot Framework test case."""

_critical: str
Expand All @@ -237,21 +251,18 @@ class Test:
message: str
name: str
robot_id: str
rp_item_id: Optional[str]
rp_parent_item_id: Optional[str]
start_time: str
status: str
template: str
type: str = "TEST"
skipped_keywords: List[Keyword]
remove_data: bool = False

def __init__(self, name: str, robot_attributes: Dict[str, Any], test_attributes: List[str]):
def __init__(self, name: str, robot_attributes: Dict[str, Any], test_attributes: List[str], parent: Entity):
"""Initialize required attributes.
:param name: Name of the test
:param robot_attributes: Attributes passed through the listener
"""
super().__init__("TEST", parent)
# for backward compatibility with Robot < 4.0 mark every test case
# as critical if not set
self._critical = robot_attributes.get("critical", "yes")
Expand All @@ -264,12 +275,9 @@ def __init__(self, name: str, robot_attributes: Dict[str, Any], test_attributes:
self.message = robot_attributes.get("message")
self.name = name
self.robot_id = robot_attributes["id"]
self.rp_item_id = None
self.rp_parent_item_id = None
self.start_time = robot_attributes["starttime"]
self.status = robot_attributes.get("status")
self.template = robot_attributes["template"]
self.type = "TEST"
self.skipped_keywords = []

@property
Expand Down
2 changes: 1 addition & 1 deletion robotframework_reportportal/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ def log(self, message: LogMessage, ts: Optional[str] = None):
"""
sl_rq = {
"attachment": message.attachment,
"item_id": message.item_id,
"item_id": None if message.launch_log else message.item_id,
"level": LOG_LEVEL_MAPPING.get(message.level, "INFO"),
"message": message.message,
"time": ts or to_epoch(message.timestamp) or timestamp(),
Expand Down
Loading

0 comments on commit 9d478ca

Please sign in to comment.