pytest-tap icon indicating copy to clipboard operation
pytest-tap copied to clipboard

Failed assertions are shown incorrectly

Open kvk1920 opened this issue 11 months ago • 1 comments

pytest tries to highlight failed assertions but fails. I guess the reason is pytest-tap unregisters "terminalreporter" plugin.

test_sample.py:

def test_fail():
    a = [1]
    b = {2}
    assert a == b

pytest output:

$ pytest

================================================================================================================= test session starts ==================================================================================================================
platform linux -- Python 3.12.3, pytest-8.3.4, pluggy-1.5.0
rootdir: /home/kvk1920/pytest-tap-repro
plugins: tap-3.5
collected 1 item                                                                                                                                                                                                                                       

test_sample.py F                                                                                                                                                                                                                                 [100%]

======================================================================================================================= FAILURES =======================================================================================================================
______________________________________________________________________________________________________________________ test_fail _______________________________________________________________________________________________________________________

    def test_fail():
        a = [1]
        b = {2}
>       assert a == b
E       assert [1] == {2}
E         
E         Use -v to get more diff

test_sample.py:4: AssertionError
=============================================================================================================== short test summary info ================================================================================================================
FAILED test_sample.py::test_fail - assert [1] == {2}
================================================================================================================== 1 failed in 0.02s ===================================================================================================================

pytest --tap output:

$ pytest --tap
1..1
not ok 1 test_sample.py::test_fail
# def test_fail():
#         a = [1]
#         b = {2}
# >       assert a == b
# 
# test_sample.py:4: 
# _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
# venv/lib/python3.12/site-packages/_pytest/assertion/rewrite.py:502: in _call_reprcompare
#     custom = util._reprcompare(ops[i], each_obj[i], each_obj[i + 1])
# venv/lib/python3.12/site-packages/_pytest/assertion/__init__.py:151: in callbinrepr
#     hook_result = ihook.pytest_assertrepr_compare(
# venv/lib/python3.12/site-packages/pluggy/_hooks.py:513: in __call__
#     return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
# venv/lib/python3.12/site-packages/pluggy/_manager.py:120: in _hookexec
#     return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
# venv/lib/python3.12/site-packages/_pytest/assertion/__init__.py:192: in pytest_assertrepr_compare
#     return util.assertrepr_compare(config=config, op=op, left=left, right=right)
# venv/lib/python3.12/site-packages/_pytest/assertion/util.py:198: in assertrepr_compare
#     highlighter = config.get_terminal_writer()._highlight
# _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
# 
# self = <_pytest.config.Config object at 0x797e07a32db0>
# 
#     def get_terminal_writer(self) -> TerminalWriter:
#         terminalreporter: TerminalReporter | None = self.pluginmanager.get_plugin(
#             "terminalreporter"
#         )
# >       assert terminalreporter is not None
# E       AssertionError
# 
# venv/lib/python3.12/site-packages/_pytest/config/__init__.py:1133: AssertionError
# --- Captured Log ---
# 
# --- Captured Out ---
# 
# --- Captured Err ---
#

python3 version: 3.12.3 pip3 version: 24.0 pip3 freeze:

iniconfig==2.0.0
packaging==24.2
pluggy==1.5.0
pytest==8.3.4
pytest-tap==3.5
tap.py==3.2.1

kvk1920 avatar Feb 22 '25 20:02 kvk1920

I encountered this as well.

To get some resemblance of reasonable output in the Meson test, I added this to my conftest.py:

class MesonTAPPlugin(pytest_tap.plugin.TAPPlugin):
    def pytest_runtest_logreport(self, report: pytest.TestReport):
        super().pytest_runtest_logreport(report)
        if report.failed:
            print(f"{'_' * 16} {report.head_line} {'_' * 16}", file=sys.stderr)
            print(report.longreprtext, file=sys.stderr)
            print(file=sys.stderr)


def pytest_addoption(parser):
    parser.addoption(
        "--meson",
        default=False,
        action="store_true",
    )


@pytest.hookimpl(trylast=True)
def pytest_configure(config):
    if config.getoption("--meson") and not config.option.help:
        config.option.tap_stream = True
        config.option.tap_log_passing_tests = True
        config.pluginmanager.register(MesonTAPPlugin(config), "tapplugin")
        devnull = open("/dev/null", "w", encoding=sys.getdefaultencoding())
        config.pluginmanager.register(
            TerminalReporter(config, devnull), "terminalreporter"
        )

The hack here is that I register terminalreporter back after the tap plugin, but I point it to the /dev/null. The alternative would be to use a virtual TextIO object that just drops all output (using /dev/null was just shorter code).

I am not submitting this as a fix to the upstream because it is pretty much an ugly hack. Pytest should instead allow some safe disablement of the terminal reporter plugin.

Cynerd avatar Sep 01 '25 11:09 Cynerd