filetypestest.cpp 18.4 KB
Newer Older
1
/* This file is part of the KDE project
2
    Copyright 2007 David Faure <faure@kde.org>
3

4
   This program is free software; you can redistribute it and/or modify
5
   it under the terms of the GNU General Public License as published by
6
7
8
   the Free Software Foundation; either version 2 of the License or ( at
   your option ) version 3 or, at the discretion of KDE e.V. ( which shall
   act as a proxy as in section 14 of the GPLv3 ), any later version.
9
10
11
12
13
14
15
16
17
18
19

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   Boston, MA 02110-1301, USA.
*/

20
#include <kservice.h>
21
22
23
24
25

#include <kconfiggroup.h>
#include <kdesktopfile.h>
#include <ksycoca.h>

26
// Qt
27
#include <QProcess>
28
29
30
#include <QDir>
#include <QStandardPaths>
#include <QTest>
31
#include <QSignalSpy>
32
#include <QLoggingCategory>
33

34
#include <mimetypedata.h>
35
#include <mimetypewriter.h>
36

37
extern Q_CORE_EXPORT int qmime_secondsBetweenChecks; // see qmimeprovider.cpp
38
39
40
41
42
43
44
45

class FileTypesTest : public QObject
{
    Q_OBJECT

private Q_SLOTS:
    void initTestCase()
    {
46
47
48
        extern KSERVICE_EXPORT bool kservice_require_kded;
        kservice_require_kded = false;

49
50
        QLoggingCategory::setFilterRules(QStringLiteral("kf5.kcoreaddons.kdirwatch.debug=true"));

51
52
        QStandardPaths::setTestModeEnabled(true);

53
54
55
        // update-mime-database needs to know about that test directory for the mimetype pattern change in testMimeTypePatterns to have an effect
        qputenv("XDG_DATA_HOME", QFile::encodeName(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)));

56
        m_mimeTypeCreatedSuccessfully = false;
57
        QStringList appsDirs = QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation);
58
        //qDebug() << appsDirs;
59
        m_localApps = appsDirs.first() + QLatin1Char('/');
Maximiliano Curia's avatar
Maximiliano Curia committed
60
        m_localConfig = QDir(QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation));
