/**************************************************************************** ** ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/ ** ** This file is part of the test suite of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** GNU Lesser General Public License Usage ** This file may be used under the terms of the GNU Lesser General Public ** License version 2.1 as published by the Free Software Foundation and ** appearing in the file LICENSE.LGPL included in the packaging of this ** file. Please review the following information to ensure the GNU Lesser ** General Public License version 2.1 requirements will be met: ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional ** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU General ** Public License version 3.0 as published by the Free Software Foundation ** and appearing in the file LICENSE.GPL included in the packaging of this ** file. Please review the following information to ensure the GNU General ** Public License version 3.0 requirements will be met: ** http://www.gnu.org/copyleft/gpl.html. ** ** Other Usage ** Alternatively, this file may be used in accordance with the terms and ** conditions contained in a signed written agreement between you and Nokia. ** ** ** ** ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include #include "qstandardpaths.h" #include #include #include #include #include #include static const char yastFileName[] ="yast2-metapackage-handler-mimetypes.xml"; void initializeLang() { qputenv("LC_ALL", ""); qputenv("LANG", "C"); QCoreApplication::setApplicationName("tst_qmimedatabase"); // temporary directory pattern } static inline QString testSuiteWarning() { QString result; QTextStream str(&result); str << "\nCannot find the shared-mime-info test suite\nstarting from: " << QDir::toNativeSeparators(QDir::currentPath()) << "\n" "cd " << QDir::toNativeSeparators(QStringLiteral("tests/auto/corelib/mimetypes/qmimedatabase")) << "\n" "wget http://cgit.freedesktop.org/xdg/shared-mime-info/snapshot/Release-1-0.zip\n" "unzip Release-1-0.zip\n"; #ifdef Q_OS_WIN str << "mkdir testfiles\nxcopy /s Release-1-0\\tests testfiles\n"; #else str << "ln -s Release-1-0/tests testfiles\n"; #endif return result; } // Set LANG before QCoreApplication is created Q_CONSTRUCTOR_FUNCTION(initializeLang) tst_QMimeDatabase::tst_QMimeDatabase() { } void tst_QMimeDatabase::initTestCase() { QVERIFY(m_temporaryDir.isValid()); // Create a "global" and a "local" XDG data dir, right here. // The local dir will be empty initially, while the global dir will contain a copy of freedesktop.org.xml const QDir here = QDir(m_temporaryDir.path()); m_globalXdgDir = m_temporaryDir.path() + QStringLiteral("/global"); m_localXdgDir = m_temporaryDir.path() + QStringLiteral("/local"); const QString globalPackageDir = m_globalXdgDir + QStringLiteral("/mime/packages"); QVERIFY(here.mkpath(globalPackageDir) && here.mkpath(m_localXdgDir)); qputenv("XDG_DATA_DIRS", QFile::encodeName(m_globalXdgDir)); qputenv("XDG_DATA_HOME", QFile::encodeName(m_localXdgDir)); qDebug() << "\nLocal XDG_DATA_HOME: " << m_localXdgDir << "\nGlobal XDG_DATA_DIRS: " << m_globalXdgDir; const QString freeDesktopXml = QStringLiteral("freedesktop.org.xml"); const QString xmlFileName = QLatin1String(CORE_SOURCES) + QStringLiteral("/mimetypes/mime/packages/") + freeDesktopXml; QVERIFY2(QFileInfo(xmlFileName).exists(), qPrintable(xmlFileName + QStringLiteral(" does not exist"))); QFile xml(xmlFileName); QVERIFY(xml.copy(globalPackageDir + '/' + freeDesktopXml)); m_testSuite = QFINDTESTDATA("testfiles"); if (m_testSuite.isEmpty()) qWarning("%s", qPrintable(testSuiteWarning())); m_yastMimeTypes = QFINDTESTDATA(yastFileName); QVERIFY2(!m_yastMimeTypes.isEmpty(), qPrintable(QString::fromLatin1("Cannot find '%1' starting from '%2'"). arg(yastFileName, QDir::currentPath()))); init(); } void tst_QMimeDatabase::mimeTypeForName() { QMimeDatabase db; QMimeType s0 = db.mimeTypeForName(QString::fromLatin1("application/x-zerosize")); QVERIFY(s0.isValid()); QCOMPARE(s0.name(), QString::fromLatin1("application/x-zerosize")); QCOMPARE(s0.comment(), QString::fromLatin1("empty document")); QMimeType s0Again = db.mimeTypeForName(QString::fromLatin1("application/x-zerosize")); QCOMPARE(s0Again.name(), s0.name()); QMimeType s1 = db.mimeTypeForName(QString::fromLatin1("text/plain")); QVERIFY(s1.isValid()); QCOMPARE(s1.name(), QString::fromLatin1("text/plain")); //qDebug("Comment is %s", qPrintable(s1.comment())); QMimeType krita = db.mimeTypeForName(QString::fromLatin1("application/x-krita")); QVERIFY(krita.isValid()); // Test parsing with application/rdf+xml which has the english comment after the other ones QMimeType rdf = db.mimeTypeForName(QString::fromLatin1("application/rdf+xml")); QVERIFY(rdf.isValid()); QCOMPARE(rdf.comment(), QString::fromLatin1("RDF file")); QMimeType bzip2 = db.mimeTypeForName(QString::fromLatin1("application/x-bzip2")); QVERIFY(bzip2.isValid()); QCOMPARE(bzip2.comment(), QString::fromLatin1("Bzip archive")); QMimeType defaultMime = db.mimeTypeForName(QString::fromLatin1("application/octet-stream")); QVERIFY(defaultMime.isValid()); QVERIFY(defaultMime.isDefault()); QMimeType doesNotExist = db.mimeTypeForName(QString::fromLatin1("foobar/x-doesnot-exist")); QVERIFY(!doesNotExist.isValid()); // TODO move to findByFile #ifdef Q_OS_LINUX QString exePath = QStandardPaths::findExecutable(QLatin1String("ls")); if (exePath.isEmpty()) qWarning() << "ls not found"; else { const QString executableType = QString::fromLatin1("application/x-executable"); //QTest::newRow("executable") << exePath << executableType; QCOMPARE(db.mimeTypeForFile(exePath).name(), executableType); } #endif } void tst_QMimeDatabase::mimeTypeForFileName_data() { QTest::addColumn("fileName"); QTest::addColumn("expectedMimeType"); QTest::newRow("text") << "textfile.txt" << "text/plain"; QTest::newRow("case-insensitive search") << "textfile.TxT" << "text/plain"; // Needs shared-mime-info > 0.91. Earlier versions wrote .Z to the mime.cache file... //QTest::newRow("case-insensitive match on a non-lowercase glob") << "foo.z" << "application/x-compress"; QTest::newRow("case-sensitive uppercase match") << "textfile.C" << "text/x-c++src"; QTest::newRow("case-sensitive lowercase match") << "textfile.c" << "text/x-csrc"; QTest::newRow("case-sensitive long-extension match") << "foo.PS.gz" << "application/x-gzpostscript"; QTest::newRow("case-sensitive-only match") << "core" << "application/x-core"; QTest::newRow("case-sensitive-only match") << "Core" << "application/octet-stream"; // #198477 QTest::newRow("desktop file") << "foo.desktop" << "application/x-desktop"; QTest::newRow("old kdelnk file is x-desktop too") << "foo.kdelnk" << "application/x-desktop"; QTest::newRow("double-extension file") << "foo.tar.bz2" << "application/x-bzip-compressed-tar"; QTest::newRow("single-extension file") << "foo.bz2" << "application/x-bzip"; QTest::newRow(".doc should assume msword") << "somefile.doc" << "application/msword"; // #204139 QTest::newRow("glob that uses [] syntax, 1") << "Makefile" << "text/x-makefile"; QTest::newRow("glob that uses [] syntax, 2") << "makefile" << "text/x-makefile"; QTest::newRow("glob that ends with *, no extension") << "README" << "text/x-readme"; QTest::newRow("glob that ends with *, extension") << "README.foo" << "text/x-readme"; QTest::newRow("glob that ends with *, also matches *.txt. Higher weight wins.") << "README.txt" << "text/plain"; QTest::newRow("glob that ends with *, also matches *.nfo. Higher weight wins.") << "README.nfo" << "text/x-nfo"; // fdo bug 15436, needs shared-mime-info >= 0.40 (and this tests the globs2-parsing code). QTest::newRow("glob that ends with *, also matches *.pdf. *.pdf has higher weight") << "README.pdf" << "application/pdf"; QTest::newRow("directory") << "/" << "inode/directory"; QTest::newRow("doesn't exist, no extension") << "IDontExist" << "application/octet-stream"; QTest::newRow("doesn't exist but has known extension") << "IDontExist.txt" << "text/plain"; QTest::newRow("empty") << "" << "application/octet-stream"; } static inline QByteArray msgMimeTypeForFileNameFailed(const QList &actual, const QString &expected) { QByteArray result = "Actual ("; foreach (const QMimeType &m, actual) { result += m.name().toLocal8Bit(); result += ' '; } result += ") , expected: "; result += expected.toLocal8Bit(); return result; } void tst_QMimeDatabase::mimeTypeForFileName() { QFETCH(QString, fileName); QFETCH(QString, expectedMimeType); QMimeDatabase db; QMimeType mime = db.mimeTypeForFile(fileName, QMimeDatabase::MatchExtension); QVERIFY(mime.isValid()); QCOMPARE(mime.name(), expectedMimeType); QList mimes = db.mimeTypesForFileName(fileName); if (expectedMimeType == "application/octet-stream") { QVERIFY(mimes.isEmpty()); } else { QVERIFY2(!mimes.isEmpty(), msgMimeTypeForFileNameFailed(mimes, expectedMimeType).constData()); QVERIFY2(mimes.count() == 1, msgMimeTypeForFileNameFailed(mimes, expectedMimeType).constData()); QCOMPARE(mimes.first().name(), expectedMimeType); } } void tst_QMimeDatabase::mimeTypesForFileName_data() { QTest::addColumn("fileName"); QTest::addColumn("expectedMimeTypes"); QTest::newRow("txt, 1 hit") << "foo.txt" << (QStringList() << "text/plain"); QTest::newRow("txtfoobar, 0 hit") << "foo.foobar" << QStringList(); QTest::newRow("m, 2 hits") << "foo.m" << (QStringList() << "text/x-matlab" << "text/x-objcsrc"); QTest::newRow("sub, 3 hits") << "foo.sub" << (QStringList() << "text/x-microdvd" << "text/x-mpsub" << "text/x-subviewer"); } void tst_QMimeDatabase::mimeTypesForFileName() { QFETCH(QString, fileName); QFETCH(QStringList, expectedMimeTypes); QMimeDatabase db; QList mimes = db.mimeTypesForFileName(fileName); QStringList mimeNames; foreach (const QMimeType &mime, mimes) mimeNames.append(mime.name()); QCOMPARE(mimeNames, expectedMimeTypes); } void tst_QMimeDatabase::inheritance() { QMimeDatabase db; // All file-like mimetypes inherit from octet-stream const QMimeType wordperfect = db.mimeTypeForName(QString::fromLatin1("application/vnd.wordperfect")); QVERIFY(wordperfect.isValid()); QCOMPARE(wordperfect.parentMimeTypes().join(QString::fromLatin1(",")), QString::fromLatin1("application/octet-stream")); QVERIFY(wordperfect.inherits(QLatin1String("application/octet-stream"))); QVERIFY(db.mimeTypeForName(QString::fromLatin1("image/svg+xml-compressed")).inherits(QLatin1String("application/x-gzip"))); // Check that msword derives from ole-storage const QMimeType msword = db.mimeTypeForName(QString::fromLatin1("application/msword")); QVERIFY(msword.isValid()); const QMimeType olestorage = db.mimeTypeForName(QString::fromLatin1("application/x-ole-storage")); QVERIFY(olestorage.isValid()); QVERIFY(msword.inherits(olestorage.name())); QVERIFY(msword.inherits(QLatin1String("application/octet-stream"))); const QMimeType directory = db.mimeTypeForName(QString::fromLatin1("inode/directory")); QVERIFY(directory.isValid()); QCOMPARE(directory.parentMimeTypes().count(), 0); QVERIFY(!directory.inherits(QLatin1String("application/octet-stream"))); // Check that text/x-patch knows that it inherits from text/plain (it says so explicitly) const QMimeType plain = db.mimeTypeForName(QString::fromLatin1("text/plain")); const QMimeType derived = db.mimeTypeForName(QString::fromLatin1("text/x-patch")); QVERIFY(derived.isValid()); QCOMPARE(derived.parentMimeTypes().join(QString::fromLatin1(",")), plain.name()); QVERIFY(derived.inherits(QLatin1String("text/plain"))); QVERIFY(derived.inherits(QLatin1String("application/octet-stream"))); // Check that application/x-shellscript inherits from application/x-executable // (Otherwise KRun cannot start shellscripts...) // This is a test for multiple inheritance... const QMimeType shellscript = db.mimeTypeForName(QString::fromLatin1("application/x-shellscript")); QVERIFY(shellscript.isValid()); QVERIFY(shellscript.inherits(QLatin1String("text/plain"))); QVERIFY(shellscript.inherits(QLatin1String("application/x-executable"))); const QStringList shellParents = shellscript.parentMimeTypes(); QVERIFY(shellParents.contains(QLatin1String("text/plain"))); QVERIFY(shellParents.contains(QLatin1String("application/x-executable"))); QCOMPARE(shellParents.count(), 2); // only the above two const QStringList allShellAncestors = shellscript.allAncestors(); QVERIFY(allShellAncestors.contains(QLatin1String("text/plain"))); QVERIFY(allShellAncestors.contains(QLatin1String("application/x-executable"))); QVERIFY(allShellAncestors.contains(QLatin1String("application/octet-stream"))); // Must be least-specific last, i.e. breadth first. QCOMPARE(allShellAncestors.last(), QString::fromLatin1("application/octet-stream")); const QStringList allSvgAncestors = db.mimeTypeForName(QString::fromLatin1("image/svg+xml")).allAncestors(); QCOMPARE(allSvgAncestors, QStringList() << QLatin1String("application/xml") << QLatin1String("text/plain") << QLatin1String("application/octet-stream")); // Check that text/x-mrml knows that it inherits from text/plain (implicitly) const QMimeType mrml = db.mimeTypeForName(QString::fromLatin1("text/x-mrml")); QVERIFY(mrml.isValid()); QVERIFY(mrml.inherits(QLatin1String("text/plain"))); QVERIFY(mrml.inherits(QLatin1String("application/octet-stream"))); // Check that msword-template inherits msword const QMimeType mswordTemplate = db.mimeTypeForName(QString::fromLatin1("application/msword-template")); QVERIFY(mswordTemplate.isValid()); QVERIFY(mswordTemplate.inherits(QLatin1String("application/msword"))); } void tst_QMimeDatabase::aliases() { QMimeDatabase db; const QMimeType canonical = db.mimeTypeForName(QString::fromLatin1("application/xml")); QVERIFY(canonical.isValid()); QMimeType resolvedAlias = db.mimeTypeForName(QString::fromLatin1("text/xml")); QVERIFY(resolvedAlias.isValid()); QCOMPARE(resolvedAlias.name(), QString::fromLatin1("application/xml")); QVERIFY(resolvedAlias.inherits(QLatin1String("application/xml"))); QVERIFY(canonical.inherits(QLatin1String("text/xml"))); // Test for kde bug 197346: does nspluginscan see that audio/mp3 already exists? bool mustWriteMimeType = !db.mimeTypeForName(QString::fromLatin1("audio/mp3")).isValid(); QVERIFY(!mustWriteMimeType); } void tst_QMimeDatabase::icons() { QMimeDatabase db; QMimeType directory = db.mimeTypeForFile(QString::fromLatin1("/")); QCOMPARE(directory.name(), QString::fromLatin1("inode/directory")); QCOMPARE(directory.iconName(), QString::fromLatin1("inode-directory")); QCOMPARE(directory.genericIconName(), QString::fromLatin1("inode-x-generic")); QMimeType pub = db.mimeTypeForFile(QString::fromLatin1("foo.epub"), QMimeDatabase::MatchExtension); QCOMPARE(pub.name(), QString::fromLatin1("application/epub+zip")); QCOMPARE(pub.iconName(), QString::fromLatin1("application-epub+zip")); QCOMPARE(pub.genericIconName(), QString::fromLatin1("x-office-document")); } // In here we do the tests that need some content in a temporary file. // This could also be added to shared-mime-info's testsuite... void tst_QMimeDatabase::mimeTypeForFileWithContent() { QMimeDatabase db; QMimeType mime; // Test a real PDF file. // If we find x-matlab because it starts with '%' then we are not ordering by priority. QTemporaryFile tempFile; QVERIFY(tempFile.open()); QString tempFileName = tempFile.fileName(); tempFile.write("%PDF-"); tempFile.close(); mime = db.mimeTypeForFile(tempFileName); QCOMPARE(mime.name(), QString::fromLatin1("application/pdf")); QFile file(tempFileName); mime = db.mimeTypeForData(&file); // QIODevice ctor QCOMPARE(mime.name(), QString::fromLatin1("application/pdf")); // by name only, we cannot find the mimetype mime = db.mimeTypeForFile(tempFileName, QMimeDatabase::MatchExtension); QVERIFY(mime.isValid()); QVERIFY(mime.isDefault()); // Test the case where the extension doesn't match the contents: extension wins { QTemporaryFile txtTempFile(QDir::tempPath() + QLatin1String("/tst_QMimeDatabase_XXXXXX.txt")); QVERIFY(txtTempFile.open()); txtTempFile.write("%PDF-"); QString txtTempFileName = txtTempFile.fileName(); txtTempFile.close(); mime = db.mimeTypeForFile(txtTempFileName); QCOMPARE(mime.name(), QString::fromLatin1("text/plain")); // fast mode finds the same mime = db.mimeTypeForFile(txtTempFileName, QMimeDatabase::MatchExtension); QCOMPARE(mime.name(), QString::fromLatin1("text/plain")); } // Now the case where extension differs from contents, but contents has >80 magic rule // XDG spec says: contents wins. But we can't sniff all files... { QTemporaryFile txtTempFile(QDir::tempPath() + QLatin1String("/tst_QMimeDatabase_XXXXXX.txt")); QVERIFY(txtTempFile.open()); txtTempFile.write("("data"); QTest::addColumn("expectedMimeTypeName"); QTest::newRow("tnef data, needs smi >= 0.20") << QByteArray("\x78\x9f\x3e\x22") << "application/vnd.ms-tnef"; QTest::newRow("PDF magic") << QByteArray("%PDF-") << "application/pdf"; QTest::newRow("PHP, High-priority rule") << QByteArray("("name"); QTest::addColumn("data"); QTest::addColumn("expectedMimeTypeName"); QTest::newRow("plain text, no extension") << QString::fromLatin1("textfile") << QByteArray("Hello world") << "text/plain"; QTest::newRow("plain text, unknown extension") << QString::fromLatin1("textfile.foo") << QByteArray("Hello world") << "text/plain"; // Needs kde/mimetypes.xml //QTest::newRow("plain text, doc extension") << QString::fromLatin1("textfile.doc") << QByteArray("Hello world") << "text/plain"; // If you get powerpoint instead, then you're hit by https://bugs.freedesktop.org/show_bug.cgi?id=435, // upgrade to shared-mime-info >= 0.22 const QByteArray oleData("\320\317\021\340\241\261\032\341"); // same as \xD0\xCF\x11\xE0 \xA1\xB1\x1A\xE1 QTest::newRow("msword file, unknown extension") << QString::fromLatin1("mswordfile") << oleData << "application/x-ole-storage"; QTest::newRow("excel file, found by extension") << QString::fromLatin1("excelfile.xls") << oleData << "application/vnd.ms-excel"; QTest::newRow("text.xls, found by extension, user is in control") << QString::fromLatin1("text.xls") << oleData << "application/vnd.ms-excel"; } void tst_QMimeDatabase::mimeTypeForFileAndContent() { QFETCH(QString, name); QFETCH(QByteArray, data); QFETCH(QString, expectedMimeTypeName); QMimeDatabase db; QCOMPARE(db.mimeTypeForNameAndData(name, data).name(), expectedMimeTypeName); QBuffer buffer(&data); QCOMPARE(db.mimeTypeForNameAndData(name, &buffer).name(), expectedMimeTypeName); QVERIFY(!buffer.isOpen()); // initial state was restored QVERIFY(buffer.open(QIODevice::ReadOnly)); QCOMPARE(db.mimeTypeForNameAndData(name, &buffer).name(), expectedMimeTypeName); QVERIFY(buffer.isOpen()); QCOMPARE(buffer.pos(), qint64(0)); } void tst_QMimeDatabase::allMimeTypes() { QMimeDatabase db; const QList lst = db.allMimeTypes(); // does NOT include aliases QVERIFY(!lst.isEmpty()); // Hardcoding this is the only way to check both providers find the same number of mimetypes. QCOMPARE(lst.count(), 661); foreach (const QMimeType &mime, lst) { const QString name = mime.name(); QVERIFY(!name.isEmpty()); QCOMPARE(name.count(QLatin1Char('/')), 1); const QMimeType lookedupMime = db.mimeTypeForName(name); QVERIFY(lookedupMime.isValid()); QCOMPARE(lookedupMime.name(), name); // if this fails, you have an alias defined as a real mimetype too! } } void tst_QMimeDatabase::inheritsPerformance() { // Check performance of inherits(). // This benchmark (which started in 2009 in kmimetypetest.cpp) uses 40 mimetypes. QStringList mimeTypes; mimeTypes << QLatin1String("image/jpeg") << QLatin1String("image/png") << QLatin1String("image/tiff") << QLatin1String("text/plain") << QLatin1String("text/html"); mimeTypes += mimeTypes; mimeTypes += mimeTypes; mimeTypes += mimeTypes; QCOMPARE(mimeTypes.count(), 40); QMimeDatabase db; QMimeType mime = db.mimeTypeForName(QString::fromLatin1("text/x-chdr")); QVERIFY(mime.isValid()); QBENCHMARK { QString match; foreach (const QString &mt, mimeTypes) { if (mime.inherits(mt)) { match = mt; // of course there would normally be a "break" here, but we're testing worse-case // performance here } } QCOMPARE(match, QString::fromLatin1("text/plain")); } // Numbers from 2011, in release mode: // KDE 4.7 numbers: 0.21 msec / 494,000 ticks / 568,345 instr. loads per iteration // QMimeBinaryProvider (with Qt 5): 0.16 msec / NA / 416,049 instr. reads per iteration // QMimeXmlProvider (with Qt 5): 0.062 msec / NA / 172,889 instr. reads per iteration // (but the startup time is way higher) // And memory usage is flat at 200K with QMimeBinaryProvider, while it peaks at 6 MB when // parsing XML, and then keeps being around 4.5 MB for all the in-memory hashes. } void tst_QMimeDatabase::suffixes_data() { QTest::addColumn("mimeType"); QTest::addColumn("patterns"); QTest::addColumn("preferredSuffix"); QTest::newRow("mimetype with a single pattern") << "application/pdf" << "*.pdf" << "pdf"; QTest::newRow("mimetype with multiple patterns") << "application/x-kpresenter" << "*.kpr;*.kpt" << "kpr"; //if (KMimeType::sharedMimeInfoVersion() > KDE_MAKE_VERSION(0, 60, 0)) { QTest::newRow("mimetype with many patterns") << "application/vnd.wordperfect" << "*.wp;*.wp4;*.wp5;*.wp6;*.wpd;*.wpp" << "wp"; //} QTest::newRow("oasis text mimetype") << "application/vnd.oasis.opendocument.text" << "*.odt" << "odt"; QTest::newRow("oasis presentation mimetype") << "application/vnd.oasis.opendocument.presentation" << "*.odp" << "odp"; QTest::newRow("mimetype with multiple patterns") << "text/plain" << "*.asc;*.txt;*,v" << "txt"; QTest::newRow("mimetype with uncommon pattern") << "text/x-readme" << "README*" << QString(); QTest::newRow("mimetype with no patterns") << "application/x-ole-storage" << QString() << QString(); } void tst_QMimeDatabase::suffixes() { QFETCH(QString, mimeType); QFETCH(QString, patterns); QFETCH(QString, preferredSuffix); QMimeDatabase db; QMimeType mime = db.mimeTypeForName(mimeType); QVERIFY(mime.isValid()); // Sort both lists; order is unreliable since shared-mime-info uses hashes internally. QStringList expectedPatterns = patterns.split(QLatin1Char(';')); expectedPatterns.sort(); QStringList mimePatterns = mime.globPatterns(); mimePatterns.sort(); QCOMPARE(mimePatterns.join(QLatin1String(";")), expectedPatterns.join(QLatin1String(";"))); QCOMPARE(mime.preferredSuffix(), preferredSuffix); } void tst_QMimeDatabase::knownSuffix() { QMimeDatabase db; QCOMPARE(db.suffixForFileName(QString::fromLatin1("foo.tar")), QString::fromLatin1("tar")); QCOMPARE(db.suffixForFileName(QString::fromLatin1("foo.bz2")), QString::fromLatin1("bz2")); QCOMPARE(db.suffixForFileName(QString::fromLatin1("foo.bar.bz2")), QString::fromLatin1("bz2")); QCOMPARE(db.suffixForFileName(QString::fromLatin1("foo.tar.bz2")), QString::fromLatin1("tar.bz2")); } void tst_QMimeDatabase::findByFileName_data() { QTest::addColumn("filePath"); QTest::addColumn("mimeTypeName"); QTest::addColumn("xFail"); if (m_testSuite.isEmpty()) QSKIP("shared-mime-info test suite not available."); const QString prefix = m_testSuite + QLatin1Char('/'); const QString fileName = prefix + QLatin1String("list"); QFile f(fileName); QVERIFY2(f.open(QIODevice::ReadOnly|QIODevice::Text), qPrintable(QString::fromLatin1("Cannot open %1: %2").arg(fileName, f.errorString()))); QByteArray line(1024, Qt::Uninitialized); while (!f.atEnd()) { int len = f.readLine(line.data(), 1023); if (len <= 2 || line.at(0) == '#') continue; QString string = QString::fromLatin1(line.constData(), len - 1).trimmed(); QStringList list = string.split(QLatin1Char(' '), QString::SkipEmptyParts); QVERIFY(list.size() >= 2); QString filePath = list.at(0); QString mimeTypeType = list.at(1); QString xFail; if (list.size() >= 3) xFail = list.at(2); QTest::newRow(filePath.toLatin1().constData()) << QString(prefix + filePath) << mimeTypeType << xFail; } } void tst_QMimeDatabase::findByFileName() { QFETCH(QString, filePath); QFETCH(QString, mimeTypeName); QFETCH(QString, xFail); QMimeDatabase database; //qDebug() << Q_FUNC_INFO << filePath; const QMimeType resultMimeType(database.mimeTypeForFile(filePath, QMimeDatabase::MatchExtension)); if (resultMimeType.isValid()) { //qDebug() << Q_FUNC_INFO << "MIME type" << resultMimeType.name() << "has generic icon name" << resultMimeType.genericIconName() << "and icon name" << resultMimeType.iconName(); // Loading icons depend on the icon theme, we can't enable this test #if 0 QCOMPARE(resultMimeType.genericIconName(), QIcon::fromTheme(resultMimeType.genericIconName()).name()); QVERIFY2(!QIcon::fromTheme(resultMimeType.genericIconName()).isNull(), qPrintable(resultMimeType.genericIconName())); QVERIFY2(QIcon::hasThemeIcon(resultMimeType.genericIconName()), qPrintable(resultMimeType.genericIconName())); QCOMPARE(resultMimeType.iconName(), QIcon::fromTheme(resultMimeType.iconName()).name()); QVERIFY2(!QIcon::fromTheme(resultMimeType.iconName()).isNull(), qPrintable(resultMimeType.iconName())); QVERIFY2(QIcon::hasThemeIcon(resultMimeType.iconName()), qPrintable(resultMimeType.iconName())); #endif } const QString resultMimeTypeName = resultMimeType.name(); //qDebug() << Q_FUNC_INFO << "mimeTypeForFile() returned" << resultMimeTypeName; const bool failed = resultMimeTypeName != mimeTypeName; const bool shouldFail = (xFail.length() >= 1 && xFail.at(0) == QLatin1Char('x')); if (shouldFail != failed) { // Results are ambiguous when multiple MIME types have the same glob // -> accept the current result if the found MIME type actually // matches the file's extension. // TODO: a better file format in testfiles/list! const QMimeType foundMimeType = database.mimeTypeForName(resultMimeTypeName); QVERIFY2(resultMimeType == foundMimeType, qPrintable(resultMimeType.name() + QString::fromLatin1(" vs. ") + foundMimeType.name())); if (foundMimeType.isValid()) { const QString extension = QFileInfo(filePath).suffix(); //qDebug() << Q_FUNC_INFO << "globPatterns:" << foundMimeType.globPatterns() << "- extension:" << QString() + "*." + extension; if (foundMimeType.globPatterns().contains(QString::fromLatin1("*.") + extension)) return; } } if (shouldFail) { // Expected to fail QVERIFY2(resultMimeTypeName != mimeTypeName, qPrintable(resultMimeTypeName)); } else { QCOMPARE(resultMimeTypeName, mimeTypeName); } // Test QFileInfo overload const QMimeType mimeForFileInfo = database.mimeTypeForFile(QFileInfo(filePath), QMimeDatabase::MatchExtension); QCOMPARE(mimeForFileInfo.name(), resultMimeTypeName); } void tst_QMimeDatabase::findByData_data() { findByFileName_data(); } void tst_QMimeDatabase::findByData() { QFETCH(QString, filePath); QFETCH(QString, mimeTypeName); QFETCH(QString, xFail); QMimeDatabase database; QFile f(filePath); QVERIFY(f.open(QIODevice::ReadOnly)); QByteArray data = f.read(16384); const QString resultMimeTypeName = database.mimeTypeForData(data).name(); if (xFail.length() >= 2 && xFail.at(1) == QLatin1Char('x')) { // Expected to fail QVERIFY2(resultMimeTypeName != mimeTypeName, qPrintable(resultMimeTypeName)); } else { QCOMPARE(resultMimeTypeName, mimeTypeName); } QFileInfo info(filePath); QString mimeForInfo = database.mimeTypeForFile(info, QMimeDatabase::MatchContent).name(); QCOMPARE(mimeForInfo, resultMimeTypeName); } void tst_QMimeDatabase::findByFile_data() { findByFileName_data(); } // Note: this test fails on "testcompress.z" when using a shared-mime-info older than 1.0. // This because of commit 0f9a506069c in shared-mime-info, which fixed the writing of // case-insensitive patterns into mime.cache. void tst_QMimeDatabase::findByFile() { QFETCH(QString, filePath); QFETCH(QString, mimeTypeName); QFETCH(QString, xFail); QMimeDatabase database; const QString resultMimeTypeName = database.mimeTypeForFile(filePath).name(); //qDebug() << Q_FUNC_INFO << filePath << "->" << resultMimeTypeName; if (xFail.length() >= 3 && xFail.at(2) == QLatin1Char('x')) { // Expected to fail QVERIFY2(resultMimeTypeName != mimeTypeName, qPrintable(resultMimeTypeName)); } else { QCOMPARE(resultMimeTypeName, mimeTypeName); } // Test QFileInfo overload const QMimeType mimeForFileInfo = database.mimeTypeForFile(QFileInfo(filePath)); QCOMPARE(mimeForFileInfo.name(), resultMimeTypeName); } void tst_QMimeDatabase::fromThreads() { QThreadPool::globalInstance()->setMaxThreadCount(20); // Note that data-based tests cannot be used here (QTest::fetchData asserts). QList > futures; futures << QtConcurrent::run(this, &tst_QMimeDatabase::mimeTypeForName); futures << QtConcurrent::run(this, &tst_QMimeDatabase::aliases); futures << QtConcurrent::run(this, &tst_QMimeDatabase::allMimeTypes); futures << QtConcurrent::run(this, &tst_QMimeDatabase::icons); futures << QtConcurrent::run(this, &tst_QMimeDatabase::inheritance); futures << QtConcurrent::run(this, &tst_QMimeDatabase::knownSuffix); futures << QtConcurrent::run(this, &tst_QMimeDatabase::mimeTypeForFileWithContent); futures << QtConcurrent::run(this, &tst_QMimeDatabase::allMimeTypes); // a second time Q_FOREACH (QFuture f, futures) f.waitForFinished(); } static bool runUpdateMimeDatabase(const QString &path) // TODO make it a QMimeDatabase method? { const QString umdCommand = QString::fromLatin1("update-mime-database"); const QString umd = QStandardPaths::findExecutable(umdCommand); if (umd.isEmpty()) { qWarning("%s does not exist.", qPrintable(umdCommand)); return false; } QProcess proc; proc.setProcessChannelMode(QProcess::MergedChannels); // silence output proc.start(umd, QStringList(path)); if (!proc.waitForStarted()) { qWarning("Cannot start %s: %s", qPrintable(umd), qPrintable(proc.errorString())); return false; } proc.waitForFinished(); //qDebug() << "runUpdateMimeDatabase" << path; return true; } static bool waitAndRunUpdateMimeDatabase(const QString &path) { QFileInfo mimeCacheInfo(path + QString::fromLatin1("/mime.cache")); if (mimeCacheInfo.exists()) { // Wait until the beginning of the next second while (mimeCacheInfo.lastModified().secsTo(QDateTime::currentDateTime()) == 0) { QTest::qSleep(200); } } return runUpdateMimeDatabase(path); } static void checkHasMimeType(const QString &mimeType) { QMimeDatabase db; QVERIFY(db.mimeTypeForName(mimeType).isValid()); bool found = false; foreach (const QMimeType &mt, db.allMimeTypes()) { if (mt.name() == mimeType) { found = true; break; } } QVERIFY(found); } QT_BEGIN_NAMESPACE extern Q_CORE_EXPORT int qmime_secondsBetweenChecks; // see qmimeprovider.cpp QT_END_NAMESPACE void tst_QMimeDatabase::installNewGlobalMimeType() { qmime_secondsBetweenChecks = 0; QMimeDatabase db; QVERIFY(!db.mimeTypeForName(QLatin1String("text/x-suse-ymp")).isValid()); const QString mimeDir = m_globalXdgDir + QLatin1String("/mime"); const QString destDir = mimeDir + QLatin1String("/packages/"); const QString destFile = destDir + QLatin1String(yastFileName); QFile::remove(destFile); //qDebug() << destFile; if (!QFileInfo(destDir).isDir()) QVERIFY(QDir(m_globalXdgDir).mkpath(destDir)); QVERIFY(QFile::copy(m_yastMimeTypes, destFile)); if (!waitAndRunUpdateMimeDatabase(mimeDir)) QSKIP("shared-mime-info not found, skipping mime.cache test"); QCOMPARE(db.mimeTypeForFile(QLatin1String("foo.ymu"), QMimeDatabase::MatchExtension).name(), QString::fromLatin1("text/x-suse-ymu")); QVERIFY(db.mimeTypeForName(QLatin1String("text/x-suse-ymp")).isValid()); checkHasMimeType("text/x-suse-ymp"); // Now test removing it again QFile::remove(destFile); if (!waitAndRunUpdateMimeDatabase(mimeDir)) QSKIP("shared-mime-info not found, skipping mime.cache test"); QCOMPARE(db.mimeTypeForFile(QLatin1String("foo.ymu"), QMimeDatabase::MatchExtension).name(), QString::fromLatin1("application/octet-stream")); QVERIFY(!db.mimeTypeForName(QLatin1String("text/x-suse-ymp")).isValid()); } void tst_QMimeDatabase::installNewLocalMimeType() { qmime_secondsBetweenChecks = 0; QMimeDatabase db; QVERIFY(!db.mimeTypeForName(QLatin1String("text/x-suse-ymp")).isValid()); const QString mimeDir = m_localXdgDir + QLatin1String("/mime"); const QString destDir = mimeDir + QLatin1String("/packages/"); QDir().mkpath(destDir); const QString destFile = destDir + QLatin1String(yastFileName); QFile::remove(destFile); QVERIFY(QFile::copy(m_yastMimeTypes, destFile)); if (!runUpdateMimeDatabase(mimeDir)) { const QString skipWarning = QStringLiteral("shared-mime-info not found, skipping mime.cache test (") + QDir::toNativeSeparators(mimeDir) + QLatin1Char(')'); QSKIP(qPrintable(skipWarning)); } QCOMPARE(db.mimeTypeForFile(QLatin1String("foo.ymu"), QMimeDatabase::MatchExtension).name(), QString::fromLatin1("text/x-suse-ymu")); QVERIFY(db.mimeTypeForName(QLatin1String("text/x-suse-ymp")).isValid()); checkHasMimeType("text/x-suse-ymp"); // Now test removing it again (note, this leaves a mostly-empty mime.cache file) QFile::remove(destFile); if (!waitAndRunUpdateMimeDatabase(mimeDir)) QSKIP("shared-mime-info not found, skipping mime.cache test"); QCOMPARE(db.mimeTypeForFile(QLatin1String("foo.ymu"), QMimeDatabase::MatchExtension).name(), QString::fromLatin1("application/octet-stream")); QVERIFY(!db.mimeTypeForName(QLatin1String("text/x-suse-ymp")).isValid()); // And now the user goes wild and uses rm -rf QFile::remove(mimeDir + QString::fromLatin1("/mime.cache")); QCOMPARE(db.mimeTypeForFile(QLatin1String("foo.ymu"), QMimeDatabase::MatchExtension).name(), QString::fromLatin1("application/octet-stream")); QVERIFY(!db.mimeTypeForName(QLatin1String("text/x-suse-ymp")).isValid()); } QTEST_GUILESS_MAIN(tst_QMimeDatabase)