diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 97916b1..d5d4310 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -54,6 +54,12 @@ jobs: PYTEST_MAJOR_VERSION: 7 PYTEST_PLUGINS: pytest_github_actions_annotate_failures + - name: Run tests with PyTest 8 + run: tox + env: + PYTEST_MAJOR_VERSION: 8 + PYTEST_PLUGINS: pytest_github_actions_annotate_failures + post-test: name: All tests passed if: always() diff --git a/plugin_test.py b/plugin_test.py index b827269..1b32b6c 100644 --- a/plugin_test.py +++ b/plugin_test.py @@ -223,7 +223,7 @@ def test_fail(): result = testdir.runpytest_subprocess("--rootdir=foo") result.stderr.fnmatch_lines( [ - "::error file=test_annotation_fail_cwd.py,line=5::test_fail*assert 0*", + "::error file=test_annotation_fail_cwd0/test_annotation_fail_cwd.py,line=5::test_fail*assert 0*", ] ) diff --git a/pytest_github_actions_annotate_failures/plugin.py b/pytest_github_actions_annotate_failures/plugin.py index 2c29fad..3f58b95 100644 --- a/pytest_github_actions_annotate_failures/plugin.py +++ b/pytest_github_actions_annotate_failures/plugin.py @@ -6,7 +6,7 @@ from typing import TYPE_CHECKING import pytest -from _pytest._code.code import ExceptionRepr +from _pytest._code.code import ExceptionRepr, ReprEntry from packaging import version if TYPE_CHECKING: @@ -38,32 +38,12 @@ def pytest_runtest_makereport(item: Item, call): # noqa: ARG001 return if report.when == "call" and report.failed: - # collect information to be annotated filesystempath, lineno, _ = report.location - runpath = os.environ.get("PYTEST_RUN_PATH") - if runpath: - filesystempath = os.path.join(runpath, filesystempath) - - # try to convert to absolute path in GitHub Actions - workspace = os.environ.get("GITHUB_WORKSPACE") - if workspace: - full_path = os.path.abspath(filesystempath) - try: - rel_path = os.path.relpath(full_path, workspace) - except ValueError: - # os.path.relpath() will raise ValueError on Windows - # when full_path and workspace have different mount points. - # https://github.com/utgwkk/pytest-github-actions-annotate-failures/issues/20 - rel_path = filesystempath - if not rel_path.startswith(".."): - filesystempath = rel_path - if lineno is not None: # 0-index to 1-index lineno += 1 - # get the name of the current failed test, with parametrize info longrepr = report.head_line or item.name # get the error message and line number from the actual error @@ -71,26 +51,52 @@ def pytest_runtest_makereport(item: Item, call): # noqa: ARG001 if report.longrepr.reprcrash is not None: longrepr += "\n\n" + report.longrepr.reprcrash.message tb_entries = report.longrepr.reprtraceback.reprentries - if len(tb_entries) > 1 and tb_entries[0].reprfileloc is not None: + if tb_entries: + entry = tb_entries[0] # Handle third-party exceptions - lineno = tb_entries[0].reprfileloc.lineno + if isinstance(entry, ReprEntry) and entry.reprfileloc is not None: + lineno = entry.reprfileloc.lineno + filesystempath = entry.reprfileloc.path + elif report.longrepr.reprcrash is not None: lineno = report.longrepr.reprcrash.lineno elif isinstance(report.longrepr, tuple): - _, lineno, message = report.longrepr + filesystempath, lineno, message = report.longrepr longrepr += "\n\n" + message elif isinstance(report.longrepr, str): longrepr += "\n\n" + report.longrepr workflow_command = _build_workflow_command( "error", - filesystempath, + compute_path(filesystempath), lineno, message=longrepr, ) print(workflow_command, file=sys.stderr) +def compute_path(filesystempath: str) -> str: + """Extract and process location information from the report.""" + runpath = os.environ.get("PYTEST_RUN_PATH") + if runpath: + filesystempath = os.path.join(runpath, filesystempath) + + # try to convert to absolute path in GitHub Actions + workspace = os.environ.get("GITHUB_WORKSPACE") + if workspace: + full_path = os.path.abspath(filesystempath) + try: + rel_path = os.path.relpath(full_path, workspace) + except ValueError: + # os.path.relpath() will raise ValueError on Windows + # when full_path and workspace have different mount points. + rel_path = filesystempath + if not rel_path.startswith(".."): + filesystempath = rel_path + + return filesystempath + + class _AnnotateWarnings: def pytest_warning_recorded(self, warning_message, when, nodeid, location): # noqa: ARG002 # enable only in a workflow of GitHub Actions @@ -139,14 +145,14 @@ def pytest_configure(config): def _build_workflow_command( - command_name, - file, - line, - end_line=None, - column=None, - end_column=None, - title=None, - message=None, + command_name: str, + file: str, + line: int, + end_line: int | None = None, + column: int | None = None, + end_column: int | None = None, + title: str | None = None, + message: str | None = None, ): """Build a command to annotate a workflow.""" result = f"::{command_name} " @@ -168,5 +174,5 @@ def _build_workflow_command( return result -def _escape(s): +def _escape(s: str) -> str: return s.replace("%", "%25").replace("\r", "%0D").replace("\n", "%0A") diff --git a/tox.ini b/tox.ini index 877d362..11016c2 100644 --- a/tox.ini +++ b/tox.ini @@ -20,7 +20,7 @@ PYTEST_MAJOR_VERSION = extras = test deps = pytest6: pytest>=6.0.0,<7.0.0 - pytest7: pytest>=7.0.0,<7.4.0 + pytest7: pytest>=7.0.0,<8.0.0 pytest8: pytest>=8.0.0,<9.0.0 commands = {envpython} -m pytest {posargs}