61
        QVERIFY(QDir().mkpath(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/mime/packages")));
62

63
        QFile::remove(m_localConfig.filePath(QStringLiteral("mimeapps.list")));
64
        QFile::remove(m_localConfig.filePath(QStringLiteral("filetypesrc")));
65

66
        // Create fake applications for some tests below.
67
        fakeApplication = QStringLiteral("fakeapplication.desktop");
68
        createDesktopFile(m_localApps + fakeApplication, {QStringLiteral("image/png")});
69
        fakeApplication2 = QStringLiteral("fakeapplication2.desktop");
70
        createDesktopFile(m_localApps + fakeApplication2, {QStringLiteral("image/png"), QStringLiteral("text/plain")});
71

72
        // Cleanup after testMimeTypePatterns if it failed mid-way
73
        const QString packageFileName = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/mime/") + QStringLiteral("packages/text-plain.xml") ;
74
75
        if (!packageFileName.isEmpty()) {
            QFile::remove(packageFileName);
76
            MimeTypeWriter::runUpdateMimeDatabase();
77
78
        }

79
        KService::Ptr fakeApplicationService = KService::serviceByStorageId(fakeApplication);
80
        QVERIFY(fakeApplicationService);
81
82
83
84
    }

    void testMimeTypeGroupAutoEmbed()
    {
85
86
87
        MimeTypeData data(QStringLiteral("text"));
        QCOMPARE(data.majorType(), QStringLiteral("text"));
        QCOMPARE(data.name(), QStringLiteral("text"));
88
89
90
91
92
93
        QVERIFY(data.isMeta());
        QCOMPARE(data.autoEmbed(), MimeTypeData::No); // text doesn't autoembed by default
        QVERIFY(!data.isDirty());
        data.setAutoEmbed(MimeTypeData::Yes);
        QCOMPARE(data.autoEmbed(), MimeTypeData::Yes);
        QVERIFY(data.isDirty());
94
        QVERIFY(!data.sync()); // save to disk. Should succeed, but return false (no need to run update-mime-database)
95
96
        QVERIFY(!data.isDirty());
        // Check what's on disk by creating another MimeTypeData instance
97
        MimeTypeData data2(QStringLiteral("text"));
98
99
100
101
        QCOMPARE(data2.autoEmbed(), MimeTypeData::Yes);
        QVERIFY(!data2.isDirty());
        data2.setAutoEmbed(MimeTypeData::No); // revert to default, for next time
        QVERIFY(data2.isDirty());
102
        QVERIFY(!data2.sync());
103
104
105
106
107
        QVERIFY(!data2.isDirty());

        // TODO test askSave after cleaning up the code
    }

108
109
    void testMimeTypeAutoEmbed()
    {
110
        QMimeDatabase db;
111
112
113
114
        MimeTypeData data(db.mimeTypeForName(QStringLiteral("text/plain")));
        QCOMPARE(data.majorType(), QStringLiteral("text"));
        QCOMPARE(data.minorType(), QStringLiteral("plain"));
        QCOMPARE(data.name(), QStringLiteral("text/plain"));
115
116
117
118
119
120
        QVERIFY(!data.isMeta());
        QCOMPARE(data.autoEmbed(), MimeTypeData::UseGroupSetting);
        QVERIFY(!data.isDirty());
        data.setAutoEmbed(MimeTypeData::Yes);
        QCOMPARE(data.autoEmbed(), MimeTypeData::Yes);
        QVERIFY(data.isDirty());
121
        QVERIFY(!data.sync()); // save to disk. Should succeed, but return false (no need to run update-mime-database)
122
123
        QVERIFY(!data.isDirty());
        // Check what's on disk by creating another MimeTypeData instance
124
        MimeTypeData data2(db.mimeTypeForName(QStringLiteral("text/plain")));
125
126
127
128
        QCOMPARE(data2.autoEmbed(), MimeTypeData::Yes);
        QVERIFY(!data2.isDirty());
        data2.setAutoEmbed(MimeTypeData::UseGroupSetting); // revert to default, for next time
        QVERIFY(data2.isDirty());
129
        QVERIFY(!data2.sync());
130
131
132
        QVERIFY(!data2.isDirty());
    }

133
134
    void testMimeTypePatterns()
    {
135
        // Given the text/plain mimetype
136
        QMimeDatabase db;
137
138
139
140
        MimeTypeData data(db.mimeTypeForName(QStringLiteral("text/plain")));
        QCOMPARE(data.name(), QStringLiteral("text/plain"));
        QCOMPARE(data.majorType(), QStringLiteral("text"));
        QCOMPARE(data.minorType(), QStringLiteral("plain"));
141
142
        QVERIFY(!data.isMeta());
        QStringList patterns = data.patterns();
143
144
        QVERIFY(patterns.contains(QStringLiteral("*.txt")));
        QVERIFY(!patterns.contains(QStringLiteral("*.toto")));
145
146

        // When the user changes the patterns
147
        const QStringList origPatterns = patterns;
148
149
        patterns.removeAll(QStringLiteral("*.txt"));
        patterns.append(QStringLiteral("*.toto")); // yes, a french guy wrote this, as you can see
150
        patterns.sort(); // for future comparisons
151
152
153
        QVERIFY(!data.isDirty());
        data.setPatterns(patterns);
        QVERIFY(data.isDirty());
154
155
        bool needUpdateMimeDb = data.sync();
        QVERIFY(needUpdateMimeDb);
156
        MimeTypeWriter::runUpdateMimeDatabase();
157
158

        // Then the GUI and the QMimeDatabase API should show the new patterns
159
        QCOMPARE(data.patterns(), patterns);
160
        data.refresh(); // reload from the xml
161
        QCOMPARE(data.patterns(), patterns);
162
        // Check what's in QMimeDatabase
163
        QStringList newPatterns = db.mimeTypeForName(QStringLiteral("text/plain")).globPatterns();
164
        newPatterns.sort();
165
166
167
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) // adjust to 5.15 if the fix gets backported
        QEXPECT_FAIL("", "QTBUG-85436 is only fixed in Qt 6.0", Continue);
#endif
168
        QCOMPARE(newPatterns, patterns);
169
170
171
        if (newPatterns == patterns) { // TODO Qt6: remove the if (keep the QVERIFY!)
            QVERIFY(!data.isDirty());
        }
172

173
        // And then removing the custom file by hand should revert to the initial state
174
        const QString packageFileName = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/mime/") + QStringLiteral("packages/text-plain.xml") ;
175
176
        QVERIFY(!packageFileName.isEmpty());
        QFile::remove(packageFileName);
177
        MimeTypeWriter::runUpdateMimeDatabase();
178
        // Check what's in QMimeDatabase
179
        newPatterns = db.mimeTypeForName(QStringLiteral("text/plain")).globPatterns();
180
181
        newPatterns.sort();
        QCOMPARE(newPatterns, origPatterns);
182
183
    }

