diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 12fd8d1e..6e94eb9a 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -7,24 +7,6 @@ assignees: '' --- ---- -name: Bug report -about: Create a report to help us improve -title: '' -labels: '' -assignees: '' - ---- - ---- -name: Bug report -about: Create a report to help us improve -title: '' -labels: bug -assignees: '' - ---- - **Describe the bug** A clear and concise description of what the bug is. diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index dc251d00..abcfd4fd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -109,11 +109,11 @@ jobs: - name: Update version file id: versionFileUpdate run: | - export CURRENT_VERSION_VALUE=`echo '${{ env.CURRENT_VERSION }}' | sed -E 's/(.*)/${{ env.VERSION_REPLACE_PATTERN }}/'` - export NEXT_VERSION_VALUE=`echo '${{ env.NEXT_VERSION }}' | sed -E 's/(.*)/${{ env.VERSION_REPLACE_PATTERN }}/'` + export CURRENT_VERSION_VALUE=`echo '${{ env.CURRENT_VERSION }}' | sed -E "s/(.*)/${{ env.VERSION_REPLACE_PATTERN }}/"` + export NEXT_VERSION_VALUE=`echo '${{ env.NEXT_VERSION }}' | sed -E "s/(.*)/${{ env.VERSION_REPLACE_PATTERN }}/"` sed "s/${CURRENT_VERSION_VALUE}/${NEXT_VERSION_VALUE}/g" ${{ env.VERSION_FILE }} > ${{ env.VERSION_FILE }}${{ env.TMP_SUFFIX }} rm ${{ env.VERSION_FILE }} mv ${{ env.VERSION_FILE }}${{ env.TMP_SUFFIX }} ${{ env.VERSION_FILE }} git add ${{ env.VERSION_FILE }} - git commit -m "Version update" + git commit -m 'Version update' git push diff --git a/CHANGELOG.md b/CHANGELOG.md index 36d2f762..8f7cbaf2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ # Changelog ## [Unreleased] +### Added +- `rp_log_batch_payload_size` parameter, by @HardNorth +### Changed +- Feature [#311](https://github.com/reportportal/agent-python-pytest/issues/311): +Adding log format config option, by @dagansandler + +## [5.1.1] ### Fixed - Issue [#304](https://github.com/reportportal/agent-python-pytest/issues/304): SSL certificate flag handling issue, by @HardNorth diff --git a/README.rst b/README.rst index c75246c4..c6c6c381 100644 --- a/README.rst +++ b/README.rst @@ -7,11 +7,11 @@ agent-python-pytest :alt: Latest Version .. image:: https://img.shields.io/pypi/pyversions/pytest-reportportal.svg :target: https://pypi.org/project/pytest-reportportal +.. image:: https://github.com/reportportal/agent-python-pytest/actions/workflows/tests.yml/badge.svg + :target: https://github.com/reportportal/agent-python-pytest/actions/workflows/tests.yml + :alt: Test status .. image:: https://codecov.io/gh/reportportal/agent-python-pytest/branch/master/graph/badge.svg :target: https://codecov.io/gh/reportportal/agent-python-pytest -.. image:: https://github.com/reportportal/agent-python-pytest/actions/workflows/tests.yml/badge.svg - :target: https://github.com/reportportal/agent-python-pytest - Pytest plugin for reporting test results of the Pytest to the Reportal Portal. @@ -100,6 +100,9 @@ The following parameters are optional: by pytest --rp-launch-description option, default value is '') - :code:`rp_log_batch_size = 20` - size of batch log request +- :code:`rp_log_batch_payload_size = 65000000` - maximum payload size in bytes of async batch log requests +- :code:`rp_log_level = INFO` - The log level that will be reported +- :code:`rp_log_format = [%(levelname)7s] (%(name)s) %(message)s (%(filename)s:%(lineno)s)` - Format string to be used for logs sent to the service. - :code:`rp_ignore_attributes = 'xfail' 'usefixture'` - Ignore specified pytest markers - :code:`rp_is_skipped_an_issue = False` - Treat skipped tests as required investigation. Default is True. - :code:`rp_hierarchy_dirs_level = 0` - Directory starting hierarchy level (from pytest.ini level) (default `0`) @@ -204,7 +207,6 @@ Test issue info ~~~~~~~~~~~~~~~ Some pytest marks could be used to specify information about skipped or failed test result. -List of this marks should be specified in pytest ini file (see :code:`rp_issue_marks`). The following mark fields are used to get information about test issue: diff --git a/pytest_reportportal/config.py b/pytest_reportportal/config.py index 0a11a2d3..c3561b50 100644 --- a/pytest_reportportal/config.py +++ b/pytest_reportportal/config.py @@ -3,6 +3,8 @@ from distutils.util import strtobool from os import getenv +from reportportal_client.core.log_manager import MAX_LOG_BATCH_PAYLOAD_SIZE + try: # This try/except can go away once we support pytest >= 5.4.0 from _pytest.logging import get_actual_log_level @@ -49,7 +51,14 @@ def __init__(self, pytest_config): 'rp_launch_description') self.rp_log_batch_size = int(self.find_option(pytest_config, 'rp_log_batch_size')) + batch_payload_size = self.find_option( + pytest_config, 'rp_log_batch_payload_size') + if batch_payload_size: + self.rp_log_batch_payload_size = int(batch_payload_size) + else: + self.rp_log_batch_payload_size = MAX_LOG_BATCH_PAYLOAD_SIZE self.rp_log_level = get_actual_log_level(pytest_config, 'rp_log_level') + self.rp_log_format = self.find_option(pytest_config, 'rp_log_format') self.rp_mode = self.find_option(pytest_config, 'rp_mode') self.rp_parent_item_id = self.find_option(pytest_config, 'rp_parent_item_id') diff --git a/pytest_reportportal/config.pyi b/pytest_reportportal/config.pyi index b90a635d..c42df1fd 100644 --- a/pytest_reportportal/config.pyi +++ b/pytest_reportportal/config.pyi @@ -20,7 +20,9 @@ class AgentConfig: rp_launch_attributes: Optional[List] = ... rp_launch_description: Text = ... rp_log_batch_size: int = ... + rp_log_batch_payload_size: int = ... rp_log_level: Optional[int] = ... + rp_log_format: Optional[Text] = ... rp_mode: Text = ... rp_parent_item_id: Optional[Text] = ... rp_project: Text = ... diff --git a/pytest_reportportal/plugin.py b/pytest_reportportal/plugin.py index e24cac09..1a4b61ac 100644 --- a/pytest_reportportal/plugin.py +++ b/pytest_reportportal/plugin.py @@ -11,6 +11,7 @@ import pytest import requests from reportportal_client import RPLogHandler +from reportportal_client.core.log_manager import MAX_LOG_BATCH_PAYLOAD_SIZE from reportportal_client.errors import ResponseError from pytest_reportportal import LAUNCH_WAIT_TIMEOUT @@ -225,6 +226,9 @@ def pytest_runtest_protocol(item): endpoint=agent_config.rp_endpoint, ignored_record_names=('reportportal_client', 'pytest_reportportal')) + log_format = agent_config.rp_log_format + if log_format: + log_handler.setFormatter(logging.Formatter(log_format)) with patching_logger_class(): with _pytest.logging.catching_logs(log_handler, level=log_level): yield @@ -309,6 +313,10 @@ def add_shared_option(name, help, default=None, action='store'): name='rp_log_level', help='Logging level for automated log records reporting', ) + add_shared_option( + name='rp_log_format', + help='Logging format for automated log records reporting', + ) add_shared_option( name='rp_rerun', help='Marks the launch as a rerun', @@ -346,6 +354,10 @@ def add_shared_option(name, help, default=None, action='store'): 'rp_log_batch_size', default='20', help='Size of batch log requests in async mode') + parser.addini( + 'rp_log_batch_payload_size', + default=str(MAX_LOG_BATCH_PAYLOAD_SIZE), + help='Maximum payload size in bytes of async batch log requests') parser.addini( 'rp_ignore_attributes', type='args', diff --git a/pytest_reportportal/service.py b/pytest_reportportal/service.py index 41f2c949..1e6e4864 100644 --- a/pytest_reportportal/service.py +++ b/pytest_reportportal/service.py @@ -21,11 +21,11 @@ from reportportal_client.client import RPClient from reportportal_client.external.google_analytics import send_event from reportportal_client.helpers import ( + dict_to_payload, gen_attributes, get_launch_sys_attrs, get_package_version ) -from reportportal_client.service import _dict_to_payload log = logging.getLogger(__name__) @@ -135,7 +135,7 @@ def _get_launch_attributes(self, ini_attrs): system_attributes = get_launch_sys_attrs() system_attributes['agent'] = ( '{}-{}'.format(self.agent_name, self.agent_version)) - return attributes + _dict_to_payload(system_attributes) + return attributes + dict_to_payload(system_attributes) def _build_start_launch_rq(self): rp_launch_attributes = self._config.rp_launch_attributes @@ -841,6 +841,7 @@ def start(self): retries=self._config.rp_retries, verify_ssl=self._config.rp_verify_ssl, launch_id=launch_id, + log_batch_payload_size=self._config.rp_log_batch_payload_size ) if self.rp and hasattr(self.rp, "get_project_settings"): self.project_settings = self.rp.get_project_settings() diff --git a/requirements.txt b/requirements.txt index 4c1e1ab0..f3ba3fe5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ dill>=0.2.7.1 pytest>=3.8.0 -reportportal-client>=5.2.0 +reportportal-client>=5.2.3 six>=1.15.0 aenum>=3.1.0 requests diff --git a/setup.py b/setup.py index 91beab6d..e9b5162b 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ from setuptools import setup -__version__ = '5.1.1' +__version__ = '5.1.2' def read_file(fname): diff --git a/tests/helpers/utils.py b/tests/helpers/utils.py index 48451bba..dfa292a0 100644 --- a/tests/helpers/utils.py +++ b/tests/helpers/utils.py @@ -16,6 +16,7 @@ import os import random import time +from multiprocessing.pool import ThreadPool import pytest @@ -132,3 +133,17 @@ def attributes_to_tuples(attributes): else: result.add((None, attribute['value'])) return result + + +# noinspection PyProtectedMember +def run_tests_with_client(client, tests, args=None, variables=None): + def test_func(): + from reportportal_client._local import set_current + set_current(client) + return run_pytest_tests(tests, args, variables) + + pool = ThreadPool(processes=1) + async_result = pool.apply_async(test_func) + result = async_result.get() + pool.terminate() + return result diff --git a/tests/integration/test_config_handling.py b/tests/integration/test_config_handling.py index f550ded0..db80b121 100644 --- a/tests/integration/test_config_handling.py +++ b/tests/integration/test_config_handling.py @@ -14,9 +14,12 @@ from delayed_assert import expect, assert_expectations from six.moves import mock +from examples.test_rp_logging import LOG_MESSAGE from tests import REPORT_PORTAL_SERVICE from tests.helpers import utils +TEST_LAUNCH_ID = 'test_launch_id' + @mock.patch(REPORT_PORTAL_SERVICE) def test_rp_launch_id(mock_client_init): @@ -25,13 +28,16 @@ def test_rp_launch_id(mock_client_init): :param mock_client_init: Pytest fixture """ variables = dict() - variables['rp_launch_id'] = "test_launch_id" + variables['rp_launch_id'] = TEST_LAUNCH_ID variables.update(utils.DEFAULT_VARIABLES.items()) result = utils.run_pytest_tests(tests=['examples/test_simple.py'], variables=variables) assert int(result) == 0, 'Exit code should be 0 (no errors)' + expect( + mock_client_init.call_args_list[0][1]['launch_id'] == TEST_LAUNCH_ID) + mock_client = mock_client_init.return_value expect(mock_client.start_launch.call_count == 0, '"start_launch" method was called') @@ -102,3 +108,40 @@ def test_rp_parent_item_id_and_rp_launch_id(mock_client_init): expect(len(start_call_args) == len(finish_call_args)) expect(start_call_args[0][1]["parent_item_id"] == parent_id) assert_expectations() + + +@mock.patch(REPORT_PORTAL_SERVICE) +def test_rp_log_format(mock_client_init): + log_format = '(%(name)s) %(message)s (%(filename)s:%(lineno)s)' + variables = {'rp_log_format': log_format} + variables.update(utils.DEFAULT_VARIABLES.items()) + + mock_client = mock_client_init.return_value + result = utils.run_tests_with_client( + mock_client, ['examples/test_rp_logging.py'], variables=variables) + + assert int(result) == 0, 'Exit code should be 0 (no errors)' + + expect(mock_client.log.call_count == 1) + message = mock_client.log.call_args_list[0][0][1] + expect(len(message) > 0) + expect(message == '(examples.test_rp_logging) ' + LOG_MESSAGE + + ' (test_rp_logging.py:24)') + assert_expectations() + + +@mock.patch(REPORT_PORTAL_SERVICE) +def test_rp_log_batch_payload_size(mock_client_init): + log_size = 123456 + variables = {'rp_log_batch_payload_size': log_size} + variables.update(utils.DEFAULT_VARIABLES.items()) + + result = utils.run_pytest_tests(['examples/test_rp_logging.py'], + variables=variables) + assert int(result) == 0, 'Exit code should be 0 (no errors)' + + expect(mock_client_init.call_count == 1) + + constructor_args = mock_client_init.call_args_list[0][1] + expect(constructor_args['log_batch_payload_size'] == log_size) + assert_expectations() diff --git a/tests/integration/test_logging_flush.py b/tests/integration/test_logging_flush.py index 50cf337b..0a932edc 100644 --- a/tests/integration/test_logging_flush.py +++ b/tests/integration/test_logging_flush.py @@ -13,8 +13,6 @@ # See the License for the specific language governing permissions and # limitations under the License -from multiprocessing.pool import ThreadPool - from six.moves import mock from tests import REPORT_PORTAL_SERVICE @@ -29,15 +27,8 @@ def test_logging_flushing(mock_client_init): """ mock_client = mock_client_init.return_value - def run_test(): - from reportportal_client._local import set_current - set_current(mock_client) - return utils.run_pytest_tests(['examples/test_rp_logging.py']) - - pool = ThreadPool(processes=1) - async_result = pool.apply_async(run_test) - result = async_result.get() - pool.terminate() + result = utils.run_tests_with_client( + mock_client, ['examples/test_rp_logging.py']) assert int(result) == 0, 'Exit code should be 0 (no errors)' assert mock_client.terminate.call_count == 1, \ diff --git a/tests/unit/test_plugin.py b/tests/unit/test_plugin.py index 4be8b70a..15c74b9b 100644 --- a/tests/unit/test_plugin.py +++ b/tests/unit/test_plugin.py @@ -311,6 +311,7 @@ def test_pytest_addoption_adds_correct_ini_file_arguments(): 'rp_launch_description', 'rp_project', 'rp_log_level', + 'rp_log_format', 'rp_rerun', 'rp_rerun_of', 'rp_parent_item_id', @@ -320,6 +321,7 @@ def test_pytest_addoption_adds_correct_ini_file_arguments(): 'rp_launch_attributes', 'rp_tests_attributes', 'rp_log_batch_size', + 'rp_log_batch_payload_size', 'rp_ignore_attributes', 'rp_is_skipped_an_issue', 'rp_hierarchy_code', @@ -353,6 +355,7 @@ def test_pytest_addoption_adds_correct_command_line_arguments(): '--rp-launch-description', '--rp-project', '--rp-log-level', + '--rp-log-format', '--rp-rerun', '--rp-rerun-of', '--rp-parent-item-id',