Skip to content

Commit

Permalink
Merge pull request #185 from reportportal/develop
Browse files Browse the repository at this point in the history
Release
  • Loading branch information
HardNorth authored Mar 19, 2024
2 parents 30397ef + b289825 commit b7701e4
Show file tree
Hide file tree
Showing 13 changed files with 149 additions and 69 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: '3.8'

Expand Down Expand Up @@ -76,7 +76,7 @@ jobs:
git push --tags
- name: Checkout develop branch
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
ref: 'develop'
fetch-depth: 0
Expand Down
7 changes: 4 additions & 3 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ jobs:
python-version: [ '3.7', '3.8', '3.9', '3.10', '3.11' ]
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

Expand All @@ -54,8 +54,9 @@ jobs:

- name: Upload coverage to Codecov
if: matrix.python-version == 3.8 && success()
uses: codecov/codecov-action@v3
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: coverage.xml
flags: unittests
name: codecov-client-reportportal
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
# Changelog

## [Unreleased]
### Added
- Binary data escaping in `listener` module (enhancing `Get Binary File` keyword logging), by @HardNorth
### Changed
- Client version updated on [5.5.5](https://github.com/reportportal/client-Python/releases/tag/5.5.5), by @HardNorth

## [5.5.1]
### Changed
- Unified ReportPortal product spelling, by @HardNorth
- Client version updated on [5.5.4](https://github.com/reportportal/client-Python/releases/tag/5.5.4), by @HardNorth
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
[![Python versions](https://img.shields.io/pypi/pyversions/robotframework-reportportal.svg)](https://pypi.org/project/robotframework-reportportal)
[![Build Status](https://github.com/reportportal/agent-Python-RobotFramework/actions/workflows/tests.yml/badge.svg)](https://github.com/reportportal/agent-Python-RobotFramework/actions/workflows/tests.yml)
[![codecov.io](https://codecov.io/gh/reportportal/agent-Python-RobotFramework/branch/develop/graph/badge.svg)](https://codecov.io/gh/reportportal/agent-Python-RobotFramework)
[![Join Slack chat!](https://slack.epmrpp.reportportal.io/badge.svg)](https://slack.epmrpp.reportportal.io/)
[![Join Slack chat!](https://img.shields.io/badge/slack-join-brightgreen.svg)](https://slack.epmrpp.reportportal.io/)
[![stackoverflow](https://img.shields.io/badge/reportportal-stackoverflow-orange.svg?style=flat)](http://stackoverflow.com/questions/tagged/reportportal)
[![Build with Love](https://img.shields.io/badge/build%20with-❤%EF%B8%8F%E2%80%8D-lightgrey.svg)](http://reportportal.io?style=flat)

Expand Down
16 changes: 16 additions & 0 deletions examples/binary_file_read.robot
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
*** Settings ***
Documentation Example of logging on binary file read
Library OperatingSystem

*** Variables ***
${PUG_IMAGE} res/pug/lucky.jpg

*** Keywords ***
Read Binary File
[Arguments] ${file}
${data} Get Binary File ${file}
Log ${data}

*** Test Cases ***
Read Pug Image
Read Binary File ${PUG_IMAGE}
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Basic dependencies
python-dateutil~=2.8.1
reportportal-client~=5.5.4
reportportal-client~=5.5.5
robotframework
85 changes: 79 additions & 6 deletions robotframework_reportportal/listener.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,79 @@

"""This module includes Robot Framework listener interfaces."""

import binascii
import logging
import os
import re
from functools import wraps
from mimetypes import guess_type
from types import MappingProxyType
from typing import Optional, Dict, Union, Any
from warnings import warn

from reportportal_client.helpers import gen_attributes, LifoQueue
from reportportal_client.helpers import gen_attributes, LifoQueue, is_binary, guess_content_type_from_bytes

from .model import Keyword, Launch, Test, LogMessage, Suite
from .service import RobotService
from .static import MAIN_SUITE_ID, PABOT_WIHOUT_LAUNCH_ID_MSG
from .variables import Variables

logger = logging.getLogger(__name__)
VARIABLE_PATTERN = r'^\s*\${[^}]*}\s*=\s*'
TRUNCATION_SIGN = "...'"
CONTENT_TYPE_TO_EXTENSIONS = MappingProxyType({
'application/pdf': 'pdf',
'application/zip': 'zip',
'application/java-archive': 'jar',
'image/jpeg': 'jpg',
'image/png': 'png',
'image/gif': 'gif',
'image/bmp': 'bmp',
'image/vnd.microsoft.icon': 'ico',
'image/webp': 'webp',
'audio/mpeg': 'mp3',
'audio/wav': 'wav',
'video/mpeg': 'mpeg',
'video/avi': 'avi',
'video/webm': 'webm',
'text/plain': 'txt',
'application/octet-stream': 'bin'
})


def _unescape(binary_string: str, stop_at: int = -1):
result = bytearray()
join_list = list()
join_idx = -3
skip_next = False
for i, b in enumerate(binary_string):
if skip_next:
skip_next = False
continue
if i < join_idx + 2:
join_list.append(b)
continue
else:
if len(join_list) > 0:
for bb in binascii.unhexlify(''.join(join_list)):
result.append(bb)
if stop_at > 0:
if len(result) >= stop_at:
break
join_list = list()
if b == '\\' and binary_string[i + 1] == 'x':
skip_next = True
join_idx = i + 2
continue
for bb in b.encode('utf-8'):
result.append(bb)
if stop_at > 0:
if len(result) >= stop_at:
break
if len(join_list) > 0:
for bb in binascii.unhexlify(''.join(join_list)):
result.append(bb)
return result


def check_rp_enabled(func):
Expand Down Expand Up @@ -88,11 +146,25 @@ def current_item(self) -> Optional[Union[Keyword, Launch, Suite, Test]]:

@check_rp_enabled
def log_message(self, message: Dict) -> None:
"""Send log message to the ReportPortal.
"""Send log message to the Report Portal.
:param message: Message passed by the Robot Framework
"""
msg = self._build_msg_struct(message)
if is_binary(msg.message):
variable_match = re.search(VARIABLE_PATTERN, msg.message)
if variable_match:
# Treat as partial binary data
msg_content = msg.message[variable_match.end():]
# remove trailing `'"...`, add `...'`
msg.message = (msg.message[variable_match.start():variable_match.end()]
+ str(msg_content.encode('utf-8'))[:-5] + TRUNCATION_SIGN)
else:
# Do not log full binary data, since it's usually corrupted
content_type = guess_content_type_from_bytes(_unescape(msg.message, 128))
msg.message = (f'Binary data of type "{content_type}" logging skipped, as it was processed as text and'
' hence corrupted.')
msg.level = 'WARN'
logger.debug('ReportPortal - Log Message: {0}'.format(message))
self.service.log(message=msg)

Expand Down Expand Up @@ -178,6 +250,7 @@ def start_suite(self, name: str, attributes: Dict, ts: Optional[Any] = None) ->
def end_suite(self, _: Optional[str], attributes: Dict, ts: Optional[Any] = None) -> None:
"""Finish started test suite at the ReportPortal.
:param _: Test suite name
:param attributes: Dictionary passed by the Robot Framework
:param ts: Timestamp(used by the ResultVisitor)
"""
Expand All @@ -204,8 +277,7 @@ def start_test(self, name: str, attributes: Dict, ts: Optional[Any] = None) -> N
attributes['source'] = getattr(self.current_item, 'source', None)
test = Test(name=name, attributes=attributes)
logger.debug('ReportPortal - Start Test: {0}'.format(attributes))
test.attributes = gen_attributes(
self.variables.test_attributes + test.tags)
test.attributes = gen_attributes(self.variables.test_attributes + test.tags)
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 All @@ -214,6 +286,7 @@ def start_test(self, name: str, attributes: Dict, ts: Optional[Any] = None) -> N
def end_test(self, _: Optional[str], attributes: Dict, ts: Optional[Any] = None) -> None:
"""Finish started test case at the ReportPortal.
:param _: Test case name
:param attributes: Dictionary passed by the Robot Framework
:param ts: Timestamp(used by the ResultVisitor)
"""
Expand All @@ -236,8 +309,7 @@ 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,
attributes=attributes)
kwd = Keyword(name=name, parent_type=self.current_item.type, attributes=attributes)
kwd.rp_parent_item_id = self.parent_id
logger.debug('ReportPortal - Start Keyword: {0}'.format(attributes))
kwd.rp_item_id = self.service.start_keyword(keyword=kwd, ts=ts)
Expand All @@ -247,6 +319,7 @@ def start_keyword(self, name: str, attributes: Dict, ts: Optional[Any] = None) -
def end_keyword(self, _: Optional[str], attributes: Dict, ts: Optional[Any] = None) -> None:
"""Finish started keyword at the ReportPortal.
:param _: Keyword name
:param attributes: Dictionary passed by the Robot Framework
:param ts: Timestamp(used by the ResultVisitor)
"""
Expand Down
6 changes: 3 additions & 3 deletions robotframework_reportportal/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import os


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

def __init__(self, name, attributes):
Expand Down Expand Up @@ -75,7 +75,7 @@ def __init__(self, name, attributes):
self.type = 'LAUNCH'


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

def __init__(self, name, attributes):
Expand Down Expand Up @@ -152,7 +152,7 @@ def update(self, attributes):
return self


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

def __init__(self, name, attributes, parent_type=None):
Expand Down
27 changes: 9 additions & 18 deletions robotframework_reportportal/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,7 @@ def to_epoch(date: Optional[str]) -> Optional[str]:
if hasattr(parsed_date, 'timestamp'):
epoch_time = parsed_date.timestamp()
else:
epoch_time = \
float(parsed_date.strftime('%s')) + parsed_date.microsecond / 1e6
epoch_time = float(parsed_date.strftime('%s')) + parsed_date.microsecond / 1e6
return str(int(epoch_time * 1000))


Expand Down Expand Up @@ -129,8 +128,7 @@ def start_launch(self, launch: Launch, mode: Optional[str] = None, rerun: bool =
'rerun_of': rerun_of,
'start_time': ts or to_epoch(launch.start_time) or timestamp()
}
logger.debug(
'ReportPortal - Start launch: request_body={0}'.format(sl_pt))
logger.debug('ReportPortal - Start launch: request_body={0}'.format(sl_pt))
return self.rp.start_launch(**sl_pt)

def finish_launch(self, launch: Launch, ts: Optional[str] = None) -> None:
Expand All @@ -143,8 +141,7 @@ def finish_launch(self, launch: Launch, ts: Optional[str] = None) -> None:
'end_time': ts or to_epoch(launch.end_time) or timestamp(),
'status': STATUS_MAPPING[launch.status]
}
logger.debug(
'ReportPortal - Finish launch: request_body={0}'.format(fl_rq))
logger.debug('ReportPortal - Finish launch: request_body={0}'.format(fl_rq))
self.rp.finish_launch(**fl_rq)

def start_suite(self, suite: Suite, ts: Optional[str] = None) -> Optional[str]:
Expand All @@ -162,8 +159,7 @@ def start_suite(self, suite: Suite, ts: Optional[str] = None) -> Optional[str]:
'parent_item_id': suite.rp_parent_item_id,
'start_time': ts or to_epoch(suite.start_time) or timestamp()
}
logger.debug(
'ReportPortal - Start suite: request_body={0}'.format(start_rq))
logger.debug('ReportPortal - Start suite: request_body={0}'.format(start_rq))
return self.rp.start_test_item(**start_rq)

def finish_suite(self, suite: Suite, issue: Optional[str] = None,
Expand All @@ -180,8 +176,7 @@ def finish_suite(self, suite: Suite, issue: Optional[str] = None,
'item_id': suite.rp_item_id,
'status': STATUS_MAPPING[suite.status]
}
logger.debug(
'ReportPortal - Finish suite: request_body={0}'.format(fta_rq))
logger.debug('ReportPortal - Finish suite: request_body={0}'.format(fta_rq))
self.rp.finish_test_item(**fta_rq)

def start_test(self, test: Test, ts: Optional[str] = None):
Expand All @@ -203,8 +198,7 @@ def start_test(self, test: Test, ts: Optional[str] = None):
'start_time': ts or to_epoch(test.start_time) or timestamp(),
'test_case_id': test.test_case_id
}
logger.debug(
'ReportPortal - Start test: request_body={0}'.format(start_rq))
logger.debug('ReportPortal - Start test: request_body={0}'.format(start_rq))
return self.rp.start_test_item(**start_rq)

def finish_test(self, test: Test, issue: Optional[str] = None, ts: Optional[str] = None):
Expand All @@ -221,8 +215,7 @@ def finish_test(self, test: Test, issue: Optional[str] = None, ts: Optional[str]
'item_id': test.rp_item_id,
'status': STATUS_MAPPING[test.status]
}
logger.debug(
'ReportPortal - Finish test: request_body={0}'.format(fta_rq))
logger.debug('ReportPortal - Finish test: request_body={0}'.format(fta_rq))
self.rp.finish_test_item(**fta_rq)

def start_keyword(self, keyword: Keyword, ts: Optional[str] = None):
Expand All @@ -239,8 +232,7 @@ def start_keyword(self, keyword: Keyword, ts: Optional[str] = None):
'parent_item_id': keyword.rp_parent_item_id,
'start_time': ts or to_epoch(keyword.start_time) or timestamp()
}
logger.debug(
'ReportPortal - Start keyword: request_body={0}'.format(start_rq))
logger.debug('ReportPortal - Start keyword: request_body={0}'.format(start_rq))
return self.rp.start_test_item(**start_rq)

def finish_keyword(self, keyword: Keyword, issue: Optional[str] = None, ts: Optional[str] = None):
Expand All @@ -256,8 +248,7 @@ def finish_keyword(self, keyword: Keyword, issue: Optional[str] = None, ts: Opti
'item_id': keyword.rp_item_id,
'status': STATUS_MAPPING[keyword.status]
}
logger.debug(
'ReportPortal - Finish keyword: request_body={0}'.format(fta_rq))
logger.debug('ReportPortal - Finish keyword: request_body={0}'.format(fta_rq))
self.rp.finish_test_item(**fta_rq)

def log(self, message: LogMessage, ts: Optional[str] = None):
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from setuptools import setup


__version__ = '5.5.1'
__version__ = '5.5.2'


def read_file(fname):
Expand Down
Loading

0 comments on commit b7701e4

Please sign in to comment.