184
185
    void testAddService()
    {
186
        QMimeDatabase db;
187
        QString mimeTypeName = QStringLiteral("application/rtf"); // use inherited mimetype to test #321706
188
        MimeTypeData data(db.mimeTypeForName(mimeTypeName));
189
        QStringList appServices = data.appServices();
190
        //qDebug() << appServices;
191
        QVERIFY(appServices.contains(fakeApplication2));
192
193
194
195
196
        QVERIFY(!appServices.contains(fakeApplication)); // already there? hmm can't really test then
        QVERIFY(!data.isDirty());
        appServices.prepend(fakeApplication);
        data.setAppServices(appServices);
        QVERIFY(data.isDirty());
197
        QVERIFY(!data.sync()); // success, but no need to run update-mime-database
198
199
        runKBuildSycoca();
        QVERIFY(!data.isDirty());
200
201
        // Check what's in ksycoca
        checkMimeTypeServices(mimeTypeName, appServices);
202
203
        // Check what's in mimeapps.list
        checkAddedAssociationsContains(mimeTypeName, fakeApplication);
204

205
206
207
208
209
210
211
212
213
214
215
216
        // Test reordering apps, i.e. move fakeApplication under oldPreferredApp
        appServices.removeFirst();
        appServices.insert(1, fakeApplication);
        data.setAppServices(appServices);
        QVERIFY(!data.sync()); // success, but no need to run update-mime-database
        runKBuildSycoca();
        QVERIFY(!data.isDirty());
        // Check what's in ksycoca
        checkMimeTypeServices(mimeTypeName, appServices);
        // Check what's in mimeapps.list
        checkAddedAssociationsContains(mimeTypeName, fakeApplication);

217
218
219
        // Then we get the signal that kbuildsycoca changed
        data.refresh();

220
        // Now test removing (in the same test, since it's inter-dependent)
221
        QVERIFY(appServices.removeAll(fakeApplication) > 0);
222
223
        data.setAppServices(appServices);
        QVERIFY(data.isDirty());
224
        QVERIFY(!data.sync()); // success, but no need to run update-mime-database
225
        runKBuildSycoca();
226
227
228
229
        // Check what's in ksycoca
        checkMimeTypeServices(mimeTypeName, appServices);
        // Check what's in mimeapps.list
        checkRemovedAssociationsContains(mimeTypeName, fakeApplication);
230
231
    }

232
233
    void testRemoveTwice()
    {
234
        QMimeDatabase db;
235
        // Remove fakeApplication from image/png
236
        QString mimeTypeName = QStringLiteral("image/png");
237
        MimeTypeData data(db.mimeTypeForName(mimeTypeName));
238
        QStringList appServices = data.appServices();
239
        qDebug() << "initial list for" << mimeTypeName << appServices;
240
241
242
243
244
245
246
247
248
249
        QVERIFY(appServices.removeAll(fakeApplication) > 0);
        data.setAppServices(appServices);
        QVERIFY(!data.sync()); // success, but no need to run update-mime-database
        runKBuildSycoca();
        // Check what's in ksycoca
        checkMimeTypeServices(mimeTypeName, appServices);
        // Check what's in mimeapps.list
        checkRemovedAssociationsContains(mimeTypeName, fakeApplication);

        // Remove fakeApplication2 from image/png; must keep the previous entry in "Removed Associations"
250
        qDebug() << "Removing fakeApplication2";
251
252
253
254
255
256
257
258
259
260
        QVERIFY(appServices.removeAll(fakeApplication2) > 0);
        data.setAppServices(appServices);
        QVERIFY(!data.sync()); // success, but no need to run update-mime-database
        runKBuildSycoca();
        // Check what's in ksycoca
        checkMimeTypeServices(mimeTypeName, appServices);
        // Check what's in mimeapps.list
        checkRemovedAssociationsContains(mimeTypeName, fakeApplication);
        // Check what's in mimeapps.list
        checkRemovedAssociationsContains(mimeTypeName, fakeApplication2);
261
262

        // And now re-add fakeApplication2...
263
        qDebug() << "Re-adding fakeApplication2";
264
265
266
267
268
269
270
271
272
        appServices.prepend(fakeApplication2);
        data.setAppServices(appServices);
        QVERIFY(!data.sync()); // success, but no need to run update-mime-database
        runKBuildSycoca();
        // Check what's in ksycoca
        checkMimeTypeServices(mimeTypeName, appServices);
        // Check what's in mimeapps.list
        checkRemovedAssociationsContains(mimeTypeName, fakeApplication);
        checkRemovedAssociationsDoesNotContain(mimeTypeName, fakeApplication2);
273
    }
