Skip to content
Open
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
12 changes: 11 additions & 1 deletion src/pytest_html/basereport.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ def _process_extras(self, report, test_id):
for extra_index, extra in enumerate(report_extras):
content = extra["content"]
asset_name = self._asset_filename(
test_id.encode("utf-8").decode("unicode_escape"),
_unescape_test_id(test_id),
extra_index,
test_index,
extra["extension"],
Expand Down Expand Up @@ -355,6 +355,16 @@ def _process_outcome(report):
return report.outcome.capitalize()


def _unescape_test_id(test_id):
# Restore the non-ASCII characters pytest escapes in test ids (e.g. "\u00e9").
# Unescaped non-ASCII characters (e.g. in function names) are preserved, and
# ids that aren't valid escape sequences (e.g. "\N") are returned as-is.
try:
return test_id.encode("ascii", "backslashreplace").decode("unicode_escape")
except UnicodeDecodeError:
return test_id


def _process_links(links):
a_tag = '<a target="_blank" href="{content}" class="col-links__extra {format_type}">{name}</a>'
return "".join([a_tag.format_map(link) for link in links])
Expand Down
65 changes: 65 additions & 0 deletions testing/test_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,68 @@ def test_custom_css_selfcontained(pytester, css_file_path, expandvar):
with open(pytester.path / "report.html") as f:
html = f.read()
assert_that(html).contains("* " + str(css_file_path)).contains("* two.css")


@pytest.fixture
def text_extra(pytester):
pytester.makeconftest(
"""
import pytest

@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_makereport(item, call):
outcome = yield
report = outcome.get_result()
if report.when == "call":
from pytest_html import extras
report.extras = [extras.text("content")]
"""
)


@pytest.mark.parametrize(
"test_file, asset_name",
[
("def test_pass(): pass", "test_pass_0_0.txt"),
("def test_pässed(): pass", "test_pässed_0_0.txt"),
(
"""
import pytest
@pytest.mark.parametrize("value", ["ä"])
def test_pass(value): pass
""",
"test_pass_ä__0_0.txt",
),
],
ids=["ascii", "non-ascii-name", "non-ascii-param"],
)
def test_extra_asset_name(pytester, text_extra, test_file, asset_name):
pytester.makepyfile(test_extra_asset_name=test_file)
result = run(pytester)
result.assert_outcomes(passed=1)

path = pytester.path.joinpath("assets", f"test_extra_asset_name.py__{asset_name}")
assert_that(str(path)).exists()


def test_extra_asset_name_invalid_escape_sequence(pytester, text_extra):
pytester.makeini(
"""
[pytest]
disable_test_id_escaping_and_forfeit_all_rights_to_community_support = true
"""
)
pytester.makepyfile(
test_extra_asset_name=r"""
import pytest
@pytest.mark.parametrize("path", ["C:\\New Folder"])
def test_pass(path): pass
"""
)
result = run(pytester)
result.assert_outcomes(passed=1)

path = pytester.path.joinpath(
"assets", "test_extra_asset_name.py__test_pass_C__New_Folder__0_0.txt"
)
assert_that(str(path)).exists()