summaryrefslogtreecommitdiffstats
path: root/tests/baselineserver
diff options
context:
space:
mode:
authoraavit <qt-info@nokia.com>2011-06-29 13:24:37 +0200
committerQt by Nokia <qt-info@nokia.com>2011-06-29 15:10:14 +0200
commitbf3c84f718454f84b2ffbfa7fd1c7998bb5b01c2 (patch)
tree22579e69f7c8eb345b7bf9b8a2ed76ec53d417c3 /tests/baselineserver
parent03deed59ba31bd2e5ac1e799e0eb03cd4e8f5e41 (diff)
Cleaning up the QPainter/arthur testing stuff
This removes various remains of historical test tools, and the entire tests/arthur directory. The living parts are now: tests/auto/lancelot - including the suite of qps scripts. The script engine now lives here. tests/baselineserver - moved to toplevel since not arthur-specific. tests/manual/lance - for manual running and editing of qps scripts. Change-Id: I7c7f5df9197f4984a918dd1f9b31f42ee80d6152 Reviewed-on: http://codereview.qt.nokia.com/895 Reviewed-by: Qt Sanity Bot <qt_sanity_bot@ovi.com> Reviewed-by: Gunnar Sletta <gunnar.sletta@nokia.com>
Diffstat (limited to 'tests/baselineserver')
-rw-r--r--tests/baselineserver/.gitignore2
-rwxr-xr-xtests/baselineserver/bin/runserver13
-rw-r--r--tests/baselineserver/shared/baselineprotocol.cpp527
-rw-r--r--tests/baselineserver/shared/baselineprotocol.h193
-rw-r--r--tests/baselineserver/shared/baselineprotocol.pri11
-rw-r--r--tests/baselineserver/shared/lookup3.cpp786
-rw-r--r--tests/baselineserver/shared/qbaselinetest.cpp193
-rw-r--r--tests/baselineserver/shared/qbaselinetest.h77
-rw-r--r--tests/baselineserver/shared/qbaselinetest.pri13
-rw-r--r--tests/baselineserver/src/baselineserver.cpp576
-rw-r--r--tests/baselineserver/src/baselineserver.h141
-rw-r--r--tests/baselineserver/src/baselineserver.pro30
-rw-r--r--tests/baselineserver/src/baselineserver.qrc5
-rw-r--r--tests/baselineserver/src/main.cpp70
-rw-r--r--tests/baselineserver/src/report.cpp311
-rw-r--r--tests/baselineserver/src/report.h91
-rw-r--r--tests/baselineserver/src/templates/view.html79
17 files changed, 3118 insertions, 0 deletions
diff --git a/tests/baselineserver/.gitignore b/tests/baselineserver/.gitignore
new file mode 100644
index 0000000000..cc513e0df2
--- /dev/null
+++ b/tests/baselineserver/.gitignore
@@ -0,0 +1,2 @@
+storage
+bin/baselineserver
diff --git a/tests/baselineserver/bin/runserver b/tests/baselineserver/bin/runserver
new file mode 100755
index 0000000000..48c5c1d086
--- /dev/null
+++ b/tests/baselineserver/bin/runserver
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+logfile=baselineserver.log
+
+while true; do
+ echo >> $logfile
+ echo -n "***RESTARTING*** " >> $logfile
+ date >> $logfile
+
+ ./baselineserver 2>&1 | tee -a $logfile
+
+ sleep 2
+done
diff --git a/tests/baselineserver/shared/baselineprotocol.cpp b/tests/baselineserver/shared/baselineprotocol.cpp
new file mode 100644
index 0000000000..0fe3aa22a9
--- /dev/null
+++ b/tests/baselineserver/shared/baselineprotocol.cpp
@@ -0,0 +1,527 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this
+** file. Please review the following information to ensure the GNU Lesser
+** General Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#include "baselineprotocol.h"
+#include <QLibraryInfo>
+#include <QImage>
+#include <QBuffer>
+#include <QHostInfo>
+#include <QSysInfo>
+#include <QProcess>
+#include <QFileInfo>
+#include <QDir>
+#include <QTime>
+#include <QPointer>
+
+const QString PI_TestCase(QLS("TestCase"));
+const QString PI_HostName(QLS("HostName"));
+const QString PI_HostAddress(QLS("HostAddress"));
+const QString PI_OSName(QLS("OSName"));
+const QString PI_OSVersion(QLS("OSVersion"));
+const QString PI_QtVersion(QLS("QtVersion"));
+const QString PI_BuildKey(QLS("BuildKey"));
+const QString PI_GitCommit(QLS("GitCommit"));
+const QString PI_QMakeSpec(QLS("QMakeSpec"));
+const QString PI_PulseGitBranch(QLS("PulseGitBranch"));
+const QString PI_PulseTestrBranch(QLS("PulseTestrBranch"));
+
+#ifndef QMAKESPEC
+#define QMAKESPEC "Unknown"
+#endif
+
+#if defined(Q_OS_WIN)
+#include <QtCore/qt_windows.h>
+#endif
+#if defined(Q_OS_UNIX)
+#include <time.h>
+#endif
+void BaselineProtocol::sysSleep(int ms)
+{
+#if defined(Q_OS_WIN)
+ Sleep(DWORD(ms));
+#else
+ struct timespec ts = { ms / 1000, (ms % 1000) * 1000 * 1000 };
+ nanosleep(&ts, NULL);
+#endif
+}
+
+PlatformInfo::PlatformInfo()
+ : QMap<QString, QString>(), replaceDefault(false)
+{
+}
+
+PlatformInfo PlatformInfo::localHostInfo()
+{
+ PlatformInfo pi;
+ pi.insert(PI_HostName, QHostInfo::localHostName());
+ pi.insert(PI_QtVersion, QLS(qVersion()));
+ pi.insert(PI_QMakeSpec, QString(QLS(QMAKESPEC)).remove(QRegExp(QLS("^.*mkspecs/"))));
+ pi.insert(PI_BuildKey, QLibraryInfo::buildKey());
+#if defined(Q_OS_LINUX)
+ pi.insert(PI_OSName, QLS("Linux"));
+ QProcess uname;
+ uname.start(QLS("uname"), QStringList() << QLS("-r"));
+ if (uname.waitForFinished(3000))
+ pi.insert(PI_OSVersion, QString::fromLocal8Bit(uname.readAllStandardOutput().constData()).simplified());
+#elif defined(Q_OS_WINCE)
+ pi.insert(PI_OSName, QLS("WinCE"));
+ pi.insert(PI_OSVersion, QString::number(QSysInfo::windowsVersion()));
+#elif defined(Q_OS_WIN)
+ pi.insert(PI_OSName, QLS("Windows"));
+ pi.insert(PI_OSVersion, QString::number(QSysInfo::windowsVersion()));
+#elif defined(Q_OS_MAC)
+ pi.insert(PI_OSName, QLS("MacOS"));
+ pi.insert(PI_OSVersion, QString::number(qMacVersion()));
+#elif defined(Q_OS_SYMBIAN)
+ pi.insert(PI_OSName, QLS("Symbian"));
+ pi.insert(PI_OSVersion, QString::number(QSysInfo::symbianVersion());
+#else
+ pi.insert(PI_OSName, QLS("Other"));
+#endif
+
+ QProcess git;
+ QString cmd;
+ QStringList args;
+#if defined(Q_OS_WIN)
+ cmd = QLS("cmd.exe");
+ args << QLS("/c") << QLS("git");
+#else
+ cmd = QLS("git");
+#endif
+ args << QLS("log") << QLS("--max-count=1") << QLS("--pretty=%H [%an] [%ad] %s");
+ git.start(cmd, args);
+ git.waitForFinished(3000);
+ if (!git.exitCode())
+ pi.insert(PI_GitCommit, QString::fromLocal8Bit(git.readAllStandardOutput().constData()).simplified());
+ else
+ pi.insert(PI_GitCommit, QLS("Unknown"));
+
+ QByteArray gb = qgetenv("PULSE_GIT_BRANCH");
+ if (!gb.isEmpty())
+ pi.insert(PI_PulseGitBranch, QString::fromLatin1(gb));
+ QByteArray tb = qgetenv("PULSE_TESTR_BRANCH");
+ if (!tb.isEmpty())
+ pi.insert(PI_PulseTestrBranch, QString::fromLatin1(tb));
+
+ return pi;
+}
+
+
+PlatformInfo::PlatformInfo(const PlatformInfo &other)
+ : QMap<QString, QString>(other)
+{
+ sigKeys = other.sigKeys;
+ replaceDefault = other.replaceDefault;
+}
+
+
+PlatformInfo &PlatformInfo::operator=(const PlatformInfo &other)
+{
+ QMap<QString, QString>::operator=(other);
+ sigKeys = other.sigKeys;
+ replaceDefault = other.replaceDefault;
+ return *this;
+}
+
+
+void PlatformInfo::addSignificantKeys(const QStringList &keys, bool replaceDefaultKeys)
+{
+ sigKeys = keys;
+ replaceDefault = replaceDefaultKeys;
+}
+
+
+QStringList PlatformInfo::addedKeys() const
+{
+ return sigKeys;
+}
+
+
+bool PlatformInfo::addedKeysReplaceDefault() const
+{
+ return replaceDefault;
+}
+
+
+QDataStream & operator<< (QDataStream &stream, const PlatformInfo &pi)
+{
+ stream << static_cast<const QMap<QString, QString>&>(pi);
+ stream << pi.sigKeys << pi.replaceDefault;
+ return stream;
+}
+
+
+QDataStream & operator>> (QDataStream &stream, PlatformInfo &pi)
+{
+ stream >> static_cast<QMap<QString, QString>&>(pi);
+ stream >> pi.sigKeys >> pi.replaceDefault;
+ return stream;
+}
+
+
+ImageItem &ImageItem::operator=(const ImageItem &other)
+{
+ testFunction = other.testFunction;
+ itemName = other.itemName;
+ itemChecksum = other.itemChecksum;
+ status = other.status;
+ image = other.image;
+ imageChecksums = other.imageChecksums;
+ return *this;
+}
+
+// Defined in lookup3.c:
+void hashword2 (
+const quint32 *k, /* the key, an array of quint32 values */
+size_t length, /* the length of the key, in quint32s */
+quint32 *pc, /* IN: seed OUT: primary hash value */
+quint32 *pb); /* IN: more seed OUT: secondary hash value */
+
+quint64 ImageItem::computeChecksum(const QImage &image)
+{
+ QImage img(image);
+ const int bpl = img.bytesPerLine();
+ const int padBytes = bpl - (img.width() * img.depth() / 8);
+ if (padBytes) {
+ uchar *p = img.bits() + bpl - padBytes;
+ const int h = img.height();
+ for (int y = 0; y < h; ++y) {
+ qMemSet(p, 0, padBytes);
+ p += bpl;
+ }
+ }
+
+ quint32 h1 = 0xfeedbacc;
+ quint32 h2 = 0x21604894;
+ hashword2((const quint32 *)img.constBits(), img.byteCount()/4, &h1, &h2);
+ return (quint64(h1) << 32) | h2;
+}
+
+#if 0
+QString ImageItem::engineAsString() const
+{
+ switch (engine) {
+ case Raster:
+ return QLS("Raster");
+ break;
+ case OpenGL:
+ return QLS("OpenGL");
+ break;
+ default:
+ break;
+ }
+ return QLS("Unknown");
+}
+
+QString ImageItem::formatAsString() const
+{
+ static const int numFormats = 16;
+ static const char *formatNames[numFormats] = {
+ "Invalid",
+ "Mono",
+ "MonoLSB",
+ "Indexed8",
+ "RGB32",
+ "ARGB32",
+ "ARGB32-Premult",
+ "RGB16",
+ "ARGB8565-Premult",
+ "RGB666",
+ "ARGB6666-Premult",
+ "RGB555",
+ "ARGB8555-Premult",
+ "RGB888",
+ "RGB444",
+ "ARGB4444-Premult"
+ };
+ if (renderFormat < 0 || renderFormat >= numFormats)
+ return QLS("UnknownFormat");
+ return QLS(formatNames[renderFormat]);
+}
+#endif
+
+void ImageItem::writeImageToStream(QDataStream &out) const
+{
+ if (image.isNull() || image.format() == QImage::Format_Invalid) {
+ out << quint8(0);
+ return;
+ }
+ out << quint8('Q') << quint8(image.format());
+ out << quint8(QSysInfo::ByteOrder) << quint8(0); // pad to multiple of 4 bytes
+ out << quint32(image.width()) << quint32(image.height()) << quint32(image.bytesPerLine());
+ out << qCompress((const uchar *)image.constBits(), image.byteCount());
+ //# can be followed by colormap for formats that use it
+}
+
+void ImageItem::readImageFromStream(QDataStream &in)
+{
+ quint8 hdr, fmt, endian, pad;
+ quint32 width, height, bpl;
+ QByteArray data;
+
+ in >> hdr;
+ if (hdr != 'Q') {
+ image = QImage();
+ return;
+ }
+ in >> fmt >> endian >> pad;
+ if (!fmt || fmt >= QImage::NImageFormats) {
+ image = QImage();
+ return;
+ }
+ if (endian != QSysInfo::ByteOrder) {
+ qWarning("ImageItem cannot read streamed image with different endianness");
+ image = QImage();
+ return;
+ }
+ in >> width >> height >> bpl;
+ in >> data;
+ data = qUncompress(data);
+ QImage res((const uchar *)data.constData(), width, height, bpl, QImage::Format(fmt));
+ image = res.copy(); //# yuck, seems there is currently no way to avoid data copy
+}
+
+QDataStream & operator<< (QDataStream &stream, const ImageItem &ii)
+{
+ stream << ii.testFunction << ii.itemName << ii.itemChecksum << quint8(ii.status) << ii.imageChecksums << ii.misc;
+ ii.writeImageToStream(stream);
+ return stream;
+}
+
+QDataStream & operator>> (QDataStream &stream, ImageItem &ii)
+{
+ quint8 encStatus;
+ stream >> ii.testFunction >> ii.itemName >> ii.itemChecksum >> encStatus >> ii.imageChecksums >> ii.misc;
+ ii.status = ImageItem::ItemStatus(encStatus);
+ ii.readImageFromStream(stream);
+ return stream;
+}
+
+BaselineProtocol::BaselineProtocol()
+{
+}
+
+BaselineProtocol::~BaselineProtocol()
+{
+ socket.close();
+ if (socket.state() != QTcpSocket::UnconnectedState)
+ socket.waitForDisconnected(Timeout);
+}
+
+
+bool BaselineProtocol::connect(const QString &testCase, bool *dryrun)
+{
+ errMsg.clear();
+ QByteArray serverName(qgetenv("QT_LANCELOT_SERVER"));
+ if (serverName.isNull())
+ serverName = "lancelot.test.qt.nokia.com";
+
+ socket.connectToHost(serverName, ServerPort);
+ if (!socket.waitForConnected(Timeout)) {
+ sysSleep(Timeout); // Wait a bit and try again, the server might just be restarting
+ if (!socket.waitForConnected(Timeout)) {
+ errMsg += QLS("TCP connectToHost failed. Host:") + serverName + QLS(" port:") + QString::number(ServerPort);
+ return false;
+ }
+ }
+
+ PlatformInfo pi = PlatformInfo::localHostInfo();
+ pi.insert(PI_TestCase, testCase);
+ QByteArray block;
+ QDataStream ds(&block, QIODevice::ReadWrite);
+ ds << pi;
+ if (!sendBlock(AcceptPlatformInfo, block)) {
+ errMsg += QLS("Failed to send data to server.");
+ return false;
+ }
+
+ Command cmd = UnknownError;
+ if (!receiveBlock(&cmd, &block)) {
+ errMsg.prepend(QLS("Failed to get response from server. "));
+ return false;
+ }
+
+ if (cmd == Abort) {
+ errMsg += QLS("Server rejected connection. Reason: ") + QString::fromLatin1(block);
+ return false;
+ }
+
+ if (dryrun)
+ *dryrun = (cmd == DoDryRun);
+
+ if (cmd != Ack && cmd != DoDryRun) {
+ errMsg += QLS("Unexpected response from server.");
+ return false;
+ }
+
+ return true;
+}
+
+
+bool BaselineProtocol::acceptConnection(PlatformInfo *pi)
+{
+ errMsg.clear();
+
+ QByteArray block;
+ Command cmd = AcceptPlatformInfo;
+ if (!receiveBlock(&cmd, &block) || cmd != AcceptPlatformInfo)
+ return false;
+
+ if (pi) {
+ QDataStream ds(block);
+ ds >> *pi;
+ pi->insert(PI_HostAddress, socket.peerAddress().toString());
+ }
+
+ return true;
+}
+
+
+bool BaselineProtocol::requestBaselineChecksums(const QString &testFunction, ImageItemList *itemList)
+{
+ errMsg.clear();
+ if (!itemList)
+ return false;
+
+ for(ImageItemList::iterator it = itemList->begin(); it != itemList->end(); it++)
+ it->testFunction = testFunction;
+
+ QByteArray block;
+ QDataStream ds(&block, QIODevice::WriteOnly);
+ ds << *itemList;
+ if (!sendBlock(RequestBaselineChecksums, block))
+ return false;
+
+ Command cmd;
+ QByteArray rcvBlock;
+ if (!receiveBlock(&cmd, &rcvBlock) || cmd != BaselineProtocol::Ack)
+ return false;
+ QDataStream rds(&rcvBlock, QIODevice::ReadOnly);
+ rds >> *itemList;
+ return true;
+}
+
+
+bool BaselineProtocol::submitNewBaseline(const ImageItem &item, QByteArray *serverMsg)
+{
+ Command cmd;
+ return (sendItem(AcceptNewBaseline, item) && receiveBlock(&cmd, serverMsg) && cmd == Ack);
+}
+
+
+bool BaselineProtocol::submitMismatch(const ImageItem &item, QByteArray *serverMsg)
+{
+ Command cmd;
+ return (sendItem(AcceptMismatch, item) && receiveBlock(&cmd, serverMsg) && cmd == Ack);
+}
+
+
+bool BaselineProtocol::sendItem(Command cmd, const ImageItem &item)
+{
+ errMsg.clear();
+ QBuffer buf;
+ buf.open(QIODevice::WriteOnly);
+ QDataStream ds(&buf);
+ ds << item;
+ if (!sendBlock(cmd, buf.data())) {
+ errMsg.prepend(QLS("Failed to submit image to server. "));
+ return false;
+ }
+ return true;
+}
+
+
+bool BaselineProtocol::sendBlock(Command cmd, const QByteArray &block)
+{
+ QDataStream s(&socket);
+ // TBD: set qds version as a constant
+ s << quint16(ProtocolVersion) << quint16(cmd);
+ s.writeBytes(block.constData(), block.size());
+ return true;
+}
+
+
+bool BaselineProtocol::receiveBlock(Command *cmd, QByteArray *block)
+{
+ while (socket.bytesAvailable() < int(2*sizeof(quint16) + sizeof(quint32))) {
+ if (!socket.waitForReadyRead(Timeout))
+ return false;
+ }
+ QDataStream ds(&socket);
+ quint16 rcvProtocolVersion, rcvCmd;
+ ds >> rcvProtocolVersion >> rcvCmd;
+ if (rcvProtocolVersion != ProtocolVersion) {
+ errMsg = QLS("Baseline protocol version mismatch, received:") + QString::number(rcvProtocolVersion)
+ + QLS(" expected:") + QString::number(ProtocolVersion);
+ return false;
+ }
+ if (cmd)
+ *cmd = Command(rcvCmd);
+
+ QByteArray uMsg;
+ quint32 remaining;
+ ds >> remaining;
+ uMsg.resize(remaining);
+ int got = 0;
+ char* uMsgBuf = uMsg.data();
+ do {
+ got = ds.readRawData(uMsgBuf, remaining);
+ remaining -= got;
+ uMsgBuf += got;
+ } while (remaining && got >= 0 && socket.waitForReadyRead(Timeout));
+
+ if (got < 0)
+ return false;
+
+ if (block)
+ *block = uMsg;
+
+ return true;
+}
+
+
+QString BaselineProtocol::errorMessage()
+{
+ QString ret = errMsg;
+ if (socket.error() >= 0)
+ ret += QLS(" Socket state: ") + socket.errorString();
+ return ret;
+}
+
diff --git a/tests/baselineserver/shared/baselineprotocol.h b/tests/baselineserver/shared/baselineprotocol.h
new file mode 100644
index 0000000000..2d09e683d4
--- /dev/null
+++ b/tests/baselineserver/shared/baselineprotocol.h
@@ -0,0 +1,193 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this
+** file. Please review the following information to ensure the GNU Lesser
+** General Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef BASELINEPROTOCOL_H
+#define BASELINEPROTOCOL_H
+
+#include <QDataStream>
+#include <QTcpSocket>
+#include <QImage>
+#include <QVector>
+#include <QMap>
+#include <QPointer>
+#include <QStringList>
+
+#define QLS QLatin1String
+#define QLC QLatin1Char
+
+#define FileFormat "png"
+
+extern const QString PI_TestCase;
+extern const QString PI_HostName;
+extern const QString PI_HostAddress;
+extern const QString PI_OSName;
+extern const QString PI_OSVersion;
+extern const QString PI_QtVersion;
+extern const QString PI_BuildKey;
+extern const QString PI_GitCommit;
+extern const QString PI_QMakeSpec;
+extern const QString PI_PulseGitBranch;
+extern const QString PI_PulseTestrBranch;
+
+class PlatformInfo : public QMap<QString, QString>
+{
+public:
+ PlatformInfo();
+ PlatformInfo(const PlatformInfo &other);
+ ~PlatformInfo()
+ {}
+ PlatformInfo &operator=(const PlatformInfo &other);
+
+ static PlatformInfo localHostInfo();
+
+ void addSignificantKeys(const QStringList& keys, bool replaceDefaultKeys=false);
+ QStringList addedKeys() const;
+ bool addedKeysReplaceDefault() const;
+
+private:
+ QStringList sigKeys;
+ bool replaceDefault;
+ friend QDataStream & operator<< (QDataStream &stream, const PlatformInfo &pi);
+ friend QDataStream & operator>> (QDataStream &stream, PlatformInfo& pi);
+};
+QDataStream & operator<< (QDataStream &stream, const PlatformInfo &pi);
+QDataStream & operator>> (QDataStream &stream, PlatformInfo& pi);
+
+
+struct ImageItem
+{
+public:
+ ImageItem()
+ : status(Ok), itemChecksum(0)
+ {}
+ ImageItem(const ImageItem &other)
+ { *this = other; }
+ ~ImageItem()
+ {}
+ ImageItem &operator=(const ImageItem &other);
+
+ static quint64 computeChecksum(const QImage& image);
+
+ enum ItemStatus {
+ Ok = 0,
+ BaselineNotFound = 1,
+ IgnoreItem = 2,
+ Mismatch = 3
+ };
+
+ QString testFunction;
+ QString itemName;
+ ItemStatus status;
+ QImage image;
+ QList<quint64> imageChecksums;
+ quint16 itemChecksum;
+ QByteArray misc;
+
+ void writeImageToStream(QDataStream &stream) const;
+ void readImageFromStream(QDataStream &stream);
+};
+QDataStream & operator<< (QDataStream &stream, const ImageItem &ii);
+QDataStream & operator>> (QDataStream &stream, ImageItem& ii);
+
+Q_DECLARE_METATYPE(ImageItem);
+
+typedef QVector<ImageItem> ImageItemList;
+
+
+class BaselineProtocol
+{
+public:
+ BaselineProtocol();
+ ~BaselineProtocol();
+
+ static BaselineProtocol *instance(QObject *parent = 0);
+
+ // ****************************************************
+ // Important constants here
+ // ****************************************************
+ enum Constant {
+ ProtocolVersion = 5,
+ ServerPort = 54129,
+ Timeout = 15000
+ };
+
+ enum Command {
+ UnknownError = 0,
+ // Queries
+ AcceptPlatformInfo = 1,
+ RequestBaselineChecksums = 2,
+ AcceptNewBaseline = 4,
+ AcceptMismatch = 5,
+ // Responses
+ Ack = 128,
+ Abort = 129,
+ DoDryRun = 130
+ };
+
+ // For client:
+
+ // For advanced client:
+ bool connect(const QString &testCase, bool *dryrun = 0);
+ bool requestBaselineChecksums(const QString &testFunction, ImageItemList *itemList);
+ bool submitNewBaseline(const ImageItem &item, QByteArray *serverMsg);
+ bool submitMismatch(const ImageItem &item, QByteArray *serverMsg);
+
+ // For server:
+ bool acceptConnection(PlatformInfo *pi);
+
+ QString errorMessage();
+
+private:
+ bool sendItem(Command cmd, const ImageItem &item);
+
+ bool sendBlock(Command cmd, const QByteArray &block);
+ bool receiveBlock(Command *cmd, QByteArray *block);
+ void sysSleep(int ms);
+
+ QString errMsg;
+ QTcpSocket socket;
+
+ friend class BaselineThread;
+ friend class BaselineHandler;
+};
+
+
+#endif // BASELINEPROTOCOL_H
diff --git a/tests/baselineserver/shared/baselineprotocol.pri b/tests/baselineserver/shared/baselineprotocol.pri
new file mode 100644
index 0000000000..62e38a6ab5
--- /dev/null
+++ b/tests/baselineserver/shared/baselineprotocol.pri
@@ -0,0 +1,11 @@
+INCLUDEPATH += $$PWD
+DEPENDPATH += $$PWD
+
+QT *= network
+
+SOURCES += \
+ $$PWD/baselineprotocol.cpp \
+ $$PWD/lookup3.cpp
+
+HEADERS += \
+ $$PWD/baselineprotocol.h
diff --git a/tests/baselineserver/shared/lookup3.cpp b/tests/baselineserver/shared/lookup3.cpp
new file mode 100644
index 0000000000..1ad2d371c4
--- /dev/null
+++ b/tests/baselineserver/shared/lookup3.cpp
@@ -0,0 +1,786 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this
+** file. Please review the following information to ensure the GNU Lesser
+** General Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+
+/*
+These functions are based on:
+
+-------------------------------------------------------------------------------
+lookup3.c, by Bob Jenkins, May 2006, Public Domain.
+
+These are functions for producing 32-bit hashes for hash table lookup.
+hashword(), hashlittle(), hashlittle2(), hashbig(), mix(), and final()
+are externally useful functions. Routines to test the hash are included
+if SELF_TEST is defined. You can use this free for any purpose. It's in
+the public domain. It has no warranty.
+
+You probably want to use hashlittle(). hashlittle() and hashbig()
+hash byte arrays. hashlittle() is is faster than hashbig() on
+little-endian machines. Intel and AMD are little-endian machines.
+On second thought, you probably want hashlittle2(), which is identical to
+hashlittle() except it returns two 32-bit hashes for the price of one.
+You could implement hashbig2() if you wanted but I haven't bothered here.
+
+If you want to find a hash of, say, exactly 7 integers, do
+ a = i1; b = i2; c = i3;
+ mix(a,b,c);
+ a += i4; b += i5; c += i6;
+ mix(a,b,c);
+ a += i7;
+ final(a,b,c);
+then use c as the hash value. If you have a variable length array of
+4-byte integers to hash, use hashword(). If you have a byte array (like
+a character string), use hashlittle(). If you have several byte arrays, or
+a mix of things, see the comments above hashlittle().
+
+Why is this so big? I read 12 bytes at a time into 3 4-byte integers,
+then mix those integers. This is fast (you can do a lot more thorough
+mixing with 12*3 instructions on 3 integers than you can with 3 instructions
+on 1 byte), but shoehorning those bytes into integers efficiently is messy.
+-------------------------------------------------------------------------------
+*/
+
+#include <QtGlobal>
+
+#if Q_BYTE_ORDER == Q_BIG_ENDIAN
+# define HASH_LITTLE_ENDIAN 0
+# define HASH_BIG_ENDIAN 1
+#else
+# define HASH_LITTLE_ENDIAN 1
+# define HASH_BIG_ENDIAN 0
+#endif
+
+#define hashsize(n) ((quint32)1<<(n))
+#define hashmask(n) (hashsize(n)-1)
+#define rot(x,k) (((x)<<(k)) | ((x)>>(32-(k))))
+
+/*
+-------------------------------------------------------------------------------
+mix -- mix 3 32-bit values reversibly.
+
+This is reversible, so any information in (a,b,c) before mix() is
+still in (a,b,c) after mix().
+
+If four pairs of (a,b,c) inputs are run through mix(), or through
+mix() in reverse, there are at least 32 bits of the output that
+are sometimes the same for one pair and different for another pair.
+This was tested for:
+* pairs that differed by one bit, by two bits, in any combination
+ of top bits of (a,b,c), or in any combination of bottom bits of
+ (a,b,c).
+* "differ" is defined as +, -, ^, or ~^. For + and -, I transformed
+ the output delta to a Gray code (a^(a>>1)) so a string of 1's (as
+ is commonly produced by subtraction) look like a single 1-bit
+ difference.
+* the base values were pseudorandom, all zero but one bit set, or
+ all zero plus a counter that starts at zero.
+
+Some k values for my "a-=c; a^=rot(c,k); c+=b;" arrangement that
+satisfy this are
+ 4 6 8 16 19 4
+ 9 15 3 18 27 15
+ 14 9 3 7 17 3
+Well, "9 15 3 18 27 15" didn't quite get 32 bits diffing
+for "differ" defined as + with a one-bit base and a two-bit delta. I
+used http://burtleburtle.net/bob/hash/avalanche.html to choose
+the operations, constants, and arrangements of the variables.
+
+This does not achieve avalanche. There are input bits of (a,b,c)
+that fail to affect some output bits of (a,b,c), especially of a. The
+most thoroughly mixed value is c, but it doesn't really even achieve
+avalanche in c.
+
+This allows some parallelism. Read-after-writes are good at doubling
+the number of bits affected, so the goal of mixing pulls in the opposite
+direction as the goal of parallelism. I did what I could. Rotates
+seem to cost as much as shifts on every machine I could lay my hands
+on, and rotates are much kinder to the top and bottom bits, so I used
+rotates.
+-------------------------------------------------------------------------------
+*/
+#define mix(a,b,c) \
+{ \
+ a -= c; a ^= rot(c, 4); c += b; \
+ b -= a; b ^= rot(a, 6); a += c; \
+ c -= b; c ^= rot(b, 8); b += a; \
+ a -= c; a ^= rot(c,16); c += b; \
+ b -= a; b ^= rot(a,19); a += c; \
+ c -= b; c ^= rot(b, 4); b += a; \
+}
+
+/*
+-------------------------------------------------------------------------------
+final -- final mixing of 3 32-bit values (a,b,c) into c
+
+Pairs of (a,b,c) values differing in only a few bits will usually
+produce values of c that look totally different. This was tested for
+* pairs that differed by one bit, by two bits, in any combination
+ of top bits of (a,b,c), or in any combination of bottom bits of
+ (a,b,c).
+* "differ" is defined as +, -, ^, or ~^. For + and -, I transformed
+ the output delta to a Gray code (a^(a>>1)) so a string of 1's (as
+ is commonly produced by subtraction) look like a single 1-bit
+ difference.
+* the base values were pseudorandom, all zero but one bit set, or
+ all zero plus a counter that starts at zero.
+
+These constants passed:
+ 14 11 25 16 4 14 24
+ 12 14 25 16 4 14 24
+and these came close:
+ 4 8 15 26 3 22 24
+ 10 8 15 26 3 22 24
+ 11 8 15 26 3 22 24
+-------------------------------------------------------------------------------
+*/
+#define final(a,b,c) \
+{ \
+ c ^= b; c -= rot(b,14); \
+ a ^= c; a -= rot(c,11); \
+ b ^= a; b -= rot(a,25); \
+ c ^= b; c -= rot(b,16); \
+ a ^= c; a -= rot(c,4); \
+ b ^= a; b -= rot(a,14); \
+ c ^= b; c -= rot(b,24); \
+}
+
+/*
+--------------------------------------------------------------------
+ This works on all machines. To be useful, it requires
+ -- that the key be an array of quint32's, and
+ -- that the length be the number of quint32's in the key
+
+ The function hashword() is identical to hashlittle() on little-endian
+ machines, and identical to hashbig() on big-endian machines,
+ except that the length has to be measured in quint32s rather than in
+ bytes. hashlittle() is more complicated than hashword() only because
+ hashlittle() has to dance around fitting the key bytes into registers.
+--------------------------------------------------------------------
+*/
+quint32 hashword(
+const quint32 *k, /* the key, an array of quint32 values */
+size_t length, /* the length of the key, in quint32s */
+quint32 initval) /* the previous hash, or an arbitrary value */
+{
+ quint32 a,b,c;
+
+ /* Set up the internal state */
+ a = b = c = 0xdeadbeef + (((quint32)length)<<2) + initval;
+
+ /*------------------------------------------------- handle most of the key */
+ while (length > 3)
+ {
+ a += k[0];
+ b += k[1];
+ c += k[2];
+ mix(a,b,c);
+ length -= 3;
+ k += 3;
+ }
+
+ /*------------------------------------------- handle the last 3 quint32's */
+ switch(length) /* all the case statements fall through */
+ {
+ case 3 : c+=k[2];
+ case 2 : b+=k[1];
+ case 1 : a+=k[0];
+ final(a,b,c);
+ case 0: /* case 0: nothing left to add */
+ break;
+ }
+ /*------------------------------------------------------ report the result */
+ return c;
+}
+
+
+/*
+--------------------------------------------------------------------
+hashword2() -- same as hashword(), but take two seeds and return two
+32-bit values. pc and pb must both be nonnull, and *pc and *pb must
+both be initialized with seeds. If you pass in (*pb)==0, the output
+(*pc) will be the same as the return value from hashword().
+--------------------------------------------------------------------
+*/
+void hashword2 (
+const quint32 *k, /* the key, an array of quint32 values */
+size_t length, /* the length of the key, in quint32s */
+quint32 *pc, /* IN: seed OUT: primary hash value */
+quint32 *pb) /* IN: more seed OUT: secondary hash value */
+{
+ quint32 a,b,c;
+
+ /* Set up the internal state */
+ a = b = c = 0xdeadbeef + ((quint32)(length<<2)) + *pc;
+ c += *pb;
+
+ /*------------------------------------------------- handle most of the key */
+ while (length > 3)
+ {
+ a += k[0];
+ b += k[1];
+ c += k[2];
+ mix(a,b,c);
+ length -= 3;
+ k += 3;
+ }
+
+ /*------------------------------------------- handle the last 3 quint32's */
+ switch(length) /* all the case statements fall through */
+ {
+ case 3 : c+=k[2];
+ case 2 : b+=k[1];
+ case 1 : a+=k[0];
+ final(a,b,c);
+ case 0: /* case 0: nothing left to add */
+ break;
+ }
+ /*------------------------------------------------------ report the result */
+ *pc=c; *pb=b;
+}
+
+
+/*
+-------------------------------------------------------------------------------
+hashlittle() -- hash a variable-length key into a 32-bit value
+ k : the key (the unaligned variable-length array of bytes)
+ length : the length of the key, counting by bytes
+ initval : can be any 4-byte value
+Returns a 32-bit value. Every bit of the key affects every bit of
+the return value. Two keys differing by one or two bits will have
+totally different hash values.
+
+The best hash table sizes are powers of 2. There is no need to do
+mod a prime (mod is sooo slow!). If you need less than 32 bits,
+use a bitmask. For example, if you need only 10 bits, do
+ h = (h & hashmask(10));
+In which case, the hash table should have hashsize(10) elements.
+
+If you are hashing n strings (quint8 **)k, do it like this:
+ for (i=0, h=0; i<n; ++i) h = hashlittle( k[i], len[i], h);
+
+By Bob Jenkins, 2006. bob_jenkins@burtleburtle.net. You may use this
+code any way you wish, private, educational, or commercial. It's free.
+
+Use for hash table lookup, or anything where one collision in 2^^32 is
+acceptable. Do NOT use for cryptographic purposes.
+-------------------------------------------------------------------------------
+*/
+
+quint32 hashlittle( const void *key, size_t length, quint32 initval)
+{
+ quint32 a,b,c; /* internal state */
+ union { const void *ptr; size_t i; } u; /* needed for Mac Powerbook G4 */
+
+ /* Set up the internal state */
+ a = b = c = 0xdeadbeef + ((quint32)length) + initval;
+
+ u.ptr = key;
+ if (HASH_LITTLE_ENDIAN && ((u.i & 0x3) == 0)) {
+ const quint32 *k = (const quint32 *)key; /* read 32-bit chunks */
+
+ /*------ all but last block: aligned reads and affect 32 bits of (a,b,c) */
+ while (length > 12)
+ {
+ a += k[0];
+ b += k[1];
+ c += k[2];
+ mix(a,b,c);
+ length -= 12;
+ k += 3;
+ }
+
+ /*----------------------------- handle the last (probably partial) block */
+ /*
+ * "k[2]&0xffffff" actually reads beyond the end of the string, but
+ * then masks off the part it's not allowed to read. Because the
+ * string is aligned, the masked-off tail is in the same word as the
+ * rest of the string. Every machine with memory protection I've seen
+ * does it on word boundaries, so is OK with this. But VALGRIND will
+ * still catch it and complain. The masking trick does make the hash
+ * noticably faster for short strings (like English words).
+ */
+#ifndef VALGRIND
+
+ switch(length)
+ {
+ case 12: c+=k[2]; b+=k[1]; a+=k[0]; break;
+ case 11: c+=k[2]&0xffffff; b+=k[1]; a+=k[0]; break;
+ case 10: c+=k[2]&0xffff; b+=k[1]; a+=k[0]; break;
+ case 9 : c+=k[2]&0xff; b+=k[1]; a+=k[0]; break;
+ case 8 : b+=k[1]; a+=k[0]; break;
+ case 7 : b+=k[1]&0xffffff; a+=k[0]; break;
+ case 6 : b+=k[1]&0xffff; a+=k[0]; break;
+ case 5 : b+=k[1]&0xff; a+=k[0]; break;
+ case 4 : a+=k[0]; break;
+ case 3 : a+=k[0]&0xffffff; break;
+ case 2 : a+=k[0]&0xffff; break;
+ case 1 : a+=k[0]&0xff; break;
+ case 0 : return c; /* zero length strings require no mixing */
+ }
+
+#else /* make valgrind happy */
+
+ const quint8 *k8 = (const quint8 *)k;
+ switch(length)
+ {
+ case 12: c+=k[2]; b+=k[1]; a+=k[0]; break;
+ case 11: c+=((quint32)k8[10])<<16; /* fall through */
+ case 10: c+=((quint32)k8[9])<<8; /* fall through */
+ case 9 : c+=k8[8]; /* fall through */
+ case 8 : b+=k[1]; a+=k[0]; break;
+ case 7 : b+=((quint32)k8[6])<<16; /* fall through */
+ case 6 : b+=((quint32)k8[5])<<8; /* fall through */
+ case 5 : b+=k8[4]; /* fall through */
+ case 4 : a+=k[0]; break;
+ case 3 : a+=((quint32)k8[2])<<16; /* fall through */
+ case 2 : a+=((quint32)k8[1])<<8; /* fall through */
+ case 1 : a+=k8[0]; break;
+ case 0 : return c;
+ }
+
+#endif /* !valgrind */
+
+ } else if (HASH_LITTLE_ENDIAN && ((u.i & 0x1) == 0)) {
+ const quint16 *k = (const quint16 *)key; /* read 16-bit chunks */
+ const quint8 *k8;
+
+ /*--------------- all but last block: aligned reads and different mixing */
+ while (length > 12)
+ {
+ a += k[0] + (((quint32)k[1])<<16);
+ b += k[2] + (((quint32)k[3])<<16);
+ c += k[4] + (((quint32)k[5])<<16);
+ mix(a,b,c);
+ length -= 12;
+ k += 6;
+ }
+
+ /*----------------------------- handle the last (probably partial) block */
+ k8 = (const quint8 *)k;
+ switch(length)
+ {
+ case 12: c+=k[4]+(((quint32)k[5])<<16);
+ b+=k[2]+(((quint32)k[3])<<16);
+ a+=k[0]+(((quint32)k[1])<<16);
+ break;
+ case 11: c+=((quint32)k8[10])<<16; /* fall through */
+ case 10: c+=k[4];
+ b+=k[2]+(((quint32)k[3])<<16);
+ a+=k[0]+(((quint32)k[1])<<16);
+ break;
+ case 9 : c+=k8[8]; /* fall through */
+ case 8 : b+=k[2]+(((quint32)k[3])<<16);
+ a+=k[0]+(((quint32)k[1])<<16);
+ break;
+ case 7 : b+=((quint32)k8[6])<<16; /* fall through */
+ case 6 : b+=k[2];
+ a+=k[0]+(((quint32)k[1])<<16);
+ break;
+ case 5 : b+=k8[4]; /* fall through */
+ case 4 : a+=k[0]+(((quint32)k[1])<<16);
+ break;
+ case 3 : a+=((quint32)k8[2])<<16; /* fall through */
+ case 2 : a+=k[0];
+ break;
+ case 1 : a+=k8[0];
+ break;
+ case 0 : return c; /* zero length requires no mixing */
+ }
+
+ } else { /* need to read the key one byte at a time */
+ const quint8 *k = (const quint8 *)key;
+
+ /*--------------- all but the last block: affect some 32 bits of (a,b,c) */
+ while (length > 12)
+ {
+ a += k[0];
+ a += ((quint32)k[1])<<8;
+ a += ((quint32)k[2])<<16;
+ a += ((quint32)k[3])<<24;
+ b += k[4];
+ b += ((quint32)k[5])<<8;
+ b += ((quint32)k[6])<<16;
+ b += ((quint32)k[7])<<24;
+ c += k[8];
+ c += ((quint32)k[9])<<8;
+ c += ((quint32)k[10])<<16;
+ c += ((quint32)k[11])<<24;
+ mix(a,b,c);
+ length -= 12;
+ k += 12;
+ }
+
+ /*-------------------------------- last block: affect all 32 bits of (c) */
+ switch(length) /* all the case statements fall through */
+ {
+ case 12: c+=((quint32)k[11])<<24;
+ case 11: c+=((quint32)k[10])<<16;
+ case 10: c+=((quint32)k[9])<<8;
+ case 9 : c+=k[8];
+ case 8 : b+=((quint32)k[7])<<24;
+ case 7 : b+=((quint32)k[6])<<16;
+ case 6 : b+=((quint32)k[5])<<8;
+ case 5 : b+=k[4];
+ case 4 : a+=((quint32)k[3])<<24;
+ case 3 : a+=((quint32)k[2])<<16;
+ case 2 : a+=((quint32)k[1])<<8;
+ case 1 : a+=k[0];
+ break;
+ case 0 : return c;
+ }
+ }
+
+ final(a,b,c);
+ return c;
+}
+
+
+/*
+ * hashlittle2: return 2 32-bit hash values
+ *
+ * This is identical to hashlittle(), except it returns two 32-bit hash
+ * values instead of just one. This is good enough for hash table
+ * lookup with 2^^64 buckets, or if you want a second hash if you're not
+ * happy with the first, or if you want a probably-unique 64-bit ID for
+ * the key. *pc is better mixed than *pb, so use *pc first. If you want
+ * a 64-bit value do something like "*pc + (((uint64_t)*pb)<<32)".
+ */
+void hashlittle2(
+ const void *key, /* the key to hash */
+ size_t length, /* length of the key */
+ quint32 *pc, /* IN: primary initval, OUT: primary hash */
+ quint32 *pb) /* IN: secondary initval, OUT: secondary hash */
+{
+ quint32 a,b,c; /* internal state */
+ union { const void *ptr; size_t i; } u; /* needed for Mac Powerbook G4 */
+
+ /* Set up the internal state */
+ a = b = c = 0xdeadbeef + ((quint32)length) + *pc;
+ c += *pb;
+
+ u.ptr = key;
+ if (HASH_LITTLE_ENDIAN && ((u.i & 0x3) == 0)) {
+ const quint32 *k = (const quint32 *)key; /* read 32-bit chunks */
+
+ /*------ all but last block: aligned reads and affect 32 bits of (a,b,c) */
+ while (length > 12)
+ {
+ a += k[0];
+ b += k[1];
+ c += k[2];
+ mix(a,b,c);
+ length -= 12;
+ k += 3;
+ }
+
+ /*----------------------------- handle the last (probably partial) block */
+ /*
+ * "k[2]&0xffffff" actually reads beyond the end of the string, but
+ * then masks off the part it's not allowed to read. Because the
+ * string is aligned, the masked-off tail is in the same word as the
+ * rest of the string. Every machine with memory protection I've seen
+ * does it on word boundaries, so is OK with this. But VALGRIND will
+ * still catch it and complain. The masking trick does make the hash
+ * noticably faster for short strings (like English words).
+ */
+#ifndef VALGRIND
+
+ switch(length)
+ {
+ case 12: c+=k[2]; b+=k[1]; a+=k[0]; break;
+ case 11: c+=k[2]&0xffffff; b+=k[1]; a+=k[0]; break;
+ case 10: c+=k[2]&0xffff; b+=k[1]; a+=k[0]; break;
+ case 9 : c+=k[2]&0xff; b+=k[1]; a+=k[0]; break;
+ case 8 : b+=k[1]; a+=k[0]; break;
+ case 7 : b+=k[1]&0xffffff; a+=k[0]; break;
+ case 6 : b+=k[1]&0xffff; a+=k[0]; break;
+ case 5 : b+=k[1]&0xff; a+=k[0]; break;
+ case 4 : a+=k[0]; break;
+ case 3 : a+=k[0]&0xffffff; break;
+ case 2 : a+=k[0]&0xffff; break;
+ case 1 : a+=k[0]&0xff; break;
+ case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */
+ }
+
+#else /* make valgrind happy */
+
+ const quint8 *k8 = (const quint8 *)k;
+ switch(length)
+ {
+ case 12: c+=k[2]; b+=k[1]; a+=k[0]; break;
+ case 11: c+=((quint32)k8[10])<<16; /* fall through */
+ case 10: c+=((quint32)k8[9])<<8; /* fall through */
+ case 9 : c+=k8[8]; /* fall through */
+ case 8 : b+=k[1]; a+=k[0]; break;
+ case 7 : b+=((quint32)k8[6])<<16; /* fall through */
+ case 6 : b+=((quint32)k8[5])<<8; /* fall through */
+ case 5 : b+=k8[4]; /* fall through */
+ case 4 : a+=k[0]; break;
+ case 3 : a+=((quint32)k8[2])<<16; /* fall through */
+ case 2 : a+=((quint32)k8[1])<<8; /* fall through */
+ case 1 : a+=k8[0]; break;
+ case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */
+ }
+
+#endif /* !valgrind */
+
+ } else if (HASH_LITTLE_ENDIAN && ((u.i & 0x1) == 0)) {
+ const quint16 *k = (const quint16 *)key; /* read 16-bit chunks */
+ const quint8 *k8;
+
+ /*--------------- all but last block: aligned reads and different mixing */
+ while (length > 12)
+ {
+ a += k[0] + (((quint32)k[1])<<16);
+ b += k[2] + (((quint32)k[3])<<16);
+ c += k[4] + (((quint32)k[5])<<16);
+ mix(a,b,c);
+ length -= 12;
+ k += 6;
+ }
+
+ /*----------------------------- handle the last (probably partial) block */
+ k8 = (const quint8 *)k;
+ switch(length)
+ {
+ case 12: c+=k[4]+(((quint32)k[5])<<16);
+ b+=k[2]+(((quint32)k[3])<<16);
+ a+=k[0]+(((quint32)k[1])<<16);
+ break;
+ case 11: c+=((quint32)k8[10])<<16; /* fall through */
+ case 10: c+=k[4];
+ b+=k[2]+(((quint32)k[3])<<16);
+ a+=k[0]+(((quint32)k[1])<<16);
+ break;
+ case 9 : c+=k8[8]; /* fall through */
+ case 8 : b+=k[2]+(((quint32)k[3])<<16);
+ a+=k[0]+(((quint32)k[1])<<16);
+ break;
+ case 7 : b+=((quint32)k8[6])<<16; /* fall through */
+ case 6 : b+=k[2];
+ a+=k[0]+(((quint32)k[1])<<16);
+ break;
+ case 5 : b+=k8[4]; /* fall through */
+ case 4 : a+=k[0]+(((quint32)k[1])<<16);
+ break;
+ case 3 : a+=((quint32)k8[2])<<16; /* fall through */
+ case 2 : a+=k[0];
+ break;
+ case 1 : a+=k8[0];
+ break;
+ case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */
+ }
+
+ } else { /* need to read the key one byte at a time */
+ const quint8 *k = (const quint8 *)key;
+
+ /*--------------- all but the last block: affect some 32 bits of (a,b,c) */
+ while (length > 12)
+ {
+ a += k[0];
+ a += ((quint32)k[1])<<8;
+ a += ((quint32)k[2])<<16;
+ a += ((quint32)k[3])<<24;
+ b += k[4];
+ b += ((quint32)k[5])<<8;
+ b += ((quint32)k[6])<<16;
+ b += ((quint32)k[7])<<24;
+ c += k[8];
+ c += ((quint32)k[9])<<8;
+ c += ((quint32)k[10])<<16;
+ c += ((quint32)k[11])<<24;
+ mix(a,b,c);
+ length -= 12;
+ k += 12;
+ }
+
+ /*-------------------------------- last block: affect all 32 bits of (c) */
+ switch(length) /* all the case statements fall through */
+ {
+ case 12: c+=((quint32)k[11])<<24;
+ case 11: c+=((quint32)k[10])<<16;
+ case 10: c+=((quint32)k[9])<<8;
+ case 9 : c+=k[8];
+ case 8 : b+=((quint32)k[7])<<24;
+ case 7 : b+=((quint32)k[6])<<16;
+ case 6 : b+=((quint32)k[5])<<8;
+ case 5 : b+=k[4];
+ case 4 : a+=((quint32)k[3])<<24;
+ case 3 : a+=((quint32)k[2])<<16;
+ case 2 : a+=((quint32)k[1])<<8;
+ case 1 : a+=k[0];
+ break;
+ case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */
+ }
+ }
+
+ final(a,b,c);
+ *pc=c; *pb=b;
+}
+
+
+
+/*
+ * hashbig():
+ * This is the same as hashword() on big-endian machines. It is different
+ * from hashlittle() on all machines. hashbig() takes advantage of
+ * big-endian byte ordering.
+ */
+quint32 hashbig( const void *key, size_t length, quint32 initval)
+{
+ quint32 a,b,c;
+ union { const void *ptr; size_t i; } u; /* to cast key to (size_t) happily */
+
+ /* Set up the internal state */
+ a = b = c = 0xdeadbeef + ((quint32)length) + initval;
+
+ u.ptr = key;
+ if (HASH_BIG_ENDIAN && ((u.i & 0x3) == 0)) {
+ const quint32 *k = (const quint32 *)key; /* read 32-bit chunks */
+
+ /*------ all but last block: aligned reads and affect 32 bits of (a,b,c) */
+ while (length > 12)
+ {
+ a += k[0];
+ b += k[1];
+ c += k[2];
+ mix(a,b,c);
+ length -= 12;
+ k += 3;
+ }
+
+ /*----------------------------- handle the last (probably partial) block */
+ /*
+ * "k[2]<<8" actually reads beyond the end of the string, but
+ * then shifts out the part it's not allowed to read. Because the
+ * string is aligned, the illegal read is in the same word as the
+ * rest of the string. Every machine with memory protection I've seen
+ * does it on word boundaries, so is OK with this. But VALGRIND will
+ * still catch it and complain. The masking trick does make the hash
+ * noticably faster for short strings (like English words).
+ */
+#ifndef VALGRIND
+
+ switch(length)
+ {
+ case 12: c+=k[2]; b+=k[1]; a+=k[0]; break;
+ case 11: c+=k[2]&0xffffff00; b+=k[1]; a+=k[0]; break;
+ case 10: c+=k[2]&0xffff0000; b+=k[1]; a+=k[0]; break;
+ case 9 : c+=k[2]&0xff000000; b+=k[1]; a+=k[0]; break;
+ case 8 : b+=k[1]; a+=k[0]; break;
+ case 7 : b+=k[1]&0xffffff00; a+=k[0]; break;
+ case 6 : b+=k[1]&0xffff0000; a+=k[0]; break;
+ case 5 : b+=k[1]&0xff000000; a+=k[0]; break;
+ case 4 : a+=k[0]; break;
+ case 3 : a+=k[0]&0xffffff00; break;
+ case 2 : a+=k[0]&0xffff0000; break;
+ case 1 : a+=k[0]&0xff000000; break;
+ case 0 : return c; /* zero length strings require no mixing */
+ }
+
+#else /* make valgrind happy */
+
+ const quint8 *k8 = (const quint8 *)k;
+ switch(length) /* all the case statements fall through */
+ {
+ case 12: c+=k[2]; b+=k[1]; a+=k[0]; break;
+ case 11: c+=((quint32)k8[10])<<8; /* fall through */
+ case 10: c+=((quint32)k8[9])<<16; /* fall through */
+ case 9 : c+=((quint32)k8[8])<<24; /* fall through */
+ case 8 : b+=k[1]; a+=k[0]; break;
+ case 7 : b+=((quint32)k8[6])<<8; /* fall through */
+ case 6 : b+=((quint32)k8[5])<<16; /* fall through */
+ case 5 : b+=((quint32)k8[4])<<24; /* fall through */
+ case 4 : a+=k[0]; break;
+ case 3 : a+=((quint32)k8[2])<<8; /* fall through */
+ case 2 : a+=((quint32)k8[1])<<16; /* fall through */
+ case 1 : a+=((quint32)k8[0])<<24; break;
+ case 0 : return c;
+ }
+
+#endif /* !VALGRIND */
+
+ } else { /* need to read the key one byte at a time */
+ const quint8 *k = (const quint8 *)key;
+
+ /*--------------- all but the last block: affect some 32 bits of (a,b,c) */
+ while (length > 12)
+ {
+ a += ((quint32)k[0])<<24;
+ a += ((quint32)k[1])<<16;
+ a += ((quint32)k[2])<<8;
+ a += ((quint32)k[3]);
+ b += ((quint32)k[4])<<24;
+ b += ((quint32)k[5])<<16;
+ b += ((quint32)k[6])<<8;
+ b += ((quint32)k[7]);
+ c += ((quint32)k[8])<<24;
+ c += ((quint32)k[9])<<16;
+ c += ((quint32)k[10])<<8;
+ c += ((quint32)k[11]);
+ mix(a,b,c);
+ length -= 12;
+ k += 12;
+ }
+
+ /*-------------------------------- last block: affect all 32 bits of (c) */
+ switch(length) /* all the case statements fall through */
+ {
+ case 12: c+=k[11];
+ case 11: c+=((quint32)k[10])<<8;
+ case 10: c+=((quint32)k[9])<<16;
+ case 9 : c+=((quint32)k[8])<<24;
+ case 8 : b+=k[7];
+ case 7 : b+=((quint32)k[6])<<8;
+ case 6 : b+=((quint32)k[5])<<16;
+ case 5 : b+=((quint32)k[4])<<24;
+ case 4 : a+=k[3];
+ case 3 : a+=((quint32)k[2])<<8;
+ case 2 : a+=((quint32)k[1])<<16;
+ case 1 : a+=((quint32)k[0])<<24;
+ break;
+ case 0 : return c;
+ }
+ }
+
+ final(a,b,c);
+ return c;
+}
diff --git a/tests/baselineserver/shared/qbaselinetest.cpp b/tests/baselineserver/shared/qbaselinetest.cpp
new file mode 100644
index 0000000000..de3150e080
--- /dev/null
+++ b/tests/baselineserver/shared/qbaselinetest.cpp
@@ -0,0 +1,193 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this
+** file. Please review the following information to ensure the GNU Lesser
+** General Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qbaselinetest.h"
+#include "baselineprotocol.h"
+
+namespace QBaselineTest {
+
+BaselineProtocol proto;
+bool connected = false;
+bool triedConnecting = false;
+
+QByteArray curFunction;
+ImageItemList itemList;
+bool gotBaselines;
+
+
+bool connect(QByteArray *msg, bool *error)
+{
+ if (!triedConnecting) {
+ triedConnecting = true;
+ if (!proto.connect(QTest::testObject()->metaObject()->className())) {
+ *msg += "Failed to connect to baseline server: " + proto.errorMessage().toLatin1();
+ *error = true;
+ return false;
+ }
+ connected = true;
+ }
+ if (!connected) {
+ *msg = "Not connected to baseline server.";
+ *error = true;
+ return false;
+ }
+ return true;
+}
+
+
+bool compareItem(const ImageItem &baseline, const QImage &img, QByteArray *msg, bool *error)
+{
+ ImageItem item = baseline;
+ item.image = img;
+ item.imageChecksums.clear();
+ item.imageChecksums.prepend(ImageItem::computeChecksum(img));
+ 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 (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;
+ }
+ *error = false;
+ // The actual comparison of the given image with the baseline:
+ if (baseline.imageChecksums.contains(item.imageChecksums.at(0)))
+ return true;
+ proto.submitMismatch(item, &srvMsg);
+ *msg += "Mismatch. See report:\n " + srvMsg;
+ return false;
+}
+
+bool checkImage(const QImage &img, const char *name, quint16 checksum, QByteArray *msg, bool *error)
+{
+ 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";
+ }
+
+ *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);
+}
+
+
+bool testImage(const QImage& img, QByteArray *msg, bool *error)
+{
+ if (!connected && !connect(msg, error))
+ return true;
+
+ if (QTest::currentTestFunction() != curFunction || itemList.isEmpty()) {
+ qWarning() << "Usage error: QBASELINE_TEST used without corresponding QBaselineTest::newRow()";
+ return true;
+ }
+
+ if (!gotBaselines) {
+ if (!proto.requestBaselineChecksums(QString::fromLatin1(QTest::currentTestFunction()), &itemList) || itemList.isEmpty()) {
+ *msg = "Communication with baseline server failed: " + proto.errorMessage().toLatin1();
+ *error = true;
+ return true;
+ }
+ 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_TEST used without corresponding QBaselineTest::newRow() for row" << curTag;
+ return true;
+ }
+ return compareItem(*it, img, msg, error);
+}
+
+}
diff --git a/tests/baselineserver/shared/qbaselinetest.h b/tests/baselineserver/shared/qbaselinetest.h
new file mode 100644
index 0000000000..e76c32562f
--- /dev/null
+++ b/tests/baselineserver/shared/qbaselinetest.h
@@ -0,0 +1,77 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this
+** file. Please review the following information to ensure the GNU Lesser
+** General Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef BASELINETEST_H
+#define BASELINETEST_H
+
+#include <QTest>
+
+namespace QBaselineTest {
+bool checkImage(const QImage& img, const char *name, quint16 checksum, QByteArray *msg, bool *error);
+bool testImage(const QImage& img, QByteArray *msg, bool *error);
+QTestData &newRow(const char *dataTag, quint16 checksum = 0);
+}
+
+#define QBASELINE_CHECK_SUM(image, name, checksum)\
+do {\
+ QByteArray _msg;\
+ bool _err = false;\
+ if (!QBaselineTest::checkImage((image), (name), (checksum), &_msg, &_err)) {\
+ QFAIL(_msg.constData());\
+ } else if (_err) {\
+ QSKIP(_msg.constData(), SkipSingle);\
+ }\
+} while (0)
+
+#define QBASELINE_CHECK(image, name) QBASELINE_CHECK_SUM(image, name, 0)
+
+#define QBASELINE_TEST(image)\
+do {\
+ QByteArray _msg;\
+ bool _err = false;\
+ if (!QBaselineTest::testImage((image), &_msg, &_err)) {\
+ QFAIL(_msg.constData());\
+ } else if (_err) {\
+ QSKIP(_msg.constData(), SkipSingle);\
+ }\
+} while (0)
+
+#endif // BASELINETEST_H
diff --git a/tests/baselineserver/shared/qbaselinetest.pri b/tests/baselineserver/shared/qbaselinetest.pri
new file mode 100644
index 0000000000..5420c6ed1c
--- /dev/null
+++ b/tests/baselineserver/shared/qbaselinetest.pri
@@ -0,0 +1,13 @@
+QT *= testlib
+
+SOURCES += \
+ $$PWD/qbaselinetest.cpp
+
+HEADERS += \
+ $$PWD/qbaselinetest.h
+
+win32|symbian*:MKSPEC=$$replace(QMAKESPEC, \\\\, /)
+else:MKSPEC=$$QMAKESPEC
+DEFINES += QMAKESPEC=\\\"$$MKSPEC\\\"
+
+include($$PWD/baselineprotocol.pri)
diff --git a/tests/baselineserver/src/baselineserver.cpp b/tests/baselineserver/src/baselineserver.cpp
new file mode 100644
index 0000000000..6ff0a0c72d
--- /dev/null
+++ b/tests/baselineserver/src/baselineserver.cpp
@@ -0,0 +1,576 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this
+** file. Please review the following information to ensure the GNU Lesser
+** General Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#define QT_USE_FAST_CONCATENATION
+#define QT_USE_FAST_OPERATOR_PLUS
+
+#include "baselineserver.h"
+#include <QBuffer>
+#include <QFile>
+#include <QDir>
+#include <QCoreApplication>
+#include <QFileInfo>
+#include <QHostInfo>
+#include <QTextStream>
+#include <QProcess>
+#include <QDirIterator>
+
+// 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;
+QString BaselineServer::settingsFile;
+
+BaselineServer::BaselineServer(QObject *parent)
+ : QTcpServer(parent), lastRunIdIdx(0)
+{
+ QFileInfo me(QCoreApplication::applicationFilePath());
+ meLastMod = me.lastModified();
+ heartbeatTimer = new QTimer(this);
+ connect(heartbeatTimer, SIGNAL(timeout()), this, SLOT(heartbeat()));
+ heartbeatTimer->start(HEARTBEAT*1000);
+}
+
+QString BaselineServer::storagePath()
+{
+ if (storage.isEmpty()) {
+ storage = QLS(qgetenv("QT_LANCELOT_DIR"));
+ if (storage.isEmpty())
+ storage = QLS("/var/www");
+ }
+ return storage;
+}
+
+QString BaselineServer::baseUrl()
+{
+ if (url.isEmpty()) {
+ url = QLS("http://")
+ + QHostInfo::localHostName().toLatin1() + '.'
+ + QHostInfo::localDomainName().toLatin1() + '/';
+ }
+ return url;
+}
+
+QString BaselineServer::settingsFilePath()
+{
+ if (settingsFile.isEmpty()) {
+ QString exeName = QCoreApplication::applicationFilePath().section(QLC('/'), -1);
+ settingsFile = storagePath() + QLC('/') + exeName + QLS(".ini");
+ }
+ return settingsFile;
+}
+
+void BaselineServer::incomingConnection(int socketDescriptor)
+{
+ 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();
+}
+
+void BaselineServer::heartbeat()
+{
+ // The idea is to exit to be restarted when modified, as soon as not actually serving
+ QFileInfo me(QCoreApplication::applicationFilePath());
+ if (me.lastModified() == meLastMod)
+ return;
+ if (!me.exists() || !me.isExecutable())
+ return;
+
+ //# (could close() here to avoid accepting new connections, to avoid livelock)
+ //# also, could check for a timeout to force exit, to avoid hung threads blocking
+ bool isServing = false;
+ foreach(BaselineThread *thread, findChildren<BaselineThread *>()) {
+ if (thread->isRunning()) {
+ isServing = true;
+ break;
+ }
+ }
+
+ if (!isServing)
+ QCoreApplication::exit();
+}
+
+BaselineThread::BaselineThread(const QString &runId, int socketDescriptor, QObject *parent)
+ : QThread(parent), runId(runId), socketDescriptor(socketDescriptor)
+{
+}
+
+void BaselineThread::run()
+{
+ BaselineHandler handler(runId, socketDescriptor);
+ exec();
+}
+
+
+BaselineHandler::BaselineHandler(const QString &runId, int socketDescriptor)
+ : QObject(), runId(runId), connectionEstablished(false)
+{
+ settings = new QSettings(BaselineServer::settingsFilePath(), QSettings::IniFormat, this);
+
+ if (socketDescriptor == -1)
+ return;
+
+ connect(&proto.socket, SIGNAL(readyRead()), this, SLOT(receiveRequest()));
+ connect(&proto.socket, SIGNAL(disconnected()), this, SLOT(receiveDisconnect()));
+ proto.socket.setSocketDescriptor(socketDescriptor);
+}
+
+const char *BaselineHandler::logtime()
+{
+ return 0;
+ //return QTime::currentTime().toString(QLS("mm:ss.zzz"));
+}
+
+bool BaselineHandler::establishConnection()
+{
+ if (!proto.acceptConnection(&plat)) {
+ qWarning() << runId << logtime() << "Accepting new connection from" << proto.socket.peerAddress().toString() << "failed." << proto.errorMessage();
+ proto.sendBlock(BaselineProtocol::Abort, proto.errorMessage().toLatin1()); // In case the client can hear us, tell it what's wrong.
+ proto.socket.disconnectFromHost();
+ return false;
+ }
+ QString logMsg;
+ foreach (QString key, plat.keys()) {
+ if (key != PI_HostName && key != PI_HostAddress)
+ logMsg += key + QLS(": '") + plat.value(key) + QLS("', ");
+ }
+ qDebug() << runId << logtime() << "Connection established with" << plat.value(PI_HostName)
+ << "[" << qPrintable(plat.value(PI_HostAddress)) << "]" << logMsg;
+
+ settings->beginGroup("ClientFilters");
+ if (!settings->childKeys().isEmpty() && !plat.value(PI_PulseGitBranch).isEmpty()) { // i.e. not adhoc client
+ // Abort if client does not match the filters
+ foreach (QString filterKey, settings->childKeys()) {
+ QString filter = settings->value(filterKey).toString();
+ QString platVal = plat.value(filterKey);
+ if (filter.isEmpty() || platVal.isEmpty())
+ continue; // tbd: add a syntax for specifying a "value-must-be-present" filter
+ if (!platVal.contains(filter)) {
+ qDebug() << runId << logtime() << "Did not pass client filter on" << filterKey << "; disconnecting.";
+ proto.sendBlock(BaselineProtocol::Abort, QByteArray("Configured to not do testing for this client or repo, ref. ") + BaselineServer::settingsFilePath().toLatin1());
+ proto.socket.disconnectFromHost();
+ return false;
+ }
+ }
+ }
+ settings->endGroup();
+
+ proto.sendBlock(BaselineProtocol::Ack, QByteArray());
+
+ report.init(this, runId, plat);
+ return true;
+}
+
+void BaselineHandler::receiveRequest()
+{
+ if (!connectionEstablished) {
+ connectionEstablished = establishConnection();
+ return;
+ }
+
+ QByteArray block;
+ BaselineProtocol::Command cmd;
+ if (!proto.receiveBlock(&cmd, &block)) {
+ qWarning() << runId << logtime() << "Command reception failed. "<< proto.errorMessage();
+ QThread::currentThread()->exit(1);
+ return;
+ }
+
+ switch(cmd) {
+ case BaselineProtocol::RequestBaselineChecksums:
+ provideBaselineChecksums(block);
+ break;
+ case BaselineProtocol::AcceptNewBaseline:
+ storeImage(block, true);
+ break;
+ case BaselineProtocol::AcceptMismatch:
+ storeImage(block, false);
+ break;
+ default:
+ qWarning() << runId << logtime() << "Unknown command received. " << proto.errorMessage();
+ proto.sendBlock(BaselineProtocol::UnknownError, QByteArray());
+ }
+}
+
+
+void BaselineHandler::provideBaselineChecksums(const QByteArray &itemListBlock)
+{
+ ImageItemList itemList;
+ QDataStream ds(itemListBlock);
+ ds >> itemList;
+ qDebug() << runId << logtime() << "Received request for checksums for" << itemList.count()
+ << "items in test function" << itemList.at(0).testFunction;
+
+ for (ImageItemList::iterator i = itemList.begin(); i != itemList.end(); ++i) {
+ i->imageChecksums.clear();
+ i->status = ImageItem::BaselineNotFound;
+ QString prefix = pathForItem(*i, true);
+ 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;
+ }
+ }
+ }
+
+ // Find and mark blacklisted items
+ QString context = pathForItem(itemList.at(0), true, false).section(QLC('/'), 0, -2);
+ if (itemList.count() > 0) {
+ QFile file(BaselineServer::storagePath() + QLC('/') + context + QLS("/BLACKLIST"));
+ if (file.open(QIODevice::ReadOnly)) {
+ QTextStream in(&file);
+ do {
+ QString itemName = in.readLine();
+ if (!itemName.isNull()) {
+ for (ImageItemList::iterator i = itemList.begin(); i != itemList.end(); ++i) {
+ if (i->itemName == itemName)
+ i->status = ImageItem::IgnoreItem;
+ }
+ }
+ } while (!in.atEnd());
+ }
+ }
+
+ QByteArray block;
+ QDataStream ods(&block, QIODevice::WriteOnly);
+ ods << itemList;
+ proto.sendBlock(BaselineProtocol::Ack, block);
+ report.addItems(itemList);
+}
+
+
+void BaselineHandler::storeImage(const QByteArray &itemBlock, bool isBaseline)
+{
+ QDataStream ds(itemBlock);
+ ImageItem item;
+ ds >> item;
+
+ QString prefix = pathForItem(item, isBaseline);
+ qDebug() << runId << logtime() << "Received" << (isBaseline ? "baseline" : "mismatched") << "image for:" << item.itemName << "Storing in" << prefix;
+
+ QString msg;
+ if (isBaseline)
+ msg = QLS("New baseline image stored: ") + pathForItem(item, true, true) + QLS(FileFormat);
+ else
+ msg = BaselineServer::baseUrl() + report.filePath();
+ proto.sendBlock(BaselineProtocol::Ack, msg.toLatin1());
+
+ QString dir = prefix.section(QLC('/'), 0, -2);
+ QDir cwd;
+ if (!cwd.exists(dir))
+ cwd.mkpath(dir);
+ item.image.save(prefix + QLS(FileFormat), FileFormat);
+
+ 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.";
+ report.end();
+ QThread::currentThread()->exit(0);
+}
+
+
+void BaselineHandler::mapPlatformInfo() const
+{
+ mapped = plat;
+
+ // Map hostname
+ QString host = plat.value(PI_HostName).section(QLC('.'), 0, 0); // Filter away domain, if any
+ if (host.isEmpty() || host == QLS("localhost")) {
+ host = plat.value(PI_HostAddress);
+ } else {
+ 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);
+ }
+ }
+ if (host.isEmpty())
+ host = QLS("unknownhost");
+ mapped.insert(PI_HostName, host);
+
+ // Map qmakespec
+ QString mkspec = plat.value(PI_QMakeSpec);
+ mapped.insert(PI_QMakeSpec, mkspec.replace(QLC('/'), QLC('_')));
+
+ // Map Qt version
+ QString ver = plat.value(PI_QtVersion);
+ mapped.insert(PI_QtVersion, ver.prepend(QLS("Qt-")));
+}
+
+QString BaselineHandler::pathForItem(const ImageItem &item, bool isBaseline, bool absolute) const
+{
+ if (mapped.isEmpty())
+ mapPlatformInfo();
+
+ QString itemName = item.itemName.simplified();
+ itemName.replace(QLC(' '), QLC('_'));
+ itemName.replace(QLC('.'), QLC('_'));
+ itemName.append(QLC('_'));
+ itemName.append(QString::number(item.itemChecksum, 16).rightJustified(4, QLC('0')));
+
+ QStringList path;
+ if (absolute)
+ path += BaselineServer::storagePath();
+ path += mapped.value(PI_TestCase);
+ path += QLS(isBaseline ? "baselines" : "mismatches");
+ path += item.testFunction;
+ path += mapped.value(PI_QtVersion);
+ path += mapped.value(PI_QMakeSpec);
+ path += mapped.value(PI_HostName);
+ if (!isBaseline)
+ path += runId;
+ path += itemName + QLC('.');
+
+ return path.join(QLS("/"));
+}
+
+
+QString BaselineHandler::view(const QString &baseline, const QString &rendered, const QString &compared)
+{
+ QFile f(":/templates/view.html");
+ f.open(QIODevice::ReadOnly);
+ return QString::fromLatin1(f.readAll()).arg('/'+baseline, '/'+rendered, '/'+compared);
+}
+
+
+QString BaselineHandler::clearAllBaselines(const QString &context)
+{
+ int tot = 0;
+ int failed = 0;
+ QDirIterator it(BaselineServer::storagePath() + QLC('/') + context,
+ QStringList() << QLS("*.") + QLS(FileFormat) << QLS("*.") + QLS(MetadataFileExt));
+ while (it.hasNext()) {
+ tot++;
+ if (!QFile::remove(it.next()))
+ failed++;
+ }
+ return QString(QLS("%1 of %2 baselines cleared from context ")).arg((tot-failed)/2).arg(tot/2) + context;
+}
+
+QString BaselineHandler::updateBaselines(const QString &context, const QString &mismatchContext, const QString &itemFile)
+{
+ 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"));
+ QStringList blackList;
+ if (file.open(QIODevice::ReadWrite)) {
+ while (!file.atEnd())
+ blackList.append(file.readLine().trimmed());
+
+ if (removeFromBlacklist)
+ blackList.removeAll(itemId);
+ else if (!blackList.contains(itemId))
+ blackList.append(itemId);
+
+ file.resize(0);
+ foreach (QString id, blackList)
+ file.write(id.toLatin1() + '\n');
+ file.close();
+ return QLS(removeFromBlacklist ? "Whitelisted " : "Blacklisted ") + itemId + QLS(" in context ") + context;
+ } else {
+ return QLS("Unable to update blacklisted tests, failed to open ") + file.fileName();
+ }
+}
+
+
+void BaselineHandler::testPathMapping()
+{
+ qDebug() << "Storage prefix:" << BaselineServer::storagePath();
+
+ QStringList hosts;
+ hosts << QLS("bq-ubuntu910-x86-01")
+ << QLS("bq-ubuntu910-x86-15")
+ << QLS("osl-mac-master-5.test.qt.nokia.com")
+ << QLS("osl-mac-master-6.test.qt.nokia.com")
+ << QLS("sv-xp-vs-010")
+ << QLS("sv-xp-vs-011")
+ << QLS("sv-solaris-sparc-008")
+ << QLS("macbuilder-02.test.troll.no")
+ << QLS("bqvm1164")
+ << QLS("chimera")
+ << QLS("localhost")
+ << QLS("");
+
+ ImageItem item;
+ item.testFunction = QLS("testPathMapping");
+ item.itemName = QLS("arcs.qps");
+ item.imageChecksums << 0x0123456789abcdefULL;
+ item.itemChecksum = 0x0123;
+
+ plat.insert(PI_QtVersion, QLS("5.0.0"));
+ plat.insert(PI_BuildKey, QLS("(nobuildkey)"));
+ 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);
+ qDebug() << "Baseline from" << host << "->" << pathForItem(item, true);
+ qDebug() << "Mismatch from" << host << "->" << pathForItem(item, false);
+ }
+}
+
+
+QString BaselineHandler::computeMismatchScore(const QImage &baseline, const QImage &rendered)
+{
+ if (baseline.size() != rendered.size() || baseline.format() != rendered.format())
+ return QLS("[No score, incomparable images.]");
+ if (baseline.depth() != 32)
+ return QLS("[Score computation not implemented for format.]");
+
+ int w = baseline.width();
+ int h = baseline.height();
+
+ uint ncd = 0; // number of differing color pixels
+ uint nad = 0; // number of differing alpha pixels
+ uint scd = 0; // sum of color pixel difference
+ uint sad = 0; // sum of alpha pixel difference
+
+ for (int y=0; y<h; ++y) {
+ const QRgb *bl = (const QRgb *) baseline.constScanLine(y);
+ const QRgb *rl = (const QRgb *) rendered.constScanLine(y);
+ for (int x=0; x<w; ++x) {
+ QRgb b = bl[x];
+ QRgb r = rl[x];
+ if (r != b) {
+ int dr = qAbs(qRed(b) - qRed(r));
+ int dg = qAbs(qGreen(b) - qGreen(r));
+ int db = qAbs(qBlue(b) - qBlue(r));
+ int ds = dr + dg + db;
+ int da = qAbs(qAlpha(b) - qAlpha(r));
+ if (ds) {
+ ncd++;
+ scd += ds;
+ }
+ if (da) {
+ nad++;
+ sad += da;
+ }
+ }
+ }
+ }
+
+ double pcd = 100.0 * ncd / (w*h); // percent of pixels that differ
+ double acd = ncd ? double(scd) / (3*ncd) : 0; // avg. difference
+ QString res = QString(QLS("Diffscore: %1% (Num:%2 Avg:%3)")).arg(pcd, 0, 'g', 2).arg(ncd).arg(acd, 0, 'g', 2);
+ if (baseline.hasAlphaChannel()) {
+ double pad = 100.0 * nad / (w*h); // percent of pixels that differ
+ double aad = nad ? double(sad) / (3*nad) : 0; // avg. difference
+ res += QString(QLS(" Alpha-diffscore: %1% (Num:%2 Avg:%3)")).arg(pad, 0, 'g', 2).arg(nad).arg(aad, 0, 'g', 2);
+ }
+ return res;
+}
diff --git a/tests/baselineserver/src/baselineserver.h b/tests/baselineserver/src/baselineserver.h
new file mode 100644
index 0000000000..d73bb974f9
--- /dev/null
+++ b/tests/baselineserver/src/baselineserver.h
@@ -0,0 +1,141 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this
+** file. Please review the following information to ensure the GNU Lesser
+** General Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#ifndef BASELINESERVER_H
+#define BASELINESERVER_H
+
+#include <QStringList>
+#include <QTcpServer>
+#include <QThread>
+#include <QTcpSocket>
+#include <QScopedPointer>
+#include <QTimer>
+#include <QDateTime>
+#include <QSettings>
+
+#include "baselineprotocol.h"
+#include "report.h"
+
+// #seconds between update checks
+#define HEARTBEAT 10
+#define MetadataFileExt "metadata"
+
+class BaselineServer : public QTcpServer
+{
+ Q_OBJECT
+
+public:
+ BaselineServer(QObject *parent = 0);
+
+ static QString storagePath();
+ static QString baseUrl();
+ static QString settingsFilePath();
+
+protected:
+ void incomingConnection(int socketDescriptor);
+
+private slots:
+ void heartbeat();
+
+private:
+ QTimer *heartbeatTimer;
+ QDateTime meLastMod;
+ QString lastRunId;
+ int lastRunIdIdx;
+ static QString storage;
+ static QString url;
+ static QString settingsFile;
+};
+
+
+
+class BaselineThread : public QThread
+{
+ Q_OBJECT
+
+public:
+ BaselineThread(const QString &runId, int socketDescriptor, QObject *parent);
+ void run();
+
+private:
+ QString runId;
+ int socketDescriptor;
+};
+
+
+class BaselineHandler : public QObject
+{
+ Q_OBJECT
+
+public:
+ 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 updateBaselines(const QString &context, const QString &mismatchContext, const QString &itemFile);
+ static QString blacklistTest(const QString &context, const QString &itemId, bool removeFromBlacklist = false);
+
+private slots:
+ void receiveRequest();
+ void receiveDisconnect();
+
+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);
+
+ BaselineProtocol proto;
+ PlatformInfo plat;
+ mutable PlatformInfo mapped;
+ QString runId;
+ bool connectionEstablished;
+ Report report;
+ QSettings *settings;
+};
+
+#endif // BASELINESERVER_H
diff --git a/tests/baselineserver/src/baselineserver.pro b/tests/baselineserver/src/baselineserver.pro
new file mode 100644
index 0000000000..b59d59d1ed
--- /dev/null
+++ b/tests/baselineserver/src/baselineserver.pro
@@ -0,0 +1,30 @@
+#-------------------------------------------------
+#
+# Project created by QtCreator 2010-08-11T11:51:09
+#
+#-------------------------------------------------
+
+QT += core network
+
+# gui needed for QImage
+# QT -= gui
+
+TARGET = baselineserver
+DESTDIR = ../bin
+CONFIG += console
+CONFIG -= app_bundle
+
+TEMPLATE = app
+
+include(../shared/baselineprotocol.pri)
+
+SOURCES += main.cpp \
+ baselineserver.cpp \
+ report.cpp
+
+HEADERS += \
+ baselineserver.h \
+ report.h
+
+RESOURCES += \
+ baselineserver.qrc
diff --git a/tests/baselineserver/src/baselineserver.qrc b/tests/baselineserver/src/baselineserver.qrc
new file mode 100644
index 0000000000..b5cd6afadb
--- /dev/null
+++ b/tests/baselineserver/src/baselineserver.qrc
@@ -0,0 +1,5 @@
+<RCC>
+ <qresource prefix="/">
+ <file>templates/view.html</file>
+ </qresource>
+</RCC>
diff --git a/tests/baselineserver/src/main.cpp b/tests/baselineserver/src/main.cpp
new file mode 100644
index 0000000000..8e5fa4e669
--- /dev/null
+++ b/tests/baselineserver/src/main.cpp
@@ -0,0 +1,70 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this
+** file. Please review the following information to ensure the GNU Lesser
+** General Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#include <QtCore/QCoreApplication>
+#include "baselineserver.h"
+
+int main(int argc, char *argv[])
+{
+ QCoreApplication a(argc, argv);
+
+ QString queryString(qgetenv("QUERY_STRING"));
+ if (!queryString.isEmpty()) {
+ // run as CGI script
+ Report::handleCGIQuery(queryString);
+ return 0;
+ }
+
+ if (a.arguments().contains(QLatin1String("-testmapping"))) {
+ BaselineHandler h(QLS("SomeRunId"));
+ h.testPathMapping();
+ return 0;
+ }
+
+ BaselineServer server;
+ if (!server.listen(QHostAddress::Any, BaselineProtocol::ServerPort)) {
+ qWarning("Failed to listen!");
+ return 1;
+ }
+
+ qDebug() << "\n*****" << argv[0] << "started, ready to serve on port" << BaselineProtocol::ServerPort
+ << "with baseline protocol version" << BaselineProtocol::ProtocolVersion << "*****\n";
+ return a.exec();
+}
diff --git a/tests/baselineserver/src/report.cpp b/tests/baselineserver/src/report.cpp
new file mode 100644
index 0000000000..7c2d6ac6df
--- /dev/null
+++ b/tests/baselineserver/src/report.cpp
@@ -0,0 +1,311 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this
+** file. Please review the following information to ensure the GNU Lesser
+** General Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#include "report.h"
+#include "baselineprotocol.h"
+#include "baselineserver.h"
+#include <QDir>
+#include <QProcess>
+#include <QUrl>
+
+Report::Report()
+ : written(false), numItems(0), numMismatches(0)
+{
+}
+
+Report::~Report()
+{
+ end();
+}
+
+QString Report::filePath()
+{
+ return path;
+}
+
+void Report::init(const BaselineHandler *h, const QString &r, const PlatformInfo &p)
+{
+ handler = h;
+ runId = r;
+ plat = p;
+ rootDir = BaselineServer::storagePath() + QLC('/');
+ reportDir = plat.value(PI_TestCase) + QLC('/') + (plat.value(PI_PulseGitBranch).isEmpty() ? QLS("reports/adhoc/") : QLS("reports/pulse/"));
+ QString dir = rootDir + reportDir;
+ QDir cwd;
+ if (!cwd.exists(dir))
+ cwd.mkpath(dir);
+ path = reportDir + QLS("Report_") + runId + QLS(".html");
+}
+
+void Report::addItems(const ImageItemList &items)
+{
+ if (items.isEmpty())
+ return;
+ numItems += items.size();
+ QString func = items.at(0).testFunction;
+ if (!testFunctions.contains(func))
+ testFunctions.append(func);
+ itemLists[func] += items;
+}
+
+void Report::addMismatch(const ImageItem &item)
+{
+ if (!testFunctions.contains(item.testFunction)) {
+ qWarning() << "Report::addMismatch: unknown testfunction" << item.testFunction;
+ return;
+ }
+ bool found = false;
+ ImageItemList &list = itemLists[item.testFunction];
+ for (ImageItemList::iterator it = list.begin(); it != list.end(); ++it) {
+ if (it->itemName == item.itemName && it->itemChecksum == item.itemChecksum) {
+ it->status = ImageItem::Mismatch;
+ found = true;
+ break;
+ }
+ }
+ if (found)
+ numMismatches++;
+ else
+ qWarning() << "Report::addMismatch: unknown item" << item.itemName << "in testfunction" << item.testFunction;
+}
+
+void Report::end()
+{
+ if (written || !numMismatches)
+ return;
+ write();
+ written = true;
+}
+
+
+void Report::write()
+{
+ QFile file(rootDir + path);
+ if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
+ qWarning() << "Failed to open report file" << file.fileName();
+ return;
+ }
+ out.setDevice(&file);
+
+ writeHeader();
+ foreach(const QString &func, testFunctions) {
+ writeFunctionResults(itemLists.value(func));
+ }
+ writeFooter();
+ file.close();
+}
+
+
+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>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())
+ out << "<tr><td>" << key << "</td><td>" << plat.value(key) << "</td></tr>\n";
+ out << "</table>\n\n";
+}
+
+
+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>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"
+ "<th width=123>Item</th>\n"
+ "<th width=246>Baseline</th>\n"
+ "<th width=246>Rendered</th>\n"
+ "<th width=246>Comparison (diffs are <span style=\"color:red\">RED</span>)</th>\n"
+ "<th width=246>Info/Action</th>\n"
+ "</tr>\n\n";
+
+ foreach (const ImageItem &item, list) {
+ out << "<tr>\n";
+ out << "<td>" << item.itemName << "</td>\n";
+ 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);
+ QString itemFile = prefix.section(QLC('/'), -1);
+ writeItem(baseline, rendered, item, itemFile, ctx, misCtx, metadata);
+ }
+ else {
+ 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) {
+ case ImageItem::BaselineNotFound:
+ out << "Baseline not found/regenerated";
+ break;
+ case ImageItem::IgnoreItem:
+ out << "<span style=\"background-color:yellow\">Blacklisted</span> "
+ << "<a href=\"/cgi-bin/server.cgi?cmd=whitelist&context=" << ctx
+ << "&itemId=" << item.itemName << "&url=" << pageUrl
+ << "\">Whitelist this item</a>";
+ break;
+ case ImageItem::Ok:
+ out << "<span style=\"color:green\"><small>No mismatch reported</small></span>";
+ break;
+ default:
+ out << "?";
+ break;
+ }
+ out << "</td>\n";
+ }
+ out << "</tr>\n\n";
+ }
+
+ out << "</table>\n";
+}
+
+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;
+
+ QStringList images = QStringList() << baseline << rendered << compared;
+ foreach (const QString& img, images)
+ out << "<td height=246 align=center><a href=\"/" << img << "\"><img src=\"/" << generateThumbnail(img) << "\"></a></td>\n";
+
+ out << "<td align=center>\n"
+ << "<p><span style=\"color:red\">Mismatch reported</span></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
+ << "&compared=" << compared << "&url=" << pageUrl << "\">Inspect</a></p>\n"
+ << "</td>\n";
+}
+
+void Report::writeFooter()
+{
+ out << "\n</body></html>\n";
+}
+
+
+QString Report::generateCompared(const QString &baseline, const QString &rendered, bool fuzzy)
+{
+ QString res = rendered;
+ QFileInfo fi(res);
+ res.chop(fi.suffix().length() + 1);
+ res += QLS(fuzzy ? "_fuzzycompared.png" : "_compared.png");
+ QStringList args;
+ if (fuzzy)
+ args << QLS("-fuzz") << QLS("5%");
+ args << rootDir+baseline << rootDir+rendered << rootDir+res;
+ QProcess::execute(QLS("compare"), args);
+ return res;
+}
+
+
+QString Report::generateThumbnail(const QString &image)
+{
+ QString res = image;
+ QFileInfo imgFI(rootDir+image);
+ res.chop(imgFI.suffix().length() + 1);
+ res += QLS("_thumbnail.jpg");
+ QFileInfo resFI(rootDir+res);
+ if (resFI.exists() && resFI.lastModified() > imgFI.lastModified())
+ return res;
+ QStringList args;
+ args << rootDir+image << QLS("-resize") << QLS("240x240>") << QLS("-quality") << QLS("50") << rootDir+res;
+ QProcess::execute(QLS("convert"), args);
+ return res;
+}
+
+
+void Report::handleCGIQuery(const QString &query)
+{
+ QUrl cgiUrl(QLS("http://dummy/cgi-bin/dummy.cgi?") + query);
+ QTextStream s(stdout);
+ s << "Content-Type: text/html\r\n\r\n"
+ << "<HTML>";
+
+ QString command(cgiUrl.queryItemValue("cmd"));
+
+ if (command == QLS("view")) {
+ s << BaselineHandler::view(cgiUrl.queryItemValue(QLS("baseline")),
+ cgiUrl.queryItemValue(QLS("rendered")),
+ cgiUrl.queryItemValue(QLS("compared")));
+ }
+ else if (command == QLS("updateSingleBaseline")) {
+ 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")) {
+ // blacklist a test
+ s << BaselineHandler::blacklistTest(cgiUrl.queryItemValue(QLS("context")),
+ cgiUrl.queryItemValue(QLS("itemId")));
+ } else if (command == QLS("whitelist")) {
+ // whitelist a test
+ s << BaselineHandler::blacklistTest(cgiUrl.queryItemValue(QLS("context")),
+ cgiUrl.queryItemValue(QLS("itemId")), true);
+ } else {
+ s << "Unknown query:<br>" << query << "<br>";
+ }
+ s << "<p><a href=\"" << cgiUrl.queryItemValue(QLS("url")) << "\">Back to report</a>";
+ s << "</HTML>";
+}
diff --git a/tests/baselineserver/src/report.h b/tests/baselineserver/src/report.h
new file mode 100644
index 0000000000..d21102d32f
--- /dev/null
+++ b/tests/baselineserver/src/report.h
@@ -0,0 +1,91 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this
+** file. Please review the following information to ensure the GNU Lesser
+** General Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#ifndef REPORT_H
+#define REPORT_H
+
+#include "baselineprotocol.h"
+#include <QFile>
+#include <QTextStream>
+#include <QMap>
+#include <QStringList>
+
+class BaselineHandler;
+
+class Report
+{
+public:
+ Report();
+ ~Report();
+
+ void init(const BaselineHandler *h, const QString &r, const PlatformInfo &p);
+ void addItems(const ImageItemList& items);
+ void addMismatch(const ImageItem& item);
+ void end();
+
+ QString filePath();
+
+ static void handleCGIQuery(const QString &query);
+
+private:
+ void write();
+ void writeFunctionResults(const ImageItemList &list);
+ 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);
+ QString generateThumbnail(const QString &image);
+
+ const BaselineHandler *handler;
+ QString runId;
+ PlatformInfo plat;
+ QString rootDir;
+ QString reportDir;
+ QString path;
+ QStringList testFunctions;
+ QMap<QString, ImageItemList> itemLists;
+ bool written;
+ int numItems;
+ int numMismatches;
+ QTextStream out;
+};
+
+#endif // REPORT_H
diff --git a/tests/baselineserver/src/templates/view.html b/tests/baselineserver/src/templates/view.html
new file mode 100644
index 0000000000..c048f4781c
--- /dev/null
+++ b/tests/baselineserver/src/templates/view.html
@@ -0,0 +1,79 @@
+<h3>Lancelot Viewer</h3>
+
+<p>
+Zoom:
+<input name="zoom" id="z1" type="radio" checked="Checked">1x</input>
+<input name="zoom" id="z2" type="radio">2x</input>
+<input name="zoom" id="z4" type="radio">4x</input>
+</p>
+
+<p><table>
+<tr>
+<td><input name="imgselect" id="baseline" type="radio" checked="Checked">Baseline</input></td>
+<td>%1</td>
+</tr>
+<tr>
+<td><input name="imgselect" id="rendered" type="radio">Rendered</input></td>
+<td>%2</td>
+</tr>
+<tr>
+<td><input name="imgselect" id="compared" type="radio">Differences</input></td>
+<td></td>
+</tr>
+</table></p>
+
+
+<p>
+<canvas id="c" width="800" height="800"></canvas>
+</p>
+
+<script>
+ var canvas = document.getElementById("c");
+ var context = canvas.getContext("2d");
+ var cat = new Image();
+ cat.src = "%1";
+ var z = 1;
+ cat.onload = function() {
+ context.mozImageSmoothingEnabled = false;
+ context.drawImage(cat, 0, 0, z*cat.width, z*cat.height);
+ };
+
+ var bbut = document.getElementById("baseline");
+ bbut.onclick = function() {
+ cat.src = "%1";
+ };
+
+ var rbut = document.getElementById("rendered");
+ rbut.onclick = function() {
+ cat.src = "%2";
+ };
+
+ var cbut = document.getElementById("compared");
+ cbut.onclick = function() {
+ cat.src = "%3";
+ };
+
+ function setZoom(zoom)
+ {
+ z = zoom;
+ canvas.width = z*800;
+ canvas.height = z*800;
+ context.mozImageSmoothingEnabled = false;
+ context.drawImage(cat, 0, 0, z*cat.width, z*cat.height);
+ }
+
+ var z1but = document.getElementById("z1");
+ z1but.onclick = function() {
+ setZoom(1);
+ };
+
+ var z2but = document.getElementById("z2");
+ z2but.onclick = function() {
+ setZoom(2);
+ };
+
+ var z4but = document.getElementById("z4");
+ z4but.onclick = function() {
+ setZoom(4);
+ };
+</script>