// Copyright (C) 2024 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include "protocplugintestcommon.h" #include #include #include #if QT_CONFIG(process) # include #endif using namespace Qt::StringLiterals; namespace { QByteArray hash(const QByteArray &fileData) { return QCryptographicHash::hash(fileData, QCryptographicHash::Sha1); } // Return size diff and first NOT equal line; QByteArray doCompare(const QByteArrayList &actual, const QByteArrayList &expected) { QByteArray ba; if (actual.size() != expected.size()) { ba.append(QString("Length count different: actual: %1, expected: %2") .arg(actual.size()) .arg(expected.size()) .toUtf8()); } for (int i = 0, n = expected.size(); i != n; ++i) { QString expectedLine = expected.at(i); if (expectedLine != actual.at(i)) { ba.append("\n<<<<<< ACTUAL\n" + actual.at(i) + "\n======\n" + expectedLine.toUtf8() + "\n>>>>>> EXPECTED\n"); break; } } return ba; } QByteArrayList splitToLines(const QByteArray &data) { return data.split('\n'); } bool copyDirectoryRecursively(const QDir &from, QDir to) { if (!from.exists()) { qDebug() << "Unable to copy directory" << from << ". Directory doesn't exists."; return false; } for (const auto &item : from.entryInfoList(QDir::AllEntries | QDir::NoDotAndDotDot | QDir::NoSymLinks)) { const auto fileName = item.fileName(); const auto absolutePath = item.absoluteFilePath(); if (fileName.isEmpty()) { qDebug() << "fileName is empty"; return false; } if (item.isFile()) { if (!QFile::copy(item.absoluteFilePath(), to.absolutePath() + u'/' + fileName)) { qDebug() << "Unable to copy " << item.absoluteFilePath() << "to" << to.absolutePath() + u'/' + fileName; return false; } } else { if (!to.mkdir(fileName)) { qDebug() << "Unable to create " << to.absolutePath() + u'/' + fileName; return false; } if (!to.cd(fileName)) { qDebug() << "Unable to enter " << to.absolutePath() + u'/' + fileName; return false; } if (!copyDirectoryRecursively(QDir(absolutePath), to)) return false; if (!to.cdUp()) return false; } } return true; } } // namespace QByteArray ProtocPluginTest::msgCannotReadFile(const QFile &file) { const QString result = QLatin1StringView("Could not read file: ") + QDir::toNativeSeparators(file.fileName()) + QLatin1StringView(": ") + file.errorString(); return result.toLocal8Bit(); } #if QT_CONFIG(process) QByteArray ProtocPluginTest::msgProcessStartFailed(const QProcess &p) { const QString result = QLatin1StringView("Could not start \"") + QDir::toNativeSeparators(p.program()) + QLatin1StringView("\": ") + p.errorString(); return result.toLocal8Bit(); } QByteArray ProtocPluginTest::msgProcessTimeout(const QProcess &p) { return '"' + QDir::toNativeSeparators(p.program()).toLocal8Bit() + "\" timed out."; } QByteArray ProtocPluginTest::msgProcessCrashed(QProcess &p) { return '"' + QDir::toNativeSeparators(p.program()).toLocal8Bit() + "\" crashed.\n" + p.readAll(); } QByteArray ProtocPluginTest::msgProcessFailed(QProcess &p) { return '"' + QDir::toNativeSeparators(p.program()).toLocal8Bit() + "\" returned " + QByteArray::number(p.exitCode()) + ":\n" + p.readAll(); } bool ProtocPluginTest::protocolCompilerAvailableToRun(const QString &protocPath) { QProcess protoc; protoc.startCommand(protocPath + " --version"); if (!protoc.waitForStarted()) return false; if (!protoc.waitForFinished()) { protoc.kill(); return false; } return protoc.exitStatus() == QProcess::NormalExit; } #endif // QT_CONFIG(process) void ProtocPluginTest::compareTwoFiles(const QString &expectedFileName, const QString &actualFileName) { QFile expectedResultFile(expectedFileName); QFile generatedFile(actualFileName); QVERIFY2(expectedResultFile.exists(), qPrintable(expectedResultFile.fileName())); QVERIFY2(generatedFile.exists(), qPrintable(expectedResultFile.fileName())); QVERIFY2(expectedResultFile.open(QIODevice::ReadOnly | QIODevice::Text), msgCannotReadFile(expectedResultFile).constData()); QVERIFY2(generatedFile.open(QIODevice::ReadOnly | QIODevice::Text), msgCannotReadFile(generatedFile).constData()); QByteArray expectedData = expectedResultFile.readAll(); QByteArray generatedData = generatedFile.readAll(); expectedResultFile.close(); generatedFile.close(); if (hash(expectedData).toHex() != hash(generatedData).toHex()) { const QString diff = doCompare(splitToLines(generatedData), splitToLines(expectedData)); QCOMPARE_GT(diff.size(), 0); // Hashes can only differ if content does. QFAIL(qPrintable(diff)); } // Ensure we do see a failure, even in the unlikely case of a hash collision: QVERIFY(generatedData == expectedData); } void ProtocPluginTest::cleanFolder(const QString &folderName) { QDir dir(folderName); dir.removeRecursively(); } QFileInfoList ProtocPluginTest::scanDirectoryRecursively(const QDir &dir) { QFileInfoList result; QDirIterator it(dir.path(), QStringList() << "*.cpp" << "*.h", QDir::Files | QDir::NoSymLinks, QDirIterator::Subdirectories); while (it.hasNext()) { it.next(); result.append(it.fileInfo()); } std::sort(result.begin(), result.end(), [&dir](const QFileInfo &lhs, const QFileInfo &rhs) { return dir.relativeFilePath(lhs.absoluteFilePath()) < dir.relativeFilePath(rhs.absoluteFilePath()); }); return result; } QStringList ProtocPluginTest::relativePaths(const QDir &dir, const QFileInfoList &files) { QStringList result; for (const auto &file : files) { result.append(dir.relativeFilePath(file.absoluteFilePath())); } return result; } namespace ProtocPluginTest { TestBase::TestBase(QObject *parent) : QObject(parent), m_copyTestResults(qgetenv("COPY_TEST_RESULTS") == "true"_L1) { } void TestBase::initPaths(const QLatin1StringView binaryDir, const QLatin1StringView cmakeGeneratedDir, const QLatin1StringView cmdLineGeneratedDir) { m_expectedResultPath = QFINDTESTDATA("data"); m_cmakeGeneratedPath = binaryDir + '/'_L1 + cmakeGeneratedDir; m_cmakeExpectedResultPath = m_expectedResultPath + '/'_L1 + cmakeGeneratedDir; #if QT_CONFIG(process) QDir(binaryDir).mkdir(cmdLineGeneratedDir); m_cmdLineGeneratedPath = binaryDir + '/'_L1 + cmdLineGeneratedDir; m_cmdLineExpectedResultPath = m_expectedResultPath + '/'_L1 + cmdLineGeneratedDir; #endif } const QString &TestBase::expectedResultPath() const { return m_expectedResultPath; } const QString &TestBase::cmakeGeneratedPath() const { return m_cmakeGeneratedPath; } const QString &TestBase::cmakeExpectedResultPath() const { return m_cmakeExpectedResultPath; } #if QT_CONFIG(process) const QString &TestBase::cmdLineGeneratedPath() const { return m_cmdLineGeneratedPath; } const QString &TestBase::cmdLineExpectedResultPath() const { return m_cmdLineExpectedResultPath; } #endif bool TestBase::cleanupTestData() const { if (m_copyTestResults) { static const auto clearAndCopy = [](const QString &actual, const QString &expected) { QFileInfo info(expected); if (info.exists()) { if (!info.isDir()) { qWarning() << info.absoluteFilePath() << " is not a directory." " Unable to copy files."; return false; } QDir(info.absoluteFilePath()).removeRecursively(); info.absoluteDir().mkdir(info.fileName()); } return copyDirectoryRecursively(QDir(actual), QDir(expected)); }; if (!clearAndCopy(m_cmakeGeneratedPath, m_cmakeExpectedResultPath)) return false; #if QT_CONFIG(process) if (!clearAndCopy(m_cmdLineGeneratedPath, m_cmdLineExpectedResultPath)) return false; #endif } #if QT_CONFIG(process) // Remove content generated by command line tests. if (!m_cmdLineGeneratedPath.isEmpty()) cleanFolder(m_cmdLineGeneratedPath); #endif return true; } } // namespace ProtocPluginTest #include "moc_protocplugintestcommon.cpp"