1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the test suite of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
21 | ** included in the packaging of this file. Please review the following |
22 | ** information to ensure the GNU General Public License requirements will |
23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
24 | ** |
25 | ** $QT_END_LICENSE$ |
26 | ** |
27 | ****************************************************************************/ |
28 | |
29 | #include <qmimedatabase.h> |
30 | |
31 | #include "qstandardpaths.h" |
32 | |
33 | #ifdef Q_OS_UNIX |
34 | #include <sys/types.h> |
35 | #include <sys/stat.h> |
36 | #endif |
37 | |
38 | #include <QtCore/QElapsedTimer> |
39 | #include <QtCore/QFile> |
40 | #include <QtCore/QFileInfo> |
41 | #include <QtCore/QStandardPaths> |
42 | #include <QtCore/QTemporaryDir> |
43 | #include <QtCore/QTextStream> |
44 | #include <QtConcurrent/QtConcurrentRun> |
45 | |
46 | #include <QtTest/QtTest> |
47 | |
48 | static const char *const additionalMimeFiles[] = { |
49 | "yast2-metapackage-handler-mimetypes.xml" , |
50 | "qml-again.xml" , |
51 | "text-x-objcsrc.xml" , |
52 | "invalid-magic1.xml" , |
53 | "invalid-magic2.xml" , |
54 | "invalid-magic3.xml" , |
55 | "magic-and-hierarchy.xml" , |
56 | 0 |
57 | }; |
58 | |
59 | #define RESOURCE_PREFIX ":/qt-project.org/qmime/" |
60 | |
61 | void initializeLang() |
62 | { |
63 | qputenv(varName: "LC_ALL" , value: "" ); |
64 | qputenv(varName: "LANG" , value: "C" ); |
65 | QCoreApplication::setApplicationName("tst_qmimedatabase" ); // temporary directory pattern |
66 | } |
67 | |
68 | static inline QString testSuiteWarning() |
69 | { |
70 | |
71 | QString result; |
72 | QTextStream str(&result); |
73 | str << "\nCannot find the shared-mime-info test suite\nstarting from: " |
74 | << QDir::toNativeSeparators(pathName: QDir::currentPath()) << "\n" |
75 | "cd " << QDir::toNativeSeparators(QStringLiteral("tests/auto/corelib/mimetypes/qmimedatabase" )) << "\n" |
76 | "wget http://cgit.freedesktop.org/xdg/shared-mime-info/snapshot/Release-1-10.zip\n" |
77 | "unzip Release-1-10.zip\n" ; |
78 | #ifdef Q_OS_WIN |
79 | str << "mkdir testfiles\nxcopy /s Release-1-10 s-m-i\n" ; |
80 | #else |
81 | str << "ln -s Release-1-10 s-m-i\n" ; |
82 | #endif |
83 | return result; |
84 | } |
85 | |
86 | static bool copyResourceFile(const QString &sourceFileName, const QString &targetFileName, |
87 | QString *errorMessage) |
88 | { |
89 | |
90 | QFile sourceFile(sourceFileName); |
91 | if (!sourceFile.exists()) { |
92 | *errorMessage = QDir::toNativeSeparators(pathName: sourceFileName) + QLatin1String(" does not exist." ); |
93 | return false; |
94 | } |
95 | if (!sourceFile.copy(newName: targetFileName)) { |
96 | *errorMessage = QLatin1String("Cannot copy " ) |
97 | + QDir::toNativeSeparators(pathName: sourceFileName) + QLatin1String(" to " ) |
98 | + QDir::toNativeSeparators(pathName: targetFileName) + QLatin1String(": " ) |
99 | + sourceFile.errorString(); |
100 | return false; |
101 | } |
102 | // QFile::copy() sets the permissions of the source file which are read-only for |
103 | // resource files. Set write permission to enable deletion of the temporary directory. |
104 | QFile targetFile(targetFileName); |
105 | if (!targetFile.setPermissions(targetFile.permissions() | QFileDevice::WriteUser)) { |
106 | *errorMessage = QLatin1String("Cannot set write permission on " ) |
107 | + QDir::toNativeSeparators(pathName: targetFileName) + QLatin1String(": " ) |
108 | + targetFile.errorString(); |
109 | return false; |
110 | } |
111 | return true; |
112 | } |
113 | |
114 | // Set LANG before QCoreApplication is created |
115 | Q_CONSTRUCTOR_FUNCTION(initializeLang) |
116 | |
117 | static QString seedAndTemplate() |
118 | { |
119 | return QDir::tempPath() + "/tst_qmimedatabase-XXXXXX" ; |
120 | } |
121 | |
122 | tst_QMimeDatabase::tst_QMimeDatabase() |
123 | : m_temporaryDir(seedAndTemplate()) |
124 | { |
125 | } |
126 | |
127 | void tst_QMimeDatabase::initTestCase() |
128 | { |
129 | QLocale::setDefault(QLocale::c()); |
130 | QVERIFY2(m_temporaryDir.isValid(), qPrintable(m_temporaryDir.errorString())); |
131 | QStandardPaths::setTestModeEnabled(true); |
132 | m_localMimeDir = QStandardPaths::writableLocation(type: QStandardPaths::GenericDataLocation) + "/mime" ; |
133 | if (QDir(m_localMimeDir).exists()) { |
134 | QVERIFY2(QDir(m_localMimeDir).removeRecursively(), qPrintable(m_localMimeDir + ": " + qt_error_string())); |
135 | } |
136 | QString errorMessage; |
137 | |
138 | #ifdef USE_XDG_DATA_DIRS |
139 | // Create a temporary "global" XDG data dir for later use |
140 | // It will initially contain a copy of freedesktop.org.xml |
141 | QVERIFY2(m_temporaryDir.isValid(), |
142 | ("Could not create temporary subdir: " + m_temporaryDir.errorString()).toUtf8()); |
143 | const QDir here = QDir(m_temporaryDir.path()); |
144 | m_globalXdgDir = m_temporaryDir.path() + QStringLiteral("/global" ); |
145 | const QString globalPackageDir = m_globalXdgDir + QStringLiteral("/mime/packages" ); |
146 | QVERIFY(here.mkpath(globalPackageDir)); |
147 | |
148 | qputenv(varName: "XDG_DATA_DIRS" , value: QFile::encodeName(fileName: m_globalXdgDir)); |
149 | qDebug() << "\nGlobal XDG_DATA_DIRS: " << m_globalXdgDir; |
150 | |
151 | const QString freeDesktopXml = QStringLiteral("freedesktop.org.xml" ); |
152 | const QString xmlFileName = QLatin1String(RESOURCE_PREFIX "packages/" ) + freeDesktopXml; |
153 | const QString xmlTargetFileName = globalPackageDir + QLatin1Char('/') + freeDesktopXml; |
154 | QVERIFY2(copyResourceFile(xmlFileName, xmlTargetFileName, &errorMessage), qPrintable(errorMessage)); |
155 | #endif |
156 | |
157 | m_testSuite = QFINDTESTDATA("s-m-i/tests" ); |
158 | if (m_testSuite.isEmpty()) |
159 | qWarning(msg: "%s" , qPrintable(testSuiteWarning())); |
160 | |
161 | errorMessage = QString::fromLatin1(str: "Cannot find '%1'" ); |
162 | for (uint i = 0; i < sizeof additionalMimeFiles / sizeof additionalMimeFiles[0] - 1; i++) { |
163 | const QString resourceFilePath = QString::fromLatin1(RESOURCE_PREFIX) + QLatin1String(additionalMimeFiles[i]); |
164 | QVERIFY2(QFile::exists(resourceFilePath), qPrintable(errorMessage.arg(resourceFilePath))); |
165 | m_additionalMimeFileNames.append(t: QLatin1String(additionalMimeFiles[i])); |
166 | m_additionalMimeFilePaths.append(t: resourceFilePath); |
167 | } |
168 | |
169 | initTestCaseInternal(); |
170 | m_isUsingCacheProvider = !qEnvironmentVariableIsSet(varName: "QT_NO_MIME_CACHE" ); |
171 | } |
172 | |
173 | void tst_QMimeDatabase::init() |
174 | { |
175 | // clean up local data from previous runs |
176 | QDir(m_localMimeDir).removeRecursively(); |
177 | } |
178 | |
179 | void tst_QMimeDatabase::cleanupTestCase() |
180 | { |
181 | QDir(m_localMimeDir).removeRecursively(); |
182 | } |
183 | |
184 | void tst_QMimeDatabase::mimeTypeForName() |
185 | { |
186 | QMimeDatabase db; |
187 | QMimeType s0 = db.mimeTypeForName(nameOrAlias: QString::fromLatin1(str: "application/x-zerosize" )); |
188 | QVERIFY(s0.isValid()); |
189 | QCOMPARE(s0.name(), QString::fromLatin1("application/x-zerosize" )); |
190 | QCOMPARE(s0.comment(), QString::fromLatin1("empty document" )); |
191 | |
192 | QMimeType s0Again = db.mimeTypeForName(nameOrAlias: QString::fromLatin1(str: "application/x-zerosize" )); |
193 | QCOMPARE(s0Again.name(), s0.name()); |
194 | |
195 | QMimeType s1 = db.mimeTypeForName(nameOrAlias: QString::fromLatin1(str: "text/plain" )); |
196 | QVERIFY(s1.isValid()); |
197 | QCOMPARE(s1.name(), QString::fromLatin1("text/plain" )); |
198 | //qDebug("Comment is %s", qPrintable(s1.comment())); |
199 | |
200 | QMimeType krita = db.mimeTypeForName(nameOrAlias: QString::fromLatin1(str: "application/x-krita" )); |
201 | QVERIFY(krita.isValid()); |
202 | |
203 | // Test <comment> parsing with application/rdf+xml which has the english comment after the other ones |
204 | QMimeType rdf = db.mimeTypeForName(nameOrAlias: QString::fromLatin1(str: "application/rdf+xml" )); |
205 | QVERIFY(rdf.isValid()); |
206 | QCOMPARE(rdf.comment(), QString::fromLatin1("RDF file" )); |
207 | |
208 | QMimeType bzip2 = db.mimeTypeForName(nameOrAlias: QString::fromLatin1(str: "application/x-bzip2" )); |
209 | QVERIFY(bzip2.isValid()); |
210 | QCOMPARE(bzip2.comment(), QString::fromLatin1("Bzip archive" )); |
211 | |
212 | QMimeType defaultMime = db.mimeTypeForName(nameOrAlias: QString::fromLatin1(str: "application/octet-stream" )); |
213 | QVERIFY(defaultMime.isValid()); |
214 | QVERIFY(defaultMime.isDefault()); |
215 | |
216 | QMimeType doesNotExist = db.mimeTypeForName(nameOrAlias: QString::fromLatin1(str: "foobar/x-doesnot-exist" )); |
217 | QVERIFY(!doesNotExist.isValid()); |
218 | QCOMPARE(doesNotExist.comment(), QString()); |
219 | QCOMPARE(doesNotExist.aliases(), QStringList()); |
220 | |
221 | // TODO move to findByFile |
222 | #ifdef Q_OS_LINUX |
223 | QString exePath = QStandardPaths::findExecutable(executableName: QLatin1String("ls" )); |
224 | if (exePath.isEmpty()) |
225 | qWarning() << "ls not found" ; |
226 | else { |
227 | const QString executableType = QString::fromLatin1(str: "application/x-executable" ); |
228 | const QString sharedLibType = QString::fromLatin1(str: "application/x-sharedlib" ); |
229 | //QTest::newRow("executable") << exePath << executableType; |
230 | QVERIFY(db.mimeTypeForFile(exePath).name() == executableType || |
231 | db.mimeTypeForFile(exePath).name() == sharedLibType); |
232 | } |
233 | #endif |
234 | |
235 | } |
236 | |
237 | void tst_QMimeDatabase::mimeTypeForFileName_data() |
238 | { |
239 | QTest::addColumn<QString>(name: "fileName" ); |
240 | QTest::addColumn<QString>(name: "expectedMimeType" ); |
241 | |
242 | QTest::newRow(dataTag: "text" ) << "textfile.txt" << "text/plain" ; |
243 | QTest::newRow(dataTag: "case-insensitive search" ) << "textfile.TxT" << "text/plain" ; |
244 | |
245 | // Needs shared-mime-info > 0.91. Earlier versions wrote .Z to the mime.cache file... |
246 | //QTest::newRow("case-insensitive match on a non-lowercase glob") << "foo.z" << "application/x-compress"; |
247 | |
248 | QTest::newRow(dataTag: "case-sensitive uppercase match" ) << "textfile.C" << "text/x-c++src" ; |
249 | QTest::newRow(dataTag: "case-sensitive lowercase match" ) << "textfile.c" << "text/x-csrc" ; |
250 | QTest::newRow(dataTag: "case-sensitive long-extension match" ) << "foo.PS.gz" << "application/x-gzpostscript" ; |
251 | QTest::newRow(dataTag: "case-sensitive-only match" ) << "core" << "application/x-core" ; |
252 | QTest::newRow(dataTag: "case-sensitive-only match" ) << "Core" << "application/octet-stream" ; // #198477 |
253 | |
254 | QTest::newRow(dataTag: "desktop file" ) << "foo.desktop" << "application/x-desktop" ; |
255 | QTest::newRow(dataTag: "old kdelnk file is x-desktop too" ) << "foo.kdelnk" << "application/x-desktop" ; |
256 | QTest::newRow(dataTag: "double-extension file" ) << "foo.tar.bz2" << "application/x-bzip-compressed-tar" ; |
257 | QTest::newRow(dataTag: "single-extension file" ) << "foo.bz2" << "application/x-bzip" ; |
258 | QTest::newRow(dataTag: ".doc should assume msword" ) << "somefile.doc" << "application/msword" ; // #204139 |
259 | QTest::newRow(dataTag: "glob that uses [] syntax, 1" ) << "Makefile" << "text/x-makefile" ; |
260 | QTest::newRow(dataTag: "glob that uses [] syntax, 2" ) << "makefile" << "text/x-makefile" ; |
261 | QTest::newRow(dataTag: "glob that ends with *, no extension" ) << "README" << "text/x-readme" ; |
262 | QTest::newRow(dataTag: "glob that ends with *, extension" ) << "README.foo" << "text/x-readme" ; |
263 | QTest::newRow(dataTag: "glob that ends with *, also matches *.txt. Higher weight wins." ) << "README.txt" << "text/plain" ; |
264 | QTest::newRow(dataTag: "glob that ends with *, also matches *.nfo. Higher weight wins." ) << "README.nfo" << "text/x-nfo" ; |
265 | // fdo bug 15436, needs shared-mime-info >= 0.40 (and this tests the globs2-parsing code). |
266 | QTest::newRow(dataTag: "glob that ends with *, also matches *.pdf. *.pdf has higher weight" ) << "README.pdf" << "application/pdf" ; |
267 | QTest::newRow(dataTag: "directory" ) << "/" << "inode/directory" ; |
268 | QTest::newRow(dataTag: "doesn't exist, no extension" ) << "IDontExist" << "application/octet-stream" ; |
269 | QTest::newRow(dataTag: "doesn't exist but has known extension" ) << "IDontExist.txt" << "text/plain" ; |
270 | QTest::newRow(dataTag: "empty" ) << "" << "application/octet-stream" ; |
271 | } |
272 | |
273 | static inline QByteArray msgMimeTypeForFileNameFailed(const QList<QMimeType> &actual, |
274 | const QString &expected) |
275 | { |
276 | QByteArray result = "Actual (" ; |
277 | foreach (const QMimeType &m, actual) { |
278 | result += m.name().toLocal8Bit(); |
279 | result += ' '; |
280 | } |
281 | result += ") , expected: " ; |
282 | result += expected.toLocal8Bit(); |
283 | return result; |
284 | } |
285 | |
286 | void tst_QMimeDatabase::mimeTypeForFileName() |
287 | { |
288 | QFETCH(QString, fileName); |
289 | QFETCH(QString, expectedMimeType); |
290 | QMimeDatabase db; |
291 | QMimeType mime = db.mimeTypeForFile(fileName, mode: QMimeDatabase::MatchExtension); |
292 | QVERIFY(mime.isValid()); |
293 | QCOMPARE(mime.name(), expectedMimeType); |
294 | |
295 | QList<QMimeType> mimes = db.mimeTypesForFileName(fileName); |
296 | if (expectedMimeType == "application/octet-stream" ) { |
297 | QVERIFY(mimes.isEmpty()); |
298 | } else { |
299 | QVERIFY2(!mimes.isEmpty(), msgMimeTypeForFileNameFailed(mimes, expectedMimeType).constData()); |
300 | QVERIFY2(mimes.count() == 1, msgMimeTypeForFileNameFailed(mimes, expectedMimeType).constData()); |
301 | QCOMPARE(mimes.first().name(), expectedMimeType); |
302 | } |
303 | } |
304 | |
305 | void tst_QMimeDatabase::mimeTypesForFileName_data() |
306 | { |
307 | QTest::addColumn<QString>(name: "fileName" ); |
308 | QTest::addColumn<QStringList>(name: "expectedMimeTypes" ); |
309 | |
310 | QTest::newRow(dataTag: "txt, 1 hit" ) << "foo.txt" << (QStringList() << "text/plain" ); |
311 | QTest::newRow(dataTag: "txtfoobar, 0 hit" ) << "foo.foobar" << QStringList(); |
312 | QTest::newRow(dataTag: "m, 2 hits" ) << "foo.m" << (QStringList() << "text/x-matlab" << "text/x-objcsrc" ); |
313 | QTest::newRow(dataTag: "sub, 3 hits" ) << "foo.sub" << (QStringList() << "text/x-microdvd" << "text/x-mpsub" << "text/x-subviewer" ); |
314 | QTest::newRow(dataTag: "non_ascii" ) << QString::fromUtf8(str: "AÄ°Ä°A.pdf" ) << (QStringList() << "application/pdf" ); |
315 | } |
316 | |
317 | void tst_QMimeDatabase::mimeTypesForFileName() |
318 | { |
319 | QFETCH(QString, fileName); |
320 | QFETCH(QStringList, expectedMimeTypes); |
321 | QMimeDatabase db; |
322 | QList<QMimeType> mimes = db.mimeTypesForFileName(fileName); |
323 | QStringList mimeNames; |
324 | foreach (const QMimeType &mime, mimes) |
325 | mimeNames.append(t: mime.name()); |
326 | QCOMPARE(mimeNames, expectedMimeTypes); |
327 | } |
328 | |
329 | void tst_QMimeDatabase::inheritance() |
330 | { |
331 | QMimeDatabase db; |
332 | |
333 | // All file-like mimetypes inherit from octet-stream |
334 | const QMimeType wordperfect = db.mimeTypeForName(nameOrAlias: QString::fromLatin1(str: "application/vnd.wordperfect" )); |
335 | QVERIFY(wordperfect.isValid()); |
336 | QCOMPARE(wordperfect.parentMimeTypes().join(QString::fromLatin1("," )), QString::fromLatin1("application/octet-stream" )); |
337 | QVERIFY(wordperfect.inherits(QLatin1String("application/octet-stream" ))); |
338 | |
339 | QVERIFY(db.mimeTypeForName(QString::fromLatin1("image/svg+xml-compressed" )).inherits(QLatin1String("application/x-gzip" ))); |
340 | |
341 | // Check that msword derives from ole-storage |
342 | const QMimeType msword = db.mimeTypeForName(nameOrAlias: QString::fromLatin1(str: "application/msword" )); |
343 | QVERIFY(msword.isValid()); |
344 | const QMimeType olestorage = db.mimeTypeForName(nameOrAlias: QString::fromLatin1(str: "application/x-ole-storage" )); |
345 | QVERIFY(olestorage.isValid()); |
346 | QVERIFY(msword.inherits(olestorage.name())); |
347 | QVERIFY(msword.inherits(QLatin1String("application/octet-stream" ))); |
348 | |
349 | const QMimeType directory = db.mimeTypeForName(nameOrAlias: QString::fromLatin1(str: "inode/directory" )); |
350 | QVERIFY(directory.isValid()); |
351 | QCOMPARE(directory.parentMimeTypes().count(), 0); |
352 | QVERIFY(!directory.inherits(QLatin1String("application/octet-stream" ))); |
353 | |
354 | // Check that text/x-patch knows that it inherits from text/plain (it says so explicitly) |
355 | const QMimeType plain = db.mimeTypeForName(nameOrAlias: QString::fromLatin1(str: "text/plain" )); |
356 | const QMimeType derived = db.mimeTypeForName(nameOrAlias: QString::fromLatin1(str: "text/x-patch" )); |
357 | QVERIFY(derived.isValid()); |
358 | QCOMPARE(derived.parentMimeTypes().join(QString::fromLatin1("," )), plain.name()); |
359 | QVERIFY(derived.inherits(QLatin1String("text/plain" ))); |
360 | QVERIFY(derived.inherits(QLatin1String("application/octet-stream" ))); |
361 | |
362 | // Check that application/x-shellscript inherits from application/x-executable |
363 | // (Otherwise KRun cannot start shellscripts...) |
364 | // This is a test for multiple inheritance... |
365 | const QMimeType shellscript = db.mimeTypeForName(nameOrAlias: QString::fromLatin1(str: "application/x-shellscript" )); |
366 | QVERIFY(shellscript.isValid()); |
367 | QVERIFY(shellscript.inherits(QLatin1String("text/plain" ))); |
368 | QVERIFY(shellscript.inherits(QLatin1String("application/x-executable" ))); |
369 | const QStringList shellParents = shellscript.parentMimeTypes(); |
370 | QVERIFY(shellParents.contains(QLatin1String("text/plain" ))); |
371 | QVERIFY(shellParents.contains(QLatin1String("application/x-executable" ))); |
372 | QCOMPARE(shellParents.count(), 2); // only the above two |
373 | const QStringList allShellAncestors = shellscript.allAncestors(); |
374 | QVERIFY(allShellAncestors.contains(QLatin1String("text/plain" ))); |
375 | QVERIFY(allShellAncestors.contains(QLatin1String("application/x-executable" ))); |
376 | QVERIFY(allShellAncestors.contains(QLatin1String("application/octet-stream" ))); |
377 | // Must be least-specific last, i.e. breadth first. |
378 | QCOMPARE(allShellAncestors.last(), QString::fromLatin1("application/octet-stream" )); |
379 | |
380 | const QStringList allSvgAncestors = db.mimeTypeForName(nameOrAlias: QString::fromLatin1(str: "image/svg+xml" )).allAncestors(); |
381 | QCOMPARE(allSvgAncestors, QStringList() << QLatin1String("application/xml" ) << QLatin1String("text/plain" ) << QLatin1String("application/octet-stream" )); |
382 | |
383 | // Check that text/x-mrml knows that it inherits from text/plain (implicitly) |
384 | const QMimeType mrml = db.mimeTypeForName(nameOrAlias: QString::fromLatin1(str: "text/x-mrml" )); |
385 | QVERIFY(mrml.isValid()); |
386 | QVERIFY(mrml.inherits(QLatin1String("text/plain" ))); |
387 | QVERIFY(mrml.inherits(QLatin1String("application/octet-stream" ))); |
388 | |
389 | // Check that msword-template inherits msword |
390 | const QMimeType mswordTemplate = db.mimeTypeForName(nameOrAlias: QString::fromLatin1(str: "application/msword-template" )); |
391 | QVERIFY(mswordTemplate.isValid()); |
392 | QVERIFY(mswordTemplate.inherits(QLatin1String("application/msword" ))); |
393 | } |
394 | |
395 | void tst_QMimeDatabase::aliases() |
396 | { |
397 | QMimeDatabase db; |
398 | |
399 | const QMimeType canonical = db.mimeTypeForName(nameOrAlias: QString::fromLatin1(str: "application/xml" )); |
400 | QVERIFY(canonical.isValid()); |
401 | |
402 | QMimeType resolvedAlias = db.mimeTypeForName(nameOrAlias: QString::fromLatin1(str: "text/xml" )); |
403 | QVERIFY(resolvedAlias.isValid()); |
404 | QCOMPARE(resolvedAlias.name(), QString::fromLatin1("application/xml" )); |
405 | |
406 | QVERIFY(resolvedAlias.inherits(QLatin1String("application/xml" ))); |
407 | QVERIFY(canonical.inherits(QLatin1String("text/xml" ))); |
408 | |
409 | // Test for kde bug 197346: does nspluginscan see that audio/mp3 already exists? |
410 | bool mustWriteMimeType = !db.mimeTypeForName(nameOrAlias: QString::fromLatin1(str: "audio/mp3" )).isValid(); |
411 | QVERIFY(!mustWriteMimeType); |
412 | } |
413 | |
414 | void tst_QMimeDatabase::listAliases_data() |
415 | { |
416 | QTest::addColumn<QString>(name: "inputMime" ); |
417 | QTest::addColumn<QString>(name: "expectedAliases" ); |
418 | |
419 | QTest::newRow(dataTag: "csv" ) << "text/csv" << "text/x-csv,text/x-comma-separated-values" ; |
420 | QTest::newRow(dataTag: "xml" ) << "application/xml" << "text/xml" ; |
421 | QTest::newRow(dataTag: "xml2" ) << "text/xml" /* gets resolved to application/xml */ << "text/xml" ; |
422 | QTest::newRow(dataTag: "no_mime" ) << "message/news" << "" ; |
423 | } |
424 | |
425 | void tst_QMimeDatabase::listAliases() |
426 | { |
427 | QFETCH(QString, inputMime); |
428 | QFETCH(QString, expectedAliases); |
429 | QMimeDatabase db; |
430 | QStringList expectedAliasesList = expectedAliases.split(sep: ',', behavior: Qt::SkipEmptyParts); |
431 | expectedAliasesList.sort(); |
432 | QMimeType mime = db.mimeTypeForName(nameOrAlias: inputMime); |
433 | QVERIFY(mime.isValid()); |
434 | QStringList aliasList = mime.aliases(); |
435 | aliasList.sort(); |
436 | QCOMPARE(aliasList, expectedAliasesList); |
437 | } |
438 | |
439 | void tst_QMimeDatabase::icons() |
440 | { |
441 | QMimeDatabase db; |
442 | QMimeType directory = db.mimeTypeForFile(fileName: QString::fromLatin1(str: "/" )); |
443 | QCOMPARE(directory.name(), QString::fromLatin1("inode/directory" )); |
444 | QCOMPARE(directory.iconName(), QString::fromLatin1("inode-directory" )); |
445 | QCOMPARE(directory.genericIconName(), QString::fromLatin1("folder" )); |
446 | |
447 | QMimeType pub = db.mimeTypeForFile(fileName: QString::fromLatin1(str: "foo.epub" ), mode: QMimeDatabase::MatchExtension); |
448 | QCOMPARE(pub.name(), QString::fromLatin1("application/epub+zip" )); |
449 | QCOMPARE(pub.iconName(), QString::fromLatin1("application-epub+zip" )); |
450 | QCOMPARE(pub.genericIconName(), QString::fromLatin1("x-office-document" )); |
451 | } |
452 | |
453 | void tst_QMimeDatabase::() |
454 | { |
455 | struct RestoreLocale |
456 | { |
457 | ~RestoreLocale() { QLocale::setDefault(QLocale::c()); } |
458 | } restoreLocale; |
459 | |
460 | QLocale::setDefault(QLocale("de" )); |
461 | QMimeDatabase db; |
462 | QMimeType directory = db.mimeTypeForName(QStringLiteral("inode/directory" )); |
463 | QCOMPARE(directory.comment(), QStringLiteral("Ordner" )); |
464 | QLocale::setDefault(QLocale("fr" )); |
465 | QCOMPARE(directory.comment(), QStringLiteral("dossier" )); |
466 | } |
467 | |
468 | // In here we do the tests that need some content in a temporary file. |
469 | // This could also be added to shared-mime-info's testsuite... |
470 | void tst_QMimeDatabase::mimeTypeForFileWithContent() |
471 | { |
472 | QMimeDatabase db; |
473 | QMimeType mime; |
474 | |
475 | // Test a real PDF file. |
476 | // If we find x-matlab because it starts with '%' then we are not ordering by priority. |
477 | QTemporaryFile tempFile; |
478 | QVERIFY(tempFile.open()); |
479 | QString tempFileName = tempFile.fileName(); |
480 | tempFile.write(data: "%PDF-" ); |
481 | tempFile.close(); |
482 | mime = db.mimeTypeForFile(fileName: tempFileName); |
483 | QCOMPARE(mime.name(), QString::fromLatin1("application/pdf" )); |
484 | QFile file(tempFileName); |
485 | mime = db.mimeTypeForData(device: &file); // QIODevice ctor |
486 | QCOMPARE(mime.name(), QString::fromLatin1("application/pdf" )); |
487 | // by name only, we cannot find the mimetype |
488 | mime = db.mimeTypeForFile(fileName: tempFileName, mode: QMimeDatabase::MatchExtension); |
489 | QVERIFY(mime.isValid()); |
490 | QVERIFY(mime.isDefault()); |
491 | |
492 | // Test the case where the extension doesn't match the contents: extension wins |
493 | { |
494 | QTemporaryFile txtTempFile(QDir::tempPath() + QLatin1String("/tst_QMimeDatabase_XXXXXX.txt" )); |
495 | QVERIFY(txtTempFile.open()); |
496 | txtTempFile.write(data: "%PDF-" ); |
497 | QString txtTempFileName = txtTempFile.fileName(); |
498 | txtTempFile.close(); |
499 | mime = db.mimeTypeForFile(fileName: txtTempFileName); |
500 | QCOMPARE(mime.name(), QString::fromLatin1("text/plain" )); |
501 | // fast mode finds the same |
502 | mime = db.mimeTypeForFile(fileName: txtTempFileName, mode: QMimeDatabase::MatchExtension); |
503 | QCOMPARE(mime.name(), QString::fromLatin1("text/plain" )); |
504 | } |
505 | |
506 | // Now the case where extension differs from contents, but contents has >80 magic rule |
507 | // XDG spec says: contents wins. But we can't sniff all files... |
508 | { |
509 | QTemporaryFile txtTempFile(QDir::tempPath() + QLatin1String("/tst_QMimeDatabase_XXXXXX.txt" )); |
510 | QVERIFY(txtTempFile.open()); |
511 | txtTempFile.write(data: "<smil" ); |
512 | QString txtTempFileName = txtTempFile.fileName(); |
513 | txtTempFile.close(); |
514 | mime = db.mimeTypeForFile(fileName: txtTempFileName); |
515 | QCOMPARE(mime.name(), QString::fromLatin1("text/plain" )); |
516 | mime = db.mimeTypeForFile(fileName: txtTempFileName, mode: QMimeDatabase::MatchContent); |
517 | QCOMPARE(mime.name(), QString::fromLatin1("application/smil+xml" )); |
518 | } |
519 | |
520 | // Test what happens with an incorrect path |
521 | mime = db.mimeTypeForFile(fileName: QString::fromLatin1(str: "file:///etc/passwd" /* incorrect code, use a path instead */)); |
522 | QVERIFY(mime.isDefault()); |
523 | |
524 | // findByData when the device cannot be opened (e.g. a directory) |
525 | QFile dir("/" ); |
526 | mime = db.mimeTypeForData(device: &dir); |
527 | QVERIFY(mime.isDefault()); |
528 | } |
529 | |
530 | void tst_QMimeDatabase::mimeTypeForUrl() |
531 | { |
532 | QMimeDatabase db; |
533 | QVERIFY(db.mimeTypeForUrl(QUrl::fromEncoded("http://foo/bar.png" )).isDefault()); // HTTP can't know before downloading |
534 | QCOMPARE(db.mimeTypeForUrl(QUrl::fromEncoded("ftp://foo/bar.png" )).name(), QString::fromLatin1("image/png" )); |
535 | QCOMPARE(db.mimeTypeForUrl(QUrl::fromEncoded("ftp://foo/bar" )).name(), QString::fromLatin1("application/octet-stream" )); // unknown extension |
536 | QCOMPARE(db.mimeTypeForUrl(QUrl("mailto:something@example.com" )).name(), QString::fromLatin1("application/octet-stream" )); // unknown |
537 | QCOMPARE(db.mimeTypeForUrl(QUrl("mailto:something@polish.pl" )).name(), QString::fromLatin1("application/octet-stream" )); // unknown, NOT perl ;) |
538 | } |
539 | |
540 | void tst_QMimeDatabase::mimeTypeForData_data() |
541 | { |
542 | QTest::addColumn<QByteArray>(name: "data" ); |
543 | QTest::addColumn<QString>(name: "expectedMimeTypeName" ); |
544 | |
545 | QTest::newRow(dataTag: "tnef data, needs smi >= 0.20" ) << QByteArray("\x78\x9f\x3e\x22" ) << "application/vnd.ms-tnef" ; |
546 | QTest::newRow(dataTag: "PDF magic" ) << QByteArray("%PDF-" ) << "application/pdf" ; |
547 | QTest::newRow(dataTag: "PHP, High-priority rule" ) << QByteArray("<?php" ) << "application/x-php" ; |
548 | QTest::newRow(dataTag: "diff\\t" ) << QByteArray("diff\t" ) << "text/x-patch" ; |
549 | QTest::newRow(dataTag: "unknown" ) << QByteArray("\001abc?}" ) << "application/octet-stream" ; |
550 | } |
551 | |
552 | void tst_QMimeDatabase::mimeTypeForData() |
553 | { |
554 | QFETCH(QByteArray, data); |
555 | QFETCH(QString, expectedMimeTypeName); |
556 | |
557 | QMimeDatabase db; |
558 | QCOMPARE(db.mimeTypeForData(data).name(), expectedMimeTypeName); |
559 | QBuffer buffer(&data); |
560 | QCOMPARE(db.mimeTypeForData(&buffer).name(), expectedMimeTypeName); |
561 | QVERIFY(!buffer.isOpen()); // initial state was restored |
562 | |
563 | QVERIFY(buffer.open(QIODevice::ReadOnly)); |
564 | QCOMPARE(db.mimeTypeForData(&buffer).name(), expectedMimeTypeName); |
565 | QVERIFY(buffer.isOpen()); |
566 | QCOMPARE(buffer.pos(), qint64(0)); |
567 | } |
568 | |
569 | void tst_QMimeDatabase::mimeTypeForFileAndContent_data() |
570 | { |
571 | QTest::addColumn<QString>(name: "name" ); |
572 | QTest::addColumn<QByteArray>(name: "data" ); |
573 | QTest::addColumn<QString>(name: "expectedMimeTypeName" ); |
574 | |
575 | QTest::newRow(dataTag: "plain text, no extension" ) << QString::fromLatin1(str: "textfile" ) << QByteArray("Hello world" ) << "text/plain" ; |
576 | QTest::newRow(dataTag: "plain text, unknown extension" ) << QString::fromLatin1(str: "textfile.foo" ) << QByteArray("Hello world" ) << "text/plain" ; |
577 | // Needs kde/mimetypes.xml |
578 | //QTest::newRow("plain text, doc extension") << QString::fromLatin1("textfile.doc") << QByteArray("Hello world") << "text/plain"; |
579 | |
580 | // If you get powerpoint instead, then you're hit by https://bugs.freedesktop.org/show_bug.cgi?id=435, |
581 | // upgrade to shared-mime-info >= 0.22 |
582 | const QByteArray oleData("\320\317\021\340\241\261\032\341" ); // same as \xD0\xCF\x11\xE0 \xA1\xB1\x1A\xE1 |
583 | QTest::newRow(dataTag: "msword file, unknown extension" ) << QString::fromLatin1(str: "mswordfile" ) << oleData << "application/x-ole-storage" ; |
584 | QTest::newRow(dataTag: "excel file, found by extension" ) << QString::fromLatin1(str: "excelfile.xls" ) << oleData << "application/vnd.ms-excel" ; |
585 | QTest::newRow(dataTag: "text.xls, found by extension, user is in control" ) << QString::fromLatin1(str: "text.xls" ) << oleData << "application/vnd.ms-excel" ; |
586 | } |
587 | |
588 | void tst_QMimeDatabase::mimeTypeForFileAndContent() |
589 | { |
590 | QFETCH(QString, name); |
591 | QFETCH(QByteArray, data); |
592 | QFETCH(QString, expectedMimeTypeName); |
593 | |
594 | QMimeDatabase db; |
595 | QCOMPARE(db.mimeTypeForFileNameAndData(name, data).name(), expectedMimeTypeName); |
596 | |
597 | QBuffer buffer(&data); |
598 | QCOMPARE(db.mimeTypeForFileNameAndData(name, &buffer).name(), expectedMimeTypeName); |
599 | QVERIFY(!buffer.isOpen()); // initial state was restored |
600 | |
601 | QVERIFY(buffer.open(QIODevice::ReadOnly)); |
602 | QCOMPARE(db.mimeTypeForFileNameAndData(name, &buffer).name(), expectedMimeTypeName); |
603 | QVERIFY(buffer.isOpen()); |
604 | QCOMPARE(buffer.pos(), qint64(0)); |
605 | } |
606 | |
607 | void tst_QMimeDatabase::allMimeTypes() |
608 | { |
609 | QMimeDatabase db; |
610 | const QList<QMimeType> lst = db.allMimeTypes(); // does NOT include aliases |
611 | QVERIFY(!lst.isEmpty()); |
612 | |
613 | // Hardcoding this is the only way to check both providers find the same number of mimetypes. |
614 | QCOMPARE(lst.count(), 779); |
615 | |
616 | foreach (const QMimeType &mime, lst) { |
617 | const QString name = mime.name(); |
618 | QVERIFY(!name.isEmpty()); |
619 | QCOMPARE(name.count(QLatin1Char('/')), 1); |
620 | const QMimeType lookedupMime = db.mimeTypeForName(nameOrAlias: name); |
621 | QVERIFY(lookedupMime.isValid()); |
622 | QCOMPARE(lookedupMime.name(), name); // if this fails, you have an alias defined as a real mimetype too! |
623 | } |
624 | } |
625 | |
626 | void tst_QMimeDatabase::suffixes_data() |
627 | { |
628 | QTest::addColumn<QString>(name: "mimeType" ); |
629 | QTest::addColumn<QString>(name: "patterns" ); |
630 | QTest::addColumn<QString>(name: "preferredSuffix" ); |
631 | |
632 | QTest::newRow(dataTag: "mimetype with a single pattern" ) << "application/pdf" << "*.pdf" << "pdf" ; |
633 | QTest::newRow(dataTag: "mimetype with multiple patterns" ) << "application/x-kpresenter" << "*.kpr;*.kpt" << "kpr" ; |
634 | QTest::newRow(dataTag: "jpeg" ) << "image/jpeg" << "*.jpe;*.jpg;*.jpeg" << "jpeg" ; |
635 | //if (KMimeType::sharedMimeInfoVersion() > KDE_MAKE_VERSION(0, 60, 0)) { |
636 | QTest::newRow(dataTag: "mimetype with many patterns" ) << "application/vnd.wordperfect" << "*.wp;*.wp4;*.wp5;*.wp6;*.wpd;*.wpp" << "wp" ; |
637 | //} |
638 | QTest::newRow(dataTag: "oasis text mimetype" ) << "application/vnd.oasis.opendocument.text" << "*.odt" << "odt" ; |
639 | QTest::newRow(dataTag: "oasis presentation mimetype" ) << "application/vnd.oasis.opendocument.presentation" << "*.odp" << "odp" ; |
640 | QTest::newRow(dataTag: "mimetype with multiple patterns" ) << "text/plain" << "*.asc;*.txt;*,v" << "txt" ; |
641 | QTest::newRow(dataTag: "mimetype with uncommon pattern" ) << "text/x-readme" << "README*" << QString(); |
642 | QTest::newRow(dataTag: "mimetype with no patterns" ) << "application/x-ole-storage" << QString() << QString(); |
643 | QTest::newRow(dataTag: "default_mimetype" ) << "application/octet-stream" << QString() << QString(); |
644 | } |
645 | |
646 | void tst_QMimeDatabase::suffixes() |
647 | { |
648 | QFETCH(QString, mimeType); |
649 | QFETCH(QString, patterns); |
650 | QFETCH(QString, preferredSuffix); |
651 | QMimeDatabase db; |
652 | QMimeType mime = db.mimeTypeForName(nameOrAlias: mimeType); |
653 | QVERIFY(mime.isValid()); |
654 | // Sort both lists; order is unreliable since shared-mime-info uses hashes internally. |
655 | QStringList expectedPatterns = patterns.split(sep: QLatin1Char(';')); |
656 | expectedPatterns.sort(); |
657 | QStringList mimePatterns = mime.globPatterns(); |
658 | mimePatterns.sort(); |
659 | QCOMPARE(mimePatterns.join(QLatin1Char(';')), expectedPatterns.join(QLatin1Char(';'))); |
660 | QCOMPARE(mime.preferredSuffix(), preferredSuffix); |
661 | } |
662 | |
663 | void tst_QMimeDatabase::knownSuffix() |
664 | { |
665 | QMimeDatabase db; |
666 | QCOMPARE(db.suffixForFileName(QString::fromLatin1("foo.tar" )), QString::fromLatin1("tar" )); |
667 | QCOMPARE(db.suffixForFileName(QString::fromLatin1("foo.bz2" )), QString::fromLatin1("bz2" )); |
668 | QCOMPARE(db.suffixForFileName(QString::fromLatin1("foo.bar.bz2" )), QString::fromLatin1("bz2" )); |
669 | QCOMPARE(db.suffixForFileName(QString::fromLatin1("foo.tar.bz2" )), QString::fromLatin1("tar.bz2" )); |
670 | QCOMPARE(db.suffixForFileName(QString::fromLatin1("foo.TAR" )), QString::fromLatin1("TAR" )); // preserve case |
671 | QCOMPARE(db.suffixForFileName(QString::fromLatin1("foo.flatpakrepo" )), QString::fromLatin1("flatpakrepo" )); |
672 | QCOMPARE(db.suffixForFileName(QString::fromLatin1("foo.anim2" )), QString()); // the glob is anim[0-9], no way to extract the extension without expensive regexp capturing |
673 | } |
674 | |
675 | void tst_QMimeDatabase::symlinkToFifo() // QTBUG-48529 |
676 | { |
677 | #ifdef Q_OS_UNIX |
678 | QTemporaryDir tempDir; |
679 | QVERIFY(tempDir.isValid()); |
680 | const QString dir = tempDir.path(); |
681 | const QString fifo = dir + "/fifo" ; |
682 | QCOMPARE(mkfifo(QFile::encodeName(fifo), 0006), 0); |
683 | |
684 | QMimeDatabase db; |
685 | QCOMPARE(db.mimeTypeForFile(fifo).name(), QString::fromLatin1("inode/fifo" )); |
686 | |
687 | // Now make a symlink to the fifo |
688 | const QString link = dir + "/link" ; |
689 | QVERIFY(QFile::link(fifo, link)); |
690 | QCOMPARE(db.mimeTypeForFile(link).name(), QString::fromLatin1("inode/fifo" )); |
691 | |
692 | #else |
693 | QSKIP("This test requires pipes and symlinks" ); |
694 | #endif |
695 | } |
696 | |
697 | void tst_QMimeDatabase::findByFileName_data() |
698 | { |
699 | QTest::addColumn<QString>(name: "filePath" ); |
700 | QTest::addColumn<QString>(name: "mimeTypeName" ); |
701 | QTest::addColumn<QString>(name: "xFail" ); |
702 | |
703 | if (m_testSuite.isEmpty()) |
704 | QSKIP("shared-mime-info test suite not available." ); |
705 | |
706 | const QString prefix = m_testSuite + QLatin1Char('/'); |
707 | const QString fileName = prefix + QLatin1String("list" ); |
708 | QFile f(fileName); |
709 | QVERIFY2(f.open(QIODevice::ReadOnly|QIODevice::Text), |
710 | qPrintable(QString::fromLatin1("Cannot open %1: %2" ).arg(fileName, f.errorString()))); |
711 | |
712 | QByteArray line(1024, Qt::Uninitialized); |
713 | |
714 | while (!f.atEnd()) { |
715 | int len = f.readLine(data: line.data(), maxlen: 1023); |
716 | |
717 | if (len <= 2 || line.at(i: 0) == '#') |
718 | continue; |
719 | |
720 | QString string = QString::fromLatin1(str: line.constData(), size: len - 1).trimmed(); |
721 | QStringList list = string.split(sep: QLatin1Char(' '), behavior: Qt::SkipEmptyParts); |
722 | QVERIFY(list.size() >= 2); |
723 | |
724 | QString filePath = list.at(i: 0); |
725 | QString mimeTypeType = list.at(i: 1); |
726 | QString xFail; |
727 | if (list.size() >= 3) |
728 | xFail = list.at(i: 2); |
729 | |
730 | QTest::newRow(dataTag: filePath.toLatin1().constData()) |
731 | << QString(prefix + filePath) |
732 | << mimeTypeType << xFail; |
733 | } |
734 | } |
735 | |
736 | void tst_QMimeDatabase::findByFileName() |
737 | { |
738 | QFETCH(QString, filePath); |
739 | QFETCH(QString, mimeTypeName); |
740 | QFETCH(QString, xFail); |
741 | |
742 | QMimeDatabase database; |
743 | |
744 | //qDebug() << Q_FUNC_INFO << filePath; |
745 | |
746 | const QMimeType resultMimeType(database.mimeTypeForFile(fileName: filePath, mode: QMimeDatabase::MatchExtension)); |
747 | if (resultMimeType.isValid()) { |
748 | //qDebug() << Q_FUNC_INFO << "MIME type" << resultMimeType.name() << "has generic icon name" << resultMimeType.genericIconName() << "and icon name" << resultMimeType.iconName(); |
749 | |
750 | // Loading icons depend on the icon theme, we can't enable this test |
751 | #if 0 |
752 | QCOMPARE(resultMimeType.genericIconName(), QIcon::fromTheme(resultMimeType.genericIconName()).name()); |
753 | QVERIFY2(!QIcon::fromTheme(resultMimeType.genericIconName()).isNull(), qPrintable(resultMimeType.genericIconName())); |
754 | QVERIFY2(QIcon::hasThemeIcon(resultMimeType.genericIconName()), qPrintable(resultMimeType.genericIconName())); |
755 | |
756 | QCOMPARE(resultMimeType.iconName(), QIcon::fromTheme(resultMimeType.iconName()).name()); |
757 | QVERIFY2(!QIcon::fromTheme(resultMimeType.iconName()).isNull(), qPrintable(resultMimeType.iconName())); |
758 | QVERIFY2(QIcon::hasThemeIcon(resultMimeType.iconName()), qPrintable(resultMimeType.iconName())); |
759 | #endif |
760 | } |
761 | const QString resultMimeTypeName = resultMimeType.name(); |
762 | //qDebug() << Q_FUNC_INFO << "mimeTypeForFile() returned" << resultMimeTypeName; |
763 | |
764 | const bool failed = resultMimeTypeName != mimeTypeName; |
765 | const bool shouldFail = (xFail.length() >= 1 && xFail.at(i: 0) == QLatin1Char('x')); |
766 | if (shouldFail != failed) { |
767 | // Results are ambiguous when multiple MIME types have the same glob |
768 | // -> accept the current result if the found MIME type actually |
769 | // matches the file's extension. |
770 | // TODO: a better file format in testfiles/list! |
771 | const QMimeType foundMimeType = database.mimeTypeForName(nameOrAlias: resultMimeTypeName); |
772 | QVERIFY2(resultMimeType == foundMimeType, qPrintable(resultMimeType.name() + QString::fromLatin1(" vs. " ) + foundMimeType.name())); |
773 | if (foundMimeType.isValid()) { |
774 | const QString extension = QFileInfo(filePath).suffix(); |
775 | //qDebug() << Q_FUNC_INFO << "globPatterns:" << foundMimeType.globPatterns() << "- extension:" << QString() + "*." + extension; |
776 | if (foundMimeType.globPatterns().contains(str: QString::fromLatin1(str: "*." ) + extension)) |
777 | return; |
778 | } |
779 | } |
780 | if (shouldFail) { |
781 | // Expected to fail |
782 | QVERIFY2(resultMimeTypeName != mimeTypeName, qPrintable(resultMimeTypeName)); |
783 | } else { |
784 | QCOMPARE(resultMimeTypeName, mimeTypeName); |
785 | } |
786 | |
787 | // Test QFileInfo overload |
788 | const QMimeType mimeForFileInfo = database.mimeTypeForFile(fileInfo: QFileInfo(filePath), mode: QMimeDatabase::MatchExtension); |
789 | QCOMPARE(mimeForFileInfo.name(), resultMimeTypeName); |
790 | |
791 | const QString suffix = database.suffixForFileName(fileName: filePath); |
792 | QVERIFY2(filePath.endsWith(suffix), qPrintable(filePath + " does not end with " + suffix)); |
793 | } |
794 | |
795 | void tst_QMimeDatabase::findByData_data() |
796 | { |
797 | findByFileName_data(); |
798 | } |
799 | |
800 | void tst_QMimeDatabase::findByData() |
801 | { |
802 | QFETCH(QString, filePath); |
803 | QFETCH(QString, mimeTypeName); |
804 | QFETCH(QString, xFail); |
805 | |
806 | QMimeDatabase database; |
807 | QFile f(filePath); |
808 | QVERIFY(f.open(QIODevice::ReadOnly)); |
809 | QByteArray data = f.read(maxlen: 16384); |
810 | |
811 | const QString resultMimeTypeName = database.mimeTypeForData(data).name(); |
812 | if (xFail.length() >= 2 && xFail.at(i: 1) == QLatin1Char('x')) { |
813 | // Expected to fail |
814 | QVERIFY2(resultMimeTypeName != mimeTypeName, qPrintable(resultMimeTypeName)); |
815 | } else { |
816 | QCOMPARE(resultMimeTypeName.toLower(), mimeTypeName.toLower()); |
817 | } |
818 | |
819 | QFileInfo info(filePath); |
820 | QString mimeForInfo = database.mimeTypeForFile(fileInfo: info, mode: QMimeDatabase::MatchContent).name(); |
821 | QCOMPARE(mimeForInfo, resultMimeTypeName); |
822 | QString mimeForFile = database.mimeTypeForFile(fileName: filePath, mode: QMimeDatabase::MatchContent).name(); |
823 | QCOMPARE(mimeForFile, resultMimeTypeName); |
824 | } |
825 | |
826 | void tst_QMimeDatabase::findByFile_data() |
827 | { |
828 | findByFileName_data(); |
829 | } |
830 | |
831 | // Note: this test fails on "testcompress.z" when using a shared-mime-info older than 1.0. |
832 | // This because of commit 0f9a506069c in shared-mime-info, which fixed the writing of |
833 | // case-insensitive patterns into mime.cache. |
834 | void tst_QMimeDatabase::findByFile() |
835 | { |
836 | QFETCH(QString, filePath); |
837 | QFETCH(QString, mimeTypeName); |
838 | QFETCH(QString, xFail); |
839 | |
840 | QMimeDatabase database; |
841 | const QString resultMimeTypeName = database.mimeTypeForFile(fileName: filePath).name(); |
842 | //qDebug() << Q_FUNC_INFO << filePath << "->" << resultMimeTypeName; |
843 | if (xFail.length() >= 3 && xFail.at(i: 2) == QLatin1Char('x')) { |
844 | // Expected to fail |
845 | QVERIFY2(resultMimeTypeName != mimeTypeName, qPrintable(resultMimeTypeName)); |
846 | } else { |
847 | QCOMPARE(resultMimeTypeName.toLower(), mimeTypeName.toLower()); |
848 | } |
849 | |
850 | // Test QFileInfo overload |
851 | const QMimeType mimeForFileInfo = database.mimeTypeForFile(fileInfo: QFileInfo(filePath)); |
852 | QCOMPARE(mimeForFileInfo.name(), resultMimeTypeName); |
853 | } |
854 | |
855 | |
856 | void tst_QMimeDatabase::fromThreads() |
857 | { |
858 | QThreadPool tp; |
859 | tp.setMaxThreadCount(20); |
860 | // Note that data-based tests cannot be used here (QTest::fetchData asserts). |
861 | QtConcurrent::run(pool: &tp, object: this, fn: &tst_QMimeDatabase::mimeTypeForName); |
862 | QtConcurrent::run(pool: &tp, object: this, fn: &tst_QMimeDatabase::aliases); |
863 | QtConcurrent::run(pool: &tp, object: this, fn: &tst_QMimeDatabase::allMimeTypes); |
864 | QtConcurrent::run(pool: &tp, object: this, fn: &tst_QMimeDatabase::icons); |
865 | QtConcurrent::run(pool: &tp, object: this, fn: &tst_QMimeDatabase::inheritance); |
866 | QtConcurrent::run(pool: &tp, object: this, fn: &tst_QMimeDatabase::knownSuffix); |
867 | QtConcurrent::run(pool: &tp, object: this, fn: &tst_QMimeDatabase::mimeTypeForFileWithContent); |
868 | QtConcurrent::run(pool: &tp, object: this, fn: &tst_QMimeDatabase::allMimeTypes); // a second time |
869 | QVERIFY(tp.waitForDone(60000)); |
870 | } |
871 | |
872 | #if QT_CONFIG(process) |
873 | |
874 | enum { |
875 | UpdateMimeDatabaseTimeout = 4 * 60 * 1000 // 4min |
876 | }; |
877 | |
878 | static bool runUpdateMimeDatabase(const QString &path) // TODO make it a QMimeDatabase method? |
879 | { |
880 | const QString umdCommand = QString::fromLatin1(str: "update-mime-database" ); |
881 | const QString umd = QStandardPaths::findExecutable(executableName: umdCommand); |
882 | if (umd.isEmpty()) { |
883 | qWarning(msg: "%s does not exist." , qPrintable(umdCommand)); |
884 | return false; |
885 | } |
886 | |
887 | QElapsedTimer timer; |
888 | QProcess proc; |
889 | proc.setProcessChannelMode(QProcess::MergedChannels); // silence output |
890 | qDebug().noquote() << "runUpdateMimeDatabase: running" << umd << path << "..." ; |
891 | timer.start(); |
892 | proc.start(program: umd, arguments: QStringList(path)); |
893 | if (!proc.waitForStarted()) { |
894 | qWarning(msg: "Cannot start %s: %s" , |
895 | qPrintable(umd), qPrintable(proc.errorString())); |
896 | return false; |
897 | } |
898 | const bool success = proc.waitForFinished(msecs: UpdateMimeDatabaseTimeout); |
899 | qDebug().noquote() << "runUpdateMimeDatabase: done," |
900 | << success << timer.elapsed() << "ms" ; |
901 | return true; |
902 | } |
903 | |
904 | static bool waitAndRunUpdateMimeDatabase(const QString &path) |
905 | { |
906 | QFileInfo mimeCacheInfo(path + QString::fromLatin1(str: "/mime.cache" )); |
907 | if (mimeCacheInfo.exists()) { |
908 | // Wait until the beginning of the next second |
909 | while (mimeCacheInfo.lastModified().secsTo(QDateTime::currentDateTime()) == 0) { |
910 | QTest::qSleep(ms: 200); |
911 | } |
912 | } |
913 | return runUpdateMimeDatabase(path); |
914 | } |
915 | #endif // QT_CONFIG(process) |
916 | |
917 | static void checkHasMimeType(const QString &mimeType) |
918 | { |
919 | QMimeDatabase db; |
920 | QVERIFY(db.mimeTypeForName(mimeType).isValid()); |
921 | |
922 | bool found = false; |
923 | foreach (const QMimeType &mt, db.allMimeTypes()) { |
924 | if (mt.name() == mimeType) { |
925 | found = true; |
926 | break; |
927 | } |
928 | } |
929 | QVERIFY(found); |
930 | } |
931 | |
932 | static void ignoreInvalidMimetypeWarnings(const QString &mimeDir) |
933 | { |
934 | const QByteArray basePath = QFile::encodeName(fileName: mimeDir) + "/packages/" ; |
935 | QTest::ignoreMessage(type: QtWarningMsg, message: ("QMimeDatabase: Error parsing " + basePath + "invalid-magic1.xml\nInvalid magic rule value \"foo\"" ).constData()); |
936 | QTest::ignoreMessage(type: QtWarningMsg, message: ("QMimeDatabase: Error parsing " + basePath + "invalid-magic2.xml\nInvalid magic rule mask \"ffff\"" ).constData()); |
937 | QTest::ignoreMessage(type: QtWarningMsg, message: ("QMimeDatabase: Error parsing " + basePath + "invalid-magic3.xml\nInvalid magic rule mask size \"0xffff\"" ).constData()); |
938 | } |
939 | |
940 | QT_BEGIN_NAMESPACE |
941 | extern Q_CORE_EXPORT int qmime_secondsBetweenChecks; // see qmimeprovider.cpp |
942 | QT_END_NAMESPACE |
943 | |
944 | void tst_QMimeDatabase::installNewGlobalMimeType() |
945 | { |
946 | #if !defined(USE_XDG_DATA_DIRS) |
947 | QSKIP("This test requires XDG_DATA_DIRS" ); |
948 | #endif |
949 | |
950 | #if !QT_CONFIG(process) |
951 | QSKIP("This test requires QProcess support" ); |
952 | #else |
953 | qmime_secondsBetweenChecks = 0; |
954 | |
955 | QMimeDatabase db; |
956 | QVERIFY(!db.mimeTypeForName(QLatin1String("text/x-suse-ymp" )).isValid()); |
957 | |
958 | const QString mimeDir = m_globalXdgDir + QLatin1String("/mime" ); |
959 | const QString destDir = mimeDir + QLatin1String("/packages/" ); |
960 | if (!QFileInfo(destDir).isDir()) |
961 | QVERIFY(QDir(m_globalXdgDir).mkpath(destDir)); |
962 | |
963 | QString errorMessage; |
964 | for (int i = 0; i < m_additionalMimeFileNames.size(); ++i) { |
965 | const QString destFile = destDir + m_additionalMimeFileNames.at(i); |
966 | QFile::remove(fileName: destFile); |
967 | QVERIFY2(copyResourceFile(m_additionalMimeFilePaths.at(i), destFile, &errorMessage), qPrintable(errorMessage)); |
968 | } |
969 | if (m_isUsingCacheProvider && !waitAndRunUpdateMimeDatabase(path: mimeDir)) |
970 | QSKIP("shared-mime-info not found, skipping mime.cache test" ); |
971 | |
972 | if (!m_isUsingCacheProvider) |
973 | ignoreInvalidMimetypeWarnings(mimeDir); |
974 | |
975 | QCOMPARE(db.mimeTypeForFile(QLatin1String("foo.ymu" ), QMimeDatabase::MatchExtension).name(), |
976 | QString::fromLatin1("text/x-SuSE-ymu" )); |
977 | QVERIFY(db.mimeTypeForName(QLatin1String("text/x-suse-ymp" )).isValid()); |
978 | checkHasMimeType(mimeType: "text/x-suse-ymp" ); |
979 | |
980 | // Test that a double-definition of a mimetype doesn't lead to sniffing ("conflicting globs"). |
981 | const QString qmlTestFile = QLatin1String(RESOURCE_PREFIX "test.qml" ); |
982 | QVERIFY2(!qmlTestFile.isEmpty(), |
983 | qPrintable(QString::fromLatin1("Cannot find '%1' starting from '%2'" ). |
984 | arg("test.qml" , QDir::currentPath()))); |
985 | QCOMPARE(db.mimeTypeForFile(qmlTestFile).name(), |
986 | QString::fromLatin1("text/x-qml" )); |
987 | |
988 | { |
989 | QMimeType objcsrc = db.mimeTypeForName(QStringLiteral("text/x-objcsrc" )); |
990 | QVERIFY(objcsrc.isValid()); |
991 | QCOMPARE(objcsrc.globPatterns(), QStringList()); |
992 | } |
993 | |
994 | const QString = QLatin1String(RESOURCE_PREFIX "magic-and-hierarchy.foo" ); |
995 | QCOMPARE(db.mimeTypeForFile(fooTestFile).name(), QString::fromLatin1("application/foo" )); |
996 | |
997 | const QString = QLatin1String(RESOURCE_PREFIX "magic-and-hierarchy2.foo" ); |
998 | QCOMPARE(db.mimeTypeForFile(fooTestFile2).name(), QString::fromLatin1("application/vnd.qnx.bar-descriptor" )); |
999 | |
1000 | // Test if we can use the default comment |
1001 | { |
1002 | struct RestoreLocale |
1003 | { |
1004 | ~RestoreLocale() { QLocale::setDefault(QLocale::c()); } |
1005 | } restoreLocale; |
1006 | |
1007 | QLocale::setDefault(QLocale("zh_CN" )); |
1008 | QMimeType suseymp = db.mimeTypeForName(nameOrAlias: "text/x-suse-ymp" ); |
1009 | QVERIFY(suseymp.isValid()); |
1010 | QCOMPARE(suseymp.comment(), |
1011 | QString::fromLatin1("YaST Meta Package" )); |
1012 | } |
1013 | |
1014 | // Now test removing the mimetype definitions again |
1015 | for (int i = 0; i < m_additionalMimeFileNames.size(); ++i) |
1016 | QFile::remove(fileName: destDir + m_additionalMimeFileNames.at(i)); |
1017 | if (m_isUsingCacheProvider && !waitAndRunUpdateMimeDatabase(path: mimeDir)) |
1018 | QSKIP("shared-mime-info not found, skipping mime.cache test" ); |
1019 | QCOMPARE(db.mimeTypeForFile(QLatin1String("foo.ymu" ), QMimeDatabase::MatchExtension).name(), |
1020 | QString::fromLatin1("application/octet-stream" )); |
1021 | QVERIFY(!db.mimeTypeForName(QLatin1String("text/x-suse-ymp" )).isValid()); |
1022 | #endif // QT_CONFIG(process) |
1023 | } |
1024 | |
1025 | void tst_QMimeDatabase::installNewLocalMimeType() |
1026 | { |
1027 | #if !QT_CONFIG(process) |
1028 | QSKIP("This test requires QProcess support" ); |
1029 | #else |
1030 | qmime_secondsBetweenChecks = 0; |
1031 | |
1032 | QMimeDatabase db; |
1033 | |
1034 | // Check that we're starting clean |
1035 | QVERIFY(!db.mimeTypeForName(QLatin1String("text/x-suse-ymp" )).isValid()); |
1036 | QVERIFY(!db.mimeTypeForName(QLatin1String("text/invalid-magic1" )).isValid()); |
1037 | |
1038 | const QString destDir = m_localMimeDir + QLatin1String("/packages/" ); |
1039 | QVERIFY(QDir().mkpath(destDir)); |
1040 | QString errorMessage; |
1041 | for (int i = 0; i < m_additionalMimeFileNames.size(); ++i) { |
1042 | const QString destFile = destDir + m_additionalMimeFileNames.at(i); |
1043 | QFile::remove(fileName: destFile); |
1044 | QVERIFY2(copyResourceFile(m_additionalMimeFilePaths.at(i), destFile, &errorMessage), qPrintable(errorMessage)); |
1045 | } |
1046 | if (m_isUsingCacheProvider && !waitAndRunUpdateMimeDatabase(path: m_localMimeDir)) { |
1047 | const QString skipWarning = QStringLiteral("shared-mime-info not found, skipping mime.cache test (" ) |
1048 | + QDir::toNativeSeparators(pathName: m_localMimeDir) + QLatin1Char(')'); |
1049 | QSKIP(qPrintable(skipWarning)); |
1050 | } |
1051 | |
1052 | if (!m_isUsingCacheProvider) |
1053 | ignoreInvalidMimetypeWarnings(mimeDir: m_localMimeDir); |
1054 | |
1055 | QVERIFY(db.mimeTypeForName(QLatin1String("text/x-suse-ymp" )).isValid()); |
1056 | |
1057 | // These mimetypes have invalid magic, but still do exist. |
1058 | QVERIFY(db.mimeTypeForName(QLatin1String("text/invalid-magic1" )).isValid()); |
1059 | QVERIFY(db.mimeTypeForName(QLatin1String("text/invalid-magic2" )).isValid()); |
1060 | QVERIFY(db.mimeTypeForName(QLatin1String("text/invalid-magic3" )).isValid()); |
1061 | |
1062 | QCOMPARE(db.mimeTypeForFile(QLatin1String("foo.ymu" ), QMimeDatabase::MatchExtension).name(), |
1063 | QString::fromLatin1("text/x-SuSE-ymu" )); |
1064 | QVERIFY(db.mimeTypeForName(QLatin1String("text/x-suse-ymp" )).isValid()); |
1065 | QCOMPARE(db.mimeTypeForName(QLatin1String("text/x-SuSE-ymu" )).comment(), QString("URL of a YaST Meta Package" )); |
1066 | checkHasMimeType(mimeType: "text/x-suse-ymp" ); |
1067 | |
1068 | { // QTBUG-85436 |
1069 | QMimeType objcsrc = db.mimeTypeForName(QStringLiteral("text/x-objcsrc" )); |
1070 | QVERIFY(objcsrc.isValid()); |
1071 | QCOMPARE(objcsrc.globPatterns(), QStringList()); |
1072 | } |
1073 | |
1074 | // Test that a double-definition of a mimetype doesn't lead to sniffing ("conflicting globs"). |
1075 | const QString qmlTestFile = QLatin1String(RESOURCE_PREFIX "test.qml" ); |
1076 | QVERIFY2(!qmlTestFile.isEmpty(), |
1077 | qPrintable(QString::fromLatin1("Cannot find '%1' starting from '%2'" ). |
1078 | arg("test.qml" , QDir::currentPath()))); |
1079 | QCOMPARE(db.mimeTypeForFile(qmlTestFile).name(), |
1080 | QString::fromLatin1("text/x-qml" )); |
1081 | |
1082 | // Now that we have two directories with mime definitions, check that everything still works |
1083 | inheritance(); |
1084 | if (QTest::currentTestFailed()) |
1085 | return; |
1086 | |
1087 | aliases(); |
1088 | if (QTest::currentTestFailed()) |
1089 | return; |
1090 | |
1091 | icons(); |
1092 | if (QTest::currentTestFailed()) |
1093 | return; |
1094 | |
1095 | comment(); |
1096 | if (QTest::currentTestFailed()) |
1097 | return; |
1098 | |
1099 | mimeTypeForFileWithContent(); |
1100 | if (QTest::currentTestFailed()) |
1101 | return; |
1102 | |
1103 | mimeTypeForName(); |
1104 | if (QTest::currentTestFailed()) |
1105 | return; |
1106 | |
1107 | // Now test removing local mimetypes |
1108 | for (int i = 1 ; i <= 3 ; ++i) |
1109 | QFile::remove(fileName: destDir + QStringLiteral("invalid-magic%1.xml" ).arg(a: i)); |
1110 | if (m_isUsingCacheProvider && !waitAndRunUpdateMimeDatabase(path: m_localMimeDir)) |
1111 | QSKIP("shared-mime-info not found, skipping mime.cache test" ); |
1112 | QVERIFY(!db.mimeTypeForName(QLatin1String("text/invalid-magic1" )).isValid()); // deleted |
1113 | QVERIFY(db.mimeTypeForName(QLatin1String("text/x-suse-ymp" )).isValid()); // still present |
1114 | |
1115 | // The user deletes the cache -> the XML provider makes things still work |
1116 | QFile::remove(fileName: m_localMimeDir + QString::fromLatin1(str: "/mime.cache" )); |
1117 | QVERIFY(!db.mimeTypeForName(QLatin1String("text/invalid-magic1" )).isValid()); // deleted |
1118 | QVERIFY(db.mimeTypeForName(QLatin1String("text/x-suse-ymp" )).isValid()); // still present |
1119 | |
1120 | // Finally, the user deletes the whole local dir |
1121 | QVERIFY2(QDir(m_localMimeDir).removeRecursively(), qPrintable(m_localMimeDir + ": " + qt_error_string())); |
1122 | QCOMPARE(db.mimeTypeForFile(QLatin1String("foo.ymu" ), QMimeDatabase::MatchExtension).name(), |
1123 | QString::fromLatin1("application/octet-stream" )); |
1124 | QVERIFY(!db.mimeTypeForName(QLatin1String("text/x-suse-ymp" )).isValid()); |
1125 | #endif // QT_CONFIG(process) |
1126 | } |
1127 | |
1128 | QTEST_GUILESS_MAIN(tst_QMimeDatabase) |
1129 | |