263 lines
8.8 KiB
C++
263 lines
8.8 KiB
C++
// Copyright (C) 2022 The Qt Company Ltd.
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
|
|
|
|
#include <qbaselinetest.h>
|
|
|
|
#include <QtCore/QDirIterator>
|
|
#include <QtCore/QDebug>
|
|
#include <QtCore/QProcess>
|
|
#include <QtGui/QGuiApplication>
|
|
#include <QtGui/QImage>
|
|
#include <QtGui/QPalette>
|
|
#include <QtGui/QFont>
|
|
#include <QtQuickControls2/QQuickStyle>
|
|
|
|
#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_Baseline_Controls : public QObject
|
|
{
|
|
Q_OBJECT
|
|
|
|
public:
|
|
tst_Baseline_Controls();
|
|
|
|
private Q_SLOTS:
|
|
void initTestCase();
|
|
void init();
|
|
void cleanup();
|
|
void cleanupTestCase();
|
|
|
|
void basic_data() { setupTestSuite(); }
|
|
void basic() { runTest("Basic"); }
|
|
void fusion_data() { setupTestSuite(); }
|
|
void fusion() { runTest("Fusion"); }
|
|
void material_data() { setupTestSuite(); }
|
|
void material() { runTest("Material"); }
|
|
void imagine_data() { setupTestSuite(); }
|
|
void imagine() { runTest("Imagine"); }
|
|
void universal_data() { setupTestSuite(); }
|
|
void universal() { runTest("Universal"); }
|
|
void fluentWinUI3_data() { setupTestSuite(); }
|
|
void fluentWinUI3() { runTest("FluentWinUI3"); }
|
|
#if defined(Q_OS_DARWIN) // the iOS style only gets build on iOS and macOS
|
|
void ios_data() { setupTestSuite(); }
|
|
void ios() { runTest("iOS"); }
|
|
#endif
|
|
void native_data() { setupTestSuite(); }
|
|
void native() { runTest(); }
|
|
|
|
private:
|
|
void test();
|
|
|
|
void setupTestSuite();
|
|
void runTest(const QString& style = QString());
|
|
bool renderAndGrab(const QString& qmlFile, const QStringList& extraArgs, QImage *screenshot, QString *errMsg);
|
|
|
|
QString testSuitePath;
|
|
QString grabberPath;
|
|
QStringList testFiles;
|
|
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_Baseline_Controls::tst_Baseline_Controls()
|
|
: consecutiveErrors(0), aborted(false)
|
|
{
|
|
}
|
|
|
|
|
|
void tst_Baseline_Controls::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();
|
|
|
|
#if defined(Q_OS_WIN)
|
|
grabberPath = QFINDTESTDATA("qmlscenegrabber.exe");
|
|
#elif defined(Q_OS_DARWIN)
|
|
grabberPath = QFINDTESTDATA("qmlscenegrabber.app/Contents/MacOS/qmlscenegrabber");
|
|
#else
|
|
grabberPath = QFINDTESTDATA("qmlscenegrabber");
|
|
#endif
|
|
if (grabberPath.isEmpty())
|
|
grabberPath = QCoreApplication::applicationDirPath() + "/../scenegraph/qmlscenegrabber";
|
|
|
|
QBaselineTest::setProject("QuickControls");
|
|
// Set key platform properties that are relevant for the appearance of controls
|
|
const QString platformName = QGuiApplication::platformName() + "-" + QSysInfo::productType();
|
|
QBaselineTest::addClientProperty("PlatformName", platformName);
|
|
QBaselineTest::addClientProperty("OSVersion", QSysInfo::productVersion());
|
|
|
|
const char *backendVarName = "QT_QUICK_BACKEND";
|
|
const QString backend = qEnvironmentVariable(backendVarName, QString::fromLatin1("default"));
|
|
QBaselineTest::addClientProperty(QString::fromLatin1(backendVarName), backend);
|
|
|
|
QString stack = backend;
|
|
if (stack != QLatin1String("software")) {
|
|
#if defined(Q_OS_WIN)
|
|
const char *defaultRhiBackend = "d3d11";
|
|
#elif defined(Q_OS_DARWIN)
|
|
const char *defaultRhiBackend = "metal";
|
|
#else
|
|
const char *defaultRhiBackend = "opengl";
|
|
#endif
|
|
const QString rhiBackend = qEnvironmentVariable("QSG_RHI_BACKEND", QString::fromLatin1(defaultRhiBackend));
|
|
stack = QString::fromLatin1("RHI_%1").arg(rhiBackend);
|
|
}
|
|
|
|
QBaselineTest::addClientProperty(QString::fromLatin1("GraphicsStack"), stack);
|
|
|
|
// Assume that text that's darker than the background means we run in light mode
|
|
// See also qwidgetbaselinetest.cpp
|
|
QPalette palette;
|
|
QFont font;
|
|
QByteArray colorSchemeBytes;
|
|
{
|
|
QDataStream colorSchemeStream(&colorSchemeBytes, QIODevice::WriteOnly);
|
|
colorSchemeStream << palette << font;
|
|
const qreal screenDpr = QGuiApplication::primaryScreen()->devicePixelRatio();
|
|
if (screenDpr != 1.0) {
|
|
qWarning() << "DPR is" << screenDpr << "- images will not be compared to 1.0 baseline!";
|
|
colorSchemeStream << screenDpr;
|
|
}
|
|
}
|
|
const quint16 colorSchemeId = qChecksum(colorSchemeBytes);
|
|
|
|
const QColor windowColor = palette.window().color();
|
|
const QColor textColor = palette.text().color();
|
|
const QString colorSchemeIdStr = (windowColor.value() > textColor.value()
|
|
? QString("light-%1") : QString("dark-%1"))
|
|
.arg(colorSchemeId, 0, 16);
|
|
QBaselineTest::addClientProperty("AppearanceID", colorSchemeIdStr);
|
|
|
|
QByteArray msg;
|
|
if (!QBaselineTest::connectToBaselineServer(&msg))
|
|
QSKIP(msg);
|
|
|
|
// let users know where they can find the results
|
|
qInfo("PlatformName computed to be : %s", qPrintable(platformName));
|
|
qInfo("Color Scheme computed as : %s", qPrintable(colorSchemeIdStr));
|
|
qInfo("Native style name is : %s", qPrintable(QQuickStyle::name()));
|
|
}
|
|
|
|
void tst_Baseline_Controls::init()
|
|
{
|
|
consecutiveErrors = 0;
|
|
aborted = false;
|
|
}
|
|
|
|
void tst_Baseline_Controls::cleanup()
|
|
{
|
|
// Allow subsystems time to settle
|
|
if (!aborted)
|
|
QTest::qWait(20);
|
|
}
|
|
|
|
void tst_Baseline_Controls::cleanupTestCase()
|
|
{
|
|
QBaselineTest::finalizeAndDisconnect();
|
|
}
|
|
|
|
void tst_Baseline_Controls::setupTestSuite()
|
|
{
|
|
QTest::addColumn<QString>("qmlFile");
|
|
|
|
const QStringView qmlExt(u".qml");
|
|
if (testFiles.isEmpty()) {
|
|
QDirIterator it(testSuitePath, QDirIterator::Subdirectories);
|
|
while (it.hasNext()) {
|
|
const QString fp = it.next().toLatin1();
|
|
if (fp.endsWith(qmlExt))
|
|
testFiles.append(it.filePath());
|
|
}
|
|
std::sort(testFiles.begin(), testFiles.end());
|
|
}
|
|
|
|
if (testFiles.isEmpty())
|
|
QSKIP("No .qml test files found in " + testSuitePath.toLatin1());
|
|
|
|
for (const auto &filePath : std::as_const(testFiles)) {
|
|
QString itemName = filePath.sliced(testSuitePath.size() + 1);
|
|
itemName = itemName.left(itemName.size() - qmlExt.size());
|
|
QBaselineTest::newRow(itemName.toLatin1()) << filePath;
|
|
}
|
|
}
|
|
|
|
|
|
void tst_Baseline_Controls::runTest(const QString& style)
|
|
{
|
|
if (aborted)
|
|
QSKIP("System too unstable.");
|
|
|
|
QFETCH(QString, qmlFile);
|
|
|
|
QImage screenShot;
|
|
QString errorMessage;
|
|
QStringList args;
|
|
if (!style.isEmpty())
|
|
args.append({"-style", style});
|
|
args.append("-useAppWindow");
|
|
if (renderAndGrab(qmlFile, args, &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_Baseline_Controls::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
|
|
QProcess grabber;
|
|
grabber.setProcessChannelMode(QProcess::ForwardedErrorChannel);
|
|
QStringList args = extraArgs;
|
|
QString tmpfile = usePipe ? QString("-") : QString("/tmp/qmlscenegrabber-%1-out.ppm").arg(QCoreApplication::applicationPid());
|
|
args << qmlFile << "-o" << tmpfile;
|
|
grabber.start(grabberPath, 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 in %1. qmlscenegrabber exitcode: %2. Process error: %3. Stderr:%4");
|
|
*errMsg = s.arg(qmlFile).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;
|
|
}
|
|
|
|
QBASELINETEST_MAIN(tst_Baseline_Controls)
|
|
|
|
#include "tst_baseline_controls.moc"
|