File indexing completed on 2024-04-14 03:56:52

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