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