filetypestest.cpp 19.2 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

#include <kconfiggroup.h>
#include <kdesktopfile.h>
#include <ksycoca.h>
25
#include <kcoreaddons_version.h>
26

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

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

David Faure's avatar
David Faure committed
38
39
extern Q_CORE_EXPORT int qmime_secondsBetweenChecks; // see qmimeprovider.cpp

40
41
42
43
44
45
46
class FileTypesTest : public QObject
{
    Q_OBJECT

private Q_SLOTS:
    void initTestCase()
    {
47
48
49
#if KCOREADDONS_VERSION >= QT_VERSION_CHECK(5, 73, 0)
        QLoggingCategory::setFilterRules(QStringLiteral("kf.coreaddons.kdirwatch.debug=true"));
#else
50
        QLoggingCategory::setFilterRules(QStringLiteral("kf5.kcoreaddons.kdirwatch.debug=true"));
51
#endif
52

53
54
        QStandardPaths::setTestModeEnabled(true);

David Faure's avatar
David Faure committed
55
56
57
        // 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)));

58
        m_mimeTypeCreatedSuccessfully = false;
59
60
        QStringList appsDirs = QStandardPaths::standardLocations(
            QStandardPaths::ApplicationsLocation);
61
        //qDebug() << appsDirs;
62
        m_localApps = appsDirs.first() + QLatin1Char('/');
63
64
65
66
        m_localConfig
            = QDir(QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation));
        QVERIFY(QDir().mkpath(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)
                              + QStringLiteral("/mime/packages")));
67

68
        QFile::remove(m_localConfig.filePath(QStringLiteral("mimeapps.list")));
69

70
        // Create fake applications for some tests below.
71
        bool mustUpdateKSycoca = false;
72
        fakeApplication = QStringLiteral("fakeapplication.desktop");
73
        if (createDesktopFile(m_localApps + fakeApplication)) {
74
            mustUpdateKSycoca = true;
75
        }
76
        fakeApplication2 = QStringLiteral("fakeapplication2.desktop");
77
        if (createDesktopFile(m_localApps + fakeApplication2)) {
78
            mustUpdateKSycoca = true;
79
        }
80

81
        // Cleanup after testMimeTypePatterns if it failed mid-way
82
83
84
        const QString packageFileName = QStandardPaths::writableLocation(
            QStandardPaths::GenericDataLocation) + QLatin1String("/mime/") + QStringLiteral(
            "packages/text-plain.xml");
85
86
        if (!packageFileName.isEmpty()) {
            QFile::remove(packageFileName);
87
            MimeTypeWriter::runUpdateMimeDatabase();
88
89
90
            mustUpdateKSycoca = true;
        }

91
92
93
        QFile::remove(QStandardPaths::writableLocation(
                          QStandardPaths::GenericConfigLocation) + QLatin1Char('/') + QStringLiteral(
                          "filetypesrc"));
94

95
        if (mustUpdateKSycoca) {
David Faure's avatar
David Faure committed
96
            // Update ksycoca in ~/.qttest after creating the above [might not be needed anymore]
97
            runKBuildSycoca();
98
        }
99
        KService::Ptr fakeApplicationService = KService::serviceByStorageId(fakeApplication);
100
        QVERIFY(fakeApplicationService);
101
102
103
104
    }

    void testMimeTypeGroupAutoEmbed()
    {
105
106
107
        MimeTypeData data(QStringLiteral("text"));
        QCOMPARE(data.majorType(), QStringLiteral("text"));
        QCOMPARE(data.name(), QStringLiteral("text"));
108
109
110
111
112
113
        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());
114
        QVERIFY(!data.sync()); // save to disk. Should succeed, but return false (no need to run update-mime-database)
115
116
        QVERIFY(!data.isDirty());
        // Check what's on disk by creating another MimeTypeData instance
117
        MimeTypeData data2(QStringLiteral("text"));
118
119
120
121
        QCOMPARE(data2.autoEmbed(), MimeTypeData::Yes);
        QVERIFY(!data2.isDirty());
        data2.setAutoEmbed(MimeTypeData::No); // revert to default, for next time
        QVERIFY(data2.isDirty());
122
        QVERIFY(!data2.sync());
123
124
125
126
127
        QVERIFY(!data2.isDirty());

        // TODO test askSave after cleaning up the code
    }

