| /**************************************************************************** |
| ** |
| ** 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 <qbaselinetest.h> |
| |
| #include <QtCore/QCoreApplication> |
| #include <QtCore/QDirIterator> |
| #include <QtCore/QDebug> |
| #include <QtCore/QProcess> |
| #include <QtGui/QImage> |
| |
| #include <algorithm> |
| |
| QString blockify(const QByteArray& s) |
| { |
| const char* indent = "\n | "; |
| QByteArray block = s.trimmed(); |
| block.replace('\n', indent); |
| block.prepend(indent); |
| block.append('\n'); |
| return block; |
| } |
| |
| |
| class tst_Quick3D : public QObject |
| { |
| Q_OBJECT |
| |
| public: |
| tst_Quick3D(); |
| |
| private Q_SLOTS: |
| void initTestCase(); |
| void cleanup(); |
| void testRendering_data(); |
| void testRendering(); |
| |
| private: |
| void setupTestSuite(const QByteArray& filter = QByteArray()); |
| void runTest(const QStringList& extraArgs = QStringList()); |
| bool renderAndGrab(const QString& qmlFile, const QStringList& extraArgs, QImage *screenshot, QString *errMsg); |
| quint16 checksumFileOrDir(const QString &path); |
| |
| QString testSuitePath; |
| int consecutiveErrors; // Not test failures (image mismatches), but system failures (so no image at all) |
| bool aborted; // This run given up because of too many system failures |
| }; |
| |
| |
| tst_Quick3D::tst_Quick3D() |
| : consecutiveErrors(0), aborted(false) |
| { |
| } |
| |
| |
| void tst_Quick3D::initTestCase() |
| { |
| QString dataDir = QFINDTESTDATA("../data/."); |
| if (dataDir.isEmpty()) |
| dataDir = QStringLiteral("data"); |
| QFileInfo fi(dataDir); |
| if (!fi.exists() || !fi.isDir() || !fi.isReadable()) |
| QSKIP("Test suite data directory missing or unreadable: " + fi.canonicalFilePath().toLatin1()); |
| testSuitePath = fi.canonicalFilePath(); |
| |
| QByteArray msg; |
| if (!QBaselineTest::connectToBaselineServer(&msg)) |
| QSKIP(msg); |
| } |
| |
| |
| void tst_Quick3D::cleanup() |
| { |
| // Allow subsystems time to settle |
| if (!aborted) |
| QTest::qWait(20); |
| } |
| |
| |
| void tst_Quick3D::testRendering_data() |
| { |
| setupTestSuite(); |
| consecutiveErrors = 0; |
| aborted = false; |
| } |
| |
| |
| void tst_Quick3D::testRendering() |
| { |
| runTest(); |
| } |
| |
| |
| void tst_Quick3D::setupTestSuite(const QByteArray& filter) |
| { |
| QTest::addColumn<QString>("qmlFile"); |
| int numItems = 0; |
| |
| QStringList ignoreItems; |
| QFile ignoreFile(testSuitePath + "/Ignore"); |
| if (ignoreFile.open(QIODevice::ReadOnly)) { |
| while (!ignoreFile.atEnd()) { |
| QByteArray line = ignoreFile.readLine().trimmed(); |
| if (!line.isEmpty() && !line.startsWith('#')) |
| ignoreItems += line; |
| } |
| } |
| |
| QStringList itemFiles; |
| QDirIterator it(testSuitePath, QDirIterator::Subdirectories); |
| while (it.hasNext()) { |
| QString fp = it.next(); |
| if (fp.endsWith(".qml")) { |
| QString itemName = fp.mid(testSuitePath.length() + 1); |
| if (!ignoreItems.contains(itemName) && (filter.isEmpty() || !itemName.startsWith(filter))) |
| itemFiles.append(it.filePath()); |
| } |
| } |
| |
| std::sort(itemFiles.begin(), itemFiles.end()); |
| for (const QString &filePath : qAsConst(itemFiles)) { |
| QByteArray itemName = filePath.mid(testSuitePath.length() + 1).toLatin1(); |
| QBaselineTest::newRow(itemName, checksumFileOrDir(filePath)) << filePath; |
| numItems++; |
| } |
| |
| if (!numItems) |
| QSKIP("No .qml test files found in " + testSuitePath.toLatin1()); |
| } |
| |
| |
| void tst_Quick3D::runTest(const QStringList& extraArgs) |
| { |
| // qDebug() << "Rendering" << QTest::currentDataTag(); |
| |
| if (aborted) |
| QSKIP("System too unstable."); |
| |
| QFETCH(QString, qmlFile); |
| |
| QImage screenShot; |
| QString errorMessage; |
| if (renderAndGrab(qmlFile, extraArgs, &screenShot, &errorMessage)) { |
| consecutiveErrors = 0; |
| } |
| else { |
| if (++consecutiveErrors >= 3) |
| aborted = true; // Just give up if screen grabbing fails 3 times in a row |
| QFAIL(qPrintable("QuickView grabbing failed: " + errorMessage)); |
| } |
| |
| QBASELINE_TEST(screenShot); |
| } |
| |
| |
| bool tst_Quick3D::renderAndGrab(const QString& qmlFile, const QStringList& extraArgs, QImage *screenshot, QString *errMsg) |
| { |
| bool usePipe = true; // Whether to transport the grabbed image using temp. file or pipe. TBD: cmdline option |
| #if defined(Q_OS_WIN) |
| usePipe = false; |
| #endif |
| QProcess grabber; |
| grabber.setProcessChannelMode(QProcess::ForwardedErrorChannel); |
| QString cmd = QCoreApplication::applicationDirPath() + "/qmlscenegrabber"; |
| QStringList args = extraArgs; |
| QString tmpfile = usePipe ? QString("-") : QString("%1/qmlscenegrabber-%2-out.ppm") |
| .arg(QDir::tempPath()).arg(QCoreApplication::applicationPid()); |
| args << qmlFile << "-o" << tmpfile; |
| grabber.start(cmd, args, QIODevice::ReadOnly); |
| grabber.waitForFinished(17000); //### hardcoded, must be larger than the scene timeout in qmlscenegrabber |
| if (grabber.state() != QProcess::NotRunning) { |
| grabber.terminate(); |
| grabber.waitForFinished(3000); |
| } |
| QImage img; |
| bool res = usePipe ? img.load(&grabber, "ppm") : img.load(tmpfile); |
| if (!res || img.isNull()) { |
| if (errMsg) { |
| QString s("Failed to grab screen. qmlscenegrabber exitcode: %1. Process error: %2. Stderr:%3"); |
| *errMsg = s.arg(grabber.exitCode()).arg(grabber.errorString()).arg(blockify(grabber.readAllStandardError())); |
| } |
| if (!usePipe) |
| QFile::remove(tmpfile); |
| return false; |
| } |
| if (screenshot) |
| *screenshot = img; |
| if (!usePipe) |
| QFile::remove(tmpfile); |
| return true; |
| } |
| |
| |
| quint16 tst_Quick3D::checksumFileOrDir(const QString &path) |
| { |
| QFileInfo fi(path); |
| if (!fi.exists() || !fi.isReadable()) |
| return 0; |
| if (fi.isFile()) { |
| QFile f(path); |
| f.open(QIODevice::ReadOnly); |
| QByteArray contents = f.readAll(); |
| return qChecksum(contents.constData(), uint(contents.size())); |
| } |
| if (fi.isDir()) { |
| static const QStringList nameFilters = QStringList() << "*.qml" << "*.cpp" << "*.png" << "*.jpg"; |
| quint16 cs = 0; |
| const auto entryList = QDir(fi.filePath()).entryList(nameFilters, |
| QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot); |
| for (const QString &item : entryList) |
| cs ^= checksumFileOrDir(path + QLatin1Char('/') + item); |
| return cs; |
| } |
| return 0; |
| } |
| |
| |
| #define main _realmain |
| QTEST_MAIN(tst_Quick3D) |
| #undef main |
| |
| int main(int argc, char *argv[]) |
| { |
| QBaselineTest::handleCmdLineArgs(&argc, &argv); |
| return _realmain(argc, argv); |
| } |
| |
| #include "tst_quick3d.moc" |