summaryrefslogtreecommitdiffstats
path: root/tests/baseline/shared/qbaselinetest.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tests/baseline/shared/qbaselinetest.cpp')
-rw-r--r--tests/baseline/shared/qbaselinetest.cpp426
1 files changed, 426 insertions, 0 deletions
diff --git a/tests/baseline/shared/qbaselinetest.cpp b/tests/baseline/shared/qbaselinetest.cpp
new file mode 100644
index 0000000000..e41b8d5321
--- /dev/null
+++ b/tests/baseline/shared/qbaselinetest.cpp
@@ -0,0 +1,426 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qbaselinetest.h"
+#include "baselineprotocol.h"
+#include <QtCore/QDir>
+#include <QFile>
+
+#define MAXCMDLINEARGS 128
+
+namespace QBaselineTest {
+
+static char *fargv[MAXCMDLINEARGS];
+static bool simfail = false;
+static PlatformInfo customInfo;
+static bool customAutoModeSet = false;
+
+static BaselineProtocol proto;
+static bool connected = false;
+static bool triedConnecting = false;
+static bool dryRunMode = false;
+static enum { UploadMissing, UploadAll, UploadNone } baselinePolicy = UploadMissing;
+static bool abortIfUnstable = true;
+
+static QByteArray curFunction;
+static ImageItemList itemList;
+static bool gotBaselines;
+
+
+void handleCmdLineArgs(int *argcp, char ***argvp)
+{
+ if (!argcp || !argvp)
+ return;
+
+ bool showHelp = false;
+
+ int fargc = 0;
+ int numArgs = *argcp;
+
+ for (int i = 0; i < numArgs; i++) {
+ QByteArray arg = (*argvp)[i];
+ QByteArray nextArg = (i+1 < numArgs) ? (*argvp)[i+1] : nullptr;
+
+ if (arg == "-simfail") {
+ simfail = true;
+ } else if (arg == "-fuzzlevel") {
+ i++;
+ bool ok = false;
+ (void)nextArg.toInt(&ok);
+ if (!ok) {
+ qWarning() << "-fuzzlevel requires integer parameter";
+ showHelp = true;
+ break;
+ }
+ customInfo.insert("FuzzLevel", QString::fromLatin1(nextArg));
+ } else if (arg == "-auto") {
+ customAutoModeSet = true;
+ customInfo.setAdHocRun(false);
+ } else if (arg == "-adhoc") {
+ customAutoModeSet = true;
+ customInfo.setAdHocRun(true);
+ } else if (arg == "-setbaselines") {
+ baselinePolicy = UploadAll;
+ } else if (arg == "-keeprunning") {
+ abortIfUnstable = false;
+ } else if (arg == "-nosetbaselines") {
+ baselinePolicy = UploadNone;
+ } else if (arg == "-compareto") {
+ i++;
+ int split = qMax(0, nextArg.indexOf('='));
+ QByteArray key = nextArg.left(split).trimmed();
+ QByteArray value = nextArg.mid(split+1).trimmed();
+ if (key.isEmpty() || value.isEmpty()) {
+ qWarning() << "-compareto requires parameter of the form <key>=<value>";
+ showHelp = true;
+ break;
+ }
+ customInfo.addOverride(key, value);
+ } else {
+ if ( (arg == "-help") || (arg == "--help") )
+ showHelp = true;
+ if (fargc >= MAXCMDLINEARGS) {
+ qWarning() << "Too many command line arguments!";
+ break;
+ }
+ fargv[fargc++] = (*argvp)[i];
+ }
+ }
+ *argcp = fargc;
+ *argvp = fargv;
+
+ if (showHelp) {
+ // TBD: arrange for this to be printed *after* QTest's help
+ QTextStream out(stdout);
+ out << "\n Baseline testing (lancelot) options:\n";
+ out << " -simfail : Force an image comparison mismatch. For testing purposes.\n";
+ out << " -fuzzlevel <int> : Specify the percentage of fuzziness in comparison. Overrides server default. 0 means exact match.\n";
+ out << " -auto : Inform server that this run is done by a daemon, CI system or similar.\n";
+ out << " -adhoc (default) : The inverse of -auto; this run is done by human, e.g. for testing.\n";
+ out << " -keeprunning : Run all tests even if the system is unstable \n";
+ out << " -setbaselines : Store ALL rendered images as new baselines. Forces replacement of previous baselines.\n";
+ out << " -nosetbaselines : Do not store rendered images as new baselines when previous baselines are missing.\n";
+ out << " -compareto KEY=VAL : Force comparison to baselines from a different client,\n";
+ out << " for example: -compareto QtVersion=4.8.0\n";
+ out << " Multiple -compareto client specifications may be given.\n";
+ out << "\n";
+ }
+}
+
+bool shouldAbortIfUnstable()
+{
+ return abortIfUnstable;
+}
+
+void addClientProperty(const QString& key, const QString& value)
+{
+ customInfo.insert(key, value);
+}
+
+
+/*
+ If a client property script is present, run it and accept its output
+ in the form of one 'key: value' property per line
+*/
+void fetchCustomClientProperties()
+{
+ QFile file("hostinfo.txt");
+ if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
+ return;
+ QTextStream in(&file);
+
+ while (!in.atEnd()) {
+ QString line = in.readLine().trimmed(); // ###local8bit? utf8?
+ if (line.startsWith(QLatin1Char('#'))) // Ignore comments in file
+ continue;
+ QString key, val;
+ int colonPos = line.indexOf(':');
+ if (colonPos > 0) {
+ key = line.left(colonPos).simplified().replace(' ', '_');
+ val = line.mid(colonPos+1).trimmed();
+ }
+ if (!key.isEmpty() && key.size() < 64 && val.size() < 256) // ###TBD: maximum 256 chars in value?
+ addClientProperty(key, val);
+ else
+ qDebug() << "Unparseable script output ignored:" << line;
+ }
+}
+
+
+bool connect(QByteArray *msg, bool *error)
+{
+ if (connected) {
+ return true;
+ }
+ else if (triedConnecting) {
+ // Avoid repeated connection attempts, to avoid the program using Timeout * #testItems seconds before giving up
+ *msg = "Not connected to baseline server.";
+ *error = true;
+ return false;
+ }
+
+ triedConnecting = true;
+ fetchCustomClientProperties();
+ // Merge the platform info set by the program with the protocols default info
+ PlatformInfo clientInfo = customInfo;
+ PlatformInfo defaultInfo = PlatformInfo::localHostInfo();
+ const auto &defaultInfoKeys = defaultInfo.keys();
+ for (const QString &key : defaultInfoKeys) {
+ if (!clientInfo.contains(key))
+ clientInfo.insert(key, defaultInfo.value(key));
+ }
+ if (!customAutoModeSet)
+ clientInfo.setAdHocRun(defaultInfo.isAdHocRun());
+
+ QString testCase = clientInfo.value(PI_TestCase);
+ if (testCase.isEmpty() && QTest::testObject() && QTest::testObject()->metaObject()) {
+ //qDebug() << "Trying to Read TestCaseName from Testlib!";
+ testCase = QTest::testObject()->metaObject()->className();
+ }
+ if (testCase.isEmpty()) {
+ qWarning("QBaselineTest::connect: No test case name specified, cannot connect.");
+ return false;
+ }
+
+ if (!proto.connect(testCase, &dryRunMode, clientInfo)) {
+ *msg += "Failed to connect to baseline server: " + proto.errorMessage().toLatin1();
+ *error = true;
+ return false;
+ }
+ connected = true;
+ return true;
+}
+
+bool disconnectFromBaselineServer()
+{
+ if (proto.disconnect()) {
+ connected = false;
+ triedConnecting = false;
+ return true;
+ }
+
+ return false;
+}
+
+bool connectToBaselineServer(QByteArray *msg)
+{
+ bool dummy;
+ QByteArray dummyMsg;
+ return connect(msg ? msg : &dummyMsg, &dummy);
+}
+
+void setAutoMode(bool mode)
+{
+ customInfo.setAdHocRun(!mode);
+ customAutoModeSet = true;
+}
+
+void setSimFail(bool fail)
+{
+ simfail = fail;
+}
+
+void setProject(const QString &projectName)
+{
+ addClientProperty(PI_Project, projectName);
+}
+
+void setProjectImageKeys(const QStringList &keys)
+{
+ QString keyList = keys.join(QLC(','));
+ addClientProperty(PI_ProjectImageKeys, keyList);
+}
+
+void modifyImage(QImage *img)
+{
+ uint c0 = 0x0000ff00;
+ uint c1 = 0x0080ff00;
+ img->setPixel(1,1,c0);
+ img->setPixel(2,1,c1);
+ img->setPixel(3,1,c0);
+ img->setPixel(1,2,c1);
+ img->setPixel(1,3,c0);
+ img->setPixel(2,3,c1);
+ img->setPixel(3,3,c0);
+ img->setPixel(1,4,c1);
+ img->setPixel(1,5,c0);
+}
+
+
+bool compareItem(const ImageItem &baseline, const QImage &img, QByteArray *msg, bool *error)
+{
+ *error = false;
+ ImageItem item = baseline;
+ if (simfail) {
+ // Simulate test failure by forcing image mismatch; for testing purposes
+ QImage misImg = img;
+ modifyImage(&misImg);
+ item.image = misImg;
+ simfail = false; // One failure is typically enough
+ } else {
+ item.image = img;
+ }
+ bool isNewItem = false;
+ item.imageChecksums.clear();
+ item.imageChecksums.prepend(ImageItem::computeChecksum(item.image));
+ QByteArray srvMsg;
+ switch (baseline.status) {
+ case ImageItem::Ok:
+ break;
+ case ImageItem::IgnoreItem :
+ qDebug() << msg->constData() << "Ignored, blacklisted on server.";
+ return true;
+ break;
+ case ImageItem::BaselineNotFound:
+ if (!customInfo.overrides().isEmpty())
+ return true;
+ if (baselinePolicy == UploadNone) {
+ isNewItem = true;
+ break;
+ }
+ if (proto.submitNewBaseline(item, &srvMsg))
+ qDebug() << msg->constData() << "Baseline not found on server. New baseline uploaded.";
+ else
+ qDebug() << msg->constData() << "Baseline not found on server. Uploading of new baseline failed:" << srvMsg;
+ return true;
+ break;
+ default:
+ qWarning() << "Unexpected reply from baseline server.";
+ return true;
+ break;
+ }
+ // The actual comparison of the given image with the baseline:
+ if (baseline.imageChecksums.contains(item.imageChecksums.at(0))) {
+ if (!proto.submitMatch(item, &srvMsg))
+ qWarning() << "Failed to report image match to server:" << srvMsg;
+ return true;
+ }
+ // At this point, we have established a legitimate mismatch
+ if (baselinePolicy == UploadAll) {
+ if (proto.submitNewBaseline(item, &srvMsg))
+ qDebug() << msg->constData() << "Forcing new baseline; uploaded ok.";
+ else
+ qDebug() << msg->constData() << "Forcing new baseline; uploading failed:" << srvMsg;
+ return true;
+ }
+ bool fuzzyMatch = false;
+ bool res = proto.submitMismatch(item, &srvMsg, &fuzzyMatch);
+ if (res && fuzzyMatch) {
+ qInfo() << "Baseline server reports:" << srvMsg;
+ return true; // The server decides: a fuzzy match means no mismatch
+ }
+ if (isNewItem)
+ *msg += "No baseline on server, so cannot compare.";
+ else
+ *msg += "Mismatch.";
+ *msg += " See report:\n " + srvMsg;
+ if (dryRunMode) {
+ qDebug() << "Dryrun, so ignoring" << *msg;
+ return true;
+ }
+ return false;
+}
+
+bool checkImage(const QImage &img, const char *name, quint16 checksum, QByteArray *msg, bool *error, int manualdatatag)
+{
+ if (!connected && !connect(msg, error))
+ return true;
+
+ QByteArray itemName;
+ bool hasName = qstrlen(name);
+
+ const char *tag = QTest::currentDataTag();
+ if (qstrlen(tag)) {
+ itemName = tag;
+ if (hasName)
+ itemName.append('_').append(name);
+ } else {
+ itemName = hasName ? name : "default_name";
+ }
+
+ if (manualdatatag > 0)
+ {
+ itemName.prepend("_");
+ itemName.prepend(QByteArray::number(manualdatatag));
+ }
+
+ *msg = "Baseline check of image '" + itemName + "': ";
+
+
+ ImageItem item;
+ item.itemName = QString::fromLatin1(itemName);
+ item.itemChecksum = checksum;
+ item.testFunction = QString::fromLatin1(QTest::currentTestFunction());
+ ImageItemList list;
+ list.append(item);
+ if (!proto.requestBaselineChecksums(QLatin1String(QTest::currentTestFunction()), &list) || list.isEmpty()) {
+ *msg = "Communication with baseline server failed: " + proto.errorMessage().toLatin1();
+ *error = true;
+ return true;
+ }
+
+ return compareItem(list.at(0), img, msg, error);
+}
+
+
+QTestData &newRow(const char *dataTag, quint16 checksum)
+{
+ if (QTest::currentTestFunction() != curFunction) {
+ curFunction = QTest::currentTestFunction();
+ itemList.clear();
+ gotBaselines = false;
+ }
+ ImageItem item;
+ item.itemName = QString::fromLatin1(dataTag);
+ item.itemChecksum = checksum;
+ item.testFunction = QString::fromLatin1(QTest::currentTestFunction());
+ itemList.append(item);
+
+ return QTest::newRow(dataTag);
+}
+
+const ImageItem *findCurrentItem(QByteArray *msg, bool *error)
+{
+ if (!connected && !connect(msg, error))
+ return nullptr;
+
+ if (QTest::currentTestFunction() != curFunction || itemList.isEmpty()) {
+ qWarning() << "Usage error: QBASELINE_ macro used without corresponding QBaselineTest::newRow()";
+ return nullptr;
+ }
+
+ if (!gotBaselines) {
+ if (!proto.requestBaselineChecksums(QString::fromLatin1(QTest::currentTestFunction()), &itemList) || itemList.isEmpty()) {
+ *msg = "Communication with baseline server failed: " + proto.errorMessage().toLatin1();
+ *error = true;
+ return nullptr;
+ }
+ gotBaselines = true;
+ }
+
+ QString curTag = QString::fromLatin1(QTest::currentDataTag());
+ ImageItemList::const_iterator it = itemList.constBegin();
+ while (it != itemList.constEnd() && it->itemName != curTag)
+ ++it;
+ if (it == itemList.constEnd()) {
+ qWarning() << "Usage error: QBASELINE_ macro used without corresponding QBaselineTest::newRow() for row" << curTag;
+ return nullptr;
+ }
+ return &(*it);
+}
+
+bool testImage(const QImage &img, QByteArray *msg, bool *error)
+{
+ const ImageItem *item = findCurrentItem(msg, error);
+ return item ? compareItem(*item, img, msg, error) : true;
+}
+
+bool isCurrentItemBlacklisted()
+{
+ QByteArray msg;
+ bool error = false;
+ const ImageItem *item = findCurrentItem(&msg, &error);
+ return item ? (item->status == ImageItem::IgnoreItem) : false;
+}
+
+}