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"