summaryrefslogtreecommitdiffstats
path: root/tests/auto/client/shared_old/mockcompositor.cpp
diff options
context:
space:
mode:
authorJohan Klokkhammer Helsing <johan.helsing@qt.io>2018-10-24 08:46:06 +0200
committerJohan Helsing <johan.helsing@qt.io>2018-12-11 15:46:49 +0000
commitddef100d9fa7014b21280b1380e6fbcef80277b3 (patch)
treecd0e674010a585fd6bc28cf0c26523392d8fbb8b /tests/auto/client/shared_old/mockcompositor.cpp
parent32b6f5f41349d109f872caf42a44352744bd48e2 (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/shared_old/mockcompositor.cpp')
-rw-r--r--tests/auto/client/shared_old/mockcompositor.cpp520
1 files changed, 520 insertions, 0 deletions
diff --git a/tests/auto/client/shared_old/mockcompositor.cpp b/tests/auto/client/shared_old/mockcompositor.cpp
new file mode 100644
index 000000000..df24b4091
--- /dev/null
+++ b/tests/auto/client/shared_old/mockcompositor.cpp
@@ -0,0 +1,520 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 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 "mockinput.h"
+#include "mockoutput.h"
+#include "mocksurface.h"
+#include "mockwlshell.h"
+#include "mockxdgshellv6.h"
+#include "mockiviapplication.h"
+
+#include <wayland-xdg-shell-unstable-v6-server-protocol.h>
+
+#include <stdio.h>
+MockCompositor::MockCompositor()
+{
+ pthread_create(&m_thread, 0, run, this);
+
+ m_mutex.lock();
+ m_waitCondition.wait(&m_mutex);
+ m_mutex.unlock();
+}
+
+MockCompositor::~MockCompositor()
+{
+ m_alive = false;
+ m_waitCondition.wakeOne();
+ pthread_join(m_thread, 0);
+}
+
+void MockCompositor::lock()
+{
+ m_mutex.lock();
+}
+
+void MockCompositor::unlock()
+{
+ m_mutex.unlock();
+}
+
+void MockCompositor::applicationInitialized()
+{
+ m_ready = true;
+}
+
+int MockCompositor::waylandFileDescriptor() const
+{
+ return m_compositor->fileDescriptor();
+}
+
+void MockCompositor::processWaylandEvents()
+{
+ m_waitCondition.wakeOne();
+}
+
+void MockCompositor::setOutputMode(const QSize &size)
+{
+ Command command = makeCommand(Impl::Compositor::setOutputMode, m_compositor);
+ command.parameters << size;
+ processCommand(command);
+}
+
+void MockCompositor::setKeyboardFocus(const QSharedPointer<MockSurface> &surface)
+{
+ Command command = makeCommand(Impl::Compositor::setKeyboardFocus, m_compositor);
+ command.parameters << QVariant::fromValue(surface);
+ processCommand(command);
+}
+
+void MockCompositor::sendMousePress(const QSharedPointer<MockSurface> &surface, const QPoint &pos)
+{
+ Command command = makeCommand(Impl::Compositor::sendMousePress, m_compositor);
+ command.parameters << QVariant::fromValue(surface) << pos;
+ processCommand(command);
+}
+
+void MockCompositor::sendMouseRelease(const QSharedPointer<MockSurface> &surface)
+{
+ Command command = makeCommand(Impl::Compositor::sendMouseRelease, m_compositor);
+ command.parameters << QVariant::fromValue(surface);
+ processCommand(command);
+}
+
+void MockCompositor::sendKeyPress(const QSharedPointer<MockSurface> &surface, uint code)
+{
+ Command command = makeCommand(Impl::Compositor::sendKeyPress, m_compositor);
+ command.parameters << QVariant::fromValue(surface) << code;
+ processCommand(command);
+}
+
+void MockCompositor::sendKeyRelease(const QSharedPointer<MockSurface> &surface, uint code)
+{
+ Command command = makeCommand(Impl::Compositor::sendKeyRelease, m_compositor);
+ command.parameters << QVariant::fromValue(surface) << code;
+ processCommand(command);
+}
+
+void MockCompositor::sendTouchDown(const QSharedPointer<MockSurface> &surface, const QPoint &position, int id)
+{
+ Command command = makeCommand(Impl::Compositor::sendTouchDown, m_compositor);
+ command.parameters << QVariant::fromValue(surface) << position << id;
+ processCommand(command);
+}
+
+void MockCompositor::sendTouchMotion(const QSharedPointer<MockSurface> &surface, const QPoint &position, int id)
+{
+ Command command = makeCommand(Impl::Compositor::sendTouchMotion, m_compositor);
+ command.parameters << QVariant::fromValue(surface) << position << id;
+ processCommand(command);
+}
+
+void MockCompositor::sendTouchUp(const QSharedPointer<MockSurface> &surface, int id)
+{
+ Command command = makeCommand(Impl::Compositor::sendTouchUp, m_compositor);
+ command.parameters << QVariant::fromValue(surface) << id;
+ processCommand(command);
+}
+
+void MockCompositor::sendTouchFrame(const QSharedPointer<MockSurface> &surface)
+{
+ Command command = makeCommand(Impl::Compositor::sendTouchFrame, m_compositor);
+ command.parameters << QVariant::fromValue(surface);
+ processCommand(command);
+}
+
+void MockCompositor::sendDataDeviceDataOffer(const QSharedPointer<MockSurface> &surface)
+{
+ Command command = makeCommand(Impl::Compositor::sendDataDeviceDataOffer, m_compositor);
+ command.parameters << QVariant::fromValue(surface);
+ processCommand(command);
+}
+
+void MockCompositor::sendDataDeviceEnter(const QSharedPointer<MockSurface> &surface, const QPoint& position)
+{
+ Command command = makeCommand(Impl::Compositor::sendDataDeviceEnter, m_compositor);
+ command.parameters << QVariant::fromValue(surface) << QVariant::fromValue(position);
+ processCommand(command);
+}
+
+void MockCompositor::sendDataDeviceMotion(const QPoint &position)
+{
+ Command command = makeCommand(Impl::Compositor::sendDataDeviceMotion, m_compositor);
+ command.parameters << QVariant::fromValue(position);
+ processCommand(command);
+}
+
+void MockCompositor::sendDataDeviceDrop(const QSharedPointer<MockSurface> &surface)
+{
+ Command command = makeCommand(Impl::Compositor::sendDataDeviceDrop, m_compositor);
+ command.parameters << QVariant::fromValue(surface);
+ processCommand(command);
+}
+
+void MockCompositor::sendDataDeviceLeave(const QSharedPointer<MockSurface> &surface)
+{
+ Command command = makeCommand(Impl::Compositor::sendDataDeviceLeave, m_compositor);
+ command.parameters << QVariant::fromValue(surface);
+ processCommand(command);
+}
+
+void MockCompositor::sendAddOutput()
+{
+ Command command = makeCommand(Impl::Compositor::sendAddOutput, m_compositor);
+ processCommand(command);
+}
+
+void MockCompositor::sendRemoveOutput(const QSharedPointer<MockOutput> &output)
+{
+ Command command = makeCommand(Impl::Compositor::sendRemoveOutput, m_compositor);
+ command.parameters << QVariant::fromValue(output);
+ processCommand(command);
+}
+
+void MockCompositor::sendOutputGeometry(const QSharedPointer<MockOutput> &output, const QRect &geometry)
+{
+ Command command = makeCommand(Impl::Compositor::sendOutputGeometry, m_compositor);
+ command.parameters << QVariant::fromValue(output);
+ command.parameters << QVariant::fromValue(geometry);
+ processCommand(command);
+}
+
+void MockCompositor::sendSurfaceEnter(const QSharedPointer<MockSurface> &surface, QSharedPointer<MockOutput> &output)
+{
+ Command command = makeCommand(Impl::Compositor::sendSurfaceEnter, m_compositor);
+ command.parameters << QVariant::fromValue(surface);
+ command.parameters << QVariant::fromValue(output);
+ processCommand(command);
+}
+
+void MockCompositor::sendSurfaceLeave(const QSharedPointer<MockSurface> &surface, QSharedPointer<MockOutput> &output)
+{
+ Command command = makeCommand(Impl::Compositor::sendSurfaceLeave, m_compositor);
+ command.parameters << QVariant::fromValue(surface);
+ command.parameters << QVariant::fromValue(output);
+ processCommand(command);
+}
+
+void MockCompositor::sendShellSurfaceConfigure(const QSharedPointer<MockSurface> surface, const QSize &size)
+{
+ Command command = makeCommand(Impl::Compositor::sendShellSurfaceConfigure, m_compositor);
+ command.parameters << QVariant::fromValue(surface);
+ command.parameters << QVariant::fromValue(size);
+ processCommand(command);
+}
+
+void MockCompositor::sendIviSurfaceConfigure(const QSharedPointer<MockIviSurface> iviSurface, const QSize &size)
+{
+ Command command = makeCommand(Impl::Compositor::sendIviSurfaceConfigure, m_compositor);
+ command.parameters << QVariant::fromValue(iviSurface);
+ command.parameters << QVariant::fromValue(size);
+ processCommand(command);
+}
+
+void MockCompositor::sendXdgToplevelV6Configure(const QSharedPointer<MockXdgToplevelV6> toplevel, const QSize &size, const QVector<uint> &states)
+{
+ Command command = makeCommand(Impl::Compositor::sendXdgToplevelV6Configure, m_compositor);
+ command.parameters << QVariant::fromValue(toplevel);
+ command.parameters << QVariant::fromValue(size);
+ QByteArray statesBytes(reinterpret_cast<const char *>(states.data()),
+ states.size() * static_cast<int>(sizeof(uint)));
+ command.parameters << statesBytes;
+ processCommand(command);
+}
+
+void MockCompositor::waitForStartDrag()
+{
+ Command command = makeCommand(Impl::Compositor::waitForStartDrag, m_compositor);
+ processCommand(command);
+}
+
+QSharedPointer<MockSurface> MockCompositor::surface()
+{
+ QSharedPointer<MockSurface> result;
+ lock();
+ QVector<Impl::Surface *> surfaces = m_compositor->surfaces();
+ foreach (Impl::Surface *surface, surfaces) {
+ // we don't want to mistake the cursor surface for a window surface
+ if (surface->isMapped()) {
+ result = surface->mockSurface();
+ break;
+ }
+ }
+ unlock();
+ return result;
+}
+
+QSharedPointer<MockOutput> MockCompositor::output(int index)
+{
+ QSharedPointer<MockOutput> result;
+ lock();
+ if (Impl::Output *output = m_compositor->outputs().value(index, nullptr))
+ result = output->mockOutput();
+ unlock();
+ return result;
+}
+
+QSharedPointer<MockIviSurface> MockCompositor::iviSurface(int index)
+{
+ QSharedPointer<MockIviSurface> result;
+ lock();
+ if (Impl::IviSurface *toplevel = m_compositor->iviApplication()->iviSurfaces().value(index, nullptr))
+ result = toplevel->mockIviSurface();
+ unlock();
+ return result;
+}
+
+QSharedPointer<MockXdgToplevelV6> MockCompositor::xdgToplevelV6(int index)
+{
+ QSharedPointer<MockXdgToplevelV6> result;
+ lock();
+ if (Impl::XdgToplevelV6 *toplevel = m_compositor->xdgShellV6()->toplevels().value(index, nullptr))
+ result = toplevel->mockToplevel();
+ unlock();
+ return result;
+}
+
+QSharedPointer<MockSurface> MockCompositor::fullScreenShellV1Surface(int index)
+{
+ QSharedPointer<MockSurface> result;
+ lock();
+ if (Impl::Surface *surface = m_compositor->fullScreenShellV1()->surfaces().value(index, nullptr))
+ result = surface->mockSurface();
+ unlock();
+ return result;
+}
+
+MockCompositor::Command MockCompositor::makeCommand(Command::Callback callback, void *target)
+{
+ Command command;
+ command.callback = callback;
+ command.target = target;
+ return command;
+}
+
+void MockCompositor::processCommand(const Command &command)
+{
+ lock();
+ m_commandQueue << command;
+ unlock();
+
+ m_waitCondition.wakeOne();
+}
+
+void MockCompositor::dispatchCommands()
+{
+ lock();
+ int count = m_commandQueue.length();
+ unlock();
+
+ for (int i = 0; i < count; ++i) {
+ lock();
+ const Command command = m_commandQueue.takeFirst();
+ unlock();
+ command.callback(command.target, command.parameters);
+ }
+}
+
+void *MockCompositor::run(void *data)
+{
+ MockCompositor *controller = static_cast<MockCompositor *>(data);
+
+ Impl::Compositor compositor;
+
+ controller->m_compositor = &compositor;
+ controller->m_waitCondition.wakeOne();
+
+ while (!controller->m_ready) {
+ controller->dispatchCommands();
+ compositor.dispatchEvents(20);
+ }
+
+ while (controller->m_alive) {
+ {
+ QMutexLocker locker(&controller->m_mutex);
+ if (controller->m_commandQueue.isEmpty())
+ controller->m_waitCondition.wait(&controller->m_mutex);
+ }
+ controller->dispatchCommands();
+ compositor.dispatchEvents(20);
+ }
+
+ return 0;
+}
+
+namespace Impl {
+
+Compositor::Compositor()
+ : m_display(wl_display_create())
+{
+ if (wl_display_add_socket(m_display, 0)) {
+ fprintf(stderr, "Fatal: Failed to open server socket\n");
+ exit(EXIT_FAILURE);
+ }
+
+ wl_global_create(m_display, &wl_compositor_interface, 1, this, bindCompositor);
+
+ m_data_device_manager.reset(new DataDeviceManager(this, m_display));
+
+ wl_display_init_shm(m_display);
+
+ m_seat.reset(new Seat(this, m_display));
+ m_pointer = m_seat->pointer();
+ m_keyboard = m_seat->keyboard();
+ m_touch = m_seat->touch();
+
+ m_outputs.append(new Output(m_display, QSize(1920, 1080), QPoint(0, 0)));
+ m_iviApplication.reset(new IviApplication(m_display));
+ m_wlShell.reset(new WlShell(m_display));
+ m_xdgShellV6.reset(new XdgShellV6(m_display));
+ m_fullScreenShellV1.reset(new FullScreenShellV1(m_display));
+
+ m_loop = wl_display_get_event_loop(m_display);
+ m_fd = wl_event_loop_get_fd(m_loop);
+}
+
+Compositor::~Compositor()
+{
+ wl_display_destroy(m_display);
+}
+
+void Compositor::dispatchEvents(int timeout)
+{
+ wl_display_flush_clients(m_display);
+ wl_event_loop_dispatch(m_loop, timeout);
+}
+
+static void compositor_create_surface(wl_client *client, wl_resource *compositorResource, uint32_t id)
+{
+ Compositor *compositor = static_cast<Compositor *>(wl_resource_get_user_data(compositorResource));
+ compositor->addSurface(new Surface(client, id, wl_resource_get_version(compositorResource), compositor));
+}
+
+static void compositor_create_region(wl_client *client, wl_resource *compositorResource, uint32_t id)
+{
+ Q_UNUSED(client);
+ Q_UNUSED(compositorResource);
+ Q_UNUSED(id);
+}
+
+void Compositor::bindCompositor(wl_client *client, void *compositorData, uint32_t version, uint32_t id)
+{
+ static const struct wl_compositor_interface compositorInterface = {
+ compositor_create_surface,
+ compositor_create_region
+ };
+
+ wl_resource *resource = wl_resource_create(client, &wl_compositor_interface, static_cast<int>(version), id);
+ wl_resource_set_implementation(resource, &compositorInterface, compositorData, nullptr);
+}
+
+static void unregisterResourceCallback(wl_listener *listener, void *data)
+{
+ struct wl_resource *resource = reinterpret_cast<struct wl_resource *>(data);
+ wl_list_remove(wl_resource_get_link(resource));
+ delete listener;
+}
+
+void registerResource(wl_list *list, wl_resource *resource)
+{
+ wl_list_insert(list, wl_resource_get_link(resource));
+
+ wl_listener *listener = new wl_listener;
+ listener->notify = unregisterResourceCallback;
+
+ wl_resource_add_destroy_listener(resource, listener);
+}
+
+QVector<Surface *> Compositor::surfaces() const
+{
+ return m_surfaces;
+}
+
+QVector<Output *> Compositor::outputs() const
+{
+ return m_outputs;
+}
+
+IviApplication *Compositor::iviApplication() const
+{
+ return m_iviApplication.data();
+}
+
+XdgShellV6 *Compositor::xdgShellV6() const
+{
+ return m_xdgShellV6.data();
+}
+
+FullScreenShellV1 *Compositor::fullScreenShellV1() const
+{
+ return m_fullScreenShellV1.data();
+}
+
+uint32_t Compositor::nextSerial()
+{
+ return wl_display_next_serial(m_display);
+}
+
+void Compositor::addSurface(Surface *surface)
+{
+ m_surfaces << surface;
+}
+
+void Compositor::removeSurface(Surface *surface)
+{
+ m_surfaces.removeOne(surface);
+ m_keyboard->handleSurfaceDestroyed(surface);
+ m_pointer->handleSurfaceDestroyed(surface);
+ m_fullScreenShellV1->removeSurface(surface);
+}
+
+Surface *Compositor::resolveSurface(const QVariant &v)
+{
+ QSharedPointer<MockSurface> mockSurface = v.value<QSharedPointer<MockSurface> >();
+ return mockSurface ? mockSurface->handle() : nullptr;
+}
+
+Output *Compositor::resolveOutput(const QVariant &v)
+{
+ QSharedPointer<MockOutput> mockOutput = v.value<QSharedPointer<MockOutput> >();
+ return mockOutput ? mockOutput->handle() : nullptr;
+}
+
+IviSurface *Compositor::resolveIviSurface(const QVariant &v)
+{
+ QSharedPointer<MockIviSurface> mockIviSurface = v.value<QSharedPointer<MockIviSurface>>();
+ return mockIviSurface ? mockIviSurface->handle() : nullptr;
+}
+
+XdgToplevelV6 *Compositor::resolveToplevel(const QVariant &v)
+{
+ QSharedPointer<MockXdgToplevelV6> mockToplevel = v.value<QSharedPointer<MockXdgToplevelV6>>();
+ return mockToplevel ? mockToplevel->handle() : nullptr;
+}
+
+}