Skip to content

Commit 2e869c7

Browse files
authored
Failed fixtures don't break test name templating from fixture values (#694)
1 parent cfb6395 commit 2e869c7

File tree

3 files changed

+74
-2
lines changed

3 files changed

+74
-2
lines changed

allure-pytest/src/utils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import six
55
import pytest
66
from itertools import chain, islice
7-
from allure_commons.utils import represent
7+
from allure_commons.utils import represent, SafeFormatter
88
from allure_commons.utils import format_exception, format_traceback, escape_non_unicode_symbols
99
from allure_commons.model2 import Status
1010
from allure_commons.model2 import StatusDetails
@@ -111,7 +111,7 @@ def allure_package(item):
111111
def allure_name(item, parameters):
112112
name = escape_name(item.name)
113113
title = allure_title(item)
114-
return title.format(**{**parameters, **item.funcargs}) if title else name
114+
return SafeFormatter().format(title, **{**parameters, **item.funcargs}) if title else name
115115

116116

117117
def allure_full_name(item):

allure-pytest/test/acceptance/display_name/display_name_test.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,3 +97,24 @@ def test_feature_label_for_titled_test():
9797
has_title("Titled test with features")
9898
)
9999
)
100+
101+
102+
def test_failed_fixture_value_in_display_name(executed_docstring_source):
103+
"""
104+
>>> import allure
105+
>>> import pytest
106+
107+
>>> @pytest.fixture
108+
... def fix():
109+
... raise AssertionError("Fixture failed for some reason")
110+
111+
>>> @allure.title('title with {fix}')
112+
... def test_fixture_value_name(fix):
113+
... pass
114+
"""
115+
116+
assert_that(executed_docstring_source.allure_report,
117+
has_test_case("test_fixture_value_name",
118+
has_title("title with {fix}")
119+
)
120+
)

allure-python-commons/src/utils.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# -*- coding: utf-8 -*-
22

33
import os
4+
import string
45
import sys
56
import six
67
import time
@@ -404,3 +405,53 @@ def get_testplan():
404405
planned_tests = plan.get("tests", [])
405406

406407
return planned_tests
408+
409+
410+
class SafeFormatter(string.Formatter):
411+
"""
412+
Format string safely - skip any non-passed keys
413+
>>> f = SafeFormatter().format
414+
415+
Make sure we don't broke default formatting behaviour
416+
>>> f("literal string")
417+
'literal string'
418+
>>> f("{expected.format}", expected=str)
419+
"<method 'format' of 'str' objects>"
420+
>>> f("{expected[0]}", expected=["value"])
421+
'value'
422+
>>> f("{expected[0]}", expected=123)
423+
Traceback (most recent call last):
424+
...
425+
TypeError: 'int' object is not subscriptable
426+
>>> f("{expected[0]}", expected=[])
427+
Traceback (most recent call last):
428+
...
429+
IndexError: list index out of range
430+
>>> f("{expected.format}", expected=int)
431+
Traceback (most recent call last):
432+
...
433+
AttributeError: type object 'int' has no attribute 'format'
434+
435+
Check that unexpected keys do not cause some errors
436+
>>> f("{expected} {unexpected}", expected="value")
437+
'value {unexpected}'
438+
>>> f("{unexpected[0]}", expected=["value"])
439+
'{unexpected[0]}'
440+
>>> f("{unexpected.format}", expected=str)
441+
'{unexpected.format}'
442+
"""
443+
444+
class SafeKeyOrIndexError(Exception):
445+
pass
446+
447+
def get_field(self, field_name, args, kwargs):
448+
try:
449+
return super().get_field(field_name, args, kwargs)
450+
except self.SafeKeyOrIndexError:
451+
return "{" + field_name + "}", field_name
452+
453+
def get_value(self, key, args, kwargs):
454+
try:
455+
return super().get_value(key, args, kwargs)
456+
except (KeyError, IndexError):
457+
raise self.SafeKeyOrIndexError()

0 commit comments

Comments
 (0)