Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
# Changelog

## [Unreleased]
### Added
- Microseconds precision for timestamps, by @HardNorth
### Changed
- Client version updated to [5.7.4](https://github.com/reportportal/client-Python/releases/tag/5.7.4), by @HardNorth
### Removed
- Redundant item name truncation (now handled on the Client level), by @HardNorth

## [5.6.5]
### Fixed
- Issue [#418](https://github.com/reportportal/agent-python-pytest/issues/418) parametrize marker IDs, by @drcrazy

Expand Down
52 changes: 18 additions & 34 deletions pytest_reportportal/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import threading
import traceback
from collections import OrderedDict
from datetime import datetime, timezone
from enum import Enum
from functools import wraps
from os import curdir
Expand All @@ -28,10 +29,10 @@

from _pytest.doctest import DoctestItem
from py.path import local
from pytest import Class, Function, Item, Module, Package, PytestWarning, Session
from pytest import Class, Function, Item, Module, Package, Session
from reportportal_client.aio import Task
from reportportal_client.core.rp_issues import ExternalIssue, Issue
from reportportal_client.helpers import markdown_helpers, timestamp
from reportportal_client.helpers import markdown_helpers

from .config import AgentConfig

Expand Down Expand Up @@ -80,8 +81,6 @@
LOGGER = logging.getLogger(__name__)

KNOWN_LOG_LEVELS = ("TRACE", "DEBUG", "INFO", "WARN", "ERROR")
MAX_ITEM_NAME_LENGTH: int = 1024
TRUNCATION_STR: str = "..."
ROOT_DIR: str = str(os.path.abspath(curdir))
PYTEST_MARKS_IGNORE: set[str] = {"parametrize", "usefixtures", "filterwarnings"}
NOT_ISSUE: Issue = Issue("NOT_ISSUE")
Expand Down Expand Up @@ -238,7 +237,7 @@ def _build_start_launch_rq(self) -> dict[str, Any]:
start_rq = {
"attributes": self._get_launch_attributes(attributes),
"name": self._config.rp_launch,
"start_time": timestamp(),
"start_time": datetime.now(tz=timezone.utc),
"description": self._config.rp_launch_description,
"rerun": self._config.rp_rerun,
"rerun_of": self._config.rp_rerun_of,
Expand Down Expand Up @@ -497,21 +496,6 @@ def collect_tests(self, session: Session) -> None:
self._merge_code(test_tree)
self._build_item_paths(test_tree, [])

def _truncate_item_name(self, name: str) -> str:
"""Get name of item.

:param name: Test Item name
:return: truncated to maximum length name if needed
"""
if len(name) > MAX_ITEM_NAME_LENGTH:
name = name[: MAX_ITEM_NAME_LENGTH - len(TRUNCATION_STR)] + TRUNCATION_STR
LOGGER.warning(
PytestWarning(
f'Test leaf ID was truncated to "{name}" because of name size constrains on Report Portal'
)
)
return name

def _get_item_description(self, test_item: Any) -> Optional[str]:
"""Get description of item.

Expand Down Expand Up @@ -580,9 +564,9 @@ def _build_start_suite_rq(self, leaf: dict[str, Any]) -> dict[str, Any]:
parent_item_id = self._lock(leaf["parent"], lambda p: p.get("item_id")) if "parent" in leaf else None
item = leaf["item"]
payload = {
"name": self._truncate_item_name(leaf["name"]),
"name": leaf["name"],
"description": self._get_item_description(item),
"start_time": timestamp(),
"start_time": datetime.now(tz=timezone.utc),
"item_type": "SUITE",
"code_ref": code_ref,
"parent_item_id": parent_item_id,
Expand Down Expand Up @@ -882,9 +866,9 @@ def _process_metadata_item_finish(self, leaf: dict[str, Any]) -> None:
def _build_start_step_rq(self, leaf: dict[str, Any]) -> dict[str, Any]:
payload = {
"attributes": leaf.get("attributes", None),
"name": self._truncate_item_name(leaf["name"]),
"name": leaf["name"],
"description": leaf["description"],
"start_time": timestamp(),
"start_time": datetime.now(tz=timezone.utc),
"item_type": "STEP",
"code_ref": leaf.get("code_ref", None),
"parameters": leaf.get("parameters", None),
Expand Down Expand Up @@ -963,7 +947,7 @@ def _build_finish_step_rq(self, leaf: dict[str, Any]) -> dict[str, Any]:
issue = None
payload = {
"attributes": leaf.get("attributes", None),
"end_time": timestamp(),
"end_time": datetime.now(tz=timezone.utc),
"status": status,
"issue": issue,
"item_id": leaf["item_id"],
Expand All @@ -979,7 +963,7 @@ def _finish_suite(self, finish_rq: dict[str, Any]) -> None:
self.rp.finish_test_item(**finish_rq)

def _build_finish_suite_rq(self, leaf) -> dict[str, Any]:
payload = {"end_time": timestamp(), "item_id": leaf["item_id"]}
payload = {"end_time": datetime.now(tz=timezone.utc), "item_id": leaf["item_id"]}
return payload

def _proceed_suite_finish(self, leaf) -> None:
Expand Down Expand Up @@ -1056,7 +1040,7 @@ def finish_suites(self) -> None:
self._lock(leaf, lambda p: self._proceed_suite_finish(p))

def _build_finish_launch_rq(self) -> dict[str, Any]:
finish_rq = {"end_time": timestamp()}
finish_rq = {"end_time": datetime.now(tz=timezone.utc)}
return finish_rq

def _finish_launch(self, finish_rq) -> None:
Expand All @@ -1074,7 +1058,7 @@ def _build_log(
) -> dict[str, Any]:
sl_rq = {
"item_id": item_id,
"time": timestamp(),
"time": datetime.now(tz=timezone.utc),
"message": message,
"level": log_level,
}
Expand Down Expand Up @@ -1123,7 +1107,7 @@ def report_fixture(self, name: str, error_msg: str) -> Generator[None, Any, None
return

reporter = self.rp.step_reporter
item_id = reporter.start_nested_step(name, timestamp())
item_id = reporter.start_nested_step(name, datetime.now(tz=timezone.utc))

try:
outcome = yield
Expand All @@ -1140,11 +1124,11 @@ def report_fixture(self, name: str, error_msg: str) -> Generator[None, Any, None
)
exception_log = self._build_log(item_id, traceback_str, log_level="ERROR")
self.rp.log(**exception_log)
reporter.finish_nested_step(item_id, timestamp(), status)
reporter.finish_nested_step(item_id, datetime.now(tz=timezone.utc), status)
except Exception as e:
LOGGER.error("Failed to report fixture: %s", name)
LOGGER.exception(e)
reporter.finish_nested_step(item_id, timestamp(), "FAILED")
reporter.finish_nested_step(item_id, datetime.now(tz=timezone.utc), "FAILED")

def _get_python_name(self, scenario: Scenario) -> str:
python_name = f"test_{make_python_name(self._get_scenario_template(scenario).name)}"
Expand Down Expand Up @@ -1301,7 +1285,7 @@ def _finish_bdd_step(self, leaf: dict[str, Any], status: str) -> None:

reporter = self.rp.step_reporter
item_id = leaf["item_id"]
reporter.finish_nested_step(item_id, timestamp(), status)
reporter.finish_nested_step(item_id, datetime.now(tz=timezone.utc), status)
leaf["exec"] = ExecStatus.FINISHED

def _is_background_step(self, step: Step, feature: Feature) -> bool:
Expand Down Expand Up @@ -1343,15 +1327,15 @@ def start_bdd_step(self, feature: Feature, scenario: Scenario, step: Step) -> No
background_leaf = scenario_leaf["children"][feature.background]
background_leaf["children"][step] = step_leaf
if background_leaf["exec"] != ExecStatus.IN_PROGRESS:
item_id = reporter.start_nested_step(BACKGROUND_STEP_NAME, timestamp())
item_id = reporter.start_nested_step(BACKGROUND_STEP_NAME, datetime.now(tz=timezone.utc))
background_leaf["item_id"] = item_id
background_leaf["exec"] = ExecStatus.IN_PROGRESS
else:
scenario_leaf["children"][step] = step_leaf
if feature.background:
background_leaf = scenario_leaf["children"][feature.background]
self._finish_bdd_step(background_leaf, "PASSED")
item_id = reporter.start_nested_step(self._truncate_item_name(f"{step.keyword} {step.name}"), timestamp())
item_id = reporter.start_nested_step(f"{step.keyword} {step.name}", datetime.now(tz=timezone.utc))
step_leaf["item_id"] = item_id
step_leaf["exec"] = ExecStatus.IN_PROGRESS

Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
reportportal-client==5.7.2
reportportal-client==5.7.4
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

from setuptools import setup

__version__ = "5.6.5"
__version__ = "5.6.6"


def read_file(fname):
Expand Down
30 changes: 0 additions & 30 deletions tests/integration/test_bdd.py
Original file line number Diff line number Diff line change
Expand Up @@ -1081,33 +1081,3 @@ def test_rp_tests_attributes_bdd_tags(mock_client_init):
assert {"key": "test_key", "value": "test_value"} in attributes
assert {"value": "ok"} in attributes
assert {"key": "key", "value": "value"} in attributes


@mock.patch(REPORT_PORTAL_SERVICE)
def test_long_step(mock_client_init):
mock_client = setup_mock_for_logging(mock_client_init)
result = utils.run_pytest_tests(tests=["examples/bdd/step_defs/long_step_scenario_steps.py"])
assert int(result) == 0, "Exit code should be 0 (no errors)"

# Verify scenario is correctly identified
scenario_call = mock_client.start_test_item.call_args_list[0]
assert scenario_call[1]["name"] == "Feature: Test long step scenario - Scenario: The scenario"
assert scenario_call[1]["item_type"] == "STEP"
assert scenario_call[1]["code_ref"] == "features/long_step_scenario.feature/[SCENARIO:The scenario]"

# Verify step is truncated but still identifiable
step_call = mock_client.start_test_item.call_args_list[1]
step_name = step_call[0][0]
assert step_name.startswith("Given A very long step.")
assert len(step_name) <= 1024, "Step name should be truncated to at most 1024 characters"
assert step_name.endswith("..."), "Truncated step should have ellipsis"

# Verify step details
assert step_call[0][2] == "step"
assert step_call[1]["parent_item_id"] == scenario_call[1]["name"] + "_1"
assert step_call[1]["has_stats"] is False

# Verify all steps pass
finish_calls = mock_client.finish_test_item.call_args_list
for call in finish_calls:
assert call[1]["status"] == "PASSED"
15 changes: 10 additions & 5 deletions tests/integration/test_config_handling.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,9 +177,10 @@ def test_rp_api_retries(mock_client_init):
retries = 5
variables = dict(utils.DEFAULT_VARIABLES)
variables.update({"rp_api_retries": str(retries)}.items())
mock_client = mock_client_init.return_value

with warnings.catch_warnings(record=True) as w:
result = utils.run_pytest_tests(["examples/test_rp_logging.py"], variables=variables)
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_init.call_count == 1)
Expand Down Expand Up @@ -210,7 +211,8 @@ 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())
result = utils.run_pytest_tests(["examples/test_rp_logging.py"], variables=variables)
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_init.call_count == 1)
expect(mock_client_init.call_args_list[0][1]["launch_uuid_print"] == print_uuid)
Expand All @@ -223,7 +225,8 @@ 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())
result = utils.run_pytest_tests(["examples/test_rp_logging.py"], variables=variables)
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_init.call_count == 1)
expect(mock_client_init.call_args_list[0][1]["launch_uuid_print"] == print_uuid)
Expand All @@ -244,7 +247,8 @@ def test_launch_uuid_print_invalid_output(mock_client_init):
@mock.patch(REPORT_PORTAL_SERVICE)
def test_no_launch_uuid_print(mock_client_init):
variables = utils.DEFAULT_VARIABLES.copy()
result = utils.run_pytest_tests(["examples/test_rp_logging.py"], variables=variables)
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_init.call_count == 1)
expect(mock_client_init.call_args_list[0][1]["launch_uuid_print"] is False)
Expand All @@ -264,7 +268,8 @@ def test_client_timeouts(mock_client_init, connect_value, read_value, expected_r
if read_value:
variables["rp_read_timeout"] = read_value

result = utils.run_pytest_tests(["examples/test_rp_logging.py"], variables=variables)
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)"
assert mock_client_init.call_count == 1
Expand Down
4 changes: 3 additions & 1 deletion tests/integration/test_empty_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

"""This module includes integration tests for the empty run."""

from datetime import datetime, timezone
from unittest import mock

from tests import REPORT_PORTAL_SERVICE
Expand All @@ -25,6 +26,7 @@ def test_empty_run(mock_client_init):

:param mock_client_init: Pytest fixture
"""
test_start_time = datetime.now(timezone.utc)
result = utils.run_pytest_tests(tests=["examples/empty/"])

assert int(result) == 5, "Exit code should be 5 (no tests)"
Expand All @@ -36,4 +38,4 @@ def test_empty_run(mock_client_init):
finish_args = mock_client.finish_launch.call_args_list
assert "status" not in finish_args[0][1], "Launch status should not be defined"
launch_end_time = finish_args[0][1]["end_time"]
assert launch_end_time is not None and int(launch_end_time) > 0, "Launch end time is empty"
assert launch_end_time is not None and launch_end_time > test_start_time, "Launch end time is empty"
33 changes: 0 additions & 33 deletions tests/integration/test_max_name_length.py

This file was deleted.

Loading