de7a4f8641
QtCore's libraries (glib, icu) can load while QtWidgets/QtGui still miss libGL - matplotlib's qt backend imports all three, so the old probe could promise QtAgg and lose even the error figure at switch_backend, one library layer deeper than the NixOS fix. The regression test's mock now mirrors the real failure (bare package and QtCore import fine, GUI modules raise): both the bare-import and the QtCore-probe variants now fail it, verified by mutation. Also drop two redundant f-prefixes on placeholder-less strings in report.py.
73 lines
2.9 KiB
Python
73 lines
2.9 KiB
Python
"""Regressions from the first macOS field test.
|
|
|
|
Two failures that only a non-Windows KiCad could produce: the board
|
|
directory was read-only (demo project opened straight from the mounted
|
|
installer image), and matplotlib picked TkAgg - macOS' bundled Python
|
|
ships tkinter, unlike KiCad's Windows Python - then refused to create
|
|
any figure because the PySide6 dialog already had a Qt event loop in
|
|
the process.
|
|
"""
|
|
import pathlib
|
|
|
|
from fill_resistance import plots, report
|
|
|
|
|
|
def test_backend_prefers_qt_over_tk():
|
|
# The dev environment has both toolkits installed, so this asserts
|
|
# the preference order for real: Qt must win, because the selection
|
|
# dialog / progress window make the process a Qt process before the
|
|
# first figure exists.
|
|
assert plots._pick_backend() in ("QtAgg", "Qt5Agg")
|
|
|
|
|
|
def test_backend_probe_requires_working_qt_gui_stack(monkeypatch):
|
|
# NixOS: `import PySide6` succeeds (a pure-Python __init__) while
|
|
# the native .so's cannot load the FHS system libraries pip wheels
|
|
# expect; on a partially provisioned system even QtCore loads
|
|
# (glib, icu present) while QtWidgets/QtGui still miss libGL. The
|
|
# probe must import QtWidgets and fall through - promising QtAgg
|
|
# kills even the error figure at switch_backend time, and the
|
|
# failure report with it. The mock mirrors that faithfully (bare
|
|
# package and QtCore succeed, GUI modules fail) so a probe reverted
|
|
# to `__import__(qt)` or `.QtCore` would wrongly return QtAgg here.
|
|
import builtins
|
|
import types
|
|
real_import = builtins.__import__
|
|
|
|
def broken_qt(name, *args, **kwargs):
|
|
root, _, sub = name.partition(".")
|
|
if root in ("PySide6", "PyQt6", "PyQt5", "PySide2"):
|
|
if sub in ("", "QtCore"):
|
|
return types.ModuleType(name)
|
|
raise ImportError("libGL.so.1: cannot open shared object "
|
|
"file: No such file or directory")
|
|
return real_import(name, *args, **kwargs)
|
|
|
|
monkeypatch.setattr(builtins, "__import__", broken_qt)
|
|
assert plots._pick_backend() in ("TkAgg", None)
|
|
|
|
|
|
def test_output_dir_falls_back_when_board_dir_unwritable(
|
|
tmp_path, monkeypatch, capsys):
|
|
board_dir = tmp_path / "board"
|
|
board_dir.mkdir()
|
|
real_mkdir = pathlib.Path.mkdir
|
|
|
|
def deny_under_board(self, *args, **kwargs):
|
|
if str(self).startswith(str(board_dir)):
|
|
raise OSError(30, "Read-only file system", str(self))
|
|
return real_mkdir(self, *args, **kwargs)
|
|
|
|
monkeypatch.setattr(pathlib.Path, "mkdir", deny_under_board)
|
|
out = report.make_output_dir(board_dir)
|
|
assert out.is_dir()
|
|
assert not str(out).startswith(str(board_dir))
|
|
assert board_dir.name in out.name # traceable back to the board
|
|
assert "not writable" in capsys.readouterr().out
|
|
|
|
|
|
def test_output_dir_normal_case_unchanged(tmp_path):
|
|
out = report.make_output_dir(tmp_path)
|
|
assert out.is_dir()
|
|
assert out.parent.parent == tmp_path
|