3e779be6f3
CI / lint (push) Failing after 13m4s
CI / test (3.11, ubuntu-latest) (push) Failing after 2m4s
CI / test (3.13, ubuntu-latest) (push) Successful in 13m30s
CI / test (3.14, ubuntu-latest) (push) Successful in 17m21s
CI / test (3.12, ubuntu-latest) (push) Successful in 17m55s
CI / discover-apps-ps (push) Successful in 1m56s
CI / test (3.9, ubuntu-latest) (push) Successful in 13m17s
CI / test (3.10, ubuntu-latest) (push) Successful in 26m21s
CI / audit (push) Successful in 13m38s
Deploy site / deploy (push) Has been cancelled
CI / test (3.14, ubuntu-24.04-arm) (push) Has been cancelled
80 lines
2.8 KiB
Python
80 lines
2.8 KiB
Python
# SPDX-License-Identifier: MIT
|
|
"""Regression tests for #550 (BusyDialog auto-close from a worker thread) and
|
|
#553 (word-wrap labels in a resizable scroll area must have a constant wrap
|
|
width so QBoxLayout::heightForWidth can't recurse to a SIGSEGV).
|
|
|
|
Runs headless via the offscreen Qt platform — no display server required.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import threading
|
|
import time
|
|
|
|
import pytest
|
|
|
|
# Must be set before the QApplication ctor.
|
|
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
|
|
|
|
pytest.importorskip("PySide6")
|
|
|
|
|
|
def _qapp():
|
|
from PySide6.QtWidgets import QApplication
|
|
|
|
return QApplication.instance() or QApplication([])
|
|
|
|
|
|
def test_busydialog_finish_from_worker_thread_closes_the_dialog():
|
|
# #550: a bare worker thread has no Qt event loop, so the old
|
|
# `QTimer.singleShot(0, dlg.finish)` never fired and the dialog hung open.
|
|
# finish() is now signal-based, so a cross-thread call queues accept() onto
|
|
# the GUI thread's exec() loop. The watchdog reject bounds the test so a
|
|
# regression fails fast instead of hanging CI.
|
|
from PySide6.QtCore import QTimer
|
|
from PySide6.QtWidgets import QDialog
|
|
|
|
from winpodx.gui._widget_helpers import BusyDialog
|
|
|
|
_qapp()
|
|
dlg = BusyDialog(None, "Working", "Doing a thing...")
|
|
|
|
def worker():
|
|
time.sleep(0.05) # let exec()'s loop start first (the realistic case)
|
|
dlg.finish() # cross-thread close
|
|
|
|
threading.Thread(target=worker, daemon=True).start()
|
|
QTimer.singleShot(3000, dlg.reject) # watchdog: a hang would reject, not hang
|
|
result = dlg.exec()
|
|
|
|
assert result == QDialog.DialogCode.Accepted # closed via finish(), not the watchdog
|
|
|
|
|
|
def test_busydialog_finish_is_signal_based():
|
|
from winpodx.gui._widget_helpers import BusyDialog
|
|
|
|
_qapp()
|
|
dlg = BusyDialog(None, "T", "m")
|
|
# The cross-thread-safe close path: a signal connected to accept().
|
|
assert hasattr(dlg, "_close_requested")
|
|
|
|
|
|
def test_empty_panel_wrapped_labels_have_constant_width():
|
|
# #553: word-wrap QLabels inside the resizable app-list scroll area must be
|
|
# fixed-width, else their height tracks the viewport width and feeds back
|
|
# into QBoxLayout::heightForWidth -> unbounded recursion -> SIGSEGV on 6.11.
|
|
from PySide6.QtWidgets import QLabel
|
|
|
|
from winpodx.gui._widget_helpers import make_empty_panel
|
|
|
|
_qapp()
|
|
panel = make_empty_panel("A title that is reasonably long", "Some body text here too")
|
|
wrapped = [lbl for lbl in panel.findChildren(QLabel) if lbl.wordWrap()]
|
|
assert wrapped, "empty panel should have word-wrap labels"
|
|
for lbl in wrapped:
|
|
# setFixedWidth makes min == max == the constant wrap width.
|
|
assert lbl.minimumWidth() == lbl.maximumWidth() > 0, (
|
|
"word-wrap label in a resizable scroll area must have a fixed width"
|
|
)
|