summaryrefslogtreecommitdiffstats
path: root/contrib/python/allure-pytest
diff options
context:
space:
mode:
authorrobot-piglet <[email protected]>2025-08-06 12:21:25 +0300
committerrobot-piglet <[email protected]>2025-08-06 12:45:52 +0300
commitb278c497092da9902bf6443e607adea8b8e0f62c (patch)
tree1133d38f2f98ec56f492a02c19166fc181ecce2d /contrib/python/allure-pytest
parentba4771ba91781c6aef0bfa2506302c4b3e2a2e73 (diff)
Intermediate changes
commit_hash:65a45f10733cde088e0f615846f2d8beb4279a81
Diffstat (limited to 'contrib/python/allure-pytest')
-rw-r--r--contrib/python/allure-pytest/.dist-info/METADATA4
-rw-r--r--contrib/python/allure-pytest/allure_pytest/listener.py2
-rw-r--r--contrib/python/allure-pytest/allure_pytest/stash.py61
-rw-r--r--contrib/python/allure-pytest/allure_pytest/utils.py72
-rw-r--r--contrib/python/allure-pytest/ya.make3
5 files changed, 118 insertions, 24 deletions
diff --git a/contrib/python/allure-pytest/.dist-info/METADATA b/contrib/python/allure-pytest/.dist-info/METADATA
index 96d707c9148..1407f592c09 100644
--- a/contrib/python/allure-pytest/.dist-info/METADATA
+++ b/contrib/python/allure-pytest/.dist-info/METADATA
@@ -1,6 +1,6 @@
Metadata-Version: 2.4
Name: allure-pytest
-Version: 2.14.3
+Version: 2.15.0
Summary: Allure pytest integration
Home-page: https://allurereport.org/
Author: Qameta Software Inc., Stanislav Seliverstov
@@ -25,7 +25,7 @@ Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Description-Content-Type: text/markdown
Requires-Dist: pytest>=4.5.0
-Requires-Dist: allure-python-commons==2.14.3
+Requires-Dist: allure-python-commons==2.15.0
Dynamic: author
Dynamic: author-email
Dynamic: classifier
diff --git a/contrib/python/allure-pytest/allure_pytest/listener.py b/contrib/python/allure-pytest/allure_pytest/listener.py
index 11153630911..42b7ff49176 100644
--- a/contrib/python/allure-pytest/allure_pytest/listener.py
+++ b/contrib/python/allure-pytest/allure_pytest/listener.py
@@ -19,6 +19,7 @@ from allure_commons.types import LabelType, AttachmentType, ParameterMode
from allure_pytest.utils import allure_description, allure_description_html
from allure_pytest.utils import allure_labels, allure_links, pytest_markers
from allure_pytest.utils import allure_full_name, allure_package, allure_name
+from allure_pytest.utils import allure_title_path
from allure_pytest.utils import allure_suite_labels
from allure_pytest.utils import get_status, get_status_details
from allure_pytest.utils import get_outcome_status, get_outcome_status_details
@@ -109,6 +110,7 @@ class AllureListener:
test_result.name = allure_name(item, params, param_id)
full_name = allure_full_name(item)
test_result.fullName = full_name
+ test_result.titlePath = [*allure_title_path(item)]
test_result.testCaseId = md5(full_name)
test_result.description = allure_description(item)
test_result.descriptionHtml = allure_description_html(item)
diff --git a/contrib/python/allure-pytest/allure_pytest/stash.py b/contrib/python/allure-pytest/allure_pytest/stash.py
new file mode 100644
index 00000000000..31d9302b802
--- /dev/null
+++ b/contrib/python/allure-pytest/allure_pytest/stash.py
@@ -0,0 +1,61 @@
+import pytest
+from functools import wraps
+
+HAS_STASH = hasattr(pytest, 'StashKey')
+
+
+def create_stashkey_safe():
+ """
+ If pytest stash is available, returns a new stash key.
+ Otherwise, returns `None`.
+ """
+
+ return pytest.StashKey() if HAS_STASH else None
+
+
+def stash_get_safe(item, key):
+ """
+ If pytest stash is available and contains the key, retrieves the associated value.
+ Otherwise, returns `None`.
+ """
+
+ if HAS_STASH and key in item.stash:
+ return item.stash[key]
+
+
+def stash_set_safe(item: pytest.Item, key, value):
+ """
+ If pytest stash is available, associates the value with the key in the stash.
+ Otherwise, does nothing.
+ """
+
+ if HAS_STASH:
+ item.stash[key] = value
+
+
+def stashed(arg=None):
+ """
+ Cashes the result of the decorated function in the pytest item stash.
+ The first argument of the function must be a pytest item.
+
+ In pytest<7.0 the stash is not available, so the decorator does nothing.
+ """
+
+ key = create_stashkey_safe() if arg is None or callable(arg) else arg
+
+ def decorator(func):
+ if not HAS_STASH:
+ return func
+
+ @wraps(func)
+ def wrapper(item, *args, **kwargs):
+ if key in item.stash:
+ return item.stash[key]
+
+ value = func(item, *args, **kwargs)
+ item.stash[key] = value
+ return value
+
+ return wrapper
+
+ return decorator(arg) if callable(arg) else decorator
diff --git a/contrib/python/allure-pytest/allure_pytest/utils.py b/contrib/python/allure-pytest/allure_pytest/utils.py
index 19145510fa6..56594a09503 100644
--- a/contrib/python/allure-pytest/allure_pytest/utils.py
+++ b/contrib/python/allure-pytest/allure_pytest/utils.py
@@ -1,11 +1,11 @@
import pytest
-from itertools import chain, islice
+from itertools import repeat
from allure_commons.utils import SafeFormatter, md5
from allure_commons.utils import format_exception, format_traceback
from allure_commons.model2 import Status
from allure_commons.model2 import StatusDetails
from allure_commons.types import LabelType
-
+from allure_pytest.stash import stashed
ALLURE_DESCRIPTION_MARK = 'allure_description'
ALLURE_DESCRIPTION_HTML_MARK = 'allure_description_html'
@@ -30,6 +30,24 @@ MARK_NAMES_TO_IGNORE = {
}
+class ParsedPytestNodeId:
+ def __init__(self, nodeid):
+ filepath, *class_names, function_segment = ensure_len(nodeid.split("::"), 2)
+ self.filepath = filepath
+ self.path_segments = filepath.split('/')
+ *parent_dirs, filename = ensure_len(self.path_segments, 1)
+ self.parent_package = '.'.join(parent_dirs)
+ self.module = filename.rsplit(".", 1)[0]
+ self.package = '.'.join(filter(None, [self.parent_package, self.module]))
+ self.class_names = class_names
+ self.test_function = function_segment.split("[", 1)[0]
+
+
+@stashed
+def parse_nodeid(item):
+ return ParsedPytestNodeId(item.nodeid)
+
+
def get_marker_value(item, keyword):
marker = item.get_closest_marker(keyword)
return marker.args[0] if marker and marker.args else None
@@ -101,9 +119,7 @@ def should_convert_mark_to_tag(mark):
def allure_package(item):
- parts = item.nodeid.split('::')
- path = parts[0].rsplit('.', 1)[0]
- return path.replace('/', '.')
+ return parse_nodeid(item).package
def allure_name(item, parameters, param_id=None):
@@ -122,27 +138,41 @@ def allure_name(item, parameters, param_id=None):
def allure_full_name(item: pytest.Item):
- package = allure_package(item)
- class_name = f".{item.parent.name}" if isinstance(item.parent, pytest.Class) else ''
- test = item.originalname if isinstance(item, pytest.Function) else item.name.split("[")[0]
- full_name = f'{package}{class_name}#{test}'
+ nodeid = parse_nodeid(item)
+ class_part = ("." + ".".join(nodeid.class_names)) if nodeid.class_names else ""
+ test = item.originalname if isinstance(item, pytest.Function) else nodeid.test_function
+ full_name = f"{nodeid.package}{class_part}#{test}"
return full_name
+def allure_title_path(item):
+ nodeid = parse_nodeid(item)
+ return list(
+ filter(None, [*nodeid.path_segments, *nodeid.class_names]),
+ )
+
+
+def ensure_len(value, min_length, fill_value=None):
+ yield from value
+ yield from repeat(fill_value, min_length - len(value))
+
+
def allure_suite_labels(item):
- head, possibly_clazz, tail = islice(chain(item.nodeid.split('::'), [None], [None]), 3)
- clazz = possibly_clazz if tail else None
- file_name, path = islice(chain(reversed(head.rsplit('/', 1)), [None]), 2)
- module = file_name.split('.')[0]
- package = path.replace('/', '.') if path else None
- pairs = dict(zip([LabelType.PARENT_SUITE, LabelType.SUITE, LabelType.SUB_SUITE], [package, module, clazz]))
- labels = dict(allure_labels(item))
- default_suite_labels = []
- for label, value in pairs.items():
- if label not in labels.keys() and value:
- default_suite_labels.append((label, value))
+ nodeid = parse_nodeid(item)
+
+ default_suite_labels = {
+ LabelType.PARENT_SUITE: nodeid.parent_package,
+ LabelType.SUITE: nodeid.module,
+ LabelType.SUB_SUITE: " > ".join(nodeid.class_names),
+ }
+
+ existing_labels = dict(allure_labels(item))
+ resolved_default_suite_labels = []
+ for label, value in default_suite_labels.items():
+ if label not in existing_labels and value:
+ resolved_default_suite_labels.append((label, value))
- return default_suite_labels
+ return resolved_default_suite_labels
def get_outcome_status(outcome):
diff --git a/contrib/python/allure-pytest/ya.make b/contrib/python/allure-pytest/ya.make
index 1680e08c937..156fdc8d0ee 100644
--- a/contrib/python/allure-pytest/ya.make
+++ b/contrib/python/allure-pytest/ya.make
@@ -2,7 +2,7 @@
PY3_LIBRARY()
-VERSION(2.14.3)
+VERSION(2.15.0)
LICENSE(Apache-2.0)
@@ -20,6 +20,7 @@ PY_SRCS(
allure_pytest/helper.py
allure_pytest/listener.py
allure_pytest/plugin.py
+ allure_pytest/stash.py
allure_pytest/utils.py
)