Skip to content

Commit 84dff8f

Browse files
committed
Add custom log level handling
1 parent f828c91 commit 84dff8f

File tree

8 files changed

+116
-48
lines changed

8 files changed

+116
-48
lines changed

examples/test_rp_custom_logging.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Copyright (c) 2022 https://reportportal.io .
2+
# Licensed under the Apache License, Version 2.0 (the "License");
3+
# you may not use this file except in compliance with the License.
4+
# You may obtain a copy of the License at
5+
#
6+
# https://www.apache.org/licenses/LICENSE-2.0
7+
#
8+
# Unless required by applicable law or agreed to in writing, software
9+
# distributed under the License is distributed on an "AS IS" BASIS,
10+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
# See the License for the specific language governing permissions and
12+
# limitations under the License
13+
14+
import logging
15+
16+
logging.basicConfig(level=logging.INFO)
17+
18+
logger = logging.getLogger(__name__)
19+
20+
LOG_LEVEL: int = 35
21+
LOG_MESSAGE: str = "Assertion error"
22+
23+
24+
def test_report_portal_logging():
25+
logger.log(LOG_LEVEL, LOG_MESSAGE)

pytest_reportportal/config.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ class AgentConfig:
8181
rp_http_timeout: Optional[Union[tuple[float, float], float]]
8282
rp_report_fixtures: bool
8383

84+
# Custom log levels and overrides
85+
rp_log_custom_levels: Optional[dict[int, str]]
86+
8487
def __init__(self, pytest_config: Config) -> None:
8588
"""Initialize required attributes."""
8689
self.rp_enabled = to_bool(getattr(pytest_config.option, "rp_enabled", True))
@@ -177,6 +180,16 @@ def __init__(self, pytest_config: Config) -> None:
177180
self.rp_http_timeout = connect_timeout or read_timeout
178181
self.rp_report_fixtures = to_bool(self.find_option(pytest_config, "rp_report_fixtures", False))
179182

