From bf3c84f718454f84b2ffbfa7fd1c7998bb5b01c2 Mon Sep 17 00:00:00 2001 From: aavit Date: Wed, 29 Jun 2011 13:24:37 +0200 Subject: 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 Reviewed-by: Gunnar Sletta --- tests/baselineserver/.gitignore | 2 + tests/baselineserver/bin/runserver | 13 + tests/baselineserver/shared/baselineprotocol.cpp | 527 +++++++++++++++ tests/baselineserver/shared/baselineprotocol.h | 193 ++++++ tests/baselineserver/shared/baselineprotocol.pri | 11 + tests/baselineserver/shared/lookup3.cpp | 786 +++++++++++++++++++++++ tests/baselineserver/shared/qbaselinetest.cpp | 193 ++++++ tests/baselineserver/shared/qbaselinetest.h | 77 +++ tests/baselineserver/shared/qbaselinetest.pri | 13 + tests/baselineserver/src/baselineserver.cpp | 576 +++++++++++++++++ tests/baselineserver/src/baselineserver.h | 141 ++++ tests/baselineserver/src/baselineserver.pro | 30 + tests/baselineserver/src/baselineserver.qrc | 5 + tests/baselineserver/src/main.cpp | 70 ++ tests/baselineserver/src/report.cpp | 311 +++++++++ tests/baselineserver/src/report.h | 91 +++ tests/baselineserver/src/templates/view.html | 79 +++ 17 files changed, 3118 insertions(+) create mode 100644 tests/baselineserver/.gitignore create mode 100755 tests/baselineserver/bin/runserver create mode 100644 tests/baselineserver/shared/baselineprotocol.cpp create mode 100644 tests/baselineserver/shared/baselineprotocol.h create mode 100644 tests/baselineserver/shared/baselineprotocol.pri create mode 100644 tests/baselineserver/shared/lookup3.cpp create mode 100644 tests/baselineserver/shared/qbaselinetest.cpp create mode 100644 tests/baselineserver/shared/qbaselinetest.h create mode 100644 tests/baselineserver/shared/qbaselinetest.pri create mode 100644 tests/baselineserver/src/baselineserver.cpp create mode 100644 tests/baselineserver/src/baselineserver.h create mode 100644 tests/baselineserver/src/baselineserver.pro create mode 100644 tests/baselineserver/src/baselineserver.qrc create mode 100644 tests/baselineserver/src/main.cpp create mode 100644 tests/baselineserver/src/report.cpp create mode 100644 tests/baselineserver/src/report.h create mode 100644 tests/baselineserver/src/templates/view.html (limited to 'tests/baselineserver') 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 +#endif +#if defined(Q_OS_UNIX) +#include +#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(), 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(other) +{ + sigKeys = other.sigKeys; + replaceDefault = other.replaceDefault; +} + + +PlatformInfo &PlatformInfo::operator=(const PlatformInfo &other) +{ + QMap::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&>(pi); + stream << pi.sigKeys << pi.replaceDefault; + return stream; +} + + +QDataStream & operator>> (QDataStream &stream, PlatformInfo &pi) +{ + stream >> static_cast&>(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 +#include +#include +#include +#include +#include +#include + +#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 +{ +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 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 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 + +#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 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 + +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 +#include +#include +#include +#include +#include +#include +#include +#include + +// 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()) { + 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 +#include +#include +#include +#include +#include +#include +#include + +#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 @@ + + + templates/view.html + + 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 +#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 +#include +#include + +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 << "" << title << "\n" + << "

" << title << "

\n" + << "

Note: This is a static page, generated at " << QDateTime::currentDateTime().toString() + << " for the test run with id " << runId << "

\n" + << "

Summary: " << numMismatches << " of " << numItems << " items reported mismatching

\n\n"; + out << "

Platform Info:

\n" + << "\n"; + foreach (QString key, plat.keys()) + out << "\n"; + out << "
" << key << "" << plat.value(key) << "
\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

 

Test function: " << testFunction << "

\n"; + out << "

Clear all baselines for this testfunction (They will be recreated by the next run)

\n"; + out << "

Let these mismatching images be the new baselines for this testfunction

\n\n"; + + out << "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n\n"; + + foreach (const ImageItem &item, list) { + out << "\n"; + out << "\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 << "\n" + << "\n" + << "\n"; + } + out << "\n\n"; + } + + out << "
ItemBaselineRenderedComparison (diffs are RED)Info/Action
" << item.itemName << "image infon/a"; + switch (item.status) { + case ImageItem::BaselineNotFound: + out << "Baseline not found/regenerated"; + break; + case ImageItem::IgnoreItem: + out << "Blacklisted " + << "Whitelist this item"; + break; + case ImageItem::Ok: + out << "No mismatch reported"; + break; + default: + out << "?"; + break; + } + out << "
\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 << "\n"; + + out << "\n" + << "

Mismatch reported

\n" + << "

Baseline Info\n" + << "

Let this be the new baseline

\n" + << "

Blacklist this item

\n" + << "

Inspect

\n" + << "\n"; +} + +void Report::writeFooter() +{ + out << "\n\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" + << ""; + + 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:
" << query << "
"; + } + s << "

Back to report"; + s << ""; +} 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 +#include +#include +#include + +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 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 @@ +

Lancelot Viewer

+ +

+Zoom: +1x +2x +4x +

+ +

+ + + + + + + + + + + + +
Baseline%1
Rendered%2
Differences

+ + +

+ +

+ + -- cgit v1.2.3