Warning, file /frameworks/kservice/autotests/ksycocatest.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002     This file is part of the KDE project
0003     SPDX-FileCopyrightText: 2015 David Faure <faure@kde.org>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include <KConfigGroup>
0009 #include <KDesktopFile>
0010 #include <QDebug>
0011 #include <QProcess>
0012 #include <QSignalSpy>
0013 #include <QTemporaryDir>
0014 #include <QTest>
0015 #include <kbuildsycoca_p.h>
0016 #include <kservice.h>
0017 #include <kservicefactory_p.h>
0018 #include <ksycoca.h>
0019 #include <ksycoca_p.h>
0020 
0021 // ## use QFile::setFileTime when it lands in Qt
0022 #include <time.h>
0023 #ifdef Q_OS_UNIX
0024 #include <sys/time.h>
0025 #include <utime.h>
0026 #endif
0027 
0028 // taken from tst_qstandardpaths
0029 #if defined(Q_OS_UNIX) && !defined(Q_OS_MAC) && !defined(Q_OS_BLACKBERRY) && !defined(Q_OS_ANDROID)
0030 #define Q_XDG_PLATFORM
0031 #endif
0032 
0033 // On Unix, lastModified() finally returns milliseconds as well, since Qt 5.8.0
0034 // Not sure about the situation on Windows though.
0035 static const int s_waitDelay = 10;
0036 
0037 extern KSERVICE_EXPORT int ksycoca_ms_between_checks;
0038 
0039 class KSycocaTest : public QObject
0040 {
0041     Q_OBJECT
0042 
0043 private Q_SLOTS:
0044     void initTestCase()
0045     {
0046         QStandardPaths::setTestModeEnabled(true);
0047 
0048         QVERIFY(m_tempDir.isValid());
0049 
0050         QDir(menusDir()).removeRecursively();
0051         QDir().mkpath(menusDir() + QLatin1String{"/fakeSubserviceDirectory"});
0052         QFile::copy(QFINDTESTDATA("data/menus/applications.menu"), menusDir() + QLatin1String("/applications.menu"));
0053 
0054 #ifdef Q_XDG_PLATFORM
0055         qputenv("XDG_DATA_DIRS", QFile::encodeName(m_tempDir.path()));
0056 
0057         // so that vfolder_menu doesn't go look into /etc and /usr
0058         qputenv("XDG_CONFIG_DIRS", QFile::encodeName(m_tempDir.path()));
0059 #else
0060         // We need to make changes to a global dir without messing up the system
0061         QSKIP("This test requires XDG_DATA_DIRS");
0062 #endif
0063         createTestApp();
0064     }
0065 
0066     void cleanupTestCase()
0067     {
0068         QFile::remove(KSycoca::absoluteFilePath());
0069     }
0070     void ensureCacheValidShouldCreateDB();
0071     void kBuildSycocaShouldEmitDatabaseChanged();
0072     void dirInFutureShouldRebuildSycocaOnce();
0073     void dirTimestampShouldBeCheckedRecursively();
0074     void recursiveCheckShouldIgnoreLinksGoingUp();
0075     void testDeletingSycoca();
0076     void testNonReadableSycoca();
0077     void extraFileInFutureShouldRebuildSycocaOnce();
0078 
0079 private:
0080     void createTestApp()
0081     {
0082         KDesktopFile app(appsDir() + QLatin1String("org.kde.test.desktop"));
0083         app.desktopGroup().writeEntry("Type", "Application");
0084         app.desktopGroup().writeEntry("Exec", "testApp");
0085         app.desktopGroup().writeEntry("Name", "Test App");
0086     }
0087 
0088     QString extraFile() const
0089     {
0090         return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String{"/mimeapps.list"};
0091     }
0092 
0093     QString menusDir() const
0094     {
0095         return QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1String{"/menus"};
0096     }
0097 
0098     QString appsDir() const
0099     {
0100         return QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation) + QLatin1Char('/');
0101     }
0102 
0103     static void runKBuildSycoca(const QProcessEnvironment &environment, bool global = false);
0104 
0105     QTemporaryDir m_tempDir;
0106 };
0107 
0108 QTEST_MAIN(KSycocaTest)
0109 
0110 void KSycocaTest::ensureCacheValidShouldCreateDB() // this is what kded does on startup
0111 {
0112     QFile::remove(KSycoca::absoluteFilePath());
0113     KSycoca::self()->ensureCacheValid();
0114     QVERIFY(QFile::exists(KSycoca::absoluteFilePath()));
0115     QVERIFY(KService::serviceByDesktopName(QStringLiteral("org.kde.test")));
0116 }
0117 
0118 void KSycocaTest::kBuildSycocaShouldEmitDatabaseChanged()
0119 {
0120     QTest::qWait(s_waitDelay);
0121     // Ensure kbuildsycoca has something to do
0122     QVERIFY(QFile::remove(appsDir() + QLatin1String{"/org.kde.test.desktop"}));
0123     // Run kbuildsycoca
0124     QSignalSpy spy(KSycoca::self(), &KSycoca::databaseChanged);
0125 
0126     runKBuildSycoca(QProcessEnvironment::systemEnvironment());
0127     qDebug() << "waiting for signal";
0128     QVERIFY(spy.wait(20000));
0129     qDebug() << "got signal";
0130     // Put it back for other tests
0131     createTestApp();
0132 }
0133 
0134 void KSycocaTest::dirInFutureShouldRebuildSycocaOnce()
0135 {
0136     const QDateTime oldTimestamp = QFileInfo(KSycoca::absoluteFilePath()).lastModified();
0137 
0138     // ### use QFile::setFileTime when it lands in Qt...
0139 #ifdef Q_OS_UNIX
0140     const QString path = appsDir();
0141     struct timeval tp;
0142     gettimeofday(&tp, nullptr);
0143     struct utimbuf utbuf;
0144     utbuf.actime = tp.tv_sec;
0145     utbuf.modtime = tp.tv_sec + 60; // 60 second in the future
0146     QCOMPARE(utime(QFile::encodeName(path).constData(), &utbuf), 0);
0147     qDebug("Time changed for %s", qPrintable(path));
0148     qDebug() << QDateTime::currentDateTime() << QFileInfo(path).lastModified();
0149 #else
0150     QSKIP("This test requires utime");
0151 #endif
0152     ksycoca_ms_between_checks = 0;
0153 
0154     QTest::qWait(s_waitDelay);
0155 
0156     KSycoca::self()->ensureCacheValid();
0157     const QDateTime newTimestamp = QFileInfo(KSycoca::absoluteFilePath()).lastModified();
0158     QVERIFY(newTimestamp > oldTimestamp);
0159 
0160     QTest::qWait(s_waitDelay);
0161 
0162     KSycoca::self()->ensureCacheValid();
0163     const QDateTime againTimestamp = QFileInfo(KSycoca::absoluteFilePath()).lastModified();
0164     QCOMPARE(againTimestamp, newTimestamp); // same mtime, it didn't get rebuilt
0165 
0166     // Ensure we don't pollute the other tests, with our dir in the future.
0167 #ifdef Q_OS_UNIX
0168     utbuf.modtime = tp.tv_sec;
0169     QCOMPARE(utime(QFile::encodeName(path).constData(), &utbuf), 0);
0170     qDebug("Time changed back for %s", qPrintable(path));
0171     qDebug() << QDateTime::currentDateTime() << QFileInfo(path).lastModified();
0172 #endif
0173 }
0174 
0175 void KSycocaTest::dirTimestampShouldBeCheckedRecursively()
0176 {
0177 #ifndef Q_OS_UNIX
0178     QSKIP("This test requires utime");
0179 #endif
0180     const QDateTime oldTimestamp = QFileInfo(KSycoca::absoluteFilePath()).lastModified();
0181 
0182     const QString path = menusDir() + QLatin1String("/fakeSubserviceDirectory");
0183 
0184     // ### use QFile::setFileTime when it lands in Qt...
0185 #ifdef Q_OS_UNIX
0186     struct timeval tp;
0187     gettimeofday(&tp, nullptr);
0188     struct utimbuf utbuf;
0189     utbuf.actime = tp.tv_sec;
0190     utbuf.modtime = tp.tv_sec + 60; // 60 second in the future
0191     QCOMPARE(utime(QFile::encodeName(path).constData(), &utbuf), 0);
0192     qDebug("Time changed for %s", qPrintable(path));
0193     qDebug() << QDateTime::currentDateTime() << QFileInfo(path).lastModified();
0194 #endif
0195 
0196     ksycoca_ms_between_checks = 0;
0197     QTest::qWait(s_waitDelay);
0198 
0199     qDebug() << "Waited 1s, calling ensureCacheValid (should rebuild)";
0200     KSycoca::self()->ensureCacheValid();
0201     const QDateTime newTimestamp = QFileInfo(KSycoca::absoluteFilePath()).lastModified();
0202     if (newTimestamp <= oldTimestamp) {
0203         qWarning() << "oldTimestamp=" << oldTimestamp << "newTimestamp=" << newTimestamp;
0204     }
0205     QVERIFY(newTimestamp > oldTimestamp);
0206 
0207     QTest::qWait(s_waitDelay);
0208 
0209     qDebug() << "Waited 1s, calling ensureCacheValid (should not rebuild)";
0210     KSycoca::self()->ensureCacheValid();
0211     const QDateTime againTimestamp = QFileInfo(KSycoca::absoluteFilePath()).lastModified();
0212     QCOMPARE(againTimestamp, newTimestamp); // same mtime, it didn't get rebuilt
0213 
0214     // Ensure we don't pollute the other tests
0215     QDir(path).removeRecursively();
0216 }
0217 
0218 void KSycocaTest::recursiveCheckShouldIgnoreLinksGoingUp()
0219 {
0220 #ifndef Q_OS_UNIX
0221     QSKIP("This test requires symlinks and utime");
0222 #endif
0223     ksycoca_ms_between_checks = 0;
0224     const QString link = menusDir() + QLatin1String("/linkGoingUp");
0225     QVERIFY(QFile::link(QStringLiteral(".."), link));
0226     QTest::qWait(s_waitDelay);
0227     KSycoca::self()->ensureCacheValid();
0228     QVERIFY2(QFile::exists(KSycoca::absoluteFilePath()), qPrintable(KSycoca::absoluteFilePath()));
0229     const QDateTime oldTimestamp = QFileInfo(KSycoca::absoluteFilePath()).lastModified();
0230     QVERIFY(oldTimestamp.isValid());
0231 
0232     const QString path = QFileInfo(menusDir()).absolutePath(); // the parent of the menus dir
0233 
0234     // ### use QFile::setFileTime when it lands in Qt...
0235 #ifdef Q_OS_UNIX
0236     struct timeval tp;
0237     gettimeofday(&tp, nullptr);
0238     struct utimbuf utbuf;
0239     utbuf.actime = tp.tv_sec;
0240     utbuf.modtime = tp.tv_sec + 60; // 60 second in the future
0241     QCOMPARE(utime(QFile::encodeName(path).constData(), &utbuf), 0);
0242     qDebug("Time changed for %s", qPrintable(path));
0243     qDebug() << QDateTime::currentDateTime() << QFileInfo(path).lastModified();
0244 #endif
0245 
0246     ksycoca_ms_between_checks = 0;
0247     QTest::qWait(s_waitDelay);
0248 
0249     qDebug() << "Waited 1s, calling ensureCacheValid (should not rebuild)";
0250     KSycoca::self()->ensureCacheValid();
0251     const QDateTime againTimestamp = QFileInfo(KSycoca::absoluteFilePath()).lastModified();
0252     QCOMPARE(againTimestamp, oldTimestamp); // same mtime, it didn't get rebuilt
0253 
0254     // Ensure we don't pollute the other tests
0255     QFile(link).remove();
0256 }
0257 
0258 void KSycocaTest::runKBuildSycoca(const QProcessEnvironment &environment, bool global)
0259 {
0260     QProcess proc;
0261     const QString kbuildsycoca = QStringLiteral(KBUILDSYCOCAEXE);
0262     QVERIFY(!kbuildsycoca.isEmpty());
0263     QStringList args;
0264     args << QStringLiteral("--testmode");
0265     if (global) {
0266         args << QStringLiteral("--global");
0267     }
0268     proc.setProcessChannelMode(QProcess::ForwardedChannels);
0269     proc.start(kbuildsycoca, args);
0270     proc.setProcessEnvironment(environment);
0271 
0272     proc.waitForFinished();
0273     QCOMPARE(proc.exitStatus(), QProcess::NormalExit);
0274 }
0275 
0276 void KSycocaTest::testDeletingSycoca()
0277 {
0278     // Mostly the same as ensureCacheValidShouldCreateDB, but KSycoca::self() already exists
0279     // So this is a check that deleting sycoca doesn't make apps crash (bug 343618).
0280     QFile::remove(KSycoca::absoluteFilePath());
0281     ksycoca_ms_between_checks = 0;
0282     QVERIFY(KService::serviceByDesktopName(QStringLiteral("org.kde.test")));
0283     QVERIFY(QFile::exists(KSycoca::absoluteFilePath()));
0284 }
0285 
0286 void KSycocaTest::testNonReadableSycoca()
0287 {
0288     // Lose readability (to simulate e.g. owned by root)
0289     QFile(KSycoca::absoluteFilePath()).setPermissions(QFile::WriteOwner);
0290     ksycoca_ms_between_checks = 0;
0291     KBuildSycoca builder;
0292     QVERIFY(builder.recreate());
0293     QVERIFY(!KService::serviceByDesktopName(QStringLiteral("org.kde.test")));
0294 
0295     // cleanup
0296     QFile::remove(KSycoca::absoluteFilePath());
0297 }
0298 
0299 void KSycocaTest::extraFileInFutureShouldRebuildSycocaOnce()
0300 {
0301     const QDateTime oldTimestamp = QFileInfo(KSycoca::absoluteFilePath()).lastModified();
0302 
0303     auto path = extraFile();
0304     QFile f(path);
0305     QVERIFY(f.open(QIODevice::WriteOnly));
0306     auto beginning = f.fileTime(QFileDevice::FileModificationTime);
0307     auto newdate = beginning.addSecs(60);
0308     qDebug() << "Time changed for " << newdate << path;
0309     QVERIFY(f.setFileTime(newdate, QFileDevice::FileModificationTime));
0310 
0311     ksycoca_ms_between_checks = 0;
0312 
0313     QTest::qWait(s_waitDelay);
0314 
0315     KSycoca::self()->ensureCacheValid();
0316     const QDateTime newTimestamp = QFileInfo(KSycoca::absoluteFilePath()).lastModified();
0317     QVERIFY(newTimestamp > oldTimestamp);
0318 
0319     QTest::qWait(s_waitDelay);
0320 
0321     KSycoca::self()->ensureCacheValid();
0322     const QDateTime againTimestamp = QFileInfo(KSycoca::absoluteFilePath()).lastModified();
0323     QCOMPARE(againTimestamp, newTimestamp); // same mtime, it didn't get rebuilt
0324 
0325     // Ensure we don't pollute the other tests, with our extra file in the future.
0326     QVERIFY(QFile::remove(path));
0327 }
0328 
0329 #include "ksycocatest.moc"