summaryrefslogtreecommitdiffstats
path: root/tests/arthur
diff options
context:
space:
mode:
Diffstat (limited to 'tests/arthur')
-rw-r--r--tests/arthur/baselineserver/src/baselineserver.cpp156
-rw-r--r--tests/arthur/baselineserver/src/baselineserver.h15
-rw-r--r--tests/arthur/baselineserver/src/main.cpp2
-rw-r--r--tests/arthur/baselineserver/src/report.cpp37
-rw-r--r--tests/arthur/baselineserver/src/report.h3
-rw-r--r--tests/arthur/common/baselineprotocol.cpp2
6 files changed, 142 insertions, 73 deletions
diff --git a/tests/arthur/baselineserver/src/baselineserver.cpp b/tests/arthur/baselineserver/src/baselineserver.cpp
index 1091374..0c0871a 100644
--- a/tests/arthur/baselineserver/src/baselineserver.cpp
+++ b/tests/arthur/baselineserver/src/baselineserver.cpp
@@ -53,11 +53,16 @@
#include <QProcess>
#include <QDirIterator>
-QString BaselineServer::storage;
+// extra fields, for use in image metadata storage
+const QString PI_ImageChecksum(QLS("ImageChecksum"));
+const QString PI_RunId(QLS("RunId"));
+const QString PI_CreationDate(QLS("CreationDate"));
+QString BaselineServer::storage;
+QString BaselineServer::url;
BaselineServer::BaselineServer(QObject *parent)
- : QTcpServer(parent)
+ : QTcpServer(parent), lastRunIdIdx(0)
{
QFileInfo me(QCoreApplication::applicationFilePath());
meLastMod = me.lastModified();
@@ -71,22 +76,32 @@ QString BaselineServer::storagePath()
if (storage.isEmpty()) {
storage = QLS(qgetenv("QT_LANCELOT_DIR"));
if (storage.isEmpty())
- storage = QLS("/var/www");
+ storage = QLS("/var/www");
}
return storage;
}
QString BaselineServer::baseUrl()
{
- return QLS("http://")
- + QHostInfo::localHostName().toLatin1() + '.'
- + QHostInfo::localDomainName().toLatin1() + '/';
+ if (url.isEmpty()) {
+ url = QLS("http://")
+ + QHostInfo::localHostName().toLatin1() + '.'
+ + QHostInfo::localDomainName().toLatin1() + '/';
+ }
+ return url;
}
void BaselineServer::incomingConnection(int socketDescriptor)
{
- qDebug() << "Server: New connection!";
- BaselineThread *thread = new BaselineThread(socketDescriptor, this);
+ QString runId = QDateTime::currentDateTime().toString(QLS("MMMdd-hhmmss"));
+ if (runId == lastRunId) {
+ runId += QLC('-') + QString::number(++lastRunIdIdx);
+ } else {
+ lastRunId = runId;
+ lastRunIdIdx = 0;
+ }
+ qDebug() << "Server: New connection! RunId:" << runId;
+ BaselineThread *thread = new BaselineThread(runId, socketDescriptor, this);
connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
thread->start();
}
@@ -114,23 +129,21 @@ void BaselineServer::heartbeat()
QCoreApplication::exit();
}
-BaselineThread::BaselineThread(int socketDescriptor, QObject *parent)
- : QThread(parent), socketDescriptor(socketDescriptor)
+BaselineThread::BaselineThread(const QString &runId, int socketDescriptor, QObject *parent)
+ : QThread(parent), runId(runId), socketDescriptor(socketDescriptor)
{
}
void BaselineThread::run()
{
- BaselineHandler handler(socketDescriptor);
+ BaselineHandler handler(runId, socketDescriptor);
exec();
}
-BaselineHandler::BaselineHandler(int socketDescriptor)
- : QObject(), connectionEstablished(false)
+BaselineHandler::BaselineHandler(const QString &runId, int socketDescriptor)
+ : QObject(), runId(runId), connectionEstablished(false)
{
- runId = QDateTime::currentDateTime().toString(QLS("MMMdd-hhmmss"));
-
if (socketDescriptor == -1)
return;
@@ -165,7 +178,7 @@ bool BaselineHandler::establishConnection()
if (branch.isEmpty()) {
// Not run by Pulse, i.e. ad hoc run: Ok.
}
- else if (branch != QLS("master-integration") || !plat.value(PI_GitCommit).contains(QLS("Merge branch 'master' of scm.dev.nokia.troll.no:qt/oslo-staging-2 into master-integration"))) {
+ else if (branch != QLS("master-integration") || !plat.value(PI_GitCommit).contains(QLS("Merge branch 'master' of scm.dev.nokia.troll.no:qt/qt-fire-staging into master-integration"))) {
qDebug() << runId << logtime() << "Did not pass branch/staging repo filter, disconnecting.";
proto.sendBlock(BaselineProtocol::Abort, QByteArray("This branch/staging repo is not assigned to be tested."));
proto.socket.disconnectFromHost();
@@ -220,16 +233,17 @@ void BaselineHandler::provideBaselineChecksums(const QByteArray &itemListBlock)
for (ImageItemList::iterator i = itemList.begin(); i != itemList.end(); ++i) {
i->imageChecksums.clear();
+ i->status = ImageItem::BaselineNotFound;
QString prefix = pathForItem(*i, true);
- QFile file(prefix + QLS("metadata"));
- if (file.open(QIODevice::ReadOnly)) {
- QDataStream checkSums(&file);
- checkSums >> i->imageChecksums;
- file.close();
- i->status = ImageItem::Ok;
+ PlatformInfo itemData = fetchItemMetadata(prefix);
+ if (itemData.contains(PI_ImageChecksum)) {
+ bool ok = false;
+ quint64 checksum = itemData.value(PI_ImageChecksum).toULongLong(&ok, 16);
+ if (ok) {
+ i->imageChecksums.prepend(checksum);
+ i->status = ImageItem::Ok;
+ }
}
- if (!i->imageChecksums.count())
- i->status = ImageItem::BaselineNotFound;
}
// Find and mark blacklisted items
@@ -280,18 +294,51 @@ void BaselineHandler::storeImage(const QByteArray &itemBlock, bool isBaseline)
cwd.mkpath(dir);
item.image.save(prefix + QLS(FileFormat), FileFormat);
- //# Could use QSettings or XML or even DB, could use common file for whole dir or even whole storage - but for now, keep it simple
- QFile file(prefix + QLS("metadata"));
- file.open(QIODevice::WriteOnly | QIODevice::Truncate);
- QDataStream checkSums(&file);
- checkSums << item.imageChecksums;
- file.close();
+ PlatformInfo itemData = plat;
+ itemData.insert(PI_ImageChecksum, QString::number(item.imageChecksums.at(0), 16)); //# Only the first is stored. TBD: get rid of list
+ itemData.insert(PI_RunId, runId);
+ itemData.insert(PI_CreationDate, QDateTime::currentDateTime().toString());
+ storeItemMetadata(itemData, prefix);
if (!isBaseline)
report.addMismatch(item);
}
+void BaselineHandler::storeItemMetadata(const PlatformInfo &metadata, const QString &path)
+{
+ QFile file(path + QLS(MetadataFileExt));
+ if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
+ qWarning() << runId << logtime() << "ERROR: could not write to file" << file.fileName();
+ return;
+ }
+ QTextStream out(&file);
+ PlatformInfo::const_iterator it = metadata.constBegin();
+ while (it != metadata.constEnd()) {
+ out << it.key() << ": " << it.value() << endl;
+ ++it;
+ }
+ file.close();
+}
+
+
+PlatformInfo BaselineHandler::fetchItemMetadata(const QString &path)
+{
+ PlatformInfo res;
+ QFile file(path + QLS(MetadataFileExt));
+ if (!file.open(QIODevice::ReadOnly))
+ return res;
+ QTextStream in(&file);
+ do {
+ QString line = in.readLine();
+ int idx = line.indexOf(QLS(": "));
+ if (idx > 0)
+ res.insert(line.left(idx), line.mid(idx+2));
+ } while (!in.atEnd());
+ return res;
+}
+
+
void BaselineHandler::receiveDisconnect()
{
qDebug() << runId << logtime() << "Client disconnected.";
@@ -309,9 +356,8 @@ void BaselineHandler::mapPlatformInfo() const
if (host.isEmpty() || host == QLS("localhost")) {
host = plat.value(PI_HostAddress);
} else {
- //# Site specific, should be in a config file
- if (!host.startsWith(QLS("oldhcp"))) {
- // remove index postfix typical of vm hostnames
+ if (!plat.value(PI_PulseGitBranch).isEmpty()) {
+ // i.e. pulse run, so remove index postfix typical of vm hostnames
host.remove(QRegExp(QLS("\\d+$")));
if (host.endsWith(QLC('-')))
host.chop(1);
@@ -327,7 +373,7 @@ void BaselineHandler::mapPlatformInfo() const
// Map Qt version
QString ver = plat.value(PI_QtVersion);
- mapped.insert(PI_QtVersion, ver.prepend(QLS("Qt-"))); //### TBD: remove patch version
+ mapped.insert(PI_QtVersion, ver.prepend(QLS("Qt-")));
}
QString BaselineHandler::pathForItem(const ImageItem &item, bool isBaseline, bool absolute) const
@@ -371,7 +417,7 @@ QString BaselineHandler::clearAllBaselines(const QString &context)
int tot = 0;
int failed = 0;
QDirIterator it(BaselineServer::storagePath() + QLC('/') + context,
- QStringList() << QLS("*.png") << QLS("*.metadata"));
+ QStringList() << QLS("*.") + QLS(FileFormat) << QLS("*.") + QLS(MetadataFileExt));
while (it.hasNext()) {
tot++;
if (!QFile::remove(it.next()))
@@ -380,27 +426,25 @@ QString BaselineHandler::clearAllBaselines(const QString &context)
return QString(QLS("%1 of %2 baselines cleared from context ")).arg((tot-failed)/2).arg(tot/2) + context;
}
-QString BaselineHandler::updateSingleBaseline(const QString &oldBaseline, const QString &newBaseline)
+QString BaselineHandler::updateBaselines(const QString &context, const QString &mismatchContext, const QString &itemFile)
{
- QString res;
- QString basePath(BaselineServer::storagePath() + QLC('/'));
- QString srcBase(basePath + newBaseline.left(newBaseline.length() - 3));
- QString dstDir(basePath + oldBaseline.left(oldBaseline.lastIndexOf(QLC('/'))));
-
- QProcess proc;
- proc.setProcessChannelMode(QProcess::MergedChannels);
- proc.start(QLS("cp"), QStringList() << QLS("-f") << srcBase + QLS("png") << srcBase + QLS("metadata") << dstDir);
- proc.waitForFinished();
- if (proc.exitCode() == 0)
- res = QString("Successfully updated '%1'").arg(oldBaseline + QLS("/metadata"));
- else
- res = QString("Error updating baseline: %1<br>"
- "Command output: <pre>%2</pre>").arg(proc.errorString(), proc.readAll().constData());
-
- return res;
+ int tot = 0;
+ int failed = 0;
+ QString storagePrefix = BaselineServer::storagePath() + QLC('/');
+ // If itemId is set, update just that one, otherwise, update all:
+ QString filter = itemFile.isEmpty() ? QLS("*_????.") : itemFile;
+ QDirIterator it(storagePrefix + mismatchContext, QStringList() << filter + QLS(FileFormat) << filter + QLS(MetadataFileExt));
+ while (it.hasNext()) {
+ tot++;
+ it.next();
+ QString oldFile = storagePrefix + context + QLC('/') + it.fileName();
+ QFile::remove(oldFile); // Remove existing baseline file
+ if (!QFile::copy(it.filePath(), oldFile)) // and replace it with the mismatch
+ failed++;
+ }
+ return QString(QLS("%1 of %2 baselines updated in context %3 from context %4")).arg((tot-failed)/2).arg(tot/2).arg(context, mismatchContext);
}
-
QString BaselineHandler::blacklistTest(const QString &context, const QString &itemId, bool removeFromBlacklist)
{
QFile file(BaselineServer::storagePath() + QLC('/') + context + QLS("/BLACKLIST"));
@@ -440,7 +484,8 @@ void BaselineHandler::testPathMapping()
<< QLS("macbuilder-02.test.troll.no")
<< QLS("bqvm1164")
<< QLS("chimera")
- << QLS("localhost");
+ << QLS("localhost")
+ << QLS("");
ImageItem item;
item.testFunction = QLS("testPathMapping");
@@ -450,7 +495,8 @@ void BaselineHandler::testPathMapping()
plat.insert(PI_QtVersion, QLS("4.8.0"));
plat.insert(PI_BuildKey, QLS("(nobuildkey)"));
- plat.insert(PI_QMakeSpec, "linux-g++");
+ plat.insert(PI_QMakeSpec, QLS("linux-g++"));
+ plat.insert(PI_PulseGitBranch, QLS("somebranch"));
foreach(const QString& host, hosts) {
mapped.clear();
plat.insert(PI_HostName, host);
diff --git a/tests/arthur/baselineserver/src/baselineserver.h b/tests/arthur/baselineserver/src/baselineserver.h
index 66b572e..cae490f 100644
--- a/tests/arthur/baselineserver/src/baselineserver.h
+++ b/tests/arthur/baselineserver/src/baselineserver.h
@@ -54,6 +54,7 @@
// #seconds between update checks
#define HEARTBEAT 10
+#define MetadataFileExt "metadata"
class BaselineServer : public QTcpServer
{
@@ -74,7 +75,10 @@ private slots:
private:
QTimer *heartbeatTimer;
QDateTime meLastMod;
+ QString lastRunId;
+ int lastRunIdIdx;
static QString storage;
+ static QString url;
};
@@ -84,10 +88,11 @@ class BaselineThread : public QThread
Q_OBJECT
public:
- BaselineThread(int socketDescriptor, QObject *parent);
+ BaselineThread(const QString &runId, int socketDescriptor, QObject *parent);
void run();
private:
+ QString runId;
int socketDescriptor;
};
@@ -97,14 +102,14 @@ class BaselineHandler : public QObject
Q_OBJECT
public:
- BaselineHandler(int socketDescriptor = -1);
+ BaselineHandler(const QString &runId, int socketDescriptor = -1);
void testPathMapping();
QString pathForItem(const ImageItem &item, bool isBaseline = true, bool absolute = true) const;
// CGI callbacks:
static QString view(const QString &baseline, const QString &rendered, const QString &compared);
static QString clearAllBaselines(const QString &context);
- static QString updateSingleBaseline(const QString &oldBaseline, const QString &newBaseline);
+ static QString updateBaselines(const QString &context, const QString &mismatchContext, const QString &itemFile);
static QString blacklistTest(const QString &context, const QString &itemId, bool removeFromBlacklist = false);
private slots:
@@ -115,6 +120,8 @@ private:
bool establishConnection();
void provideBaselineChecksums(const QByteArray &itemListBlock);
void storeImage(const QByteArray &itemBlock, bool isBaseline);
+ void storeItemMetadata(const PlatformInfo &metadata, const QString &path);
+ PlatformInfo fetchItemMetadata(const QString &path);
void mapPlatformInfo() const;
const char *logtime();
QString computeMismatchScore(const QImage& baseline, const QImage& rendered);
@@ -122,8 +129,8 @@ private:
BaselineProtocol proto;
PlatformInfo plat;
mutable PlatformInfo mapped;
- bool connectionEstablished;
QString runId;
+ bool connectionEstablished;
Report report;
};
diff --git a/tests/arthur/baselineserver/src/main.cpp b/tests/arthur/baselineserver/src/main.cpp
index 73c7082..3a03b2f 100644
--- a/tests/arthur/baselineserver/src/main.cpp
+++ b/tests/arthur/baselineserver/src/main.cpp
@@ -53,7 +53,7 @@ int main(int argc, char *argv[])
}
if (a.arguments().contains(QLatin1String("-testmapping"))) {
- BaselineHandler h;
+ BaselineHandler h(QLS("SomeRunId"));
h.testPathMapping();
return 0;
}
diff --git a/tests/arthur/baselineserver/src/report.cpp b/tests/arthur/baselineserver/src/report.cpp
index ae7b72e..5854706 100644
--- a/tests/arthur/baselineserver/src/report.cpp
+++ b/tests/arthur/baselineserver/src/report.cpp
@@ -138,8 +138,9 @@ void Report::writeHeader()
QString title = plat.value(PI_TestCase) + QLS(" Qt Baseline Test Report");
out << "<head><title>" << title << "</title></head>\n"
<< "<html><body><h1>" << title << "</h1>\n"
- << "<p>Run Id: <b>" << runId << "</b></p>\n"
- << "<p>Summary: <b>" << numMismatches << " of " << numItems << "</b> items reported mismatching</p>\n\n";
+ << "<p>Note: This is a <i>static</i> page, generated at " << QDateTime::currentDateTime().toString()
+ << " for the test run with id " << runId << "</p>\n"
+ << "<p>Summary: <b><span style=\"color:red\">" << numMismatches << " of " << numItems << "</b></span> items reported mismatching</p>\n\n";
out << "<h3>Platform Info:</h3>\n"
<< "<table>\n";
foreach (QString key, plat.keys())
@@ -153,10 +154,14 @@ void Report::writeFunctionResults(const ImageItemList &list)
QString testFunction = list.at(0).testFunction;
QString pageUrl = BaselineServer::baseUrl() + path;
QString ctx = handler->pathForItem(list.at(0), true, false).section(QLC('/'), 0, -2);
+ QString misCtx = handler->pathForItem(list.at(0), false, false).section(QLC('/'), 0, -2);
+
out << "\n<p>&nbsp;</p><h3>Test function: " << testFunction << "</h3>\n";
out << "<p><a href=\"/cgi-bin/server.cgi?cmd=clearAllBaselines&context=" << ctx << "&url=" << pageUrl
- << "\"><b><big>Clear all baselines</big></b></a></h3> (They will be recreated by the next run)</p>\n\n";
+ << "\"><b>Clear all baselines</b></a> for this testfunction (They will be recreated by the next run)</p>\n";
+ out << "<p><a href=\"/cgi-bin/server.cgi?cmd=updateAllBaselines&context=" << ctx << "&mismatchContext=" << misCtx << "&url=" << pageUrl
+ << "\"><b>Let these mismatching images be the new baselines</b></a> for this testfunction</p>\n\n";
out << "<table border=\"2\">\n"
"<tr>\n"
@@ -170,13 +175,16 @@ void Report::writeFunctionResults(const ImageItemList &list)
foreach (const ImageItem &item, list) {
out << "<tr>\n";
out << "<td>" << item.itemName << "</td>\n";
- QString baseline = handler->pathForItem(item, true, false) + QLS(FileFormat);
+ QString prefix = handler->pathForItem(item, true, false);
+ QString baseline = prefix + QLS(FileFormat);
+ QString metadata = prefix + QLS(MetadataFileExt);
if (item.status == ImageItem::Mismatch) {
QString rendered = handler->pathForItem(item, false, false) + QLS(FileFormat);
- writeItem(baseline, rendered, item, ctx);
+ QString itemFile = prefix.section(QLC('/'), -1);
+ writeItem(baseline, rendered, item, itemFile, ctx, misCtx, metadata);
}
else {
- out << "<td align=center><a href=\"/" << baseline << "\">view</a></td>\n"
+ out << "<td align=center><a href=\"/" << baseline << "\">image</a> <a href=\"/" << metadata << "\">info</a></td>\n"
<< "<td align=center colspan=2><small>n/a</small></td>\n"
<< "<td align=center>";
switch (item.status) {
@@ -204,7 +212,8 @@ void Report::writeFunctionResults(const ImageItemList &list)
out << "</table>\n";
}
-void Report::writeItem(const QString &baseline, const QString &rendered, const ImageItem &item, const QString &ctx)
+void Report::writeItem(const QString &baseline, const QString &rendered, const ImageItem &item,
+ const QString &itemFile, const QString &ctx, const QString &misCtx, const QString &metadata)
{
QString compared = generateCompared(baseline, rendered);
QString pageUrl = BaselineServer::baseUrl() + path;
@@ -215,8 +224,9 @@ void Report::writeItem(const QString &baseline, const QString &rendered, const I
out << "<td align=center>\n"
<< "<p><span style=\"color:red\">Mismatch reported</span></p>\n"
- << "<p><a href=\"/cgi-bin/server.cgi?cmd=updateSingleBaseline&oldBaseline=" << baseline
- << "&newBaseline=" << rendered << "&url=" << pageUrl << "\">Replace baseline with rendered</a></p>\n"
+ << "<p><a href=\"/" << metadata << "\">Baseline Info</a>\n"
+ << "<p><a href=\"/cgi-bin/server.cgi?cmd=updateSingleBaseline&context=" << ctx << "&mismatchContext=" << misCtx
+ << "&itemFile=" << itemFile << "&url=" << pageUrl << "\">Let this be the new baseline</a></p>\n"
<< "<p><a href=\"/cgi-bin/server.cgi?cmd=blacklist&context=" << ctx
<< "&itemId=" << item.itemName << "&url=" << pageUrl << "\">Blacklist this item</a></p>\n"
<< "<p><a href=\"/cgi-bin/server.cgi?cmd=view&baseline=" << baseline << "&rendered=" << rendered
@@ -276,8 +286,13 @@ void Report::handleCGIQuery(const QString &query)
cgiUrl.queryItemValue(QLS("compared")));
}
else if (command == QLS("updateSingleBaseline")) {
- s << BaselineHandler::updateSingleBaseline(cgiUrl.queryItemValue(QLS("oldBaseline")),
- cgiUrl.queryItemValue(QLS("newBaseline")));
+ s << BaselineHandler::updateBaselines(cgiUrl.queryItemValue(QLS("context")),
+ cgiUrl.queryItemValue(QLS("mismatchContext")),
+ cgiUrl.queryItemValue(QLS("itemFile")));
+ } else if (command == QLS("updateAllBaselines")) {
+ s << BaselineHandler::updateBaselines(cgiUrl.queryItemValue(QLS("context")),
+ cgiUrl.queryItemValue(QLS("mismatchContext")),
+ QString());
} else if (command == QLS("clearAllBaselines")) {
s << BaselineHandler::clearAllBaselines(cgiUrl.queryItemValue(QLS("context")));
} else if (command == QLS("blacklist")) {
diff --git a/tests/arthur/baselineserver/src/report.h b/tests/arthur/baselineserver/src/report.h
index 894f74c..d549a72 100644
--- a/tests/arthur/baselineserver/src/report.h
+++ b/tests/arthur/baselineserver/src/report.h
@@ -67,7 +67,8 @@ public:
private:
void write();
void writeFunctionResults(const ImageItemList &list);
- void writeItem(const QString &baseline, const QString &rendered, const ImageItem &item, const QString &ctx);
+ void writeItem(const QString &baseline, const QString &rendered, const ImageItem &item,
+ const QString &itemFile, const QString &ctx, const QString &misCtx, const QString &metadata);
void writeHeader();
void writeFooter();
QString generateCompared(const QString &baseline, const QString &rendered, bool fuzzy = false);
diff --git a/tests/arthur/common/baselineprotocol.cpp b/tests/arthur/common/baselineprotocol.cpp
index 86c26de..0e7e507 100644
--- a/tests/arthur/common/baselineprotocol.cpp
+++ b/tests/arthur/common/baselineprotocol.cpp
@@ -310,7 +310,7 @@ bool BaselineProtocol::connect(const QString &testCase, bool *dryrun)
}
if (cmd == Abort) {
- errMsg += QLS("Server aborted connection. Reason: ") + QString::fromLatin1(block);
+ errMsg += QLS("Server rejected connection. Reason: ") + QString::fromLatin1(block);
return false;
}