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/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 + 7 files changed, 1800 insertions(+) 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 (limited to 'tests/baselineserver/shared') 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) -- cgit v1.2.3