Closing a QMessageBox via automated tests causes "Cannot set parent" warning and eventual crash

41 Views Asked by At

I have a QtPy6 application that creates QMessageBox instances anonymously, only from the main thread:

msg = QMessageBox.critical(None, "Invalid name!", "Your output name can only be alphanumeric!")

I also have an automation test suite that must close opened QMessageBoxes when start_gui_repack() is called, which is done by scheduling a call to close_active_modals() as follows (see this question for an explanation):

app = QApplication(sys.argv)

class clientGUILib():
    def __init__(self):
        self.gui = client.MyMainWindow() 
        self.gui.show()
        self.repackPanel = self.gui.repackFilePanel
        
    def close_active_modals(self):
        while isinstance(QApplication.activeWindow(), QMessageBox):
            win: QMessageBox = QApplication.activeWindow()
            closeBtn = win.defaultButton()
            QTest.mouseClick(closeBtn, Qt.MouseButton.LeftButton)
            QApplication.processEvents()
            time.sleep(0.5)

    def start_gui_repack(self, delay_time=1):
        threading.Timer(delay_time, self.close_active_modals).start()
        QTest.mouseClick(self.repackPanel.repackButton, Qt.MouseButton.LeftButton)
        QApplication.processEvents()

Any test that uses these methods prints the following warnings to console:

QObject::setParent: Cannot set parent, new parent is in a different thread

I'm not sure how to trace what object this warning is coming from. Rarely, either of these warnings will also appear; again, I'm not sure what in my code could be causing them or if they are relevant at all.

QWidget::repaint: Recursive repaint detected
QBackingStore::endPaint() called with active painter; did you forget to destroy it or call QPainter::end() on it?

In my test suite, each test individually runs, triggering a repack operation which creates up to 3 message boxes. These message boxes all get closed by the close_active_modals() method, but leave behind more copies of the warning each time. The application eventually crashes, which seems to be related to how many of these warnings are printed; the amount of tests completed is always inconsistent - for example (//comments added by me):

Repack T0 And Delete Files And Folders                                ..
QObject::setParent: Cannot set parent, new parent is in a different thread
QObject::setParent: Cannot set parent, new parent is in a different thread
Repack T0 And Delete Files And Folders                                | PASS |
//1 message box expected
------------------------------------------------------------------------------
Repack T12 Without Deletion                                           ...
QObject::setParent: Cannot set parent, new parent is in a different thread
QObject::setParent: Cannot set parent, new parent is in a different thread
Repack T12 Without Deletion                                           | PASS |
//1 message box expected
------------------------------------------------------------------------------
Repack T12 And Delete Files                                           ...
QObject::setParent: Cannot set parent, new parent is in a different thread
QObject::setParent: Cannot set parent, new parent is in a different thread
QObject::setParent: Cannot set parent, new parent is in a different thread
QObject::setParent: Cannot set parent, new parent is in a different thread
QObject::setParent: Cannot set parent, new parent is in a different thread
QObject::setParent: Cannot set parent, new parent is in a different thread
QObject::setParent: Cannot set parent, new parent is in a different thread
QObject::setParent: Cannot set parent, new parent is in a different thread
//3 message boxes expected, crashes here

As my message boxes are created using the static functions method, I can't use the msg.close() function without code changes. However, I did try converting some of my code to use the property-based method but got the same behaviour.

I'm not sure if this has to do with the way my test suite is set up; clientGuiLib.py is set as a library import for a RobotFramework test file and I'm assuming the global-scoped app = QApplication(sys.argv) is enough to run the application properly; I'm not familiar enough with RobotFramework to know exactly how this is handled.

Unrelated questions regarding the same warning all seem to suggest that interacting with the GUI from a non-GUI thread isn't a good idea. However, my method of closing QMessageBoxes seems to be the only way to do it, as the main thread is blocked while a message box is open. I'm not sure if using QTest methods is considered "interacting with the GUI from an outside thread".

I can't figure out how to avoid the application crashing when all tests are run sequentially, and have no idea why it's so inconsistent or what precisely is causing it. I wasn't able to find a way to turn on a verbose logging mode for PyQt6 that would help me trace where the warning is coming from exactly either. I'm looking for first and foremost a solution to the crash, but if I haven't provided enough information, a solution to the QObject::setParent warning or at least a way to trace it would be super appreciated.

0

There are 0 best solutions below