File indexing completed on 2024-04-14 14:29:39

0001 /*  This file is part of the KDE libraries
0002 
0003     SPDX-FileCopyrightText: 2012, 2019 David Faure <faure@kde.org>
0004     SPDX-FileCopyrightText: 2012 Kai Dombrowe <just89@gmx.de>
0005 
0006     SPDX-License-Identifier: LGPL-2.1-or-later
0007 */
0008 
0009 #include "netwm.h"
0010 #include <QSignalSpy>
0011 #include <QWidget>
0012 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
0013 #include <private/qtx11extras_p.h>
0014 #else
0015 #include <QX11Info>
0016 #endif
0017 
0018 #include <kstartupinfo.h>
0019 #include <qtest_widgets.h>
0020 
0021 #include <xcb/xcb.h>
0022 
0023 #include "cptr_p.h"
0024 
0025 Q_DECLARE_METATYPE(KStartupInfoId)
0026 Q_DECLARE_METATYPE(KStartupInfoData)
0027 
0028 class KStartupInfo_UnitTest : public QObject
0029 {
0030     Q_OBJECT
0031 public:
0032     KStartupInfo_UnitTest()
0033         : m_listener(KStartupInfo::CleanOnCantDetect, this)
0034         , m_receivedCount(0)
0035     {
0036         qRegisterMetaType<KStartupInfoId>();
0037         qRegisterMetaType<KStartupInfoData>();
0038         connect(&m_listener, &KStartupInfo::gotNewStartup, this, &KStartupInfo_UnitTest::slotNewStartup);
0039     }
0040 
0041 protected Q_SLOTS:
0042     void slotNewStartup(const KStartupInfoId &id, const KStartupInfoData &data)
0043     {
0044         ++m_receivedCount;
0045         m_receivedId = id;
0046         m_receivedData = data;
0047         Q_EMIT ready();
0048     }
0049 Q_SIGNALS:
0050     void ready();
0051 
0052 private Q_SLOTS:
0053     void testStart();
0054     void dontCrashCleanup_data();
0055     void dontCrashCleanup();
0056     void checkCleanOnCantDetectTest();
0057     void checkStartupTest_data();
0058     void checkStartupTest();
0059     void createNewStartupIdTest();
0060     void createNewStartupIdForTimestampTest();
0061     void setNewStartupIdTest();
0062 
0063 private:
0064     KStartupInfo m_listener;
0065 
0066     int m_receivedCount;
0067     KStartupInfoId m_receivedId;
0068     KStartupInfoData m_receivedData;
0069 };
0070 
0071 void KStartupInfo_UnitTest::testStart()
0072 {
0073     KStartupInfoId id;
0074     id.initId(KStartupInfo::createNewStartupId());
0075 
0076     KStartupInfoData data;
0077     const QString appId = "/dir with space/kstartupinfo_unittest.desktop";
0078     data.setApplicationId(appId);
0079     const QString iconPath = "/dir with space/kstartupinfo_unittest.png";
0080     data.setIcon(iconPath);
0081     const QString description = "A description";
0082     data.setDescription(description);
0083     const QString name = "A name";
0084     data.setName(name);
0085     const int pid = 12345;
0086     data.addPid(pid);
0087     const QString bin = "dir with space/kstartupinfo_unittest";
0088     data.setBin(bin);
0089 
0090     QSignalSpy removedSpy(&m_listener, &KStartupInfo::gotRemoveStartup);
0091     QVERIFY(removedSpy.isValid());
0092 
0093     KStartupInfo::sendStartup(id, data);
0094     KStartupInfo::sendFinish(id, data);
0095 
0096     QSignalSpy spy(this, &KStartupInfo_UnitTest::ready);
0097     spy.wait(5000);
0098 
0099     QCOMPARE(m_receivedCount, 1);
0100     // qDebug() << m_receivedId.id(); // something like "$HOSTNAME;1342544979;490718;8602_TIME0"
0101     QCOMPARE(m_receivedData.name(), name);
0102     QCOMPARE(m_receivedData.description(), description);
0103     QCOMPARE(m_receivedData.applicationId(), appId);
0104     QCOMPARE(m_receivedData.icon(), iconPath);
0105     QCOMPARE(m_receivedData.bin(), bin);
0106     // qDebug() << m_receivedData.bin() << m_receivedData.name() << m_receivedData.description() << m_receivedData.icon() << m_receivedData.pids() <<
0107     // m_receivedData.hostname() << m_receivedData.applicationId();
0108 
0109     int waitTime = 0;
0110     while (waitTime < 5000 && removedSpy.count() < 1) {
0111         QTest::qWait(200);
0112         waitTime += 200;
0113     }
0114     QCOMPARE(removedSpy.count(), 1);
0115 }
0116 
0117 static void doSync()
0118 {
0119     auto *c = QX11Info::connection();
0120     const auto cookie = xcb_get_input_focus(c);
0121     xcb_generic_error_t *error = nullptr;
0122     UniqueCPointer<xcb_get_input_focus_reply_t> sync(xcb_get_input_focus_reply(c, cookie, &error));
0123     if (error) {
0124         free(error);
0125     }
0126 }
0127 
0128 void KStartupInfo_UnitTest::dontCrashCleanup_data()
0129 {
0130     QTest::addColumn<bool>("silent");
0131     QTest::addColumn<bool>("change");
0132     QTest::addColumn<int>("countRemoveStartup");
0133 
0134     QTest::newRow("normal") << false << false << 2;
0135     QTest::newRow("silent") << true << false << 0;
0136     QTest::newRow("uninited") << false << true << 0;
0137 }
0138 
0139 void KStartupInfo_UnitTest::dontCrashCleanup()
0140 {
0141     qputenv("KSTARTUPINFO_TIMEOUT", QByteArrayLiteral("1"));
0142 
0143     KStartupInfoId id;
0144     KStartupInfoId id2;
0145     id.initId(QByteArrayLiteral("somefancyidwhichisrandom_kstartupinfo_unittest_0"));
0146     id2.initId(QByteArrayLiteral("somefancyidwhichisrandom_kstartupinfo_unittest_1"));
0147 
0148     KStartupInfoData data;
0149     data.setApplicationId(QStringLiteral("/dir with space/kstartupinfo_unittest.desktop"));
0150     data.setIcon(QStringLiteral("/dir with space/kstartupinfo_unittest.png"));
0151     data.setDescription(QStringLiteral("A description"));
0152     data.setName(QStringLiteral("A name"));
0153     data.addPid(12345);
0154     data.setBin(QStringLiteral("dir with space/kstartupinfo_unittest"));
0155     QFETCH(bool, silent);
0156     if (silent) {
0157         data.setSilent(KStartupInfoData::Yes);
0158     }
0159 
0160     QSignalSpy spy(&m_listener, &KStartupInfo::gotRemoveStartup);
0161     QFETCH(bool, change);
0162     if (change) {
0163         KStartupInfo::sendChange(id, data);
0164         KStartupInfo::sendChange(id2, data);
0165     } else {
0166         KStartupInfo::sendStartup(id, data);
0167         KStartupInfo::sendStartup(id2, data);
0168     }
0169 
0170     // let's do a roundtrip to the X server
0171     doSync();
0172 
0173     QFETCH(int, countRemoveStartup);
0174     int waitTime = 1900;
0175     QTest::qWait(1900);
0176     while (waitTime <= 5000) {
0177         QTest::qWait(200);
0178         waitTime += 200;
0179         if (spy.count() == countRemoveStartup) {
0180             break;
0181         }
0182     }
0183     QCOMPARE(spy.count(), countRemoveStartup);
0184 }
0185 
0186 void KStartupInfo_UnitTest::checkCleanOnCantDetectTest()
0187 {
0188     KStartupInfoId id;
0189     KStartupInfoId id2;
0190     id.initId(QByteArrayLiteral("somefancyidwhichisrandom_kstartupinfo_unittest_0"));
0191     id2.initId(QByteArrayLiteral("somefancyidwhichisrandom_kstartupinfo_unittest_1"));
0192 
0193     KStartupInfoData data;
0194     data.setApplicationId(QStringLiteral("/dir with space/kstartupinfo_unittest.desktop"));
0195     data.setIcon(QStringLiteral("/dir with space/kstartupinfo_unittest.png"));
0196     data.setDescription(QStringLiteral("A description"));
0197     data.setName(QStringLiteral("A name"));
0198     data.setBin(QStringLiteral("dir with space/kstartupinfo_unittest"));
0199     data.setWMClass(QByteArrayLiteral("0"));
0200 
0201     xcb_connection_t *c = QX11Info::connection();
0202     xcb_window_t window = xcb_generate_id(c);
0203     uint32_t values[] = {XCB_EVENT_MASK_PROPERTY_CHANGE};
0204     xcb_create_window(c,
0205                       XCB_COPY_FROM_PARENT,
0206                       window,
0207                       QX11Info::appRootWindow(),
0208                       0,
0209                       0,
0210                       100,
0211                       100,
0212                       0,
0213                       XCB_COPY_FROM_PARENT,
0214                       XCB_COPY_FROM_PARENT,
0215                       XCB_CW_EVENT_MASK,
0216                       values);
0217 
0218     KStartupInfo::sendStartup(id, data);
0219     KStartupInfo::sendStartup(id2, data);
0220 
0221     int previousCount = m_receivedCount;
0222 
0223     doSync();
0224     QTest::qWait(10);
0225 
0226     xcb_map_window(c, window);
0227     xcb_flush(c);
0228     QTest::qWait(10);
0229 
0230     xcb_unmap_window(c, window);
0231     xcb_flush(c);
0232     QTest::qWait(100);
0233     xcb_map_window(c, window);
0234     xcb_flush(c);
0235 
0236     QCOMPARE(m_receivedCount, previousCount + 2);
0237     QCOMPARE(m_receivedId, id2);
0238 }
0239 
0240 void KStartupInfo_UnitTest::checkStartupTest_data()
0241 {
0242     QTest::addColumn<QByteArray>("wmClass");
0243     QTest::addColumn<int>("pid");
0244 
0245     QTest::newRow("wmClass") << QByteArrayLiteral("kstartupinfotest") << 0;
0246     QTest::newRow("pid") << QByteArray() << 12345;
0247 }
0248 
0249 void KStartupInfo_UnitTest::checkStartupTest()
0250 {
0251     KStartupInfoId id;
0252     KStartupInfoId id2;
0253     id.initId(QByteArrayLiteral("somefancyidwhichisrandom_kstartupinfo_unittest_0"));
0254     id2.initId(QByteArrayLiteral("somefancyidwhichisrandom_kstartupinfo_unittest_1"));
0255 
0256     KStartupInfoData data;
0257     data.setApplicationId(QStringLiteral("/dir with space/kstartupinfo_unittest.desktop"));
0258     data.setIcon(QStringLiteral("/dir with space/kstartupinfo_unittest.png"));
0259     data.setDescription(QStringLiteral("A description"));
0260     data.setName(QStringLiteral("A name"));
0261     data.setBin(QStringLiteral("dir with space/kstartupinfo_unittest"));
0262     QFETCH(int, pid);
0263     data.addPid(pid);
0264     data.setHostname(QByteArrayLiteral("localhost"));
0265 
0266     // important for this test: WMClass
0267     QFETCH(QByteArray, wmClass);
0268     data.setWMClass(wmClass);
0269 
0270     xcb_connection_t *c = QX11Info::connection();
0271     xcb_window_t window = xcb_generate_id(c);
0272     uint32_t values[] = {XCB_EVENT_MASK_PROPERTY_CHANGE};
0273     xcb_create_window(c,
0274                       XCB_COPY_FROM_PARENT,
0275                       window,
0276                       QX11Info::appRootWindow(),
0277                       0,
0278                       0,
0279                       100,
0280                       100,
0281                       0,
0282                       XCB_COPY_FROM_PARENT,
0283                       XCB_COPY_FROM_PARENT,
0284                       XCB_CW_EVENT_MASK,
0285                       values);
0286 
0287     xcb_change_property(c,
0288                         XCB_PROP_MODE_REPLACE,
0289                         window,
0290                         XCB_ATOM_WM_CLASS,
0291                         XCB_ATOM_STRING,
0292                         8,
0293                         wmClass.length() * 2 + 1,
0294                         "kstartupinfotest\0kstartupinfotest");
0295     xcb_change_property(c, XCB_PROP_MODE_REPLACE, window, XCB_ATOM_WM_CLIENT_MACHINE, XCB_ATOM_STRING, 8, 9, "localhost");
0296     NETWinInfo winInfo(QX11Info::connection(), window, QX11Info::appRootWindow(), NET::Properties(), NET::Properties2());
0297     winInfo.setPid(pid);
0298 
0299     KStartupInfo info(KStartupInfo::DisableKWinModule | KStartupInfo::AnnounceSilenceChanges, this);
0300     KStartupInfo::sendStartup(id, data);
0301     KStartupInfo::sendStartup(id2, data);
0302 
0303     doSync();
0304     QTest::qWait(100);
0305 
0306     QCOMPARE(info.checkStartup(window), KStartupInfo::Match);
0307     QCOMPARE(info.checkStartup(window), KStartupInfo::Match);
0308 }
0309 
0310 void KStartupInfo_UnitTest::createNewStartupIdTest()
0311 {
0312     const QByteArray &id = KStartupInfo::createNewStartupId();
0313     QVERIFY(!id.isEmpty());
0314     const int index = id.indexOf(QByteArrayLiteral("TIME"));
0315     QVERIFY(index != -1);
0316     const QByteArray time = id.mid(index + 4);
0317     QVERIFY(time.toULongLong() != 0u);
0318 }
0319 
0320 void KStartupInfo_UnitTest::createNewStartupIdForTimestampTest()
0321 {
0322     const QByteArray &id = KStartupInfo::createNewStartupIdForTimestamp(5);
0323     QVERIFY(!id.isEmpty());
0324     const int index = id.indexOf(QByteArrayLiteral("TIME"));
0325     QVERIFY(index != -1);
0326     QCOMPARE(id.mid(index + 4).toULongLong(), 5u);
0327 }
0328 
0329 void KStartupInfo_UnitTest::setNewStartupIdTest()
0330 {
0331 #if KWINDOWSYSTEM_BUILD_DEPRECATED_SINCE(5, 102)
0332     {
0333         QWindow window;
0334         const QByteArray str = "somefancyidwhichisrandom_kstartupinfo_unittest_2";
0335         KStartupInfo::setNewStartupId(&window, str);
0336         QCOMPARE(KStartupInfo::startupId(), str);
0337     }
0338 #endif
0339 
0340 #if KWINDOWSYSTEM_ENABLE_DEPRECATED_SINCE(5, 62)
0341     {
0342         QWidget widget;
0343         const QByteArray str = "somefancyidwhichisrandom_kstartupinfo_unittest_3";
0344         KStartupInfo::setNewStartupId(&widget, str); // deprecated
0345         QCOMPARE(KStartupInfo::startupId(), str);
0346     }
0347 #endif
0348 }
0349 
0350 QTEST_MAIN(KStartupInfo_UnitTest)
0351 
0352 #include "kstartupinfo_unittest.moc"