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
6 changes: 3 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,20 +47,20 @@ jobs:
run: tox
env:
PYTEST_MAJOR_VERSION: 9
PYTEST_PLUGINS: pytest_github_actions_annotate_failures
PYTEST_PLUGINS: pytest_github_actions_annotate_failures,xdist

- name: Run tests with PyTest 8
run: tox
env:
PYTEST_MAJOR_VERSION: 8
PYTEST_PLUGINS: pytest_github_actions_annotate_failures
PYTEST_PLUGINS: pytest_github_actions_annotate_failures,xdist

- name: Run tests with PyTest 7
run: tox
if: runner.os != 'Windows'
env:
PYTEST_MAJOR_VERSION: 7
PYTEST_PLUGINS: pytest_github_actions_annotate_failures
PYTEST_PLUGINS: pytest_github_actions_annotate_failures,xdist

post-test:
name: All tests passed
Expand Down
25 changes: 25 additions & 0 deletions plugin_test.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

import os
from collections import Counter

import pytest
from packaging import version
Expand Down Expand Up @@ -399,6 +400,30 @@ def test(subtests):
)


def test_with_xdist(testdir: pytest.Testdir):
testdir.makepyfile(
"""
import pytest
import warnings
import sys
pytest_plugins = ['pytest_github_actions_annotate_failures','xdist']

@pytest.mark.parametrize("n", range(10))
def test_fails(n):
warnings.warn(f"running {n}th")
assert n == 100
"""
)
testdir.monkeypatch.setenv("GITHUB_ACTIONS", "true")
result = testdir.runpytest_subprocess("-n", "10", "-s")
lines = Counter(
line for line in result.errlines if line.startswith(("::error ", "::warning "))
)

assert len(lines) == 20
assert {*lines.values()} == {1}


# Debugging / development tip:
# Add a breakpoint() to the place you are going to check,
# uncomment this example, and run it with:
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ pytest_github_actions_annotate_failures = "pytest_github_actions_annotate_failur

[dependency-groups]
dev = [{ include-group = "test" }]
test = ["packaging"]
test = ["packaging", "pytest-xdist"]

[build-system]
requires = [
Expand Down
97 changes: 53 additions & 44 deletions pytest_github_actions_annotate_failures/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,52 +26,53 @@
PYTEST_VERSION = version.parse(pytest.__version__)


@pytest.hookimpl(tryfirst=True)
def pytest_runtest_logreport(report: TestReport):
"""Handle test reporting for all pytest versions."""
class _AnnotateErrors:
@pytest.hookimpl(tryfirst=True)
def pytest_runtest_logreport(self, report: TestReport):
"""Handle test reporting for all pytest versions."""

# enable only in a workflow of GitHub Actions
# ref: https://help.github.com/en/actions/configuring-and-managing-workflows/using-environment-variables#default-environment-variables
if os.environ.get("GITHUB_ACTIONS") != "true":
return

# Only handle failed tests in call phase
if report.when == "call" and report.failed:
filesystempath, lineno, _ = report.location

if lineno is not None:
# 0-index to 1-index
lineno += 1

longrepr = report.head_line or "test"

# get the error message and line number from the actual error
if isinstance(report.longrepr, ExceptionRepr):
if report.longrepr.reprcrash is not None:
longrepr += "\n\n" + report.longrepr.reprcrash.message
tb_entries = report.longrepr.reprtraceback.reprentries
if tb_entries:
entry = tb_entries[0]
# Handle third-party exceptions
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):
filesystempath, lineno, message = report.longrepr
longrepr += "\n\n" + message
elif isinstance(report.longrepr, str):
longrepr += "\n\n" + report.longrepr
# enable only in a workflow of GitHub Actions
# ref: https://help.github.com/en/actions/configuring-and-managing-workflows/using-environment-variables#default-environment-variables
if os.environ.get("GITHUB_ACTIONS") != "true":
return

workflow_command = _build_workflow_command(
"error",
compute_path(filesystempath),
lineno,
message=longrepr,
)
print(workflow_command, file=sys.stderr)
# Only handle failed tests in call phase
if report.when == "call" and report.failed:
filesystempath, lineno, _ = report.location

if lineno is not None:
# 0-index to 1-index
lineno += 1

longrepr = report.head_line or "test"

# get the error message and line number from the actual error
if isinstance(report.longrepr, ExceptionRepr):
if report.longrepr.reprcrash is not None:
longrepr += "\n\n" + report.longrepr.reprcrash.message
tb_entries = report.longrepr.reprtraceback.reprentries
if tb_entries:
entry = tb_entries[0]
# Handle third-party exceptions
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):
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",
compute_path(filesystempath),
lineno,
message=longrepr,
)
print(workflow_command, file=sys.stderr)


def compute_path(filesystempath: str) -> str:
Expand Down Expand Up @@ -129,9 +130,17 @@ def pytest_addoption(parser):


def pytest_configure(config):
# Plugins should not be registered for workers.
# On xdist workers the controller re-emits reports,
# so register only there to avoid duplicates.
if config.pluginmanager.hasplugin("xdist") and hasattr(config, "workerinput"):
return

if not config.option.exclude_warning_annotations:
config.pluginmanager.register(_AnnotateWarnings(), "annotate_warnings")

config.pluginmanager.register(_AnnotateErrors(), "annotate_errors")


def _build_workflow_command(
command_name: str,
Expand Down
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ PYTEST_MAJOR_VERSION =

[testenv]
min_version = 4.22.0
groups = test
dependency_groups = test
deps =
pytest7: pytest>=7.0.0,<8.0.0
pytest8: pytest>=8.0.0,<9.0.0
Expand Down
Loading