274

275
276
    void testCreateMimeType()
    {
277
        QMimeDatabase db;
278
        const QString mimeTypeName = QStringLiteral("fake/unit-test-fake-mimetype");
279
280
281
        // Clean up after previous runs if necessary
        if (MimeTypeWriter::hasDefinitionFile(mimeTypeName))
            MimeTypeWriter::removeOwnMimeType(mimeTypeName);
282

283
        MimeTypeData data(mimeTypeName, true);
284
285
        data.setComment(QStringLiteral("Fake MimeType"));
        QStringList patterns = QStringList() << QStringLiteral("*.pkg.tar.gz");
286
287
288
289
        data.setPatterns(patterns);
        QVERIFY(data.isDirty());
        QVERIFY(data.sync());
        MimeTypeWriter::runUpdateMimeDatabase();
290
        qmime_secondsBetweenChecks = 0; // ensure QMimeDatabase sees it immediately
291
292
        QMimeType mime = db.mimeTypeForName(mimeTypeName);
        QVERIFY(mime.isValid());
293
        QCOMPARE(mime.comment(), QStringLiteral("Fake MimeType"));
294
        QCOMPARE(mime.globPatterns(), patterns); // must sort them if more than one
295
296

        // Testcase for the shaman.xml bug
297
        QCOMPARE(db.mimeTypeForFile(QStringLiteral("/whatever/foo.pkg.tar.gz")).name(), QStringLiteral("fake/unit-test-fake-mimetype"));
298

299
300
301
302
303
        m_mimeTypeCreatedSuccessfully = true;
    }

    void testDeleteMimeType()
    {
304
        QMimeDatabase db;
305
        if (!m_mimeTypeCreatedSuccessfully)
306
            QSKIP("This test relies on testCreateMimeType");
307
        const QString mimeTypeName = QStringLiteral("fake/unit-test-fake-mimetype");
308
309
310
        QVERIFY(MimeTypeWriter::hasDefinitionFile(mimeTypeName));
        MimeTypeWriter::removeOwnMimeType(mimeTypeName);
        MimeTypeWriter::runUpdateMimeDatabase();
311
312
        const QMimeType mime = db.mimeTypeForName(mimeTypeName);
        QVERIFY2(!mime.isValid(), qPrintable(mimeTypeName));
313
    }
314

315
316
    void testModifyMimeTypeComment() // of a system mimetype. And check that it's re-read correctly.
    {
317
        QMimeDatabase db;
318
        QString mimeTypeName = QStringLiteral("image/png");
319
        MimeTypeData data(db.mimeTypeForName(mimeTypeName));
320
        QCOMPARE(data.comment(), QString::fromLatin1("PNG image"));
321
        QString fakeComment = QStringLiteral("PNG image [testing]");
322
323
324
325
        data.setComment(fakeComment);
        QVERIFY(data.isDirty());
        QVERIFY(data.sync());
        MimeTypeWriter::runUpdateMimeDatabase();
326
327
        QMimeType mime = db.mimeTypeForName(mimeTypeName);
        QVERIFY(mime.isValid());
328
        QCOMPARE(mime.comment(), fakeComment);
329
330
331
332
333
334

        // Cleanup
        QVERIFY(MimeTypeWriter::hasDefinitionFile(mimeTypeName));
        MimeTypeWriter::removeOwnMimeType(mimeTypeName);
    }