183+
# Custom log levels and overrides
184+
log_custom_levels = self.find_option(pytest_config, "rp_log_custom_levels")
185+
self.rp_log_custom_levels = None
186+
if log_custom_levels:
187+
levels = {}
188+
for custom_level in log_custom_levels:
189+
level, level_name = str(custom_level).split(":", maxsplit=1)
190+
levels[int(level)] = level_name
191+
self.rp_log_custom_levels = levels
192+
180193
# noinspection PyMethodMayBeStatic
181194
def find_option(self, pytest_config: Config, option_name: str, default: Any = None) -> Any:
182195
"""

pytest_reportportal/plugin.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,7 @@ def pytest_runtest_protocol(item: Item) -> Generator[None, Any, None]:
254254
filter_client_logs=True,
255255
endpoint=agent_config.rp_endpoint,
256256
ignored_record_names=("reportportal_client", "pytest_reportportal"),
257+
custom_levels=agent_config.rp_log_custom_levels,
257258
)
258259
log_format = agent_config.rp_log_format
259260
if log_format:
@@ -600,6 +601,12 @@ def add_shared_option(name, help_str, default=None, action="store"):
600601
"rp_log_batch_payload_size",
601602
help="DEPRECATED: Maximum payload size in bytes of async batch log requests",
602603
)
604+
parser.addini(
605+
"rp_log_custom_levels",
606+
type="args",
607+
help="Custom log levels specified as 'int level:string'. E.G.: '35:ASSERTION'. Overrides existing level if int"
608+
" level matches.",
609+
)
603610
parser.addini("rp_ignore_attributes", type="args", help="Ignore specified pytest markers, i.e parametrize")
604611
parser.addini(
605612
"rp_is_skipped_an_issue", default=True, type="bool", help="Treat skipped tests as required investigation"

pytest_reportportal/service.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -631,7 +631,7 @@ def _get_test_case_id(
631631

632632
def _get_issue_ids(self, mark):
633633
issue_ids = mark.kwargs.get("issue_id", [])
634-
if not isinstance(issue_ids, List):
634+
if not isinstance(issue_ids, list):
635635
issue_ids = [issue_ids]
636636
return issue_ids
637637

tests/integration/__init__.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@
1212
# limitations under the License
1313

1414
"""This package contains integration tests for the project."""
15+
from collections import defaultdict
16+
from typing import Optional
17+
18+
from reportportal_client import set_current
19+
from reportportal_client.steps import StepReporter
1520

1621
from tests.helpers import utils
1722

@@ -272,3 +277,48 @@
272277
(test, HIERARCHY_TEST_VARIABLES[idx], HIERARCHY_TEST_EXPECTED_ITEMS[idx])
273278
for idx, test in enumerate(HIERARCHY_TESTS)
274279
]
280+
281+
ITEM_ID_DICT = defaultdict(lambda: 0)
282+
ITEM_ID_LIST = []
283+
284+
285+
def generate_item_id(*args, **kwargs) -> str:
286+
global ITEM_ID_DICT
287+
global ITEM_ID_LIST
288+
if args:
289+
name = args[0]
290+
else:
291+
name = kwargs["name"]
292+
count = ITEM_ID_DICT[name]
293+
count += 1
294+
ITEM_ID_DICT[name] = count
295+
item_id = f"{name}_{count}"
296+
ITEM_ID_LIST.append(item_id)
297+
return item_id
298+
299+
300+
def get_last_item_id() -> Optional[str]:
301+
global ITEM_ID_LIST
302+
if len(ITEM_ID_LIST) > 0:
303+
return ITEM_ID_LIST[-1]
304+
305+
306+
def remove_last_item_id(*_, **__) -> Optional[str]:
307+
global ITEM_ID_LIST
308+
if len(ITEM_ID_LIST) > 0:
309+
return ITEM_ID_LIST.pop()
310+
311+
312+
def setup_mock(mock_client_init):
313+
mock_client = mock_client_init.return_value
314+
mock_client.step_reporter = StepReporter(mock_client)
315+
set_current(mock_client)
316+
return mock_client
317+
318+
319+
def setup_mock_for_logging(mock_client_init):
320+
mock_client = setup_mock(mock_client_init)
321+
mock_client.start_test_item.side_effect = generate_item_id
322+
mock_client.finish_test_item.side_effect = remove_last_item_id
323+
mock_client.current_item.side_effect = get_last_item_id
324+
return mock_client

tests/integration/test_bdd.py

Lines changed: 1 addition & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -18,59 +18,13 @@
1818
from unittest import mock
1919

2020
import pytest
21-
from reportportal_client import set_current
22-
from reportportal_client.steps import StepReporter
21+
from integration import setup_mock, setup_mock_for_logging
2322

2423
from tests import REPORT_PORTAL_SERVICE
2524
from tests.helpers import utils
2625

2726
pytest_bdd_version = [int(p) for p in importlib.metadata.version("pytest-bdd").split(".")]
2827

29-
ITEM_ID_DICT = defaultdict(lambda: 0)
30-
ITEM_ID_LIST = []
31-
32-
33-
def generate_item_id(*args, **kwargs) -> str:
34-
global ITEM_ID_DICT
35-
global ITEM_ID_LIST
36-
if args:
37-
name = args[0]
38-
else:
39-
name = kwargs["name"]
40-
count = ITEM_ID_DICT[name]
41-
count += 1
42-
ITEM_ID_DICT[name] = count
43-
item_id = f"{name}_{count}"
44-
ITEM_ID_LIST.append(item_id)
45-
return item_id
46-
47-
48-
def get_last_item_id() -> Optional[str]:
49-
global ITEM_ID_LIST
50-
if len(ITEM_ID_LIST) > 0:
51-
return ITEM_ID_LIST[-1]
52-
53-
54-
def remove_last_item_id(*_, **__) -> Optional[str]:
55-
global ITEM_ID_LIST
56-
if len(ITEM_ID_LIST) > 0:
57-
return ITEM_ID_LIST.pop()
58-
59-
60-
def setup_mock(mock_client_init):
61-
mock_client = mock_client_init.return_value
62-
mock_client.step_reporter = StepReporter(mock_client)
63-
set_current(mock_client)
64-
return mock_client
65-
66-
67-
def setup_mock_for_logging(mock_client_init):
68-
mock_client = setup_mock(mock_client_init)
69-
mock_client.start_test_item.side_effect = generate_item_id
70-
mock_client.finish_test_item.side_effect = remove_last_item_id
71-
mock_client.current_item.side_effect = get_last_item_id
72-
return mock_client
73-
7428

7529
STEP_NAMES = [
7630
"Given there are 5 cucumbers",

tests/integration/test_config_handling.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
from unittest import mock
1818

1919
import pytest
20+
import test_rp_custom_logging
2021
from delayed_assert import assert_expectations, expect
22+
from integration import setup_mock_for_logging
2123
from reportportal_client import OutputType
2224

2325
from examples.test_rp_logging import LOG_MESSAGE
@@ -268,3 +270,19 @@ def test_client_timeouts(mock_client_init, connect_value, read_value, expected_r
268270
assert int(result) == 0, "Exit code should be 0 (no errors)"
269271
assert mock_client_init.call_count == 1
270272
assert mock_client_init.call_args_list[0][1]["http_timeout"] == expected_result
273+
274+
275+
@mock.patch(REPORT_PORTAL_SERVICE)
276+
def test_rp_log_custom_levels(mock_client_init):
277+
setup_mock_for_logging(mock_client_init)
278+
custom_log_level = test_rp_custom_logging.LOG_LEVEL
279+
custom_log_name = "ASSERTION"
280+
variables = dict(utils.DEFAULT_VARIABLES)
281+
variables.update({"rp_log_custom_levels": str(custom_log_level) + ":" + custom_log_name})
282+
283+
result = utils.run_pytest_tests(["examples/test_rp_custom_logging.py"], variables=variables)
284+
assert int(result) == 0, "Exit code should be 0 (no errors)"
285+
286+
mock_client = mock_client_init.return_value
287+
assert mock_client.log.call_count == 1
288+
assert mock_client.log.call_args_list[0][1]["level"] == custom_log_name

tests/unit/test_plugin.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,7 @@ def test_pytest_addoption_adds_correct_ini_file_arguments():
259259
"rp_log_batch_size",
260260
"rp_log_batch_payload_limit",
261261
"rp_log_batch_payload_size",
262+
"rp_log_custom_levels",
262263
"rp_ignore_attributes",
263264
"rp_is_skipped_an_issue",
264265
"rp_hierarchy_code",

0 commit comments

Comments
 (0)