1 /*
2 SPDX-FileCopyrightText: 2016 Martin Gräßlin <mgraesslin@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
5 */
6 // Qt
7 #include <QSignalSpy>
8 #include <QTest>
9 // client
10 #include "../../src/client/compositor.h"
11 #include "../../src/client/connection_thread.h"
12 #include "../../src/client/event_queue.h"
13 #include "../../src/client/plasmashell.h"
14 #include "../../src/client/registry.h"
15 #include "../../src/client/shell.h"
16 #include "../../src/client/surface.h"
17 // server
18 #include "../../src/server/compositor_interface.h"
19 #include "../../src/server/display.h"
20 #include "../../src/server/plasmashell_interface.h"
21 #include "../../src/server/shell_interface.h"
22
23 #include <wayland-client-protocol.h>
24
25 #include <cerrno> // For EPROTO
26
27 using namespace KWayland::Client;
28 using namespace KWayland::Server;
29
30 class ErrorTest : public QObject
31 {
32 Q_OBJECT
33 private Q_SLOTS:
34 void init();
35 void cleanup();
36
37 void testMultipleShellSurfacesForSurface();
38 void testMultiplePlasmaShellSurfacesForSurface();
39 void testTransientForSameSurface_data();
40 void testTransientForSameSurface();
41
42 private:
43 Display *m_display = nullptr;
44 CompositorInterface *m_ci = nullptr;
45 ShellInterface *m_si = nullptr;
46 PlasmaShellInterface *m_psi = nullptr;
47 ConnectionThread *m_connection = nullptr;
48 QThread *m_thread = nullptr;
49 EventQueue *m_queue = nullptr;
50 Compositor *m_compositor = nullptr;
51 Shell *m_shell = nullptr;
52 PlasmaShell *m_plasmaShell = nullptr;
53 };
54
55 static const QString s_socketName = QStringLiteral("kwayland-test-error-0");
56
init()57 void ErrorTest::init()
58 {
59 delete m_display;
60 m_display = new Display(this);
61 m_display->setSocketName(s_socketName);
62 m_display->start();
63 QVERIFY(m_display->isRunning());
64 m_display->createShm();
65 m_ci = m_display->createCompositor(m_display);
66 m_ci->create();
67 m_si = m_display->createShell(m_display);
68 m_si->create();
69 m_psi = m_display->createPlasmaShell(m_display);
70 m_psi->create();
71
72 // setup connection
73 m_connection = new KWayland::Client::ConnectionThread;
74 QSignalSpy connectedSpy(m_connection, &ConnectionThread::connected);
75 QVERIFY(connectedSpy.isValid());
76 m_connection->setSocketName(s_socketName);
77
78 m_thread = new QThread(this);
79 m_connection->moveToThread(m_thread);
80 m_thread->start();
81
82 m_connection->initConnection();
83 QVERIFY(connectedSpy.wait());
84
85 m_queue = new EventQueue(this);
86 m_queue->setup(m_connection);
87
88 Registry registry;
89 QSignalSpy interfacesAnnouncedSpy(®istry, &Registry::interfacesAnnounced);
90 QVERIFY(interfacesAnnouncedSpy.isValid());
91 registry.setEventQueue(m_queue);
92 registry.create(m_connection);
93 QVERIFY(registry.isValid());
94 registry.setup();
95 QVERIFY(interfacesAnnouncedSpy.wait());
96
97 auto compositorInterface = registry.interface(Registry::Interface::Compositor);
98 m_compositor = registry.createCompositor(compositorInterface.name, compositorInterface.version, this);
99 QVERIFY(m_compositor);
100 auto shellInterface = registry.interface(Registry::Interface::Shell);
101 m_shell = registry.createShell(shellInterface.name, shellInterface.version, this);
102 QVERIFY(m_shell);
103 m_plasmaShell = registry.createPlasmaShell(registry.interface(Registry::Interface::PlasmaShell).name,
104 registry.interface(Registry::Interface::PlasmaShell).version,
105 this);
106 QVERIFY(m_plasmaShell);
107 }
108
cleanup()109 void ErrorTest::cleanup()
110 {
111 #define CLEANUP(variable) \
112 if (variable) { \
113 delete variable; \
114 variable = nullptr; \
115 }
116 CLEANUP(m_plasmaShell)
117 CLEANUP(m_shell)
118 CLEANUP(m_compositor)
119 CLEANUP(m_queue)
120 if (m_connection) {
121 m_connection->deleteLater();
122 m_connection = nullptr;
123 }
124 if (m_thread) {
125 m_thread->quit();
126 m_thread->wait();
127 delete m_thread;
128 m_thread = nullptr;
129 }
130 CLEANUP(m_psi)
131 CLEANUP(m_si)
132 CLEANUP(m_ci)
133 CLEANUP(m_display)
134 #undef CLEANUP
135 }
136
testMultipleShellSurfacesForSurface()137 void ErrorTest::testMultipleShellSurfacesForSurface()
138 {
139 // this test verifies that creating two ShellSurfaces for the same Surface triggers a protocol error
140 QSignalSpy errorSpy(m_connection, &ConnectionThread::errorOccurred);
141 QVERIFY(errorSpy.isValid());
142 QScopedPointer<Surface> surface(m_compositor->createSurface());
143 QScopedPointer<ShellSurface> shellSurface1(m_shell->createSurface(surface.data()));
144 QScopedPointer<ShellSurface> shellSurface2(m_shell->createSurface(surface.data()));
145 QVERIFY(errorSpy.wait());
146 QVERIFY(m_connection->hasError());
147 QCOMPARE(m_connection->errorCode(), EPROTO);
148 }
149
testMultiplePlasmaShellSurfacesForSurface()150 void ErrorTest::testMultiplePlasmaShellSurfacesForSurface()
151 {
152 // this test verifies that creating two ShellSurfaces for the same Surface triggers a protocol error
153 QSignalSpy errorSpy(m_connection, &ConnectionThread::errorOccurred);
154 QVERIFY(errorSpy.isValid());
155 // PlasmaShell is too smart and doesn't allow us to create a second PlasmaShellSurface
156 // thus we need to cheat by creating a surface manually
157 auto surface = wl_compositor_create_surface(*m_compositor);
158 QScopedPointer<PlasmaShellSurface> shellSurface1(m_plasmaShell->createSurface(surface));
159 QScopedPointer<PlasmaShellSurface> shellSurface2(m_plasmaShell->createSurface(surface));
160 QVERIFY(!m_connection->hasError());
161 QVERIFY(errorSpy.wait());
162 QVERIFY(m_connection->hasError());
163 QCOMPARE(m_connection->errorCode(), EPROTO);
164 wl_surface_destroy(surface);
165 }
166
testTransientForSameSurface_data()167 void ErrorTest::testTransientForSameSurface_data()
168 {
169 QTest::addColumn<ShellSurface::TransientFlag>("flag");
170
171 QTest::newRow("transient") << ShellSurface::TransientFlag::Default;
172 QTest::newRow("transient no focus") << ShellSurface::TransientFlag::NoFocus;
173 }
174
testTransientForSameSurface()175 void ErrorTest::testTransientForSameSurface()
176 {
177 // this test verifies that creating a transient shell surface for itself triggers a protocol error
178 QSignalSpy errorSpy(m_connection, &ConnectionThread::errorOccurred);
179 QVERIFY(errorSpy.isValid());
180 QScopedPointer<Surface> surface(m_compositor->createSurface());
181 QScopedPointer<ShellSurface> shellSurface(m_shell->createSurface(surface.data()));
182 QFETCH(ShellSurface::TransientFlag, flag);
183 shellSurface->setTransient(surface.data(), QPoint(), flag);
184 QVERIFY(errorSpy.wait());
185 QVERIFY(m_connection->hasError());
186 QCOMPARE(m_connection->errorCode(), EPROTO);
187 }
188
189 QTEST_GUILESS_MAIN(ErrorTest)
190 #include "test_error.moc"
191