Skip to content
Open
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 @@ -44,6 +44,7 @@ Anna Tasiopoulou
Anthon van der Neut
Anthony Shaw
Anthony Sottile
Antoine Leclair
Anton Grinevich
Anton Lodder
Anton Zhilin
Expand Down
1 change: 1 addition & 0 deletions changelog/14377.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed :meth:`pytest.Config.get_terminal_writer` crashing with an internal ``AssertionError`` when the terminal reporter plugin has been unregistered (for example, by :pypi:`pytest-tap` in streaming mode). A fresh terminal writer is now created on demand.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The get_terminal_writer function is private, so you can't ref it. Also, let's remove the last sentence describing the solution since that's not relevant for a changelog.

Suggested change
Fixed :meth:`pytest.Config.get_terminal_writer` crashing with an internal ``AssertionError`` when the terminal reporter plugin has been unregistered (for example, by :pypi:`pytest-tap` in streaming mode). A fresh terminal writer is now created on demand.
Fixed a crash from `Config.get_terminal_writer` when the terminalreporter plugin has been unregistered (for example, by :pypi:`pytest-tap` in streaming mode).

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @bluetech.

I would rather look at removing the dependency on TerminalWriter from assertrepr_compare.

I think you're right. I was proposing a solution, mostly to highlight the problem and in case the solution would be acceptable (after all... it's crashing without the change, and it's not crashing with the change).

Let me know if you want to try it, if not, I'll try looking into it myself.

I think you have more knowledge of the code base and will do a better job with less effort. So if you're OK, I'd leave that one to you. We can close the PR whenever you want.

Or I may try to tackle it in a few months if I see it's still not been touched (which would be totally understandable).

Thank you for your time.

3 changes: 2 additions & 1 deletion src/_pytest/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1173,7 +1173,8 @@ def get_terminal_writer(self) -> TerminalWriter:
terminalreporter: TerminalReporter | None = self.pluginmanager.get_plugin(
"terminalreporter"
)
assert terminalreporter is not None
if terminalreporter is None:
return create_terminal_writer(self)
return terminalreporter._tw

def pytest_cmdline_parse(
Expand Down
23 changes: 23 additions & 0 deletions testing/test_assertion.py
Original file line number Diff line number Diff line change
Expand Up @@ -1682,6 +1682,29 @@ def test_hello():
)


def test_assertrepr_compare_without_terminalreporter(pytester: Pytester) -> None:
pytester.makeconftest(
"""
import pytest

@pytest.hookimpl(trylast=True)
def pytest_configure(config):
reporter = config.pluginmanager.get_plugin("terminalreporter")
config.pluginmanager.unregister(reporter)
"""
)
pytester.makepyfile(
"""
def test_hello():
assert "actual" == "expected"
"""
)
reprec = pytester.inline_run()
_passed, _skipped, failed = reprec.listoutcomes()
assert len(failed) == 1
assert "assert 'actual' == 'expected'" in str(failed[0].longrepr)


def test_assertrepr_loaded_per_dir(pytester: Pytester) -> None:
pytester.makepyfile(test_base=["def test_base(): assert 1 == 2"])
a = pytester.mkdir("a")
Expand Down
9 changes: 9 additions & 0 deletions testing/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -1386,6 +1386,15 @@ def cleanup_first():

assert report == ["cleanup_first", "raise_1", "raise_2", "cleanup_last"]

def test_get_terminal_writer_without_terminalreporter(
self, pytester: Pytester
) -> None:
from _pytest._io import TerminalWriter

config = pytester.parseconfig()
config.pluginmanager.unregister(name="terminalreporter")
assert isinstance(config.get_terminal_writer(), TerminalWriter)


class TestConfigFromdictargs:
def test_basic_behavior(self, _sys_snapshot) -> None:
Expand Down