diff options
Diffstat (limited to 'tests/arthur')
-rw-r--r-- | tests/arthur/baselineserver/src/baselineserver.cpp | 156 | ||||
-rw-r--r-- | tests/arthur/baselineserver/src/baselineserver.h | 15 | ||||
-rw-r--r-- | tests/arthur/baselineserver/src/main.cpp | 2 | ||||
-rw-r--r-- | tests/arthur/baselineserver/src/report.cpp | 37 | ||||
-rw-r--r-- | tests/arthur/baselineserver/src/report.h | 3 | ||||
-rw-r--r-- | tests/arthur/common/baselineprotocol.cpp | 2 |
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> </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; } |