From ca94a734e60acef4bd746966b31a6cbf5c886897 Mon Sep 17 00:00:00 2001 From: "reportportal.io" Date: Tue, 5 Dec 2023 10:59:05 +0000 Subject: [PATCH 01/15] Changelog update --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index def7617..c3551aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Changelog ## [Unreleased] + +## [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 From 8ad74a422a06ada9855d4d5f6fb61409d8aa3375 Mon Sep 17 00:00:00 2001 From: "reportportal.io" Date: Tue, 5 Dec 2023 10:59:06 +0000 Subject: [PATCH 02/15] Version update --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 554d35f..f96de59 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ from setuptools import setup -__version__ = '5.5.1' +__version__ = '5.5.2' def read_file(fname): From 9b0d2d0b7e43da2f70d5dc316eb9ee926dd12088 Mon Sep 17 00:00:00 2001 From: Reingold Shekhtel <13565058+raikbitters@users.noreply.github.com> Date: Fri, 9 Feb 2024 13:46:42 +0400 Subject: [PATCH 03/15] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 70e9004..a534926 100644 --- a/README.md +++ b/README.md @@ -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) From da205f96b76fa14aa3130754911e944eac14fc15 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Tue, 20 Feb 2024 16:58:03 +0300 Subject: [PATCH 04/15] Update tests.yml --- .github/workflows/tests.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e2a445c..becc28f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -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 }} @@ -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 From 23c9427cf0c430faa201f9134bb7870c4e61741c Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Tue, 20 Feb 2024 16:58:31 +0300 Subject: [PATCH 05/15] Update release.yml --- .github/workflows/release.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c0305ce..e4291d4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -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' @@ -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 From 3723b6cad2a85d4803af1251579e5ce7bad39a92 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Wed, 28 Feb 2024 17:45:29 +0300 Subject: [PATCH 06/15] Reformat --- robotframework_reportportal/service.py | 27 +++++++++----------------- 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/robotframework_reportportal/service.py b/robotframework_reportportal/service.py index 0919d4e..b5bd69a 100644 --- a/robotframework_reportportal/service.py +++ b/robotframework_reportportal/service.py @@ -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)) @@ -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: @@ -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]: @@ -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, @@ -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): @@ -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): @@ -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): @@ -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): @@ -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): From c9301be4efc9133c1cd6d88d2695360ffb398512 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Fri, 15 Mar 2024 17:04:17 +0300 Subject: [PATCH 07/15] Binary data escaping in `listener` module --- CHANGELOG.md | 2 ++ robotframework_reportportal/listener.py | 26 ++++++++++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c3551aa..a42dd66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Changelog ## [Unreleased] +### Added +- Binary data escaping in `listener` module (enhancing `Get Binary File` keyword logging), by @HardNorth ## [5.5.1] ### Changed diff --git a/robotframework_reportportal/listener.py b/robotframework_reportportal/listener.py index 17ea9b2..3a0ef4d 100644 --- a/robotframework_reportportal/listener.py +++ b/robotframework_reportportal/listener.py @@ -29,6 +29,23 @@ from .variables import Variables logger = logging.getLogger(__name__) +DATA_SIGN = '${data} = ' + + +def is_binary(iterable: Union[bytes, bytearray, str]) -> bool: + """Check if given iterable is binary. + + :param iterable: iterable to check + :return: True if iterable contains binary bytes, False otherwise + """ + if isinstance(iterable, str): + byte_iterable = iterable.encode('utf-8') + else: + byte_iterable = iterable + + if 0x00 in byte_iterable: + return True + return False def check_rp_enabled(func): @@ -88,11 +105,15 @@ 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 msg.message.startswith(DATA_SIGN): + msg_content = msg.message[len(DATA_SIGN):] + if is_binary(msg_content): + msg.message = DATA_SIGN + str(msg_content.encode('utf-8')[:-4]) # remove trailing '"...' logger.debug('ReportPortal - Log Message: {0}'.format(message)) self.service.log(message=msg) @@ -178,6 +199,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) """ @@ -214,6 +236,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) """ @@ -247,6 +270,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) """ From e85570991658443f7ae2bc32ce715e1047105fad Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Mon, 18 Mar 2024 12:17:12 +0300 Subject: [PATCH 08/15] Update binary logging --- robotframework_reportportal/listener.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/robotframework_reportportal/listener.py b/robotframework_reportportal/listener.py index 3a0ef4d..6fa29c6 100644 --- a/robotframework_reportportal/listener.py +++ b/robotframework_reportportal/listener.py @@ -30,6 +30,7 @@ logger = logging.getLogger(__name__) DATA_SIGN = '${data} = ' +TRUNCATION_SIGN = '...' def is_binary(iterable: Union[bytes, bytearray, str]) -> bool: @@ -113,7 +114,8 @@ def log_message(self, message: Dict) -> None: if msg.message.startswith(DATA_SIGN): msg_content = msg.message[len(DATA_SIGN):] if is_binary(msg_content): - msg.message = DATA_SIGN + str(msg_content.encode('utf-8')[:-4]) # remove trailing '"...' + # remove trailing `'"...`, add `'...` + msg.message = DATA_SIGN + str(msg_content.encode('utf-8')[:-5]) + TRUNCATION_SIGN logger.debug('ReportPortal - Log Message: {0}'.format(message)) self.service.log(message=msg) @@ -259,8 +261,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) From de43f83c4df9a8c224a5088ae69d9b272be54b8a Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Mon, 18 Mar 2024 12:17:42 +0300 Subject: [PATCH 09/15] Update binary logging --- robotframework_reportportal/listener.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/robotframework_reportportal/listener.py b/robotframework_reportportal/listener.py index 6fa29c6..5e9f96d 100644 --- a/robotframework_reportportal/listener.py +++ b/robotframework_reportportal/listener.py @@ -30,7 +30,7 @@ logger = logging.getLogger(__name__) DATA_SIGN = '${data} = ' -TRUNCATION_SIGN = '...' +TRUNCATION_SIGN = "'..." def is_binary(iterable: Union[bytes, bytearray, str]) -> bool: From 42801d35736072a147272111713a2382c447408d Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Mon, 18 Mar 2024 12:29:26 +0300 Subject: [PATCH 10/15] Update binary logging --- robotframework_reportportal/listener.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/robotframework_reportportal/listener.py b/robotframework_reportportal/listener.py index 5e9f96d..460f85e 100644 --- a/robotframework_reportportal/listener.py +++ b/robotframework_reportportal/listener.py @@ -30,7 +30,7 @@ logger = logging.getLogger(__name__) DATA_SIGN = '${data} = ' -TRUNCATION_SIGN = "'..." +TRUNCATION_SIGN = "...'" def is_binary(iterable: Union[bytes, bytearray, str]) -> bool: @@ -114,8 +114,8 @@ def log_message(self, message: Dict) -> None: if msg.message.startswith(DATA_SIGN): msg_content = msg.message[len(DATA_SIGN):] if is_binary(msg_content): - # remove trailing `'"...`, add `'...` - msg.message = DATA_SIGN + str(msg_content.encode('utf-8')[:-5]) + TRUNCATION_SIGN + # remove trailing `'"...`, add `...'` + msg.message = DATA_SIGN + str(msg_content.encode('utf-8'))[:-5] + TRUNCATION_SIGN logger.debug('ReportPortal - Log Message: {0}'.format(message)) self.service.log(message=msg) From 176ce3884eaf5e82ab756b9b98fbd766254f3c44 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Mon, 18 Mar 2024 16:14:49 +0300 Subject: [PATCH 11/15] Remove python 2.7 artifacts --- robotframework_reportportal/model.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/robotframework_reportportal/model.py b/robotframework_reportportal/model.py index 2fc2059..969b623 100644 --- a/robotframework_reportportal/model.py +++ b/robotframework_reportportal/model.py @@ -16,7 +16,7 @@ import os -class Suite(object): +class Suite: """Class represents Robot Framework test suite.""" def __init__(self, name, attributes): @@ -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): @@ -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): From 22951a08c81e111aa7dd07e1d5eae3d8cb885f39 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Mon, 18 Mar 2024 19:52:48 +0300 Subject: [PATCH 12/15] Client version update --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index fe5764f..6286d8a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ # Basic dependencies python-dateutil~=2.8.1 -reportportal-client~=5.5.4 +reportportal-client~=5.5.5 robotframework From 694ec976beb618e28f5756e8153e4dd5bc11dc30 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Tue, 19 Mar 2024 15:39:09 +0300 Subject: [PATCH 13/15] Add binary logging test --- CHANGELOG.md | 2 + examples/binary_file_read.robot | 16 +++++ robotframework_reportportal/listener.py | 96 ++++++++++++++++++------- tests/helpers/utils.py | 17 +++-- tests/helpers/utils.pyi | 28 -------- tests/integration/test_logger.py | 14 ++++ 6 files changed, 116 insertions(+), 57 deletions(-) create mode 100644 examples/binary_file_read.robot delete mode 100644 tests/helpers/utils.pyi diff --git a/CHANGELOG.md b/CHANGELOG.md index a42dd66..939fa4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ ## [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 diff --git a/examples/binary_file_read.robot b/examples/binary_file_read.robot new file mode 100644 index 0000000..29683ca --- /dev/null +++ b/examples/binary_file_read.robot @@ -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} diff --git a/robotframework_reportportal/listener.py b/robotframework_reportportal/listener.py index 460f85e..9232c8c 100644 --- a/robotframework_reportportal/listener.py +++ b/robotframework_reportportal/listener.py @@ -14,14 +14,17 @@ """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 @@ -29,24 +32,61 @@ from .variables import Variables logger = logging.getLogger(__name__) -DATA_SIGN = '${data} = ' +VARIABLE_PATTERN = r'^\s*\${[^}]*}\s*=\s*' TRUNCATION_SIGN = "...'" - - -def is_binary(iterable: Union[bytes, bytearray, str]) -> bool: - """Check if given iterable is binary. - - :param iterable: iterable to check - :return: True if iterable contains binary bytes, False otherwise - """ - if isinstance(iterable, str): - byte_iterable = iterable.encode('utf-8') - else: - byte_iterable = iterable - - if 0x00 in byte_iterable: - return True - return False +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): @@ -111,11 +151,20 @@ def log_message(self, message: Dict) -> None: :param message: Message passed by the Robot Framework """ msg = self._build_msg_struct(message) - if msg.message.startswith(DATA_SIGN): - msg_content = msg.message[len(DATA_SIGN):] - if is_binary(msg_content): + 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 = DATA_SIGN + str(msg_content.encode('utf-8'))[:-5] + TRUNCATION_SIGN + 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) @@ -228,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) diff --git a/tests/helpers/utils.py b/tests/helpers/utils.py index 49811d7..8fdfe21 100644 --- a/tests/helpers/utils.py +++ b/tests/helpers/utils.py @@ -16,10 +16,11 @@ import random import time +from typing import List, Optional, Dict, Any, Tuple from robot.run import RobotFramework -DEFAULT_VARIABLES = { +DEFAULT_VARIABLES: Dict[str, Any] = { 'RP_LAUNCH': 'Robot Framework', 'RP_ENDPOINT': 'http://localhost:8080', 'RP_PROJECT': 'default_personal', @@ -28,8 +29,10 @@ } -def run_robot_tests(tests, listener='robotframework_reportportal.listener', - variables=None, arguments=None): +def run_robot_tests(tests: List[str], + listener: str = 'robotframework_reportportal.listener', + variables: Optional[Dict[str, Any]] = None, + arguments: Optional[Dict[str, Any]] = None) -> int: cmd_arguments = ['--listener', listener] if arguments: for k, v in arguments.items(): @@ -51,11 +54,15 @@ def run_robot_tests(tests, listener='robotframework_reportportal.listener', return RobotFramework().execute_cli(cmd_arguments, False) -def get_launch_log_calls(mock): +def get_launch_log_calls(mock) -> List[Tuple[List[Any], Dict[str, Any]]]: return [e for e in mock.log.call_args_list if 'item_id' in e[1] and e[1]['item_id'] is None] -def item_id_gen(**kwargs): +def get_log_calls(mock) -> List[Tuple[List[Any], Dict[str, Any]]]: + return [e for e in mock.log.call_args_list if 'item_id' in e[1] and e[1]['item_id']] + + +def item_id_gen(**kwargs) -> str: return "{}-{}-{}".format(kwargs['name'], str(round(time.time() * 1000)), random.randint(0, 9999)) diff --git a/tests/helpers/utils.pyi b/tests/helpers/utils.pyi deleted file mode 100644 index 40013fa..0000000 --- a/tests/helpers/utils.pyi +++ /dev/null @@ -1,28 +0,0 @@ -# Copyright 2022 EPAM Systems -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from typing import Dict, List, Any, Optional, Tuple, Text - -DEFAULT_VARIABLES: Dict[str, Any] = ... - - -def run_robot_tests(tests: List[str], - listener: str = 'robotframework_reportportal.listener', - variables: Optional[Dict[str, Any]] = None, - arguments: Optional[Dict[str, Any]] = None) -> int: ... - - -def get_launch_log_calls(mock) -> List[Tuple[List[Any], Dict[str, Any]]]: ... - -def item_id_gen(**kwargs) -> Text:... diff --git a/tests/integration/test_logger.py b/tests/integration/test_logger.py index 259a49d..f80fa96 100644 --- a/tests/integration/test_logger.py +++ b/tests/integration/test_logger.py @@ -29,3 +29,17 @@ def test_launch_log(mock_client_init): messages = set(map(lambda x: x[1]['message'], calls)) assert messages == {'Hello, world!', 'Goodbye, world!', 'Enjoy my pug!'} + + +@mock.patch(REPORT_PORTAL_SERVICE) +def test_binary_file_log(mock_client_init): + result = utils.run_robot_tests(['examples/binary_file_read.robot']) + assert result == 0 # the test successfully passed + + mock_client = mock_client_init.return_value + calls = utils.get_log_calls(mock_client) + assert len(calls) == 3 + + messages = set(map(lambda x: x[1]['message'], calls)) + error_message = 'Binary data of type "image/jpeg" logging skipped, as it was processed as text and hence corrupted.' + assert error_message in messages From bdfb09baed841a47d57db7a67ce87a5e2c423018 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Tue, 19 Mar 2024 15:43:45 +0300 Subject: [PATCH 14/15] flake8 fixes --- robotframework_reportportal/listener.py | 4 ++-- tests/integration/test_logger.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/robotframework_reportportal/listener.py b/robotframework_reportportal/listener.py index 9232c8c..82030a3 100644 --- a/robotframework_reportportal/listener.py +++ b/robotframework_reportportal/listener.py @@ -162,8 +162,8 @@ def log_message(self, message: Dict) -> None: 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.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) diff --git a/tests/integration/test_logger.py b/tests/integration/test_logger.py index f80fa96..c6d69a1 100644 --- a/tests/integration/test_logger.py +++ b/tests/integration/test_logger.py @@ -41,5 +41,5 @@ def test_binary_file_log(mock_client_init): assert len(calls) == 3 messages = set(map(lambda x: x[1]['message'], calls)) - error_message = 'Binary data of type "image/jpeg" logging skipped, as it was processed as text and hence corrupted.' - assert error_message in messages + error_msg = 'Binary data of type "image/jpeg" logging skipped, as it was processed as text and hence corrupted.' + assert error_msg in messages From b289825047a5ebf7021bfeb5a7b4e2beb4fd844d Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Tue, 19 Mar 2024 16:00:44 +0300 Subject: [PATCH 15/15] flake8 fixes --- robotframework_reportportal/listener.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/robotframework_reportportal/listener.py b/robotframework_reportportal/listener.py index 82030a3..db87617 100644 --- a/robotframework_reportportal/listener.py +++ b/robotframework_reportportal/listener.py @@ -54,7 +54,7 @@ }) -def unescape(binary_string: str, stop_at: int = -1): +def _unescape(binary_string: str, stop_at: int = -1): result = bytearray() join_list = list() join_idx = -3 @@ -161,7 +161,7 @@ def log_message(self, message: Dict) -> None: + 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)) + 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'