File indexing completed on 2024-05-12 05:29:24
0001 /* This file is part of the KDE project 0002 SPDX-FileCopyrightText: 2007 David Faure <faure@kde.org> 0003 0004 SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0005 */ 0006 0007 #include <KService> 0008 0009 #include <KConfigGroup> 0010 #include <KDesktopFile> 0011 #include <KSycoca> 0012 0013 // Qt 0014 #include <QDir> 0015 #include <QLoggingCategory> 0016 #include <QMimeDatabase> 0017 #include <QProcess> 0018 #include <QSignalSpy> 0019 #include <QStandardPaths> 0020 #include <QTest> 0021 0022 #include <mimetypedata.h> 0023 #include <mimetypewriter.h> 0024 0025 // Unfortunately this isn't available in non-developer builds of Qt... 0026 // extern Q_CORE_EXPORT int qmime_secondsBetweenChecks; // see qmimeprovider.cpp 0027 0028 class FileTypesTest : public QObject 0029 { 0030 Q_OBJECT 0031 0032 private Q_SLOTS: 0033 void initTestCase() 0034 { 0035 QLoggingCategory::setFilterRules(QStringLiteral("kf.coreaddons.kdirwatch.debug=true")); 0036 0037 QStandardPaths::setTestModeEnabled(true); 0038 0039 // update-mime-database needs to know about that test directory for the mimetype pattern change in testMimeTypePatterns to have an effect 0040 qputenv("XDG_DATA_HOME", QFile::encodeName(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation))); 0041 0042 m_mimeTypeCreatedSuccessfully = false; 0043 QStringList appsDirs = QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation); 0044 // qDebug() << appsDirs; 0045 m_localApps = appsDirs.first() + QLatin1Char('/'); 0046 m_localConfig = QDir(QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation)); 0047 QVERIFY(QDir().mkpath(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/mime/packages"))); 0048 0049 QFile::remove(m_localConfig.filePath(QStringLiteral("mimeapps.list"))); 0050 QFile::remove(m_localConfig.filePath(QStringLiteral("kpartsrc"))); 0051 QFile::remove(m_localConfig.filePath(QStringLiteral("filetypesrc"))); 0052 0053 // Create fake applications for some tests below. 0054 fakeApplication = QStringLiteral("fakeapplication.desktop"); 0055 createDesktopFile(m_localApps + fakeApplication, {QStringLiteral("image/png")}); 0056 fakeApplication2 = QStringLiteral("fakeapplication2.desktop"); 0057 createDesktopFile(m_localApps + fakeApplication2, {QStringLiteral("image/png"), QStringLiteral("text/plain")}); 0058 0059 // Cleanup after testMimeTypePatterns if it failed mid-way 0060 const QString packageFileName = 0061 QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/mime/") + QStringLiteral("packages/text-plain.xml"); 0062 if (!packageFileName.isEmpty()) { 0063 QFile::remove(packageFileName); 0064 MimeTypeWriter::runUpdateMimeDatabase(); 0065 } 0066 0067 KService::Ptr fakeApplicationService = KService::serviceByStorageId(fakeApplication); 0068 QVERIFY(fakeApplicationService); 0069 } 0070 0071 void testMimeTypeGroupAutoEmbed() 0072 { 0073 MimeTypeData data(QStringLiteral("text")); 0074 QCOMPARE(data.majorType(), QStringLiteral("text")); 0075 QCOMPARE(data.name(), QStringLiteral("text")); 0076 QVERIFY(data.isMeta()); 0077 QCOMPARE(data.autoEmbed(), MimeTypeData::No); // text doesn't autoembed by default 0078 QVERIFY(!data.isDirty()); 0079 data.setAutoEmbed(MimeTypeData::Yes); 0080 QCOMPARE(data.autoEmbed(), MimeTypeData::Yes); 0081 QVERIFY(data.isDirty()); 0082 QVERIFY(!data.sync()); // save to disk. Should succeed, but return false (no need to run update-mime-database) 0083 QVERIFY(!data.isDirty()); 0084 // Check what's on disk by creating another MimeTypeData instance 0085 MimeTypeData data2(QStringLiteral("text")); 0086 QCOMPARE(data2.autoEmbed(), MimeTypeData::Yes); 0087 QVERIFY(!data2.isDirty()); 0088 data2.setAutoEmbed(MimeTypeData::No); // revert to default, for next time 0089 QVERIFY(data2.isDirty()); 0090 QVERIFY(!data2.sync()); 0091 QVERIFY(!data2.isDirty()); 0092 0093 // TODO test askSave after cleaning up the code 0094 } 0095 0096 void testMimeTypeAutoEmbed() 0097 { 0098 QMimeDatabase db; 0099 MimeTypeData data(db.mimeTypeForName(QStringLiteral("text/plain"))); 0100 QCOMPARE(data.majorType(), QStringLiteral("text")); 0101 QCOMPARE(data.minorType(), QStringLiteral("plain")); 0102 QCOMPARE(data.name(), QStringLiteral("text/plain")); 0103 QVERIFY(!data.isMeta()); 0104 QCOMPARE(data.autoEmbed(), MimeTypeData::UseGroupSetting); 0105 QVERIFY(!data.isDirty()); 0106 data.setAutoEmbed(MimeTypeData::Yes); 0107 QCOMPARE(data.autoEmbed(), MimeTypeData::Yes); 0108 QVERIFY(data.isDirty()); 0109 QVERIFY(!data.sync()); // save to disk. Should succeed, but return false (no need to run update-mime-database) 0110 QVERIFY(!data.isDirty()); 0111 // Check what's on disk by creating another MimeTypeData instance 0112 MimeTypeData data2(db.mimeTypeForName(QStringLiteral("text/plain"))); 0113 QCOMPARE(data2.autoEmbed(), MimeTypeData::Yes); 0114 QVERIFY(!data2.isDirty()); 0115 data2.setAutoEmbed(MimeTypeData::UseGroupSetting); // revert to default, for next time 0116 QVERIFY(data2.isDirty()); 0117 QVERIFY(!data2.sync()); 0118 QVERIFY(!data2.isDirty()); 0119 } 0120 0121 void testMimeTypePatterns() 0122 { 0123 // Given the text/plain mimetype 0124 QMimeDatabase db; 0125 MimeTypeData data(db.mimeTypeForName(QStringLiteral("text/plain"))); 0126 QCOMPARE(data.name(), QStringLiteral("text/plain")); 0127 QCOMPARE(data.majorType(), QStringLiteral("text")); 0128 QCOMPARE(data.minorType(), QStringLiteral("plain")); 0129 QVERIFY(!data.isMeta()); 0130 QStringList patterns = data.patterns(); 0131 QVERIFY(patterns.contains(QStringLiteral("*.txt"))); 0132 QVERIFY(!patterns.contains(QStringLiteral("*.toto"))); 0133 0134 // When the user changes the patterns 0135 const QStringList origPatterns = patterns; 0136 patterns.removeAll(QStringLiteral("*.txt")); 0137 patterns.append(QStringLiteral("*.toto")); // yes, a french guy wrote this, as you can see 0138 patterns.sort(); // for future comparisons 0139 QVERIFY(!data.isDirty()); 0140 data.setPatterns(patterns); 0141 QVERIFY(data.isDirty()); 0142 const bool needUpdateMimeDb = data.sync(); 0143 QVERIFY(needUpdateMimeDb); 0144 MimeTypeWriter::runUpdateMimeDatabase(); 0145 0146 // Then the GUI and the QMimeDatabase API should show the new patterns 0147 QCOMPARE(data.patterns(), patterns); 0148 data.refresh(); // reload from the xml 0149 QCOMPARE(data.patterns(), patterns); 0150 // Check what's in QMimeDatabase 0151 QStringList newPatterns = db.mimeTypeForName(QStringLiteral("text/plain")).globPatterns(); 0152 newPatterns.sort(); 0153 QCOMPARE(newPatterns, patterns); 0154 QVERIFY(!data.isDirty()); 0155 0156 // And then removing the custom file by hand should revert to the initial state 0157 const QString packageFileName = 0158 QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/mime/") + QStringLiteral("packages/text-plain.xml"); 0159 QVERIFY(!packageFileName.isEmpty()); 0160 QFile::remove(packageFileName); 0161 MimeTypeWriter::runUpdateMimeDatabase(); 0162 // Check what's in QMimeDatabase 0163 newPatterns = db.mimeTypeForName(QStringLiteral("text/plain")).globPatterns(); 0164 newPatterns.sort(); 0165 QCOMPARE(newPatterns, origPatterns); 0166 } 0167 0168 void testAddService() 0169 { 0170 QMimeDatabase db; 0171 QString mimeTypeName = QStringLiteral("application/rtf"); // use inherited mimetype to test #321706 0172 MimeTypeData data(db.mimeTypeForName(mimeTypeName)); 0173 QStringList appServices = data.appServices(); 0174 // qDebug() << appServices; 0175 QVERIFY(appServices.contains(fakeApplication2)); 0176 QVERIFY(!appServices.contains(fakeApplication)); // already there? hmm can't really test then 0177 QVERIFY(!data.isDirty()); 0178 appServices.prepend(fakeApplication); 0179 data.setAppServices(appServices); 0180 QVERIFY(data.isDirty()); 0181 QVERIFY(!data.sync()); // success, but no need to run update-mime-database 0182 runKBuildSycoca(); 0183 QVERIFY(!data.isDirty()); 0184 // Check what's in ksycoca 0185 checkMimeTypeServices(mimeTypeName, appServices); 0186 // Check what's in kpartsrc 0187 checkAddedAssociationsContains(mimeTypeName, fakeApplication); 0188 0189 // Test reordering apps, i.e. move fakeApplication under oldPreferredApp 0190 appServices.removeFirst(); 0191 appServices.insert(1, fakeApplication); 0192 data.setAppServices(appServices); 0193 QVERIFY(!data.sync()); // success, but no need to run update-mime-database 0194 runKBuildSycoca(); 0195 QVERIFY(!data.isDirty()); 0196 // Check what's in ksycoca 0197 checkMimeTypeServices(mimeTypeName, appServices); 0198 // Check what's in kpartsrc 0199 checkAddedAssociationsContains(mimeTypeName, fakeApplication); 0200 0201 // Then we get the signal that kbuildsycoca changed 0202 data.refresh(); 0203 0204 // Now test removing (in the same test, since it's inter-dependent) 0205 QVERIFY(appServices.removeAll(fakeApplication) > 0); 0206 data.setAppServices(appServices); 0207 QVERIFY(data.isDirty()); 0208 QVERIFY(!data.sync()); // success, but no need to run update-mime-database 0209 runKBuildSycoca(); 0210 // Check what's in ksycoca 0211 checkMimeTypeServices(mimeTypeName, appServices); 0212 // Check what's in mimeapps.list 0213 checkRemovedAssociationsContains(mimeTypeName, fakeApplication); 0214 } 0215 0216 void testRemoveTwice() 0217 { 0218 QMimeDatabase db; 0219 // Remove fakeApplication from image/png 0220 QString mimeTypeName = QStringLiteral("image/png"); 0221 MimeTypeData data(db.mimeTypeForName(mimeTypeName)); 0222 QStringList appServices = data.appServices(); 0223 qDebug() << "initial list for" << mimeTypeName << appServices; 0224 QVERIFY(appServices.removeAll(fakeApplication) > 0); 0225 data.setAppServices(appServices); 0226 QVERIFY(!data.sync()); // success, but no need to run update-mime-database 0227 runKBuildSycoca(); 0228 // Check what's in ksycoca 0229 checkMimeTypeServices(mimeTypeName, appServices); 0230 // Check what's in mimeapps.list 0231 checkRemovedAssociationsContains(mimeTypeName, fakeApplication); 0232 0233 // Remove fakeApplication2 from image/png; must keep the previous entry in "Removed Associations" 0234 qDebug() << "Removing fakeApplication2"; 0235 QVERIFY(appServices.removeAll(fakeApplication2) > 0); 0236 data.setAppServices(appServices); 0237 QVERIFY(!data.sync()); // success, but no need to run update-mime-database 0238 runKBuildSycoca(); 0239 // Check what's in ksycoca 0240 checkMimeTypeServices(mimeTypeName, appServices); 0241 // Check what's in mimeapps.list 0242 checkRemovedAssociationsContains(mimeTypeName, fakeApplication); 0243 // Check what's in mimeapps.list 0244 checkRemovedAssociationsContains(mimeTypeName, fakeApplication2); 0245 0246 // And now re-add fakeApplication2... 0247 qDebug() << "Re-adding fakeApplication2"; 0248 appServices.prepend(fakeApplication2); 0249 data.setAppServices(appServices); 0250 QVERIFY(!data.sync()); // success, but no need to run update-mime-database 0251 runKBuildSycoca(); 0252 // Check what's in ksycoca 0253 checkMimeTypeServices(mimeTypeName, appServices); 0254 // Check what's in mimeapps.list 0255 checkRemovedAssociationsContains(mimeTypeName, fakeApplication); 0256 checkRemovedAssociationsDoesNotContain(mimeTypeName, fakeApplication2); 0257 } 0258 0259 void testCreateMimeType() 0260 { 0261 QMimeDatabase db; 0262 const QString mimeTypeName = QStringLiteral("fake/unit-test-fake-mimetype"); 0263 // Clean up after previous runs if necessary 0264 if (MimeTypeWriter::hasDefinitionFile(mimeTypeName)) { 0265 MimeTypeWriter::removeOwnMimeType(mimeTypeName); 0266 } 0267 0268 MimeTypeData data(mimeTypeName, true); 0269 data.setComment(QStringLiteral("Fake MimeType")); 0270 QStringList patterns = QStringList() << QStringLiteral("*.pkg.tar.gz"); 0271 data.setPatterns(patterns); 0272 QVERIFY(data.isDirty()); 0273 QVERIFY(data.sync()); 0274 MimeTypeWriter::runUpdateMimeDatabase(); 0275 // QMimeDatabase doesn't even try to update the cache if less than 0276 // 5000 ms have passed (can't use qmime_secondsBetweenChecks) 0277 QTest::qSleep(5000); 0278 QMimeType mime = db.mimeTypeForName(mimeTypeName); 0279 QVERIFY(mime.isValid()); 0280 QCOMPARE(mime.comment(), QStringLiteral("Fake MimeType")); 0281 QCOMPARE(mime.globPatterns(), patterns); // must sort them if more than one 0282 0283 // Testcase for the shaman.xml bug 0284 QCOMPARE(db.mimeTypeForFile(QStringLiteral("/whatever/foo.pkg.tar.gz")).name(), QStringLiteral("fake/unit-test-fake-mimetype")); 0285 0286 m_mimeTypeCreatedSuccessfully = true; 0287 } 0288 0289 void testDeleteMimeType() 0290 { 0291 QMimeDatabase db; 0292 if (!m_mimeTypeCreatedSuccessfully) { 0293 QSKIP("This test relies on testCreateMimeType"); 0294 } 0295 const QString mimeTypeName = QStringLiteral("fake/unit-test-fake-mimetype"); 0296 QVERIFY(MimeTypeWriter::hasDefinitionFile(mimeTypeName)); 0297 MimeTypeWriter::removeOwnMimeType(mimeTypeName); 0298 MimeTypeWriter::runUpdateMimeDatabase(); 0299 // QMimeDatabase doesn't even try to update the cache if less than 0300 // 5000 ms have passed (can't use qmime_secondsBetweenChecks) 0301 QTest::qSleep(5000); 0302 const QMimeType mime = db.mimeTypeForName(mimeTypeName); 0303 QVERIFY2(!mime.isValid(), qPrintable(mimeTypeName)); 0304 } 0305 0306 void testModifyMimeTypeComment() // of a system mimetype. And check that it's re-read correctly. 0307 { 0308 QMimeDatabase db; 0309 QString mimeTypeName = QStringLiteral("image/png"); 0310 MimeTypeData data(db.mimeTypeForName(mimeTypeName)); 0311 QCOMPARE(data.comment(), QString::fromLatin1("PNG image")); 0312 QString fakeComment = QStringLiteral("PNG image [testing]"); 0313 data.setComment(fakeComment); 0314 QVERIFY(data.isDirty()); 0315 QVERIFY(data.sync()); 0316 MimeTypeWriter::runUpdateMimeDatabase(); 0317 QMimeType mime = db.mimeTypeForName(mimeTypeName); 0318 QVERIFY(mime.isValid()); 0319 QCOMPARE(mime.comment(), fakeComment); 0320 0321 // Cleanup 0322 QVERIFY(MimeTypeWriter::hasDefinitionFile(mimeTypeName)); 0323 MimeTypeWriter::removeOwnMimeType(mimeTypeName); 0324 } 0325 0326 void cleanupTestCase() 0327 { 0328 const QString localAppsDir = QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation); 0329 QFile::remove(localAppsDir + QLatin1String("/fakeapplication.desktop")); 0330 QFile::remove(localAppsDir + QLatin1String("/fakeapplication2.desktop")); 0331 } 0332 0333 private: // helper methods 0334 void checkAddedAssociationsContains(const QString &mimeTypeName, const QString &application) 0335 { 0336 const KConfig config(m_localConfig.filePath(QStringLiteral("mimeapps.list")), KConfig::NoGlobals); 0337 const KConfigGroup group(&config, "Added Associations"); 0338 const QStringList addedEntries = group.readXdgListEntry(mimeTypeName); 0339 if (!addedEntries.contains(application)) { 0340 qWarning() << addedEntries << "does not contain" << application; 0341 QVERIFY(addedEntries.contains(application)); 0342 } 0343 } 0344 0345 void checkRemovedAssociationsContains(const QString &mimeTypeName, const QString &application) 0346 { 0347 const KConfig config(m_localConfig.filePath(QStringLiteral("mimeapps.list")), KConfig::NoGlobals); 0348 const KConfigGroup group(&config, "Removed Associations"); 0349 const QStringList removedEntries = group.readXdgListEntry(mimeTypeName); 0350 if (!removedEntries.contains(application)) { 0351 qWarning() << removedEntries << "does not contain" << application; 0352 QVERIFY(removedEntries.contains(application)); 0353 } 0354 } 0355 0356 void checkRemovedAssociationsDoesNotContain(const QString &mimeTypeName, const QString &application) 0357 { 0358 const KConfig config(m_localConfig.filePath(QStringLiteral("mimeapps.list")), KConfig::NoGlobals); 0359 const KConfigGroup group(&config, "Removed Associations"); 0360 const QStringList removedEntries = group.readXdgListEntry(mimeTypeName); 0361 if (removedEntries.contains(application)) { 0362 qWarning() << removedEntries << "contains" << application; 0363 QVERIFY(!removedEntries.contains(application)); 0364 } 0365 } 0366 0367 void runKBuildSycoca() 0368 { 0369 // Wait for notifyDatabaseChanged DBus signal 0370 // (The real KCM code simply does the refresh in a slot, asynchronously) 0371 0372 QProcess proc; 0373 // proc.setProcessChannelMode(QProcess::ForwardedChannels); 0374 const QString kbuildsycoca = QStandardPaths::findExecutable(QLatin1String(KBUILDSYCOCA_EXENAME)); 0375 QVERIFY(!kbuildsycoca.isEmpty()); 0376 QStringList args; 0377 args << QStringLiteral("--testmode"); 0378 proc.start(kbuildsycoca, args); 0379 QSignalSpy spy(KSycoca::self(), qOverload<>(&KSycoca::databaseChanged)); 0380 proc.waitForFinished(); 0381 qDebug() << "waiting for signal"; 0382 QVERIFY(spy.wait(10000)); 0383 qDebug() << "got signal"; 0384 } 0385 0386 void createDesktopFile(const QString &path, const QStringList &mimeTypes) 0387 { 0388 KDesktopFile file(path); 0389 KConfigGroup group = file.desktopGroup(); 0390 group.writeEntry("Name", "FakeApplication"); 0391 group.writeEntry("Type", "Application"); 0392 group.writeEntry("Exec", "ls"); 0393 group.writeXdgListEntry("MimeType", mimeTypes); 0394 } 0395 0396 void checkMimeTypeServices(const QString &mimeTypeName, const QStringList &expectedServices) 0397 { 0398 QMimeDatabase db; 0399 MimeTypeData data2(db.mimeTypeForName(mimeTypeName)); 0400 if (data2.appServices() != expectedServices) { 0401 qDebug() << "got" << data2.appServices() << "expected" << expectedServices; 0402 } 0403 QCOMPARE(data2.appServices(), expectedServices); 0404 } 0405 0406 QString fakeApplication; // storage id of the fake application 0407 QString fakeApplication2; // storage id of the fake application2 0408 QString m_localApps; 0409 QDir m_localConfig; 0410 bool m_mimeTypeCreatedSuccessfully; 0411 }; 0412 0413 QTEST_MAIN(FileTypesTest) 0414 0415 #include "filetypestest.moc"