128
129
    void testMimeTypeAutoEmbed()
    {
130
        QMimeDatabase db;
131
132
133
134
        MimeTypeData data(db.mimeTypeForName(QStringLiteral("text/plain")));
        QCOMPARE(data.majorType(), QStringLiteral("text"));
        QCOMPARE(data.minorType(), QStringLiteral("plain"));
        QCOMPARE(data.name(), QStringLiteral("text/plain"));
135
136
137
138
139
140
        QVERIFY(!data.isMeta());
        QCOMPARE(data.autoEmbed(), MimeTypeData::UseGroupSetting);
        QVERIFY(!data.isDirty());
        data.setAutoEmbed(MimeTypeData::Yes);
        QCOMPARE(data.autoEmbed(), MimeTypeData::Yes);
        QVERIFY(data.isDirty());
141
        QVERIFY(!data.sync()); // save to disk. Should succeed, but return false (no need to run update-mime-database)
142
143
        QVERIFY(!data.isDirty());
        // Check what's on disk by creating another MimeTypeData instance
144
        MimeTypeData data2(db.mimeTypeForName(QStringLiteral("text/plain")));
145
146
147
148
        QCOMPARE(data2.autoEmbed(), MimeTypeData::Yes);
        QVERIFY(!data2.isDirty());
        data2.setAutoEmbed(MimeTypeData::UseGroupSetting); // revert to default, for next time
        QVERIFY(data2.isDirty());
149
        QVERIFY(!data2.sync());
150
151
152
        QVERIFY(!data2.isDirty());
    }

153
154
    void testMimeTypePatterns()
    {
155
        // Given the text/plain mimetype
156
        QMimeDatabase db;
157
158
159
160
        MimeTypeData data(db.mimeTypeForName(QStringLiteral("text/plain")));
        QCOMPARE(data.name(), QStringLiteral("text/plain"));
        QCOMPARE(data.majorType(), QStringLiteral("text"));
        QCOMPARE(data.minorType(), QStringLiteral("plain"));
161
162
        QVERIFY(!data.isMeta());
        QStringList patterns = data.patterns();
163
164
        QVERIFY(patterns.contains(QStringLiteral("*.txt")));
        QVERIFY(!patterns.contains(QStringLiteral("*.toto")));
165
166

        // When the user changes the patterns
167
        const QStringList origPatterns = patterns;
168
169
        patterns.removeAll(QStringLiteral("*.txt"));
        patterns.append(QStringLiteral("*.toto")); // yes, a french guy wrote this, as you can see
170
        patterns.sort(); // for future comparisons
171
172
173
        QVERIFY(!data.isDirty());
        data.setPatterns(patterns);
        QVERIFY(data.isDirty());
David Faure's avatar
David Faure committed
174
        const bool needUpdateMimeDb = data.sync();
175
        QVERIFY(needUpdateMimeDb);
176
        MimeTypeWriter::runUpdateMimeDatabase();
177
178

        // Then the GUI and the QMimeDatabase API should show the new patterns
179
        QCOMPARE(data.patterns(), patterns);
180
        data.refresh(); // reload from the xml
181
        QCOMPARE(data.patterns(), patterns);
182
        // Check what's in QMimeDatabase
183
        QStringList newPatterns = db.mimeTypeForName(QStringLiteral("text/plain")).globPatterns();
184
        newPatterns.sort();
David Faure's avatar
David Faure committed
185
        qDebug() << "QMimeDatabase says" << newPatterns << "we just saved" << patterns;
186
        QCOMPARE(newPatterns, patterns);
187
188
189
        if (newPatterns == patterns) { // TODO Qt6: remove the if (keep the QVERIFY!)
            QVERIFY(!data.isDirty());
        }
190

191
        // And then removing the custom file by hand should revert to the initial state
192
193
194
        const QString packageFileName = QStandardPaths::writableLocation(
            QStandardPaths::GenericDataLocation) + QLatin1String("/mime/") + QStringLiteral(
            "packages/text-plain.xml");
195
196
        QVERIFY(!packageFileName.isEmpty());
        QFile::remove(packageFileName);
197
        MimeTypeWriter::runUpdateMimeDatabase();
198
        // Check what's in QMimeDatabase
199
        newPatterns = db.mimeTypeForName(QStringLiteral("text/plain")).globPatterns();
200
201
        newPatterns.sort();
        QCOMPARE(newPatterns, origPatterns);
202
203
    }

