2

NB OS is W10.

The context here is pytest-qt. I found this answer and tried to use it (see later).

I get this error when a test method calls an application method which creates a QFileDialog and then calls exec on it. The console then puts out this message and the console hangs. I have no option but to close the console manually.

One factor involved here is this setting (set during testing before running the test):

os.environ['QT_QPA_PLATFORM'] = 'offscreen'

... without this line the QFileDialog displays OK on exec, the test concludes, and the console does not hang.

I don't of course know which plugin is the culprit... having googled on "propagateSizeHints" nothing came up. pip freeze output is as follows:

atomicwrites==1.4.0
attrs==21.2.0
colorama==0.4.4
coloredlogs==15.0.1
concurrent-log-handler==0.9.19
humanfriendly==10.0
iniconfig==1.1.1
packaging==21.2
pluggy==1.0.0
portalocker==2.3.2
py==1.11.0
pyparsing==2.4.7
PyQt5==5.15.6
PyQt5-Qt5==5.15.2
PyQt5-sip==12.9.0
pyreadline3==3.3
pytest==6.2.5
pytest-qt==4.0.2
pywin32==302
toml==0.10.2

Having searched instead on problems with exec I found the above answer by eyllanesc. So I tried this:

def test_file_dialog_should_do_x(request, qtbot):
    t_logger.info(f'\n>>>>>> test name: {request.node.originalname}')
    main_win = main_window.AutoTransMainWindow() 
    proj = project.Project(main_win)
    def on_timeout():
        proj.modify_working_files()
        print('A1')
        assert proj.file_dlg.windowTitle() == 'Nonsense'
        print('A2')
    print('A3')
    QtCore.QTimer.singleShot(0, on_timeout)
    print('A4')
    assert proj.file_dlg.windowTitle() == 'Nonsense'

(These asserts are meant to fail)

The app code is as simple as possible:

def modify_working_files(self):
    self.file_dlg = QtWidgets.QFileDialog()
    self.file_dlg.exec()

... but I again get the same "This plugin does not..." message, and the console again hangs. Only "A3" and "A4" get printed out.

One solution might be just to mock out exec universally. But eyllanesc knows his onions, and presumably would have said so if this were the case. And if it were the case I don't see how it would be possible to do any testing made possible by qtbot.

So far I've had no problems with the "offscreen" setting. But maybe there are limitations... this post seems to suggest this might the root of the problem.

MRE:

import os

from PyQt5 import QtWidgets, QtCore, QtGui

os.environ['QT_QPA_PLATFORM'] = 'offscreen'

QtWidgets.QApplication([])

class MainWin(QtWidgets.QMainWindow):
    pass

class Project():
    def __init__(self, main_win):
        self.main_win = main_win
        
    def modify_working_files(self):
        self.file_dlg = QtWidgets.QFileDialog()
        self.file_dlg.exec()        
        
        
def test_file_dialog_should_do_x(request, qtbot):
    print(f'\n>>>>>> test name: {request.node.originalname}')
    
    main_win = MainWin() 
    proj = Project(main_win)
    def on_timeout():
        proj.modify_working_files()
        print('A1')
        assert proj.file_dlg.windowTitle() == 'Nonsense'
        print('A2')
    print('A3')
    QtCore.QTimer.singleShot(0, on_timeout)
    print('A4')
    assert proj.file_dlg.windowTitle() == 'Nonsense'

On my machine this produces (as expected) a FAIL on the line in function on_timeout if the QT_QPA_PLATFORM line is commented out.

If the latter is not commented out I get the plugin error message and the console hang.

Even if MainWin is made to show() in its constructor, and even if QFileDialog is created with QtWidgets.QFileDialog(self.main_win), the error still happens.

I also tried class Project(object) and even (longshot) class Project(QtCore.QObject). No joy.

NB I also searched, in all files in my virtual environment, and all files in my core Python installation (v. 3.9.4) for the string "propagatesize". Nothing!

mike rodent
  • 14,126
  • 11
  • 103
  • 157

1 Answers1

1

The source of this message appears to be here:

void QPlatformWindow::propagateSizeHints() {qWarning("This plugin does not support propagateSizeHints()"); }

So it's about using PyQt5 (or Qt5) on a Windows platform. It would appear from my experiments that it is a symptom but not a cause of the failure noted. Unfortunately this page, which is meant to list "all Qt5 classes" (not just PyQt5) doesn't include it.

From here, this specific message can be suppressed by putting this in a file imported by all test files:

def qt_message_handler(mode, context, message):
    if mode == QtCore.QtInfoMsg:
        mode = 'INFO'
    elif mode == QtCore.QtWarningMsg:
        mode = 'WARNING'
        if 'propagateSizeHints' in message:
            return
    elif mode == QtCore.QtCriticalMsg:
        mode = 'CRITICAL'
    elif mode == QtCore.QtFatalMsg:
        mode = 'FATAL'
    else:
        mode = 'DEBUG'
    print('qt_message_handler: line: %d, func: %s(), file: %s' % (
          context.line, context.function, context.file))
    print('  %s: %s\n' % (mode, message))

QtCore.qInstallMessageHandler(qt_message_handler)

To overcome the problem of exec, the following counts as a workaround. I note from the Qt5 docs:

QDialog.exec()

Note: Avoid using this function; instead, use open(). Unlike exec(), open() is asynchronous, and does not spin an additional event loop. This prevents a series of dangerous bugs from happening (e.g. deleting the dialog's parent while the dialog is open via exec()). When using open() you can connect to the finished() signal of QDialog to be notified when the dialog is closed.

Swapping this in, I still get the message "This plugin does not support propagateSizeHints()"... but the console does not hang.

exec() returns an int value, but open doesn't. A small price to pay to get over this.

BUT... in fact, on my machine, open always results in a non-modal dialog, even if I setModal(True) (before it).

Fortunately show() honours setModal(...) ... and also does not cause the console to crash. Seems the one to use.

mike rodent
  • 14,126
  • 11
  • 103
  • 157