Add QDockWidget save & restore auto test

Implement auto test for (de)serialization of dock widget properties.
Test settings compatibility by importing a hard coded byte array.
Test serialization format and sequence by comparing expected and real
serialization output.
Test dock widget closing behavior (QTBUG-103474) to ensure that
no closing inconsistency is caused by serialization incompatibility.
Extensively document/comment the test to ensure failures can be easily
interpreted in the future.

Task-number: QTBUG-103474
Change-Id: I3c1589c2c291b2ed56334d09b85536d4c7388f92
Pick-to: 6.4 6.3 6.2
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
This commit is contained in:
Axel Spoerl 2022-08-26 13:42:23 +02:00 committed by Volker Hilsheimer
parent 08d2ae8833
commit 0c54ac20eb

View File

@ -76,6 +76,9 @@ private slots:
// test closing and deleting consistency
void closeAndDelete();
// test save and restore consistency
void saveAndRestore();
private:
// helpers and consts for dockPermissions, hideAndShow, closeAndDelete
#ifdef QT_BUILD_INTERNAL
@ -1599,5 +1602,162 @@ void tst_QDockWidget::dockPermissions()
#endif // QT_BUILD_INTERNAL
}
/*!
\internal
This test checks consistency of QMainWindow::saveState() / QMainWindow::restoreState().
These methods (de)serialize dock widget properties via a QDataStream into a QByteArray.
If the logic of (de)serializing Qt datatypes and classes changes, old settings can fail
to restore properly without triggering warnings or assertions.
The test consists of two parts:
\list 1
\li Read properties from a hard coded byte array and check if it is deserialized correctly.
\li Serialize properties into a \a QByteArray and check if it is serialized correctly.
\endlist
*/
void tst_QDockWidget::saveAndRestore()
{
#ifdef Q_OS_WIN
QSKIP("Test skipped on Windows platforms");
#endif // Q_OS_WIN
#ifndef QT_BUILD_INTERNAL
QSKIP("test requires -developer-build option");
#else
// Hard coded byte array for test initialization
const QByteArray testArray = QByteArrayLiteral(
"\x00\x00\x00\xFF\x00\x00\x00\x00\xFD\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x13\x00\x00\x05\xE8\xFC\x02\x00\x00\x00\x01\xFB\x00\x00\x00\x04\x00"
"D\x00"
"1\x03\x00\x00\x01\f\x00\x00\x00\x97\x00\x00\x02\x19\x00\x00\x01z\x00\x00\x00\x01\x00\x00\x00\x13\x00\x00\x05\xE8\xFC\x02\x00\x00\x00\x01\xFB\x00\x00\x00\x04\x00"
"D\x00"
"2\x03\x00\x00\x06L\x00\x00\x00\xFF\x00\x00\x01\f\x00\x00\x00\xE2\x00\x00\n\x80\x00\x00\x05\xE8\x00\x00\x00\x04\x00\x00\x00\x04\x00\x00\x00\b\x00\x00\x00\b\xFC\x00\x00\x00\x00"
);
QByteArray referenceArray; // Copy of testArray, corrected for current screen limits
QPoint topLeft1; // Top left point of dock widget d1
QPoint topLeft2; // Top left point of dock widget d2
QSize widgetSize1; // Size of dock widget d1
QSize widgetSize2; // Size of dock widget d2
bool isFloating1; // Floating status of dock widget d1
bool isFloating2; // Floating status of dock widget d2
// Create a mainwindow with a central widget and two dock widgets.
// Import properties from hard coded byte array.
// Use a scope to delete objects from screen after test.
{
QPointer<QDockWidget> d1;
QPointer<QDockWidget> d2;
QPointer<QWidget> cent;
QMainWindow* mainWindow;
createTestWidgets(mainWindow, cent, d1, d2);
// Failure to restore properties might lead to inconsistencies and crash.
// To leave a clean environment when the test inexpectedly goes out of scope,
// => store main window pointer in a std::unique_ptr
std::unique_ptr<QMainWindow> up_mainWindow(mainWindow);
// Restore, wait for events to be processed
mainWindow->restoreState(testArray);
QVERIFY(QTest::qWaitForWindowExposed(d1));
QVERIFY(QTest::qWaitForWindowExposed(d2));
// Serialized dock widget positions and sizes might be overridden due
// screen size limitations => do not check them here.
// If the test fails between here and scope end, serialization format/sequence have changed
QTRY_VERIFY(d1->isFloating());
QTRY_VERIFY(d2->isFloating());
// Hide main window and save their floating status.
// Reason:
// - KDE window managers do not take control over dock widgets.
// => They always close with the main window.
// - Some non KDE window managers do take control over dock widgets.
// => They prevent them from closing with the main window (QTBUG-103474).
// If properties are restored correctly, closing behavior must be consistent
// throughout this test.
mainWindow->hide();
// FIXME: No method exists in 6.5 to wait for a window to be hidden.
// => wait and hope the best, replace with qWaitForWindowHidden once implemented.
QTest::qWait(200);
isFloating1 = d1->isFloating();
isFloating2 = d2->isFloating();
}
// Create a mainwindow with a central widget and two dock widgets.
// Assign different properties to each dock widgets.
// Write properties to a byte array.
// Remember position and size properties for comparison.
// Use a scope to delete objects from screen after test.
{
QPointer<QDockWidget> d1;
QPointer<QDockWidget> d2;
QPointer<QWidget> cent;
QMainWindow* mainWindow;
createTestWidgets(mainWindow, cent, d1, d2);
std::unique_ptr<QMainWindow> up_mainWindow(mainWindow);
// unplug, position and resize both dock widgets relative to screen size
unplugAndResize(mainWindow, d1, home1(mainWindow), size1(mainWindow));
unplugAndResize(mainWindow, d2, home2(mainWindow), size2(mainWindow));
topLeft1 = d1->pos();
topLeft2 = d2->pos();
widgetSize1 = d1->size();
widgetSize2 = d2->size();
// save properties, potentially corrected for screen limits
referenceArray = mainWindow->saveState();
// Check closing behavior consistency
mainWindow->hide();
QTRY_VERIFY(d1->isFloating());
QTRY_VERIFY(d2->isFloating());
QCOMPARE(d1->isFloating(), isFloating1);
QCOMPARE(d2->isFloating(), isFloating2);
}
// Create a new main window, central window and two dock widgets.
QPointer<QDockWidget> d1;
QPointer<QDockWidget> d2;
QPointer<QWidget> cent;
QMainWindow* mainWindow;
createTestWidgets(mainWindow, cent, d1, d2);
// Failure to restore properties might lead to inconsistencies and crash.
// To leave a clean environment when the test inexpectedly goes out of scope,
// - store main window pointer in a std::unique_ptr
std::unique_ptr<QMainWindow> up_mainWindow(mainWindow);
// Restore properties and wait for events to be processed
mainWindow->restoreState(referenceArray);
QVERIFY(QTest::qWaitForWindowExposed(d1));
QVERIFY(QTest::qWaitForWindowExposed(d2));
// Compare positions, sizes and floating status
// If the test fails in the following 12 lines,
// the de-serialization format/sequence have changed
QCOMPARE(topLeft1, d1->pos());
QCOMPARE(topLeft2, d2->pos());
QCOMPARE(widgetSize1, d1->size());
QCOMPARE(widgetSize2, d2->size());
QVERIFY(d1->isFloating());
QVERIFY(d2->isFloating());
// Serialize again to compare all remaining properties
const QByteArray comparisonArray = mainWindow->saveState();
QCOMPARE(comparisonArray, referenceArray);
// Check closing behavior consistency
mainWindow->hide();
QTRY_VERIFY(d1->isFloating());
QTRY_VERIFY(d2->isFloating());
QCOMPARE(d1->isFloating(), isFloating1);
QCOMPARE(d2->isFloating(), isFloating2);
#endif // QT_BUILD_INTERNAL
}
QTEST_MAIN(tst_QDockWidget)
#include "tst_qdockwidget.moc"