204
205
    void testAddService()
    {
206
        QMimeDatabase db;
207
        QString mimeTypeName = QStringLiteral("application/rtf"); // use inherited mimetype to test #321706
208
        MimeTypeData data(db.mimeTypeForName(mimeTypeName));
209
        QStringList appServices = data.appServices();
210
        //qDebug() << appServices;
211
        QVERIFY(!appServices.isEmpty());
212
213
214
215
216
        QVERIFY(!appServices.contains(fakeApplication)); // already there? hmm can't really test then
        QVERIFY(!data.isDirty());
        appServices.prepend(fakeApplication);
        data.setAppServices(appServices);
        QVERIFY(data.isDirty());
217
        QVERIFY(!data.sync()); // success, but no need to run update-mime-database
218
219
        runKBuildSycoca();
        QVERIFY(!data.isDirty());
220
221
        // Check what's in ksycoca
        checkMimeTypeServices(mimeTypeName, appServices);
222
223
        // Check what's in mimeapps.list
        checkAddedAssociationsContains(mimeTypeName, fakeApplication);
224

225
226
227
228
229
230
231
232
233
234
235
236
        // 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);

237
238
239
        // Then we get the signal that kbuildsycoca changed
        data.refresh();

240
        // Now test removing (in the same test, since it's inter-dependent)
241
        QVERIFY(appServices.removeAll(fakeApplication) > 0);
242
243
        data.setAppServices(appServices);
        QVERIFY(data.isDirty());
244
        QVERIFY(!data.sync()); // success, but no need to run update-mime-database
245
        runKBuildSycoca();
246
247
248
249
        // Check what's in ksycoca
        checkMimeTypeServices(mimeTypeName, appServices);
        // Check what's in mimeapps.list
        checkRemovedAssociationsContains(mimeTypeName, fakeApplication);
250
251
    }

252
253
    void testRemoveTwice()
    {
254
        QMimeDatabase db;
255
        // Remove fakeApplication from image/png
256
        QString mimeTypeName = QStringLiteral("image/png");
257
        MimeTypeData data(db.mimeTypeForName(mimeTypeName));
258
        QStringList appServices = data.appServices();
259
        qDebug() << "initial list for" << mimeTypeName << appServices;
260
261
262
263
264
265
266
267
268
269
        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"
270
        qDebug() << "Removing fakeApplication2";
271
272
273
274
275
276
277
278
279
280
        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);
281
282

        // And now re-add fakeApplication2...
283
        qDebug() << "Re-adding fakeApplication2";
284
285
286
287
288
289
290
291
292
        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);
293
    }
294

295
296
    void testCreateMimeType()
    {
297
        QMimeDatabase db;
298
        const QString mimeTypeName = QStringLiteral("fake/unit-test-fake-mimetype");
299
        // Clean up after previous runs if necessary
300
        if (MimeTypeWriter::hasDefinitionFile(mimeTypeName)) {
301
            MimeTypeWriter::removeOwnMimeType(mimeTypeName);
302
        }
303

304
        MimeTypeData data(mimeTypeName, true);
305
306
        data.setComment(QStringLiteral("Fake MimeType"));
        QStringList patterns = QStringList() << QStringLiteral("*.pkg.tar.gz");
307
308
309
310
        data.setPatterns(patterns);
        QVERIFY(data.isDirty());
        QVERIFY(data.sync());
        MimeTypeWriter::runUpdateMimeDatabase();
311
        qmime_secondsBetweenChecks = 0; // ensure QMimeDatabase sees it immediately
312
313
        QMimeType mime = db.mimeTypeForName(mimeTypeName);
        QVERIFY(mime.isValid());
314
        QCOMPARE(mime.comment(), QStringLiteral("Fake MimeType"));
315
        QCOMPARE(mime.globPatterns(), patterns); // must sort them if more than one
316
317

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

322
323
324
325
326
        m_mimeTypeCreatedSuccessfully = true;
    }

    void testDeleteMimeType()
    {
327
        QMimeDatabase db;
328
        if (!m_mimeTypeCreatedSuccessfully) {
329
            QSKIP("This test relies on testCreateMimeType");
330
        }
331
        const QString mimeTypeName = QStringLiteral("fake/unit-test-fake-mimetype");
332
333
334
        QVERIFY(MimeTypeWriter::hasDefinitionFile(mimeTypeName));
        MimeTypeWriter::removeOwnMimeType(mimeTypeName);
        MimeTypeWriter::runUpdateMimeDatabase();
335
336
        const QMimeType mime = db.mimeTypeForName(mimeTypeName);
        QVERIFY2(!mime.isValid(), qPrintable(mimeTypeName));
337
    }
338

