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(&registry, &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