Failed assertions are shown incorrectly
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
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.