Fork mode: a failing assertion in a worker crashes in Config.get_terminal_writer instead of printing the diff
Summary
In fork mode (pytest-parallex 0.2.3, pytest 9.0.3, Python 3.13), a failing == assertion inside a worker does not produce pytest's usual comparison diff. It raises a secondary AssertionError from _pytest/config/__init__.py (Config.get_terminal_writer), and that traceback replaces the real one, so the actual left/right values are never shown.
Observed traceback (worker side)
> assert [call.args[1] for call in mock.call_args_list] == [["a"], ["b"], ["c"]]
_pytest/assertion/rewrite.py:507: in _call_reprcompare
custom = util._reprcompare(ops[i], each_obj[i], each_obj[i + 1])
_pytest/assertion/__init__.py:167: in callbinrepr
hook_result = ihook.pytest_assertrepr_compare(
_pytest/assertion/__init__.py:208: in pytest_assertrepr_compare
return util.assertrepr_compare(config=config, op=op, left=left, right=right)
_pytest/assertion/util.py:206: in assertrepr_compare
highlighter = config.get_terminal_writer()._highlight
def get_terminal_writer(self) -> TerminalWriter:
terminalreporter: TerminalReporter | None = self.pluginmanager.get_plugin("terminalreporter")
> assert terminalreporter is not None
E AssertionError
_pytest/config/__init__.py:1185: AssertionErrorCause
runner_fork.py unregisters the terminalreporter plugin in the forked child so that the child does not write progress to the shared stdout:
terminal = self.config.pluginmanager.getplugin("terminalreporter")
if terminal is not None:
self.config.pluginmanager.unregister(terminal)pytest's assertion machinery does not treat that plugin as optional. util.assertrepr_compare fetches the syntax highlighter through config.get_terminal_writer(), which asserts the plugin is registered. The same accessor is used by _pytest/runner.py (--setup-show output) and _pytest/setuponly.py. So in a worker, the first comparison that fails triggers the accessor assertion, and the user sees "AssertionError" with no explanation of what was compared.
Impact
Any test failure that relies on pytest_assertrepr_compare (every ==, in, < on containers, dataclasses, strings) is undiagnosable from CI output in fork mode. The test is correctly reported as failed, but the reason is hidden. Thread and process modes are presumably unaffected because they do not unregister the reporter.
Reproduction
# test_repro.py
def test_diff():
assert [1, 2, 3] == [1, 3, 2]pytest --parallel fork --parallel-workers 2 test_repro.pyExpected: pytest's "At index 1 diff: 2 != 3" comparison output. Actual: the get_terminal_writer AssertionError above.
Suggested fix
Keep the terminalreporter plugin registered in the child and silence it instead of removing it, for example by pointing its writer at a discard stream:
terminal = self.config.pluginmanager.getplugin("terminalreporter")
if terminal is not None:
terminal._tw = TerminalWriter(file=open(os.devnull, "w"))Alternatively, register a minimal stand-in under the name terminalreporter that exposes _tw bound to a null TerminalWriter. Either way config.get_terminal_writer() keeps working and the child still produces no terminal output; the controller remains the only writer when it replays the collected reports.