diff options
author | Johan Klokkhammer Helsing <johan.helsing@qt.io> | 2018-10-24 08:46:06 +0200 |
---|---|---|
committer | Johan Helsing <johan.helsing@qt.io> | 2018-12-11 15:46:49 +0000 |
commit | ddef100d9fa7014b21280b1380e6fbcef80277b3 (patch) | |
tree | cd0e674010a585fd6bc28cf0c26523392d8fbb8b /tests/auto/client/xdgshell/tst_xdgshell.cpp | |
parent | 32b6f5f41349d109f872caf42a44352744bd48e2 (diff) |
Create a new type of mock compositor for client tests
There are a number of issues with the current client testing:
- Adding new compositor functionality is cumbersome (need to add compositor
send method, command, implementation, not to mention creating new wrapper
objects.
- Customizing available globals and their versions is not possible and would be
hard to implement. I.e. how to test that functionality works with old and new
versions of an interface? Handle globals being destroyed. We did this with
wl_output, but it was painfully cumbersome.
- Hard to verify that the compositor state is clean between tests. It is
currently done in some tests, but requires boiler plate code which needs to
be added and maintained for each test.
- In general lots of boiler-plate for new tests. (We have to have separate
tests as long as Qt has global/static state. I.e. if one shell extension has
been initialized, we can't deinitialize and initialize another one, so tests
have to be separate.)
- Dispatching server events tied to the client event loop sometimes makes it
hard to write tests without deadlocks.
- Abstraction, encapsulation and automatic behavior that can't be disabled
makes it hard to test low-level functionality like surface exposure.
So, in an attempt to mitigate these issues, I wrote a new testing framework.
- Compositor dispatch is running continuously in it's own thread, access to
compositor state is guarded by a mutex on the compositor, locking this will
make dispatching stop, so the test can safely access internals. Although a
bit cumbersome at first this makes it much easier to directly use server
protocol commands from the test itself, i.e. no need to create commands for
every single thing we want to test.
- The CoreCompositor::exec template method can accept a lambda that will be run
with dispatching stopped. It can also return a value, conveniently letting us
safely extract or modify compositor state from tests.
- This framework also takes full advantage of the qtwaylandscanner, using
wrapper classes for everything, reducing boiler plate considerably.
- The compositor parts are designed to do as little as possible automatically,
but still provide easy ways to enable common functionality, like releasing
buffers automatically, configuring shell surfaces etc.
- Compositor globals are pluggable, use add<GlobalClass>() and
remove<GlobalClass>() to add new global interfaces. I.e. easy to create a
compositor with or without data_device_manager for instance.
- DefaultCompositor provides a sensible default set of functionality and
convenience methods for most test-cases. Custom ones can still be made by
inheriting from CoreCompositor directly instead or by removing or adding
globals to DefaultCompositor.
- Globals have an isClean() method. Implement it to verify that the client
didn't leave any objects lying around from the previous test.
CoreCompositor::isClean calls isClean on the globals so a single call is all
that's needed.
In short, we've traded mock compositor encapsulation and thread safety
guarantees for less boiler-plate, easier and more convenient access to
internals.
Anything accessing compositor state should go into a exec() call, or through
the wrapper macros QCOMPOSITOR_VERIFY and QCOMPOSITOR_COMPARE (or the TRY
versions). I've also tried to make the compositor print warnings if compositor
state is accessed in an unsafe way.
The mock compositor is currently built once per test due to CI limitations
(same thing as with the old tests).
Change-Id: Ia3feb80ce175d3814292b7f4768a0cc719f8b0e8
Reviewed-by: Paul Olav Tvete <paul.tvete@qt.io>
Diffstat (limited to 'tests/auto/client/xdgshell/tst_xdgshell.cpp')
-rw-r--r-- | tests/auto/client/xdgshell/tst_xdgshell.cpp | 269 |
1 files changed, 269 insertions, 0 deletions
diff --git a/tests/auto/client/xdgshell/tst_xdgshell.cpp b/tests/auto/client/xdgshell/tst_xdgshell.cpp new file mode 100644 index 000000000..55e994b06 --- /dev/null +++ b/tests/auto/client/xdgshell/tst_xdgshell.cpp @@ -0,0 +1,269 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "mockcompositor.h" +#include <QtGui/QRasterWindow> +#include <QtGui/QOpenGLWindow> + +using namespace MockCompositor; + +class tst_xdgshell : public QObject, private DefaultCompositor +{ + Q_OBJECT +private slots: + void cleanup() { QTRY_VERIFY2(isClean(), qPrintable(dirtyMessage())); } + void showMinimized(); + void basicConfigure(); + void configureSize(); + void configureStates(); + void popup(); + void pongs(); +}; + +void tst_xdgshell::showMinimized() +{ + QSKIP("TODO: This currently fails, needs a fix"); + // On xdg-shell there's really no way for the compositor to tell the window if it's minimized + // There are wl_surface.enter events and so on, but there's really no way to differentiate + // between a window preview and an unminimized window. + QWindow window; + window.showMinimized(); + QCOMPARE(window.windowStates(), Qt::WindowMinimized); // should return minimized until + QTRY_COMPARE(window.windowStates(), Qt::WindowNoState); // rejected by handleWindowStateChanged + + // Make sure the window on the compositor side is/was created here, and not after the test + // finishes, as that may mess up for later tests. + QCOMPOSITOR_TRY_VERIFY(surface()); + QVERIFY(!window.isExposed()); +} + +void tst_xdgshell::basicConfigure() +{ + QRasterWindow window; + window.resize(64, 48); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); + + QSignalSpy configureSpy(exec([=] { return xdgSurface(); }), &XdgSurface::configureCommitted); + + QTRY_VERIFY(window.isVisible()); + // The window should not be exposed before the first xdg_surface configure event + QTRY_VERIFY(!window.isExposed()); + + exec([=] { + xdgToplevel()->sendConfigure({0, 0}, {}); // Let the window decide the size + }); + + // Nothing should happen before the *xdg_surface* configure + QTRY_VERIFY(!window.isExposed()); //Window should not be exposed before the first configure event + QVERIFY(configureSpy.isEmpty()); + + const uint serial = exec([=] { return nextSerial(); }); + + exec([=] { + xdgSurface()->sendConfigure(serial); + }); + + // Finally, we're exposed + QTRY_VERIFY(window.isExposed()); + + // The client is now going to ack the configure + QTRY_COMPARE(configureSpy.count(), 1); + QCOMPARE(configureSpy.takeFirst().at(0).toUInt(), serial); + + // And attach a buffer + exec([&] { + Buffer *buffer = xdgToplevel()->surface()->m_committed.buffer; + QVERIFY(buffer); + QCOMPARE(buffer->size(), window.frameGeometry().size()); + }); +} + +void tst_xdgshell::configureSize() +{ + QRasterWindow window; + window.resize(64, 48); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); + + QSignalSpy configureSpy(exec([=] { return xdgSurface(); }), &XdgSurface::configureCommitted); + + const QSize configureSize(60, 40); + + exec([=] { + xdgToplevel()->sendCompleteConfigure(configureSize); + }); + + QTRY_COMPARE(configureSpy.count(), 1); + + exec([=] { + Buffer *buffer = xdgToplevel()->surface()->m_committed.buffer; + QVERIFY(buffer); + QCOMPARE(buffer->size(), configureSize); + }); +} + +void tst_xdgshell::configureStates() +{ + QRasterWindow window; + window.resize(64, 48); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); + + const QSize windowedSize(320, 240); + const uint windowedSerial = exec([=] { + return xdgToplevel()->sendCompleteConfigure(windowedSize, { XdgToplevel::state_activated }); + }); + QCOMPOSITOR_TRY_COMPARE(xdgSurface()->m_committedConfigureSerial, windowedSerial); + QCOMPARE(window.visibility(), QWindow::Windowed); + QCOMPARE(window.windowStates(), Qt::WindowNoState); + QCOMPARE(window.frameGeometry().size(), windowedSize); + // Toplevel windows don't know their position on xdg-shell +// QCOMPARE(window.frameGeometry().topLeft(), QPoint()); // TODO: this doesn't currently work when window decorations are enabled + +// QEXPECT_FAIL("", "configure has already been acked, we shouldn't have to wait for isActive", Continue); +// QVERIFY(window.isActive()); + QTRY_VERIFY(window.isActive()); // Just make sure it eventually get's set correctly + + const QSize screenSize(640, 480); + const uint maximizedSerial = exec([=] { + return xdgToplevel()->sendCompleteConfigure(screenSize, { XdgToplevel::state_activated, XdgToplevel::state_maximized }); + }); + QCOMPOSITOR_TRY_COMPARE(xdgSurface()->m_committedConfigureSerial, maximizedSerial); + QCOMPARE(window.visibility(), QWindow::Maximized); + QCOMPARE(window.windowStates(), Qt::WindowMaximized); + QCOMPARE(window.frameGeometry().size(), screenSize); +// QCOMPARE(window.frameGeometry().topLeft(), QPoint()); // TODO: this doesn't currently work when window decorations are enabled + + const uint fullscreenSerial = exec([=] { + return xdgToplevel()->sendCompleteConfigure(screenSize, { XdgToplevel::state_activated, XdgToplevel::state_fullscreen }); + }); + QCOMPOSITOR_TRY_COMPARE(xdgSurface()->m_committedConfigureSerial, fullscreenSerial); + QCOMPARE(window.visibility(), QWindow::FullScreen); + QCOMPARE(window.windowStates(), Qt::WindowFullScreen); + QCOMPARE(window.frameGeometry().size(), screenSize); +// QCOMPARE(window.frameGeometry().topLeft(), QPoint()); // TODO: this doesn't currently work when window decorations are enabled + + // The window should remember its original size + const uint restoreSerial = exec([=] { + return xdgToplevel()->sendCompleteConfigure({0, 0}, { XdgToplevel::state_activated }); + }); + QCOMPOSITOR_TRY_COMPARE(xdgSurface()->m_committedConfigureSerial, restoreSerial); + QCOMPARE(window.visibility(), QWindow::Windowed); + QCOMPARE(window.windowStates(), Qt::WindowNoState); + QCOMPARE(window.frameGeometry().size(), windowedSize); +// QCOMPARE(window.frameGeometry().topLeft(), QPoint()); // TODO: this doesn't currently work when window decorations are enabled +} + +void tst_xdgshell::popup() +{ + class Window : public QRasterWindow { + public: + void mousePressEvent(QMouseEvent *event) override + { + QRasterWindow::mousePressEvent(event); + m_popup.reset(new QRasterWindow); + m_popup->setTransientParent(this); + m_popup->setFlags(Qt::Popup); + m_popup->resize(100, 100); + m_popup->show(); + } + QScopedPointer<QRasterWindow> m_popup; + }; + Window window; + window.resize(200, 200); + window.show(); + + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); + QSignalSpy toplevelConfigureSpy(exec([=] { return xdgSurface(); }), &XdgSurface::configureCommitted); + exec([=] { xdgToplevel()->sendCompleteConfigure(); }); + QTRY_COMPARE(toplevelConfigureSpy.count(), 1); + + uint clickSerial = exec([=] { + auto *surface = xdgToplevel()->surface(); + auto *p = pointer(); + p->sendEnter(surface, {100, 100}); +// p->sendFrame(); //TODO: uncomment when we support seat v5 + uint serial = p->sendButton(client(), BTN_LEFT, Pointer::button_state_pressed); + p->sendButton(client(), BTN_LEFT, Pointer::button_state_released); + return serial; +// p->sendFrame(); //TODO: uncomment when we support seat v5 + }); + + QTRY_VERIFY(window.m_popup); + QCOMPOSITOR_TRY_VERIFY(xdgPopup()); + QSignalSpy popupConfigureSpy(exec([=] { return xdgPopup()->m_xdgSurface; }), &XdgSurface::configureCommitted); + QCOMPOSITOR_TRY_VERIFY(xdgPopup()->m_grabbed); + QCOMPOSITOR_TRY_COMPARE(xdgPopup()->m_grabSerial, clickSerial); + + QRasterWindow *popup = window.m_popup.get(); + QVERIFY(!popup->isExposed()); // wait for configure + + //TODO: Verify it works with a different configure window geometry + exec([=] { xdgPopup()->sendConfigure(QRect(100, 100, 100, 100)); }); + + // Nothing should happen before the *xdg_surface* configure + QTRY_VERIFY(!popup->isExposed()); // Popup shouldn't be exposed before the first configure event + QVERIFY(popupConfigureSpy.isEmpty()); + + const uint configureSerial = exec([=] { + return xdgPopup()->m_xdgSurface->sendConfigure(); + }); + + // Finally, we're exposed + QTRY_VERIFY(popup->isExposed()); + + // The client is now going to ack the configure + QTRY_COMPARE(popupConfigureSpy.count(), 1); + QCOMPARE(popupConfigureSpy.takeFirst().at(0).toUInt(), configureSerial); + + // And attach a buffer + exec([&] { + Buffer *buffer = xdgPopup()->surface()->m_committed.buffer; + QVERIFY(buffer); + QCOMPARE(buffer->size(), popup->frameGeometry().size()); + }); +} + +void tst_xdgshell::pongs() +{ + QSignalSpy pongSpy(exec([=] { return get<XdgWmBase>(); }), &XdgWmBase::pong); + // Verify that the client has bound to the global + QCOMPOSITOR_TRY_COMPARE(get<XdgWmBase>()->resourceMap().size(), 1); + const uint serial = exec([=] { return nextSerial(); }); + exec([=] { + auto *base = get<XdgWmBase>(); + wl_resource *resource = base->resourceMap().first()->handle; + base->send_ping(resource, serial); + }); + QTRY_COMPARE(pongSpy.count(), 1); + QCOMPARE(pongSpy.first().at(0).toUInt(), serial); +} + +QCOMPOSITOR_TEST_MAIN(tst_xdgshell) +#include "tst_xdgshell.moc" |