blob: 42a4134e5f153417d4af849cbae78d14852ba221 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2012 Giuseppe D'Angelo <dangelog@gmail.com>
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <QtTest/QtTest>
#include <QtCore/QString>
#include <QtCore/QCoreApplication>
#include <QtCore/QByteArray>
#include <QtCore/QDir>
#include <QtCore/QFile>
#include <QtCore/QProcess>
#include <QtCore/QDirIterator>
#include <QtCore/QMap>
#include <QtCore/QList>
#include <QtCore/QResource>
#include <QtCore/QLocale>
#include <QtCore/QtGlobal>
#include <algorithm>
typedef QMap<QString, QString> QStringMap;
Q_DECLARE_METATYPE(QStringMap)
static QByteArray msgProcessStartFailed(const QProcess &p)
{
const QString result = QLatin1String("Could not start \"")
+ QDir::toNativeSeparators(p.program()) + QLatin1String("\": ")
+ p.errorString();
return result.toLocal8Bit();
}
static QByteArray msgProcessTimeout(const QProcess &p)
{
return '"' + QDir::toNativeSeparators(p.program()).toLocal8Bit()
+ "\" timed out.";
}
static QByteArray msgProcessCrashed(QProcess &p)
{
return '"' + QDir::toNativeSeparators(p.program()).toLocal8Bit()
+ "\" crashed.\n" + p.readAllStandardError();
}
static QByteArray msgProcessFailed(QProcess &p)
{
return '"' + QDir::toNativeSeparators(p.program()).toLocal8Bit()
+ "\" returned " + QByteArray::number(p.exitCode()) + ":\n"
+ p.readAllStandardError();
}
class tst_rcc : public QObject
{
Q_OBJECT
private slots:
void initTestCase();
void rcc_data();
void rcc();
void binary_data();
void binary();
void readback_data();
void readback();
void depFileGeneration_data();
void depFileGeneration();
void python();
void cleanupTestCase();
private:
QString m_rcc;
QString m_dataPath;
};
void tst_rcc::initTestCase()
{
// rcc uses a QHash to store files in the resource system.
// we must force a certain hash order when testing or tst_rcc will fail, see QTBUG-25078
QVERIFY(qputenv("QT_RCC_TEST", "1"));
m_rcc = QLibraryInfo::location(QLibraryInfo::BinariesPath) + QLatin1String("/rcc");
m_dataPath = QFINDTESTDATA("data");
QVERIFY(!m_dataPath.isEmpty());
}
static inline bool isPythonComment(const QString &line)
{
return line.startsWith(QLatin1Char('#'));
}
static QString doCompare(const QStringList &actual, const QStringList &expected,
const QString &timeStampPath)
{
if (actual.size() != expected.size()) {
return QString("Length count different: actual: %1, expected: %2")
.arg(actual.size()).arg(expected.size());
}
QByteArray ba;
const bool isPython = isPythonComment(expected.constFirst());
for (int i = 0, n = expected.size(); i != n; ++i) {
QString expectedLine = expected.at(i);
if (expectedLine.startsWith("IGNORE:"))
continue;
if (isPython && isPythonComment(expectedLine) && isPythonComment(actual.at(i)))
continue;
if (expectedLine.startsWith("TIMESTAMP:")) {
const QString relativePath = expectedLine.mid(strlen("TIMESTAMP:"));
const QFileInfo fi(timeStampPath + QLatin1Char('/') + relativePath);
if (!fi.isFile()) {
ba.append("File " + fi.absoluteFilePath().toUtf8() + " does not exist!");
break;
}
const quint64 timeStamp = quint64(fi.lastModified().toMSecsSinceEpoch());
expectedLine.clear();
for (int shift = 56; shift >= 0; shift -= 8) {
expectedLine.append(QLatin1String("0x"));
expectedLine.append(QString::number(quint8(timeStamp >> shift), 16));
expectedLine.append(QLatin1Char(','));
}
}
if (expectedLine != actual.at(i)) {
qDebug() << "LINES" << (i + 1) << "DIFFER";
ba.append(
"\n<<<<<< actual\n" + actual.at(i) + "\n======\n" + expectedLine
+ "\n>>>>>> expected\n"
);
}
}
return ba;
}
void tst_rcc::rcc_data()
{
QTest::addColumn<QString>("directory");
QTest::addColumn<QString>("qrcfile");
QTest::addColumn<QString>("expected");
const QString imagesPath = m_dataPath + QLatin1String("/images");
QTest::newRow("images") << imagesPath << "images.qrc" << "images.expected";
const QString sizesPath = m_dataPath + QLatin1String("/sizes");
QTest::newRow("size-0") << sizesPath << "size-0.qrc" << "size-0.expected";
QTest::newRow("size-1") << sizesPath << "size-1.qrc" << "size-1.expected";
QTest::newRow("size-2-0-35-1") << sizesPath << "size-2-0-35-1.qrc" << "size-2-0-35-1.expected";
}
static QStringList readLinesFromFile(const QString &fileName,
QString::SplitBehavior splitBehavior)
{
QFile file(fileName);
bool ok = file.open(QIODevice::ReadOnly | QIODevice::Text);
if (!ok) {
QWARN(qPrintable(QString::fromLatin1("Could not open testdata file %1: %2")
.arg(fileName, file.errorString())));
}
return QString::fromUtf8(file.readAll()).split(QLatin1Char('\n'), splitBehavior);
}
void tst_rcc::rcc()
{
QFETCH(QString, directory);
QFETCH(QString, qrcfile);
QFETCH(QString, expected);
// If the file expectedoutput.txt exists, compare the
// console output with the content of that file
// Launch; force no compression, otherwise the output would be different
// depending on the compression algorithm we're using
QProcess process;
process.setWorkingDirectory(directory);
process.start(m_rcc, { "-no-compress", qrcfile });
QVERIFY2(process.waitForStarted(), msgProcessStartFailed(process).constData());
if (!process.waitForFinished()) {
process.kill();
QFAIL(msgProcessTimeout(process).constData());
}
QVERIFY2(process.exitStatus() == QProcess::NormalExit,
msgProcessCrashed(process).constData());
QVERIFY2(process.exitCode() == 0,
msgProcessFailed(process).constData());
const QChar cr = QLatin1Char('\r');
const QString err = QString::fromLocal8Bit(process.readAllStandardError()).remove(cr);
const QString out = QString::fromLatin1(process.readAllStandardOutput()).remove(cr);
if (!err.isEmpty()) {
qDebug() << "UNEXPECTED STDERR CONTENTS: " << err;
QFAIL("UNEXPECTED STDERR CONTENTS");
}
const QChar nl = QLatin1Char('\n');
const QStringList actualLines = out.split(nl);
const QStringList expectedLines =
readLinesFromFile(directory + QLatin1Char('/') + expected, QString::KeepEmptyParts);
QVERIFY(!expectedLines.isEmpty());
const QString diff = doCompare(actualLines, expectedLines, directory);
if (diff.size())
QFAIL(qPrintable(diff));
}
static QStringMap readExpectedFiles(const QString &fileName)
{
QStringMap expectedFiles;
QStringList lines = readLinesFromFile(fileName, QString::SkipEmptyParts);
foreach (const QString &line, lines) {
QString resourceFileName = line.section(QLatin1Char(' '), 0, 0, QString::SectionSkipEmpty);
QString actualFileName = line.section(QLatin1Char(' '), 1, 1, QString::SectionSkipEmpty);
expectedFiles[resourceFileName] = actualFileName;
}
return expectedFiles;
}
/*
The following test looks for all *.qrc files under data/binary/. For each
.qrc file found, these files are processed (assuming the file found is
called "base.qrc"):
- base.qrc : processed by rcc; creates base.rcc
- base.locale : (optional) list of locales to test, one per line
- base.expected : list of pairs (file path in resource, path to real file),
one per line; the pair separated by a whitespace; the paths to real files
relative to data/binary/ (for testing the C locale)
- base.localeName.expected : for each localeName in the base.locale file,
as the above .expected file
*/
void tst_rcc::binary_data()
{
QTest::addColumn<QString>("resourceFile");
QTest::addColumn<QLocale>("locale");
QTest::addColumn<QString>("baseDirectory");
QTest::addColumn<QStringMap>("expectedFiles");
QString dataPath = m_dataPath + QLatin1String("/binary/");
QDirIterator iter(dataPath, QStringList() << QLatin1String("*.qrc"));
while (iter.hasNext())
{
iter.next();
QFileInfo qrcFileInfo = iter.fileInfo();
QString absoluteBaseName = QFileInfo(qrcFileInfo.absolutePath(), qrcFileInfo.baseName()).absoluteFilePath();
QString rccFileName = absoluteBaseName + QLatin1String(".rcc");
// same as above: force no compression
QProcess rccProcess;
rccProcess.setWorkingDirectory(dataPath);
rccProcess.start(m_rcc, { "-binary", "-no-compress", "-o", rccFileName, qrcFileInfo.absoluteFilePath() });
QVERIFY2(rccProcess.waitForStarted(), msgProcessStartFailed(rccProcess).constData());
if (!rccProcess.waitForFinished()) {
rccProcess.kill();
QFAIL(msgProcessTimeout(rccProcess).constData());
}
QVERIFY2(rccProcess.exitStatus() == QProcess::NormalExit,
msgProcessCrashed(rccProcess).constData());
QVERIFY2(rccProcess.exitCode() == 0,
msgProcessFailed(rccProcess).constData());
QByteArray output = rccProcess.readAllStandardOutput();
if (!output.isEmpty())
qWarning("rcc stdout: %s", output.constData());
output = rccProcess.readAllStandardError();
if (!output.isEmpty())
qWarning("rcc stderr: %s", output.constData());
QString localeFileName = absoluteBaseName + QLatin1String(".locale");
QFile localeFile(localeFileName);
if (localeFile.exists()) {
QStringList locales = readLinesFromFile(localeFileName, QString::SkipEmptyParts);
foreach (const QString &locale, locales) {
QString expectedFileName = QString::fromLatin1("%1.%2.%3").arg(absoluteBaseName, locale, QLatin1String("expected"));
QStringMap expectedFiles = readExpectedFiles(expectedFileName);
QTest::newRow(qPrintable(qrcFileInfo.baseName() + QLatin1Char('_') + locale)) << rccFileName
<< QLocale(locale)
<< dataPath
<< expectedFiles;
}
}
// always test for the C locale as well
QString expectedFileName = absoluteBaseName + QLatin1String(".expected");
QStringMap expectedFiles = readExpectedFiles(expectedFileName);
QTest::newRow(qPrintable(qrcFileInfo.baseName() + QLatin1String("_C"))) << rccFileName
<< QLocale::c()
<< dataPath
<< expectedFiles;
}
}
void tst_rcc::binary()
{
QFETCH(QString, baseDirectory);
QFETCH(QString, resourceFile);
QFETCH(QLocale, locale);
QFETCH(QStringMap, expectedFiles);
const QString rootPrefix = QLatin1String("/test_root/");
const QString resourceRootPrefix = QLatin1Char(':') + rootPrefix;
QLocale oldDefaultLocale;
QLocale::setDefault(locale);
QVERIFY(QFile::exists(resourceFile));
QVERIFY(QResource::registerResource(resourceFile, rootPrefix));
{ // need to destroy the iterators on the resource, in order to be able to unregister it
// read all the files inside the resources
QDirIterator iter(resourceRootPrefix, QDir::Files, QDirIterator::Subdirectories);
QList<QString> filesFound;
while (iter.hasNext())
filesFound << iter.next();
// add the test root prefix to the expected file names
QList<QString> expectedFileNames = expectedFiles.keys();
for (QList<QString>::iterator i = expectedFileNames.begin(); i < expectedFileNames.end(); ++i) {
// poor man's canonicalPath, which doesn't work with resources
if ((*i).startsWith(QLatin1Char('/')))
(*i).remove(0, 1);
*i = resourceRootPrefix + *i;
}
// check that we have all (and only) the expected files
std::sort(filesFound.begin(), filesFound.end());
std::sort(expectedFileNames.begin(), expectedFileNames.end());
QCOMPARE(filesFound, expectedFileNames);
// now actually check the file contents
QDir directory(baseDirectory);
for (QStringMap::const_iterator i = expectedFiles.constBegin(); i != expectedFiles.constEnd(); ++i) {
QString resourceFileName = i.key();
QString actualFileName = i.value();
QFile resourceFile(resourceRootPrefix + resourceFileName);
QVERIFY(resourceFile.open(QIODevice::ReadOnly));
QByteArray resourceData = resourceFile.readAll();
resourceFile.close();
QFile actualFile(QFileInfo(directory, actualFileName).absoluteFilePath());
QVERIFY(actualFile.open(QIODevice::ReadOnly));
QByteArray actualData = actualFile.readAll();
actualFile.close();
QCOMPARE(resourceData, actualData);
}
}
QVERIFY(QResource::unregisterResource(resourceFile, rootPrefix));
QLocale::setDefault(oldDefaultLocale);
}
void tst_rcc::readback_data()
{
QTest::addColumn<QString>("resourceName");
QTest::addColumn<QString>("fileSystemName");
QTest::newRow("data-0") << ":data/data-0.txt" << "sizes/data/data-0.txt";
QTest::newRow("data-1") << ":data/data-1.txt" << "sizes/data/data-1.txt";
QTest::newRow("data-2") << ":data/data-2.txt" << "sizes/data/data-2.txt";
QTest::newRow("data-35") << ":data/data-35.txt" << "sizes/data/data-35.txt";
QTest::newRow("circle") << ":images/circle.png" << "images/images/circle.png";
QTest::newRow("square") << ":images/square.png" << "images/images/square.png";
QTest::newRow("triangle") << ":images/subdir/triangle.png"
<< "images/images/subdir/triangle.png";
}
void tst_rcc::readback()
{
QFETCH(QString, resourceName);
QFETCH(QString, fileSystemName);
QFile resourceFile(resourceName);
QVERIFY(resourceFile.open(QIODevice::ReadOnly));
QByteArray resourceData = resourceFile.readAll();
resourceFile.close();
QFile fileSystemFile(m_dataPath + QLatin1Char('/') + fileSystemName);
QVERIFY(fileSystemFile.open(QIODevice::ReadOnly));
QByteArray fileSystemData = fileSystemFile.readAll();
fileSystemFile.close();
QCOMPARE(resourceData, fileSystemData);
}
void tst_rcc::depFileGeneration_data()
{
QTest::addColumn<QString>("qrcfile");
QTest::addColumn<QString>("depfile");
QTest::addColumn<QString>("expected");
QTest::newRow("simple") << "simple.qrc" << "simple.d" << "simple.d.expected";
QTest::newRow("specialchar") << "specialchar.qrc" << "specialchar.d" << "specialchar.d.expected";
}
void tst_rcc::depFileGeneration()
{
QFETCH(QString, qrcfile);
QFETCH(QString, depfile);
QFETCH(QString, expected);
const QString directory = m_dataPath + QLatin1String("/depfile");
QProcess process;
process.setWorkingDirectory(directory);
process.start(m_rcc, { "-d", depfile, "-o", qrcfile + ".cpp", qrcfile });
QVERIFY2(process.waitForStarted(), msgProcessStartFailed(process).constData());
if (!process.waitForFinished()) {
process.kill();
QFAIL(msgProcessTimeout(process).constData());
}
QVERIFY2(process.exitStatus() == QProcess::NormalExit,
msgProcessCrashed(process).constData());
QVERIFY2(process.exitCode() == 0,
msgProcessFailed(process).constData());
QFile depFileOutput(directory + QLatin1String("/") + depfile);
QVERIFY(depFileOutput.open(QIODevice::ReadOnly | QIODevice::Text));
QByteArray depFileData = depFileOutput.readAll();
depFileOutput.close();
QFile depFileExpected(directory + QLatin1String("/") + expected);
QVERIFY(depFileExpected.open(QIODevice::ReadOnly | QIODevice::Text));
QByteArray expectedData = depFileExpected.readAll();
depFileExpected.close();
QCOMPARE(depFileData, expectedData);
}
void tst_rcc::python()
{
const QString path = m_dataPath + QLatin1String("/sizes");
const QString testFileRoot = path + QLatin1String("/size-2-0-35-1");
const QString qrcFile = testFileRoot + QLatin1String(".qrc");
const QString expectedFile = testFileRoot + QLatin1String("_python.expected");
const QString actualFile = testFileRoot + QLatin1String(".rcc");
QProcess process;
process.setWorkingDirectory(path);
process.start(m_rcc, { "-g", "python", "-o", actualFile, qrcFile});
QVERIFY2(process.waitForStarted(), msgProcessStartFailed(process).constData());
if (!process.waitForFinished()) {
process.kill();
QFAIL(msgProcessTimeout(process).constData());
}
QVERIFY2(process.exitStatus() == QProcess::NormalExit,
msgProcessCrashed(process).constData());
QVERIFY2(process.exitCode() == 0,
msgProcessFailed(process).constData());
const auto actualLines = readLinesFromFile(actualFile, QString::KeepEmptyParts);
QVERIFY(!actualLines.isEmpty());
const auto expectedLines = readLinesFromFile(expectedFile, QString::KeepEmptyParts);
QVERIFY(!expectedLines.isEmpty());
const QString diff = doCompare(actualLines, expectedLines, path);
if (!diff.isEmpty())
QFAIL(qPrintable(diff));
}
void tst_rcc::cleanupTestCase()
{
QDir dataDir(m_dataPath + QLatin1String("/binary"));
QFileInfoList entries = dataDir.entryInfoList(QStringList() << QLatin1String("*.rcc"));
QDir dataDepDir(m_dataPath + QLatin1String("/depfile"));
entries += dataDepDir.entryInfoList({QLatin1String("*.d"), QLatin1String("*.qrc.cpp")});
foreach (const QFileInfo &entry, entries)
QFile::remove(entry.absoluteFilePath());
}
QTEST_MAIN(tst_rcc)
#include "tst_rcc.moc"