diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1fe16d4..e2a445c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -53,7 +53,7 @@ jobs: run: tox - name: Upload coverage to Codecov - if: matrix.python-version == 3.7 && success() + if: matrix.python-version == 3.8 && success() uses: codecov/codecov-action@v3 with: files: coverage.xml diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a8728e..20e470e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ ## [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 + +## [5.4.0] +### Added - `RP_LAUNCH_UUID_PRINT` and `RP_LAUNCH_UUID_PRINT_OUTPUT` configuration variables, by @HardNorth ### Changed - Internal item list was replaced with LifoQueue, by @HardNorth diff --git a/README.md b/README.md index bc178a1..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,14 +46,24 @@ 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" - Description for the launch --variable RP_LAUNCH_ATTRIBUTES:"RF tag_name:tag_value" - Space-separated list of tags/attributes for the launch +--variable RP_LAUNCH_UUID_PRINT:"True" + - Default value is "False", enables printing Launch UUID on test run start. +--variable RP_LAUNCH_UUID_PRINT_OUTPUT:"stderr" + - 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/requirements.txt b/requirements.txt index 303ccc8..7ff207f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,4 @@ # Basic dependencies -python-dateutil>=2.8.1 -reportportal-client==5.4.0 +python-dateutil~=2.8.1 +reportportal-client~=5.5.1 robotframework -six>=1.15.0 diff --git a/robotframework_reportportal/__init__.py b/robotframework_reportportal/__init__.py index 5c2e8dc..96dfbf3 100644 --- a/robotframework_reportportal/__init__.py +++ b/robotframework_reportportal/__init__.py @@ -1,5 +1,3 @@ -"""This package contains modules for reporting to Report Portal.""" - # 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. @@ -12,3 +10,5 @@ # 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 + +"""This package contains modules for reporting to Report Portal.""" diff --git a/robotframework_reportportal/listener.py b/robotframework_reportportal/listener.py index 534ca25..fadccd9 100644 --- a/robotframework_reportportal/listener.py +++ b/robotframework_reportportal/listener.py @@ -1,5 +1,3 @@ -"""This module includes Robot Framework listener interfaces.""" - # Copyright 2023 EPAM Systems # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,15 +12,16 @@ # See the License for the specific language governing permissions and # limitations under the License. +"""This module includes Robot Framework listener interfaces.""" + import logging import os from functools import wraps from mimetypes import guess_type from typing import Optional, Dict, Union, Any -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 +31,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 +46,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/model.py b/robotframework_reportportal/model.py index 06602d1..d4225ce 100644 --- a/robotframework_reportportal/model.py +++ b/robotframework_reportportal/model.py @@ -1,5 +1,3 @@ -"""This module contains models representing Robot Framework test items.""" - # 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. @@ -13,10 +11,9 @@ # See the License for the specific language governing permissions and # limitations under the License -import os +"""This module contains models representing Robot Framework test items.""" -from reportportal_client.helpers import convert_string -from six import text_type +import os class Suite(object): @@ -184,14 +181,10 @@ def __init__(self, name, attributes, parent_type=None): def get_name(self): """Get name of the keyword suitable for Report Portal.""" - assign = convert_string(', '.join(self.assign)) + assign = ', '.join(self.assign) assignment = '{0} = '.format(assign) if self.assign else '' arguments = ', '.join(self.args) - full_name = '{0}{1} ({2})'.format( - assignment, - convert_string(self.name), - convert_string(arguments) - ) + full_name = f'{assignment}{self.name} ({arguments})' return full_name[:256] def get_type(self): @@ -216,7 +209,7 @@ def update(self, attributes): return self -class LogMessage(text_type): +class LogMessage(str): """Class represents Robot Framework messages.""" def __init__(self, message): diff --git a/robotframework_reportportal/model.pyi b/robotframework_reportportal/model.pyi index 80b1c9b..79fd73d 100644 --- a/robotframework_reportportal/model.pyi +++ b/robotframework_reportportal/model.pyi @@ -11,92 +11,91 @@ # See the License for the specific language governing permissions and # limitations under the License -from six import text_type -from typing import Any, Dict, List, Optional, Text, Tuple, Union +from typing import Any, Dict, List, Optional, Tuple, Union class Suite: - attributes: Union[List[Text], Dict[Text]] = ... - doc: Text = ... - end_time: Text = ... - longname: Text = ... - message: Text = ... - metadata: Dict[Text, Text] = ... - name: Text = ... - robot_id: Text = ... - rp_item_id: Optional[Text] = ... - rp_parent_item_id: Optional[Text] = ... - start_time: Optional[Text] = ... - statistics: Text = ... - status: Text = ... - suites: List[Text] = ... - tests: List[Text] = ... + attributes: Union[List[str], Dict[str, Any]] = ... + doc: str = ... + end_time: str = ... + longname: str = ... + message: str = ... + 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: Text = 'SUITE' - def __init__(self, name: Text, attributes: Dict[Text, Any]) -> None: ... + type: str = 'SUITE' + def __init__(self, name: str, attributes: Dict[str, Any]) -> None: ... @property - def source(self) -> Text: ... - def update(self, attributes: Dict[Text, Any]) -> Union[Launch, Suite]: ... + def source(self) -> str: ... + def update(self, attributes: Dict[str, Any]) -> Union[Launch, Suite]: ... class Launch(Suite): - type: Text = 'LAUNCH' - def __init__(self, name: Text, attributes: Dict[Text, Any]) -> None: ... + type: str = 'LAUNCH' + def __init__(self, name: str, attributes: Dict[str, Any]) -> None: ... class Test: - _critical: Text = ... - _tags: List[Text] = ... - _attributes: Dict[Text, Any] = ... - attributes: List[Dict[Text, Text]] = ... - doc: Text = ... - end_time: Text = ... - longname: Text = ... - message: Text = ... - name: Text = ... - robot_id: Text = ... - rp_item_id: Optional[Text] = ... - rp_parent_item_id: Optional[Text] = ... - start_time: Text = ... - status: Text = ... - template: Text = ... - type: Text = 'TEST' - def __init__(self, name: Text, attributes: Dict[Text, Any]) -> None: ... + _critical: str = ... + _tags: List[str] = ... + _attributes: Dict[str, Any] = ... + attributes: List[Dict[str, str]] = ... + doc: str = ... + end_time: str = ... + longname: str = ... + 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' + def __init__(self, name: str, attributes: Dict[str, Any]) -> None: ... @property def critical(self) -> bool: ... @property - def tags(self) -> List[Text]: ... + def tags(self) -> List[str]: ... @property - def source(self) -> Text: ... + def source(self) -> str: ... @property - def code_ref(self) -> Text: ... + def code_ref(self) -> str: ... @property - def test_case_id(self) -> Optional[Text]: ... - def update(self, attributes: Dict[Text, Any]) -> Test: ... + def test_case_id(self) -> Optional[str]: ... + def update(self, attributes: Dict[str, Any]) -> Test: ... class Keyword: - attributes: Dict[Text, Any] = ... - args: List[Text] = ... - assign: List[Text] = ... - doc: Text = ... - end_time: Text = ... - keyword_name: Text = ... - keyword_type: Text = ... - libname: Text = ... - name: Text = ... - rp_item_id: Optional[Text] = ... - rp_parent_item_id: Optional[Text] = ... - parent_type: Text = ... - start_time: Text = ... - status: Text = ... - tags: List[Text] = ... - type: Text = 'KEYWORD' - def __init__(self, name: Text, attributes: Dict[Text, Any], parent_type: Optional[Text] = None) -> None: ... - def get_name(self) -> Text: ... - def get_type(self) -> Text: ... - def update(self, attributes: Dict[Text, Any]) -> Keyword: ... + attributes: Dict[str, Any] = ... + args: List[str] = ... + assign: List[str] = ... + doc: str = ... + end_time: str = ... + keyword_name: str = ... + 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] = ... + type: str = 'KEYWORD' + def __init__(self, name: str, attributes: Dict[str, Any], parent_type: Optional[str] = None) -> None: ... + def get_name(self) -> str: ... + def get_type(self) -> str: ... + def update(self, attributes: Dict[str, Any]) -> Keyword: ... -class LogMessage(text_type): - attachment: Optional[Dict[Text, Text]] = ... +class LogMessage(str): + attachment: Optional[Dict[str, str]] = ... launch_log: bool = ... - item_id: Optional[Text] = ... - level: Text = ... - message: Text = ... + item_id: Optional[str] = ... + level: str = ... + message: str = ... def __init__(self, *args: Tuple, **kwargs: Dict) -> None: ... diff --git a/robotframework_reportportal/result_visitor.py b/robotframework_reportportal/result_visitor.py index f7a071d..23ab5ab 100644 --- a/robotframework_reportportal/result_visitor.py +++ b/robotframework_reportportal/result_visitor.py @@ -17,7 +17,7 @@ from datetime import datetime from robot.api import ResultVisitor -from six.moves.urllib.parse import unquote +from urllib.parse import unquote from . import listener from .time_visitor import corrections diff --git a/robotframework_reportportal/service.py b/robotframework_reportportal/service.py index 784118c..172281c 100644 --- a/robotframework_reportportal/service.py +++ b/robotframework_reportportal/service.py @@ -1,7 +1,5 @@ -"""This module is a Robot service for reporting results to Report Portal.""" -from typing import Optional - -# Copyright (c) 2023 EPAM Systems +# Copyright 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 @@ -12,7 +10,11 @@ # 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 +# limitations under the License. + +"""This module is a Robot service for reporting results to Report Portal.""" + +from typing import Optional from dateutil.parser import parse import logging @@ -23,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 @@ -55,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.""" @@ -80,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/static.py b/robotframework_reportportal/static.py index fdf536a..856c465 100644 --- a/robotframework_reportportal/static.py +++ b/robotframework_reportportal/static.py @@ -1,16 +1,18 @@ -"""This module includes static variables of the agent. +# Copyright 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. -Copyright (c) 2021 https://reportportal.io . -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. -""" +"""This module includes static variables of the agent.""" LOG_LEVEL_MAPPING = { 'INFO': 'INFO', diff --git a/robotframework_reportportal/variables.py b/robotframework_reportportal/variables.py index d9938be..5c16f5d 100644 --- a/robotframework_reportportal/variables.py +++ b/robotframework_reportportal/variables.py @@ -1,5 +1,3 @@ -"""This module contains model that stores Robot Framework variables.""" - # 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. @@ -13,24 +11,20 @@ # See the License for the specific language governing permissions and # limitations under the License -import sys +"""This module contains model that stores Robot Framework variables.""" from distutils.util import strtobool from os import path -from typing import Optional, Union, Dict, Any, TextIO +from typing import Optional, Union, Dict, Tuple, Any from warnings import warn -from reportportal_client.logs.log_manager import MAX_LOG_BATCH_PAYLOAD_SIZE +from reportportal_client import OutputType, ClientType +from reportportal_client.logs import MAX_LOG_BATCH_PAYLOAD_SIZE from robot.libraries.BuiltIn import BuiltIn, RobotNotRunningError # This is a storage for the result visitor _variables: Dict[str, Any] = {} -OUTPUT_TYPES: Dict[str, TextIO] = { - 'stdout': sys.stdout, - 'stderr': sys.stderr -} - def get_variable(name: str, default: Optional[str] = None) -> Optional[str]: """Get Robot Framework variable. @@ -70,7 +64,9 @@ class Variables: skipped_issue: bool = ... log_batch_payload_size: int = ... launch_uuid_print: bool - launch_uuid_print_output: TextIO + launch_uuid_print_output: Optional[OutputType] + client_type: Optional[ClientType] + http_timeout: Optional[Union[Tuple[float, float], float]] def __init__(self) -> None: """Initialize instance attributes.""" @@ -105,27 +101,39 @@ def __init__(self) -> None: 'RP_LOG_BATCH_PAYLOAD_SIZE', default=str(MAX_LOG_BATCH_PAYLOAD_SIZE))) self.launch_uuid_print = bool(strtobool(get_variable('RP_LAUNCH_UUID_PRINT', default='False'))) - self.launch_uuid_print_output = OUTPUT_TYPES.get( - get_variable('RP_LAUNCH_UUID_PRINT_OUTPUT', default='stdout').lower(), OUTPUT_TYPES['stdout']) + 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: token = get_variable('RP_UUID') if token: warn( - message="Argument `token` is deprecated since 2.0.4 and " - "will be subject for removing in the next major " - "version. Use `api_key` argument instead.", + message="Argument `RP_UUID` is deprecated since version 5.3.3 and will be subject for " + "removing in the next major version. Use `RP_API_KEY` argument instead.", category=DeprecationWarning, stacklevel=2 ) self.api_key = token else: warn( - message="Argument `api_key` is `None` or empty string, " - "that's not supposed to happen because Report " - "Portal is usually requires an authorization key. " - "Please check your code.", + message="Argument `RP_API_KEY` is `None` or empty string, that's not supposed to happen " + "because ReportPortal is usually requires an authorization key. Please check your" + " configuration.", category=RuntimeWarning, stacklevel=2 ) diff --git a/setup.py b/setup.py index 6a802a3..730eff1 100644 --- a/setup.py +++ b/setup.py @@ -1,10 +1,24 @@ +# Copyright 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. + """Setup instructions for the package.""" import os from setuptools import setup -__version__ = '5.4.0' +__version__ = '5.5.0' def read_file(fname): 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/helpers/__init__.py b/tests/helpers/__init__.py index 1b24557..cb5c8e3 100644 --- a/tests/helpers/__init__.py +++ b/tests/helpers/__init__.py @@ -1,15 +1,15 @@ -"""This package contains test helpers. +# Copyright 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. -Copyright (c) 2021 https://reportportal.io . -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 -""" +"""This package contains test helpers.""" diff --git a/tests/helpers/utils.py b/tests/helpers/utils.py index 99aa716..49811d7 100644 --- a/tests/helpers/utils.py +++ b/tests/helpers/utils.py @@ -1,18 +1,19 @@ -"""This module contains utility code for unit tests. +# Copyright 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. + +"""This module contains utility code for unit tests.""" -Copyright (c) 2021 https://reportportal.io . -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 -""" import random import time diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py index fa2bf7d..685789d 100644 --- a/tests/integration/__init__.py +++ b/tests/integration/__init__.py @@ -1,15 +1,15 @@ -"""This package contains integration tests for the project. +# Copyright 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. -Copyright (c) 2021 https://reportportal.io . -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 -""" +"""This package contains integration tests for the project.""" diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index feda408..4fc1b08 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -1,18 +1,19 @@ -"""This module contains common Pytest fixtures and hooks for unit tests. +# Copyright 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. + +"""This module contains common Pytest fixtures and hooks for unit tests.""" -Copyright (c) 2021 https://reportportal.io . -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 -""" import sys KEYWORDS_EXPECTED_TEST_NAMES = ['Invalid Password'] diff --git a/tests/integration/test_attachments.py b/tests/integration/test_attachments.py index b148119..bdbf04a 100644 --- a/tests/integration/test_attachments.py +++ b/tests/integration/test_attachments.py @@ -1,17 +1,17 @@ -""" -Copyright (c) 2021 https://reportportal.io . -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 +# Copyright 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. -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 delayed_assert import assert_expectations, expect from unittest import mock diff --git a/tests/integration/test_before_after.py b/tests/integration/test_before_after.py index 7119dc8..c6dcfc1 100644 --- a/tests/integration/test_before_after.py +++ b/tests/integration/test_before_after.py @@ -1,17 +1,16 @@ -""" -Copyright (c) 2022 https://reportportal.io . -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 -""" +# Copyright 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. import pytest from unittest import mock diff --git a/tests/integration/test_dynamic_tags.py b/tests/integration/test_dynamic_tags.py index a2e55fd..b116877 100644 --- a/tests/integration/test_dynamic_tags.py +++ b/tests/integration/test_dynamic_tags.py @@ -1,17 +1,16 @@ -""" -Copyright (c) 2021 https://reportportal.io . -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 -""" +# Copyright 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. import uuid diff --git a/tests/integration/test_logger.py b/tests/integration/test_logger.py index 2497e63..259a49d 100644 --- a/tests/integration/test_logger.py +++ b/tests/integration/test_logger.py @@ -1,17 +1,16 @@ -""" -Copyright (c) 2021 https://reportportal.io . -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 -""" +# Copyright 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 tests.helpers import utils from unittest import mock diff --git a/tests/integration/test_no_keyword_message.py b/tests/integration/test_no_keyword_message.py index c0ce8af..32751ea 100644 --- a/tests/integration/test_no_keyword_message.py +++ b/tests/integration/test_no_keyword_message.py @@ -1,17 +1,16 @@ -""" -Copyright (c) 2022 https://reportportal.io . -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 -""" +# Copyright 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.s import re from tests.helpers import utils diff --git a/tests/integration/test_templates_test.py b/tests/integration/test_templates_test.py index 4e44a81..75e6f5c 100644 --- a/tests/integration/test_templates_test.py +++ b/tests/integration/test_templates_test.py @@ -1,17 +1,16 @@ -""" -Copyright (c) 2021 https://reportportal.io . -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 -""" +# Copyright 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.s from delayed_assert import assert_expectations, expect from unittest import mock diff --git a/tests/integration/test_variables.py b/tests/integration/test_variables.py index 84c4d2b..d6be789 100644 --- a/tests/integration/test_variables.py +++ b/tests/integration/test_variables.py @@ -11,12 +11,13 @@ # 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. -import sys + import warnings -from io import StringIO from unittest import mock +# noinspection PyPackageRequirements import pytest +from reportportal_client import OutputType, RPClient, ThreadedRPClient, BatchedRPClient from tests import REPORT_PORTAL_SERVICE from tests.helpers import utils @@ -55,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)]) @@ -168,71 +166,105 @@ def test_rp_api_key_empty(mock_client_init): assert len(filter_agent_calls(w)) == 2 -@mock.patch('robotframework_reportportal.variables.OUTPUT_TYPES', new_callable=dict) @mock.patch(REPORT_PORTAL_SERVICE) -def test_launch_uuid_print(mock_client_init, output_types): +def test_launch_uuid_print(mock_client_init): print_uuid = True variables = utils.DEFAULT_VARIABLES.copy() variables.update({'RP_LAUNCH_UUID_PRINT': str(print_uuid)}.items()) - str_io = StringIO() - output_types['stdout'] = str_io 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'] == print_uuid - assert mock_client_init.call_args_list[0][1]['print_output'] is str_io + assert mock_client_init.call_args_list[0][1]['print_output'] is None -@mock.patch('robotframework_reportportal.variables.OUTPUT_TYPES', new_callable=dict) @mock.patch(REPORT_PORTAL_SERVICE) -def test_launch_uuid_print_stderr(mock_client_init, output_types): +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()) - str_io = StringIO() - output_types['stderr'] = str_io - output_types['stdout'] = sys.stdout 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'] == print_uuid - assert mock_client_init.call_args_list[0][1]['print_output'] is str_io + assert mock_client_init.call_args_list[0][1]['print_output'] is OutputType.STDERR -@mock.patch('robotframework_reportportal.variables.OUTPUT_TYPES', new_callable=dict) @mock.patch(REPORT_PORTAL_SERVICE) -def test_launch_uuid_print_invalid_output(mock_client_init, output_types): +def test_launch_uuid_print_invalid_output(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': 'something'}.items()) + variables.update({'RP_LAUNCH_UUID_PRINT': str(print_uuid), + 'RP_LAUNCH_UUID_PRINT_OUTPUT': 'something'}.items()) - str_io = StringIO() - output_types['stdout'] = str_io 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'] == print_uuid - assert mock_client_init.call_args_list[0][1]['print_output'] is str_io + assert mock_client_init.call_count == 0 -@mock.patch('robotframework_reportportal.variables.OUTPUT_TYPES', new_callable=dict) @mock.patch(REPORT_PORTAL_SERVICE) -def test_no_launch_uuid_print(mock_client_init, output_types): +def test_no_launch_uuid_print(mock_client_init): variables = utils.DEFAULT_VARIABLES.copy() - str_io = StringIO() - output_types['stdout'] = str_io - 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 str_io + 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_timeouts(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 diff --git a/tests/integration/test_wuks_keyword.py b/tests/integration/test_wuks_keyword.py index 5257e68..014e39f 100644 --- a/tests/integration/test_wuks_keyword.py +++ b/tests/integration/test_wuks_keyword.py @@ -1,17 +1,16 @@ -""" -Copyright (c) 2022 https://reportportal.io . -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 -""" +# Copyright 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 unittest import mock diff --git a/tox.ini b/tox.ini index a801ad3..6b18893 100644 --- a/tox.ini +++ b/tox.ini @@ -25,8 +25,8 @@ commands = pre-commit run --all-files --show-diff-on-failure [gh-actions] python = - 3.7: pep, py37 - 3.8: py38 + 3.7: py37 + 3.8: pep, py38 3.9: py39 3.10: py310 3.11: py311