335
336
    void cleanupTestCase()
    {
337
338
339
        const QString localAppsDir = QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation);
        QFile::remove(localAppsDir + QLatin1String("/fakeapplication.desktop"));
        QFile::remove(localAppsDir + QLatin1String("/fakeapplication2.desktop"));
340
    }
341
342
343

private: // helper methods

344
345
    void checkAddedAssociationsContains(const QString& mimeTypeName, const QString& application)
    {
346
        const KConfig config(m_localConfig.filePath(QStringLiteral("mimeapps.list")), KConfig::NoGlobals);
347
348
349
        const KConfigGroup group(&config, "Added Associations");
        const QStringList addedEntries = group.readXdgListEntry(mimeTypeName);
        if (!addedEntries.contains(application)) {
350
            qWarning() << addedEntries << "does not contain" << application;
351
352
353
354
            QVERIFY(addedEntries.contains(application));
        }
    }

355
356
    void checkRemovedAssociationsContains(const QString& mimeTypeName, const QString& application)
    {
357
        const KConfig config(m_localConfig.filePath(QStringLiteral("mimeapps.list")), KConfig::NoGlobals);
358
359
360
        const KConfigGroup group(&config, "Removed Associations");
        const QStringList removedEntries = group.readXdgListEntry(mimeTypeName);
        if (!removedEntries.contains(application)) {
361
            qWarning() << removedEntries << "does not contain" << application;
362
363
364
365
            QVERIFY(removedEntries.contains(application));
        }
    }

366
367
    void checkRemovedAssociationsDoesNotContain(const QString& mimeTypeName, const QString& application)
    {
368
        const KConfig config(m_localConfig.filePath(QStringLiteral("mimeapps.list")), KConfig::NoGlobals);
369
370
371
        const KConfigGroup group(&config, "Removed Associations");
        const QStringList removedEntries = group.readXdgListEntry(mimeTypeName);
        if (removedEntries.contains(application)) {
372
            qWarning() << removedEntries << "contains" << application;
373
374
375
376
            QVERIFY(!removedEntries.contains(application));
        }
    }

377
378
379
380
    void runKBuildSycoca()
    {
        // Wait for notifyDatabaseChanged DBus signal
        // (The real KCM code simply does the refresh in a slot, asynchronously)
381
382

        QProcess proc;
383
        //proc.setProcessChannelMode(QProcess::ForwardedChannels);
384
        const QString kbuildsycoca = QStandardPaths::findExecutable(QLatin1String(KBUILDSYCOCA_EXENAME));
385
386
        QVERIFY(!kbuildsycoca.isEmpty());
        QStringList args;
387
        args << QStringLiteral("--testmode");
388
        proc.start(kbuildsycoca, args);
389
        QSignalSpy spy(KSycoca::self(), SIGNAL(databaseChanged(QStringList)));
390
391
392
393
        proc.waitForFinished();
        qDebug() << "waiting for signal";
        QVERIFY(spy.wait(10000));
        qDebug() << "got signal";
394
395
    }

396
    void createDesktopFile(const QString& path, const QStringList &mimeTypes)
397
    {
398
399
400
401
402
403
        KDesktopFile file(path);
        KConfigGroup group = file.desktopGroup();
        group.writeEntry("Name", "FakeApplication");
        group.writeEntry("Type", "Application");
        group.writeEntry("Exec", "ls");
        group.writeXdgListEntry("MimeType", mimeTypes);
404
405
406
407
    }

    void checkMimeTypeServices(const QString& mimeTypeName, const QStringList& expectedServices)
    {
408
409
        QMimeDatabase db;
        MimeTypeData data2(db.mimeTypeForName(mimeTypeName));
410
        if (data2.appServices() != expectedServices)
411
            qDebug() << "got" << data2.appServices() << "expected" << expectedServices;
412
413
414
415
416
417
        QCOMPARE(data2.appServices(), expectedServices);
    }

    QString fakeApplication; // storage id of the fake application
    QString fakeApplication2; // storage id of the fake application2
    QString m_localApps;
Maximiliano Curia's avatar
Maximiliano Curia committed
418
    QDir m_localConfig;
419
    bool m_mimeTypeCreatedSuccessfully;
420
421
};

422
QTEST_MAIN(FileTypesTest)
423
424

#include "filetypestest.moc"