From 8a1c1a881d09e5b737db2d1d36895603edb2fa04 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Mon, 16 Oct 2023 22:12:10 +0300 Subject: [PATCH] Use client factory, add several configuration variables --- CHANGELOG.md | 5 ++ README.md | 28 +++-------- robotframework_reportportal/listener.py | 13 ++--- robotframework_reportportal/service.py | 35 +++++--------- robotframework_reportportal/variables.py | 21 ++++++-- tests/__init__.py | 2 +- tests/integration/test_variables.py | 61 ++++++++++++++++++++---- 7 files changed, 99 insertions(+), 66 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a754b4a..20e470e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ # Changelog ## [Unreleased] +### Added +- `RP_CLIENT_TYPE` configuration variable, by @HardNorth +- `RP_CONNECT_TIMEOUT` and `RP_READ_TIMEOUT` configuration variables, by @HardNorth +### Changed +- Client version updated on [5.5.0](https://github.com/reportportal/client-Python/releases/tag/5.5.0), by @HardNorth ### Removed - Dependency on `six`, by @HardNorth diff --git a/README.md b/README.md index 56d229a..dac0b01 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![PyPI](https://img.shields.io/pypi/v/robotframework-reportportal.svg?maxAge=259200)](https://pypi.python.org/pypi/robotframework-reportportal) [![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/master/graph/badge.svg)](https://codecov.io/gh/reportportal/agent-Python-RobotFramework) +[![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/) [![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) @@ -26,26 +26,6 @@ The latest stable version of library is available on PyPI: pip install robotframework-reportportal -[reportportal-client](https://github.com/reportportal/client-Python) -and [six](https://pypi.org/project/six/) will be installed as dependencies - -**IMPORTANT!** -The latest version **does not** support Report Portal versions below 5.0.0. - -Specify the last one release of the client version 3 to install or update the -client for other versions of Report Portal below 5.0.0: - -``` -pip install robotframework-reportportal~=3.0 -``` - -## Contribution - -All the fixes for the agent that supports Report Portal versions below 5.0.0 -should go into the v3 branch. -The master branch will store the code base for the agent for Report Portal -versions 5 and above. - ## Usage ### Properties @@ -66,6 +46,8 @@ REQUIRED: NOT REQUIRED: ``` +--variable RP_CLIENT_TYPE:"SYNC" + - Type of the under-the-hood ReportPortal client implamentation. Possible values: [SYNC, ASYNC_THREAD, ASYNC_BATCHED]. --variable RP_LAUNCH_UUID:"id_of_existing_rp_launch" - ID of existing Report Portal launch --variable RP_LAUNCH_DOC:"some_documentation_for_launch" @@ -78,6 +60,10 @@ NOT REQUIRED: - Default value is "stdout", Launch UUID print output. Possible values: [stderr, stdout]. --variable RP_TEST_ATTRIBUTES:"key1:value1 key1:value2 tag key2:value3" - Space-separated list of tags/attributes for the tests +--variable RP_CONNECT_TIMEOUT:"20" + - Default value is "10.0", connection timeout to ReportPortal server. +--variable RP_READ_TIMEOUT:"20" + - Default value is "10.0", response read timeout for ReportPortal connection. --variable RP_LOG_BATCH_SIZE:"10" - Default value is "20", affects size of async batch log requests --variable RP_LOG_BATCH_PAYLOAD_SIZE:"10240000" diff --git a/robotframework_reportportal/listener.py b/robotframework_reportportal/listener.py index f72ec52..1ad62ab 100644 --- a/robotframework_reportportal/listener.py +++ b/robotframework_reportportal/listener.py @@ -22,7 +22,7 @@ from queue import LifoQueue from warnings import warn -from reportportal_client.helpers import gen_attributes +from reportportal_client.helpers import gen_attributes, LifoQueue from .model import Keyword, Launch, Test, LogMessage, Suite from .service import RobotService @@ -32,13 +32,6 @@ logger = logging.getLogger(__name__) -class _LifoQueue(LifoQueue): - def last(self): - with self.mutex: - if self._qsize(): - return self.queue[-1] - - def check_rp_enabled(func): """Verify is RP is enabled in config.""" @wraps(func) @@ -54,14 +47,14 @@ def wrap(*args, **kwargs): class listener: """Robot Framework listener interface for reporting to Report Portal.""" - _items: _LifoQueue = ... + _items: LifoQueue = ... _service: Optional[RobotService] = ... _variables: Optional[Variables] = ... ROBOT_LISTENER_API_VERSION = 2 def __init__(self) -> None: """Initialize listener attributes.""" - self._items = _LifoQueue() + self._items = LifoQueue() self._service = None self._variables = None diff --git a/robotframework_reportportal/service.py b/robotframework_reportportal/service.py index 5fbea80..172281c 100644 --- a/robotframework_reportportal/service.py +++ b/robotframework_reportportal/service.py @@ -16,19 +16,6 @@ from typing import Optional -# Copyright (c) 2023 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 dateutil.parser import parse import logging @@ -38,7 +25,7 @@ get_package_version, timestamp ) -from reportportal_client.client import RPClient +from reportportal_client import RP, create_client from .model import Launch, Suite, Test, Keyword, LogMessage from .variables import Variables @@ -70,7 +57,7 @@ class RobotService(object): agent_name: str agent_version: str - rp: Optional[RPClient] + rp: Optional[RP] def __init__(self) -> None: """Initialize service attributes.""" @@ -95,28 +82,30 @@ def init_service(self, variables: Variables) -> None: :param variables: Report Portal variables """ if self.rp is None: - logger.debug(f'ReportPortal - Init service: endpoint={variables.endpoint}, project={variables.project}, ' - f'api_key={variables.api_key}') - self.rp = RPClient( + logger.debug(f'ReportPortal - Init service: endpoint={variables.endpoint}, ' + f'project={variables.project}, api_key={variables.api_key}') + + self.rp = create_client( + client_type=variables.client_type, endpoint=variables.endpoint, project=variables.project, api_key=variables.api_key, is_skipped_an_issue=variables.skipped_issue, log_batch_size=variables.log_batch_size, - retries=True, + retries=5, verify_ssl=variables.verify_ssl, max_pool_size=variables.pool_size, log_batch_payload_size=variables.log_batch_payload_size, launch_id=variables.launch_id, launch_uuid_print=variables.launch_uuid_print, - print_output=variables.launch_uuid_print_output + print_output=variables.launch_uuid_print_output, + http_timeout=variables.http_timeout ) - self.rp.start() def terminate_service(self) -> None: - """Terminate common reportportal client.""" + """Terminate common ReportPortal client.""" if self.rp: - self.rp.terminate() + self.rp.close() def start_launch(self, launch: Launch, mode: Optional[str] = None, rerun: bool = False, rerun_of: Optional[str] = None, diff --git a/robotframework_reportportal/variables.py b/robotframework_reportportal/variables.py index 55a99eb..5c16f5d 100644 --- a/robotframework_reportportal/variables.py +++ b/robotframework_reportportal/variables.py @@ -15,10 +15,10 @@ from distutils.util import strtobool from os import path -from typing import Optional, Union, Dict, Any +from typing import Optional, Union, Dict, Tuple, Any from warnings import warn -from reportportal_client import OutputType +from reportportal_client import OutputType, ClientType from reportportal_client.logs import MAX_LOG_BATCH_PAYLOAD_SIZE from robot.libraries.BuiltIn import BuiltIn, RobotNotRunningError @@ -65,7 +65,8 @@ class Variables: log_batch_payload_size: int = ... launch_uuid_print: bool launch_uuid_print_output: Optional[OutputType] - client_type: [] + client_type: Optional[ClientType] + http_timeout: Optional[Union[Tuple[float, float], float]] def __init__(self) -> None: """Initialize instance attributes.""" @@ -102,6 +103,20 @@ def __init__(self) -> None: self.launch_uuid_print = bool(strtobool(get_variable('RP_LAUNCH_UUID_PRINT', default='False'))) output_type = get_variable('RP_LAUNCH_UUID_PRINT_OUTPUT') self.launch_uuid_print_output = OutputType[output_type.upper()] if output_type else None + client_type = get_variable('RP_CLIENT_TYPE') + self.client_type = ClientType[client_type.upper()] if client_type else ClientType.SYNC + connect_timeout = get_variable('RP_CONNECT_TIMEOUT') + connect_timeout = float(connect_timeout) if connect_timeout else None + + read_timeout = get_variable('RP_READ_TIMEOUT') + read_timeout = float(read_timeout) if read_timeout else None + + if connect_timeout is None and read_timeout is None: + self.http_timeout = None + elif connect_timeout is not None and read_timeout is not None: + self.http_timeout = (connect_timeout, read_timeout) + else: + self.http_timeout = connect_timeout or read_timeout self.api_key = get_variable('RP_API_KEY') if not self.api_key: diff --git a/tests/__init__.py b/tests/__init__.py index c84a291..7c58005 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -14,4 +14,4 @@ limitations under the License """ -REPORT_PORTAL_SERVICE = 'robotframework_reportportal.service.RPClient' +REPORT_PORTAL_SERVICE = 'reportportal_client.RPClient' diff --git a/tests/integration/test_variables.py b/tests/integration/test_variables.py index 2bed30e..eb779f8 100644 --- a/tests/integration/test_variables.py +++ b/tests/integration/test_variables.py @@ -12,13 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -import sys import warnings from unittest import mock +# noinspection PyPackageRequirements import pytest +from reportportal_client import OutputType, RPClient, ThreadedRPClient, BatchedRPClient -from reportportal_client import OutputType from tests import REPORT_PORTAL_SERVICE from tests.helpers import utils @@ -56,9 +56,6 @@ def test_agent_pass_launch_uuid_variable(mock_client_init): assert mock_client.start_launch.call_count == 0 -@pytest.mark.skipif(sys.version_info < (3, 6), - reason='For some reasons the test passes only for the ' - 'first variable for Python 2.7') @pytest.mark.parametrize('variable, warn_num', [('RP_PROJECT', 1), ('RP_API_KEY', 2), ('RP_ENDPOINT', 1), ('RP_LAUNCH', 1)]) @@ -188,7 +185,8 @@ def test_launch_uuid_print(mock_client_init): def test_launch_uuid_print_stderr(mock_client_init): print_uuid = True variables = utils.DEFAULT_VARIABLES.copy() - variables.update({'RP_LAUNCH_UUID_PRINT': str(print_uuid), 'RP_LAUNCH_UUID_PRINT_OUTPUT': 'stderr'}.items()) + variables.update( + {'RP_LAUNCH_UUID_PRINT': str(print_uuid), 'RP_LAUNCH_UUID_PRINT_OUTPUT': 'stderr'}.items()) result = utils.run_robot_tests(['examples/simple.robot'], variables=variables) @@ -216,10 +214,57 @@ def test_launch_uuid_print_invalid_output(mock_client_init): def test_no_launch_uuid_print(mock_client_init): variables = utils.DEFAULT_VARIABLES.copy() - result = utils.run_robot_tests(['examples/simple.robot'], - variables=variables) + result = utils.run_robot_tests(['examples/simple.robot'], variables=variables) assert int(result) == 0, 'Exit code should be 0 (no errors)' assert mock_client_init.call_count == 1 assert mock_client_init.call_args_list[0][1]['launch_uuid_print'] is False assert mock_client_init.call_args_list[0][1]['print_output'] is None + + +@pytest.mark.parametrize( + 'variable_value, expected_type', + [('SYNC', RPClient), ('ASYNC_THREAD', ThreadedRPClient), + ('ASYNC_BATCHED', BatchedRPClient), (None, RPClient)] +) +@mock.patch('reportportal_client.aio.client.Client') +@mock.patch(REPORT_PORTAL_SERVICE) +def test_client_types(mock_client_init, mock_async_client_init, variable_value, expected_type): + variables = utils.DEFAULT_VARIABLES.copy() + if variable_value: + variables['RP_CLIENT_TYPE'] = variable_value + + result = utils.run_robot_tests(['examples/simple.robot'], variables=variables) + + assert int(result) == 0, 'Exit code should be 0 (no errors)' + if expected_type is RPClient: + assert mock_async_client_init.call_count == 0 + assert mock_client_init.call_count == 1 + else: + assert mock_async_client_init.call_count == 1 + assert mock_client_init.call_count == 0 + + +@pytest.mark.parametrize( + 'connect_value, read_value, expected_result', + [ + ('5', '15', (5.0, 15.0)), + ('5.5', '15.5', (5.5, 15.5)), + (None, None, None), + (None, '5', 5), + ('5', None, 5) + ] +) +@mock.patch(REPORT_PORTAL_SERVICE) +def test_client_types(mock_client_init, connect_value, read_value, expected_result): + variables = utils.DEFAULT_VARIABLES.copy() + if connect_value: + variables['RP_CONNECT_TIMEOUT'] = connect_value + if read_value: + variables['RP_READ_TIMEOUT'] = read_value + + result = utils.run_robot_tests(['examples/simple.robot'], variables=variables) + + assert int(result) == 0, 'Exit code should be 0 (no errors)' + assert mock_client_init.call_count == 1 + assert mock_client_init.call_args_list[0][1]['http_timeout'] == expected_result