339
340
    void testModifyMimeTypeComment() // of a system mimetype. And check that it's re-read correctly.
    {
341
        QMimeDatabase db;
342
        QString mimeTypeName = QStringLiteral("image/png");
343
        MimeTypeData data(db.mimeTypeForName(mimeTypeName));
344
        QCOMPARE(data.comment(), QString::fromLatin1("PNG image"));
345
        QString fakeComment = QStringLiteral("PNG image [testing]");
346
347
348
349
350
        data.setComment(fakeComment);
        QVERIFY(data.isDirty());
        QVERIFY(data.sync());
        MimeTypeWriter::runUpdateMimeDatabase();
        //runKBuildSycoca();
351
352
        QMimeType mime = db.mimeTypeForName(mimeTypeName);
        QVERIFY(mime.isValid());
353
        QCOMPARE(mime.comment(), fakeComment);
354
355
356
357
358
359

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

360
361
362
    void cleanupTestCase()
    {
        // If we remove it, then every run of the unit test has to run kbuildsycoca... slow.
363
        //QFile::remove(QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation) + QLatin1Char('/') + "fakeapplication.desktop");
364
    }
365
366
367

private: // helper methods

368
    void checkAddedAssociationsContains(const QString &mimeTypeName, const QString &application)
369
    {
370
371
        const KConfig config(m_localConfig.filePath(QStringLiteral("mimeapps.list")),
                             KConfig::NoGlobals);
372
373
374
        const KConfigGroup group(&config, "Added Associations");
        const QStringList addedEntries = group.readXdgListEntry(mimeTypeName);
        if (!addedEntries.contains(application)) {
375
            qWarning() << addedEntries << "does not contain" << application;
376
377
378
379
            QVERIFY(addedEntries.contains(application));
        }
    }

380
    void checkRemovedAssociationsContains(const QString &mimeTypeName, const QString &application)
381
    {
382
383
        const KConfig config(m_localConfig.filePath(QStringLiteral("mimeapps.list")),
                             KConfig::NoGlobals);
384
385
386
        const KConfigGroup group(&config, "Removed Associations");
        const QStringList removedEntries = group.readXdgListEntry(mimeTypeName);
        if (!removedEntries.contains(application)) {
387
            qWarning() << removedEntries << "does not contain" << application;
388
389
390
391
            QVERIFY(removedEntries.contains(application));
        }
    }

392
393
    void checkRemovedAssociationsDoesNotContain(const QString &mimeTypeName,
                                                const QString &application)
394
    {
395
396
        const KConfig config(m_localConfig.filePath(QStringLiteral("mimeapps.list")),
                             KConfig::NoGlobals);
397
398
399
        const KConfigGroup group(&config, "Removed Associations");
        const QStringList removedEntries = group.readXdgListEntry(mimeTypeName);
        if (removedEntries.contains(application)) {
400
            qWarning() << removedEntries << "contains" << application;
401
402
403
404
            QVERIFY(!removedEntries.contains(application));
        }
    }

405
406
407
408
    void runKBuildSycoca()
    {
        // Wait for notifyDatabaseChanged DBus signal
        // (The real KCM code simply does the refresh in a slot, asynchronously)
409
410

        QProcess proc;
411
        //proc.setProcessChannelMode(QProcess::ForwardedChannels);
412
413
        const QString kbuildsycoca
            = QStandardPaths::findExecutable(QLatin1String(KBUILDSYCOCA_EXENAME));
414
415
        QVERIFY(!kbuildsycoca.isEmpty());
        QStringList args;
416
        args << QStringLiteral("--testmode");
417
        proc.start(kbuildsycoca, args);
418
        QSignalSpy spy(KSycoca::self(), SIGNAL(databaseChanged(QStringList)));
419
420
421
422
        proc.waitForFinished();
        qDebug() << "waiting for signal";
        QVERIFY(spy.wait(10000));
        qDebug() << "got signal";
423
424
    }

425
    bool createDesktopFile(const QString &path)
426
427
428
429
430
431
432
433
434
435
436
437
438
    {
        if (!QFile::exists(path)) {
            KDesktopFile file(path);
            KConfigGroup group = file.desktopGroup();
            group.writeEntry("Name", "FakeApplication");
            group.writeEntry("Type", "Application");
            group.writeEntry("Exec", "ls");
            group.writeEntry("MimeType", "image/png");
            return true;
        }
        return false;
    }

439
    void checkMimeTypeServices(const QString &mimeTypeName, const QStringList &expectedServices)
440
    {
441
442
        QMimeDatabase db;
        MimeTypeData data2(db.mimeTypeForName(mimeTypeName));
443
        if (data2.appServices() != expectedServices) {
444
            qDebug() << "got" << data2.appServices() << "expected" << expectedServices;
445
        }
446
447
448
449
450
451
        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
452
    QDir m_localConfig;
453
    bool m_mimeTypeCreatedSuccessfully;
454
455
};

456
QTEST_MAIN(FileTypesTest)
457
458

#include "filetypestest.moc"