1 /* This file is part of the KDE libraries
2
3 SPDX-FileCopyrightText: 2012, 2019 David Faure <faure@kde.org>
4 SPDX-FileCopyrightText: 2012 Kai Dombrowe <just89@gmx.de>
5
6 SPDX-License-Identifier: LGPL-2.1-or-later
7 */
8
9 #include "netwm.h"
10 #include <QSignalSpy>
11 #include <QWidget>
12 #include <QX11Info>
13 #include <kstartupinfo.h>
14 #include <qtest_widgets.h>
15
16 #include <xcb/xcb.h>
17
18 Q_DECLARE_METATYPE(KStartupInfoId)
19 Q_DECLARE_METATYPE(KStartupInfoData)
20
21 class KStartupInfo_UnitTest : public QObject
22 {
23 Q_OBJECT
24 public:
KStartupInfo_UnitTest()25 KStartupInfo_UnitTest()
26 : m_listener(KStartupInfo::CleanOnCantDetect, this)
27 , m_receivedCount(0)
28 {
29 qRegisterMetaType<KStartupInfoId>();
30 qRegisterMetaType<KStartupInfoData>();
31 connect(&m_listener, SIGNAL(gotNewStartup(KStartupInfoId, KStartupInfoData)), this, SLOT(slotNewStartup(KStartupInfoId, KStartupInfoData)));
32 }
33
34 protected Q_SLOTS:
slotNewStartup(const KStartupInfoId & id,const KStartupInfoData & data)35 void slotNewStartup(const KStartupInfoId &id, const KStartupInfoData &data)
36 {
37 ++m_receivedCount;
38 m_receivedId = id;
39 m_receivedData = data;
40 Q_EMIT ready();
41 }
42 Q_SIGNALS:
43 void ready();
44
45 private Q_SLOTS:
46 void testStart();
47 void dontCrashCleanup_data();
48 void dontCrashCleanup();
49 void checkCleanOnCantDetectTest();
50 void checkStartupTest_data();
51 void checkStartupTest();
52 void createNewStartupIdTest();
53 void createNewStartupIdForTimestampTest();
54 void setNewStartupIdTest();
55
56 private:
57 KStartupInfo m_listener;
58
59 int m_receivedCount;
60 KStartupInfoId m_receivedId;
61 KStartupInfoData m_receivedData;
62 };
63
testStart()64 void KStartupInfo_UnitTest::testStart()
65 {
66 KStartupInfoId id;
67 id.initId(KStartupInfo::createNewStartupId());
68
69 KStartupInfoData data;
70 const QString appId = "/dir with space/kstartupinfo_unittest.desktop";
71 data.setApplicationId(appId);
72 const QString iconPath = "/dir with space/kstartupinfo_unittest.png";
73 data.setIcon(iconPath);
74 const QString description = "A description";
75 data.setDescription(description);
76 const QString name = "A name";
77 data.setName(name);
78 const int pid = 12345;
79 data.addPid(pid);
80 const QString bin = "dir with space/kstartupinfo_unittest";
81 data.setBin(bin);
82
83 QSignalSpy removedSpy(&m_listener, SIGNAL(gotRemoveStartup(KStartupInfoId, KStartupInfoData)));
84 QVERIFY(removedSpy.isValid());
85
86 KStartupInfo::sendStartup(id, data);
87 KStartupInfo::sendFinish(id, data);
88
89 QSignalSpy spy(this, SIGNAL(ready()));
90 spy.wait(5000);
91
92 QCOMPARE(m_receivedCount, 1);
93 // qDebug() << m_receivedId.id(); // something like "$HOSTNAME;1342544979;490718;8602_TIME0"
94 QCOMPARE(m_receivedData.name(), name);
95 QCOMPARE(m_receivedData.description(), description);
96 QCOMPARE(m_receivedData.applicationId(), appId);
97 QCOMPARE(m_receivedData.icon(), iconPath);
98 QCOMPARE(m_receivedData.bin(), bin);
99 // qDebug() << m_receivedData.bin() << m_receivedData.name() << m_receivedData.description() << m_receivedData.icon() << m_receivedData.pids() <<
100 // m_receivedData.hostname() << m_receivedData.applicationId();
101
102 int waitTime = 0;
103 while (waitTime < 5000 && removedSpy.count() < 1) {
104 QTest::qWait(200);
105 waitTime += 200;
106 }
107 QCOMPARE(removedSpy.count(), 1);
108 }
109
doSync()110 static void doSync()
111 {
112 auto *c = QX11Info::connection();
113 const auto cookie = xcb_get_input_focus(c);
114 xcb_generic_error_t *error = nullptr;
115 QScopedPointer<xcb_get_input_focus_reply_t, QScopedPointerPodDeleter> sync(xcb_get_input_focus_reply(c, cookie, &error));
116 if (error) {
117 free(error);
118 }
119 }
120
dontCrashCleanup_data()121 void KStartupInfo_UnitTest::dontCrashCleanup_data()
122 {
123 QTest::addColumn<bool>("silent");
124 QTest::addColumn<bool>("change");
125 QTest::addColumn<int>("countRemoveStartup");
126
127 QTest::newRow("normal") << false << false << 2;
128 QTest::newRow("silent") << true << false << 0;
129 QTest::newRow("uninited") << false << true << 0;
130 }
131
dontCrashCleanup()132 void KStartupInfo_UnitTest::dontCrashCleanup()
133 {
134 qputenv("KSTARTUPINFO_TIMEOUT", QByteArrayLiteral("1"));
135
136 KStartupInfoId id;
137 KStartupInfoId id2;
138 id.initId(QByteArrayLiteral("somefancyidwhichisrandom_kstartupinfo_unittest_0"));
139 id2.initId(QByteArrayLiteral("somefancyidwhichisrandom_kstartupinfo_unittest_1"));
140
141 KStartupInfoData data;
142 data.setApplicationId(QStringLiteral("/dir with space/kstartupinfo_unittest.desktop"));
143 data.setIcon(QStringLiteral("/dir with space/kstartupinfo_unittest.png"));
144 data.setDescription(QStringLiteral("A description"));
145 data.setName(QStringLiteral("A name"));
146 data.addPid(12345);
147 data.setBin(QStringLiteral("dir with space/kstartupinfo_unittest"));
148 QFETCH(bool, silent);
149 if (silent) {
150 data.setSilent(KStartupInfoData::Yes);
151 }
152
153 QSignalSpy spy(&m_listener, SIGNAL(gotRemoveStartup(KStartupInfoId, KStartupInfoData)));
154 QFETCH(bool, change);
155 if (change) {
156 KStartupInfo::sendChange(id, data);
157 KStartupInfo::sendChange(id2, data);
158 } else {
159 KStartupInfo::sendStartup(id, data);
160 KStartupInfo::sendStartup(id2, data);
161 }
162
163 // let's do a roundtrip to the X server
164 doSync();
165
166 QFETCH(int, countRemoveStartup);
167 int waitTime = 1900;
168 QTest::qWait(1900);
169 while (waitTime <= 5000) {
170 QTest::qWait(200);
171 waitTime += 200;
172 if (spy.count() == countRemoveStartup) {
173 break;
174 }
175 }
176 QCOMPARE(spy.count(), countRemoveStartup);
177 }
178
checkCleanOnCantDetectTest()179 void KStartupInfo_UnitTest::checkCleanOnCantDetectTest()
180 {
181 KStartupInfoId id;
182 KStartupInfoId id2;
183 id.initId(QByteArrayLiteral("somefancyidwhichisrandom_kstartupinfo_unittest_0"));
184 id2.initId(QByteArrayLiteral("somefancyidwhichisrandom_kstartupinfo_unittest_1"));
185
186 KStartupInfoData data;
187 data.setApplicationId(QStringLiteral("/dir with space/kstartupinfo_unittest.desktop"));
188 data.setIcon(QStringLiteral("/dir with space/kstartupinfo_unittest.png"));
189 data.setDescription(QStringLiteral("A description"));
190 data.setName(QStringLiteral("A name"));
191 data.setBin(QStringLiteral("dir with space/kstartupinfo_unittest"));
192 data.setWMClass(QByteArrayLiteral("0"));
193
194 xcb_connection_t *c = QX11Info::connection();
195 xcb_window_t window = xcb_generate_id(c);
196 uint32_t values[] = {XCB_EVENT_MASK_PROPERTY_CHANGE};
197 xcb_create_window(c,
198 XCB_COPY_FROM_PARENT,
199 window,
200 QX11Info::appRootWindow(),
201 0,
202 0,
203 100,
204 100,
205 0,
206 XCB_COPY_FROM_PARENT,
207 XCB_COPY_FROM_PARENT,
208 XCB_CW_EVENT_MASK,
209 values);
210
211 KStartupInfo::sendStartup(id, data);
212 KStartupInfo::sendStartup(id2, data);
213
214 int previousCount = m_receivedCount;
215
216 doSync();
217 QTest::qWait(10);
218
219 xcb_map_window(c, window);
220 xcb_flush(c);
221 QTest::qWait(10);
222
223 xcb_unmap_window(c, window);
224 xcb_flush(c);
225 QTest::qWait(100);
226 xcb_map_window(c, window);
227 xcb_flush(c);
228
229 QCOMPARE(m_receivedCount, previousCount + 2);
230 QCOMPARE(m_receivedId, id2);
231 }
232
checkStartupTest_data()233 void KStartupInfo_UnitTest::checkStartupTest_data()
234 {
235 QTest::addColumn<QByteArray>("wmClass");
236 QTest::addColumn<int>("pid");
237
238 QTest::newRow("wmClass") << QByteArrayLiteral("kstartupinfotest") << 0;
239 QTest::newRow("pid") << QByteArray() << 12345;
240 }
241
checkStartupTest()242 void KStartupInfo_UnitTest::checkStartupTest()
243 {
244 KStartupInfoId id;
245 KStartupInfoId id2;
246 id.initId(QByteArrayLiteral("somefancyidwhichisrandom_kstartupinfo_unittest_0"));
247 id2.initId(QByteArrayLiteral("somefancyidwhichisrandom_kstartupinfo_unittest_1"));
248
249 KStartupInfoData data;
250 data.setApplicationId(QStringLiteral("/dir with space/kstartupinfo_unittest.desktop"));
251 data.setIcon(QStringLiteral("/dir with space/kstartupinfo_unittest.png"));
252 data.setDescription(QStringLiteral("A description"));
253 data.setName(QStringLiteral("A name"));
254 data.setBin(QStringLiteral("dir with space/kstartupinfo_unittest"));
255 QFETCH(int, pid);
256 data.addPid(pid);
257 data.setHostname(QByteArrayLiteral("localhost"));
258
259 // important for this test: WMClass
260 QFETCH(QByteArray, wmClass);
261 data.setWMClass(wmClass);
262
263 xcb_connection_t *c = QX11Info::connection();
264 xcb_window_t window = xcb_generate_id(c);
265 uint32_t values[] = {XCB_EVENT_MASK_PROPERTY_CHANGE};
266 xcb_create_window(c,
267 XCB_COPY_FROM_PARENT,
268 window,
269 QX11Info::appRootWindow(),
270 0,
271 0,
272 100,
273 100,
274 0,
275 XCB_COPY_FROM_PARENT,
276 XCB_COPY_FROM_PARENT,
277 XCB_CW_EVENT_MASK,
278 values);
279
280 xcb_change_property(c,
281 XCB_PROP_MODE_REPLACE,
282 window,
283 XCB_ATOM_WM_CLASS,
284 XCB_ATOM_STRING,
285 8,
286 wmClass.length() * 2 + 1,
287 "kstartupinfotest\0kstartupinfotest");
288 xcb_change_property(c, XCB_PROP_MODE_REPLACE, window, XCB_ATOM_WM_CLIENT_MACHINE, XCB_ATOM_STRING, 8, 9, "localhost");
289 NETWinInfo winInfo(QX11Info::connection(), window, QX11Info::appRootWindow(), NET::Properties(), NET::Properties2());
290 winInfo.setPid(pid);
291
292 KStartupInfo info(KStartupInfo::DisableKWinModule | KStartupInfo::AnnounceSilenceChanges, this);
293 KStartupInfo::sendStartup(id, data);
294 KStartupInfo::sendStartup(id2, data);
295
296 doSync();
297 QTest::qWait(100);
298
299 QCOMPARE(info.checkStartup(window), KStartupInfo::Match);
300 QCOMPARE(info.checkStartup(window), KStartupInfo::Match);
301 }
302
createNewStartupIdTest()303 void KStartupInfo_UnitTest::createNewStartupIdTest()
304 {
305 const QByteArray &id = KStartupInfo::createNewStartupId();
306 QVERIFY(!id.isEmpty());
307 const int index = id.indexOf(QByteArrayLiteral("TIME"));
308 QVERIFY(index != -1);
309 const QByteArray time = id.mid(index + 4);
310 QVERIFY(time.toULongLong() != 0u);
311 }
312
createNewStartupIdForTimestampTest()313 void KStartupInfo_UnitTest::createNewStartupIdForTimestampTest()
314 {
315 const QByteArray &id = KStartupInfo::createNewStartupIdForTimestamp(5);
316 QVERIFY(!id.isEmpty());
317 const int index = id.indexOf(QByteArrayLiteral("TIME"));
318 QVERIFY(index != -1);
319 QCOMPARE(id.mid(index + 4).toULongLong(), 5u);
320 }
321
setNewStartupIdTest()322 void KStartupInfo_UnitTest::setNewStartupIdTest()
323 {
324 {
325 QWindow window;
326 const QByteArray str = "somefancyidwhichisrandom_kstartupinfo_unittest_2";
327 KStartupInfo::setNewStartupId(&window, str);
328 QCOMPARE(KStartupInfo::startupId(), str);
329 }
330
331 #if KWINDOWSYSTEM_ENABLE_DEPRECATED_SINCE(5, 62)
332 {
333 QWidget widget;
334 const QByteArray str = "somefancyidwhichisrandom_kstartupinfo_unittest_3";
335 KStartupInfo::setNewStartupId(&widget, str); // deprecated
336 QCOMPARE(KStartupInfo::startupId(), str);
337 }
338 #endif
339 }
340
341 QTEST_MAIN(KStartupInfo_UnitTest)
342
343 #include "kstartupinfo_unittest.moc"
344