Skip to content

Add logging to TAP file #94

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
Jan 30, 2025
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
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Contributors
------------

* Allison Karlitskaya
* Cody D'Ambrosio
* Dan Dofter
* Erik Cederstrand
* Frédéric Mangano-Tarumi
Expand Down
6 changes: 6 additions & 0 deletions conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,17 @@ def sample_test_file(testdir):
testdir.makepyfile(
"""
import pytest
import logging

LOGGER = logging.getLogger(__name__)

def test_ok():
LOGGER.info("Running test_ok")
LOGGER.debug("Debug logging info")
assert True

def test_not_ok():
LOGGER.error("Running test_not_ok")
assert False

@pytest.mark.parametrize('param', ("foo", "bar"))
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
include_package_data=True,
zip_safe=False,
platforms="any",
install_requires=["pytest>=3.0", "tap.py>=3.0,<4.0"],
install_requires=["pytest>=3.0", "tap.py>=3.2,<4.0"],
classifiers=[
"Development Status :: 5 - Production/Stable",
"Framework :: Pytest",
Expand Down
42 changes: 39 additions & 3 deletions src/pytest_tap/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
from tap.formatter import format_as_diagnostics
from tap.tracker import Tracker

SHOW_CAPTURE_LOG = ("log", "all")
SHOW_CAPTURE_OUT = ("stdout", "all")
SHOW_CAPTUER_ERR = ("stderr", "all")


class TAPPlugin:
def __init__(self, config: pytest.Config) -> None:
Expand All @@ -25,6 +29,9 @@ def __init__(self, config: pytest.Config) -> None:
# Disable it automatically for streaming.
self._tracker.header = False

self.tap_logging = config.option.showcapture
self.tap_log_passing_tests = config.option.tap_log_passing_tests

@pytest.hookimpl()
def pytest_runtestloop(self, session):
"""Output the plan line first."""
Expand Down Expand Up @@ -70,9 +77,12 @@ def pytest_runtest_logreport(self, report: pytest.TestReport):
directive = "TODO unexpected success{}".format(reason)
self._tracker.add_ok(testcase, description, directive=directive)
elif report.passed:
self._tracker.add_ok(testcase, description)
diagnostics = None
if self.tap_log_passing_tests:
diagnostics = _make_as_diagnostics(report, self.tap_logging)
self._tracker.add_ok(testcase, description, diagnostics=diagnostics)
elif report.failed:
diagnostics = _make_as_diagnostics(report)
diagnostics = _make_as_diagnostics(report, self.tap_logging)

# pytest treats an unexpected success from unitest.expectedFailure
# as a failure.
Expand Down Expand Up @@ -143,6 +153,12 @@ def pytest_addoption(parser):
"If the directory does not exist, it will be created."
),
)
group.addoption(
"--tap-log-passing-tests",
default=False,
action="store_true",
help="Capture log information for passing tests to TAP report",
)


@pytest.hookimpl(trylast=True)
Expand All @@ -161,7 +177,27 @@ def pytest_configure(config: pytest.Config) -> None:
config.pluginmanager.register(TAPPlugin(config), "tapplugin")


def _make_as_diagnostics(report):
def _make_as_diagnostics(report, tap_logging):
"""Format a report as TAP diagnostic output."""
lines = report.longreprtext.splitlines(keepends=True)

if tap_logging in SHOW_CAPTURE_LOG:
if lines:
lines[-1] += "\n"
lines += ["--- Captured Log ---\n"] + (
report.caplog.splitlines(keepends=True) or [""]
)
if tap_logging in SHOW_CAPTURE_OUT:
if lines:
lines[-1] += "\n"
lines += ["--- Captured Out ---\n"] + (
report.capstdout.splitlines(keepends=True) or [""]
)
if tap_logging in SHOW_CAPTUER_ERR:
if lines:
lines[-1] += "\n"
lines += ["--- Captured Err ---\n"] + (
report.capstderr.splitlines(keepends=True) or [""]
)

return format_as_diagnostics(lines)
1 change: 1 addition & 0 deletions tests/test_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ def test_includes_options(testdir):
"*--tap-files*",
"*--tap-combined*",
"*--tap-outdir=path*",
"*--tap-log-passing-tests*",
]
result.stdout.fnmatch_lines(expected_option_flags)

Expand Down
28 changes: 28 additions & 0 deletions tests/test_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,34 @@ def test_outdir(testdir, sample_test_file):
assert testresults.check()


def test_logging(testdir, sample_test_file):
"""Test logs are added to TAP diagnostics."""
result = testdir.runpytest_subprocess("--tap")
result.stdout.fnmatch_lines(
[
"# --- Captured Log ---",
"*Running test_not_ok*",
"# --- Captured Out ---",
"# --- Captured Err ---",
]
)
result.stdout.no_fnmatch_line("*Running test_ok*")


def test_log_passing_tests(testdir, sample_test_file):
"""Test logs are added to TAP diagnostics."""
result = testdir.runpytest_subprocess(
"--tap", "--tap-log-passing-tests", "--log-level", "INFO"
)
result.stdout.fnmatch_lines(
[
"# --- Captured Log ---",
"*Running test_ok*",
]
)
result.stdout.no_fnmatch_line("*Debug logging info*")


def test_xfail_no_reason(testdir):
"""xfails output gracefully when no reason is provided."""
testdir.makepyfile(
Expand Down