diff options
Diffstat (limited to 'tests/baselineserver')
-rw-r--r-- | tests/baselineserver/.gitignore | 2 | ||||
-rwxr-xr-x | tests/baselineserver/bin/runserver | 13 | ||||
-rw-r--r-- | tests/baselineserver/shared/baselineprotocol.cpp | 546 | ||||
-rw-r--r-- | tests/baselineserver/shared/baselineprotocol.h | 187 | ||||
-rw-r--r-- | tests/baselineserver/shared/baselineprotocol.pri | 10 | ||||
-rw-r--r-- | tests/baselineserver/shared/lookup3.cpp | 846 | ||||
-rw-r--r-- | tests/baselineserver/shared/qbaselinetest.cpp | 423 | ||||
-rw-r--r-- | tests/baselineserver/shared/qbaselinetest.h | 70 | ||||
-rw-r--r-- | tests/baselineserver/shared/qbaselinetest.pri | 13 | ||||
-rw-r--r-- | tests/baselineserver/src/baselineserver.cpp | 853 | ||||
-rw-r--r-- | tests/baselineserver/src/baselineserver.h | 151 | ||||
-rw-r--r-- | tests/baselineserver/src/baselineserver.pro | 24 | ||||
-rw-r--r-- | tests/baselineserver/src/baselineserver.qrc | 5 | ||||
-rw-r--r-- | tests/baselineserver/src/main.cpp | 57 | ||||
-rw-r--r-- | tests/baselineserver/src/report.cpp | 503 | ||||
-rw-r--r-- | tests/baselineserver/src/report.h | 98 | ||||
-rw-r--r-- | tests/baselineserver/src/templates/view.html | 84 |
17 files changed, 0 insertions, 3885 deletions
diff --git a/tests/baselineserver/.gitignore b/tests/baselineserver/.gitignore deleted file mode 100644 index cc513e0df2..0000000000 --- a/tests/baselineserver/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -storage -bin/baselineserver diff --git a/tests/baselineserver/bin/runserver b/tests/baselineserver/bin/runserver deleted file mode 100755 index 48c5c1d086..0000000000 --- a/tests/baselineserver/bin/runserver +++ /dev/null @@ -1,13 +0,0 @@ -#!/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 deleted file mode 100644 index 63cce0bd80..0000000000 --- a/tests/baselineserver/shared/baselineprotocol.cpp +++ /dev/null @@ -1,546 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2021 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ -#include "baselineprotocol.h" -#include <QLibraryInfo> -#include <QImage> -#include <QBuffer> -#include <QHostInfo> -#include <QSysInfo> -#if QT_CONFIG(process) -# include <QProcess> -#endif -#include <QFileInfo> -#include <QDir> -#include <QTime> -#include <QPointer> -#include <QRegularExpression> - -const QString PI_Project(QLS("Project")); -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_QtBuildMode(QLS("QtBuildMode")); -const QString PI_GitCommit(QLS("GitCommit")); -const QString PI_QMakeSpec(QLS("QMakeSpec")); -const QString PI_PulseGitBranch(QLS("PulseGitBranch")); -const QString PI_PulseTestrBranch(QLS("PulseTestrBranch")); - -#ifndef QMAKESPEC -#define QMAKESPEC "Unknown" -#endif - -#if defined(Q_OS_WIN) -#include <QtCore/qt_windows.h> -#endif -#if defined(Q_OS_UNIX) -#include <time.h> -#endif -void BaselineProtocol::sysSleep(int ms) -{ -#if defined(Q_OS_WIN) - Sleep(DWORD(ms)); -#else - struct timespec ts = { ms / 1000, (ms % 1000) * 1000 * 1000 }; - nanosleep(&ts, NULL); -#endif -} - -PlatformInfo::PlatformInfo() - : QMap<QString, QString>(), adHoc(true) -{ -} - -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(QRegularExpression(QLS("^.*mkspecs/")))); - pi.insert(PI_QtBuildMode, QLibraryInfo::isDebugBuild() ? QLS("QtDebug") : QLS("QtRelease")); -#if defined(Q_OS_LINUX) && QT_CONFIG(process) - pi.insert(PI_OSName, QLS("Linux")); -#elif defined(Q_OS_WIN) - pi.insert(PI_OSName, QLS("Windows")); -#elif defined(Q_OS_DARWIN) - pi.insert(PI_OSName, QLS("Darwin")); -#else - pi.insert(PI_OSName, QLS("Other")); -#endif - pi.insert(PI_OSVersion, QSysInfo::kernelVersion()); - -#if QT_CONFIG(process) - 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)); - pi.setAdHocRun(false); - } - QByteArray tb = qgetenv("PULSE_TESTR_BRANCH"); - if (!tb.isEmpty()) { - pi.insert(PI_PulseTestrBranch, QString::fromLatin1(tb)); - pi.setAdHocRun(false); - } - if (!qgetenv("JENKINS_HOME").isEmpty()) { - pi.setAdHocRun(false); - gb = qgetenv("GIT_BRANCH"); - if (!gb.isEmpty()) { - // FIXME: the string "Pulse" should be eliminated, since that is not the used tool. - pi.insert(PI_PulseGitBranch, QString::fromLatin1(gb)); - } - } -#endif // QT_CONFIG(process) - - return pi; -} - - -PlatformInfo::PlatformInfo(const PlatformInfo &other) - : QMap<QString, QString>(other) -{ - orides = other.orides; - adHoc = other.adHoc; -} - - -PlatformInfo &PlatformInfo::operator=(const PlatformInfo &other) -{ - QMap<QString, QString>::operator=(other); - orides = other.orides; - adHoc = other.adHoc; - return *this; -} - - -void PlatformInfo::addOverride(const QString& key, const QString& value) -{ - orides.append(key); - orides.append(value); -} - - -QStringList PlatformInfo::overrides() const -{ - return orides; -} - - -void PlatformInfo::setAdHocRun(bool isAdHoc) -{ - adHoc = isAdHoc; -} - - -bool PlatformInfo::isAdHocRun() const -{ - return adHoc; -} - - -QDataStream & operator<< (QDataStream &stream, const PlatformInfo &pi) -{ - stream << static_cast<const QMap<QString, QString>&>(pi); - stream << pi.orides << pi.adHoc; - return stream; -} - - -QDataStream & operator>> (QDataStream &stream, PlatformInfo &pi) -{ - stream >> static_cast<QMap<QString, QString>&>(pi); - stream >> pi.orides >> pi.adHoc; - 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 qsizetype bpl = img.bytesPerLine(); - const int padBytes = bpl - (qsizetype(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) { - memset(p, 0, padBytes); - p += bpl; - } - } - - quint32 h1 = 0xfeedbacc; - quint32 h2 = 0x21604894; - hashword2((const quint32 *)img.constBits(), img.sizeInBytes()/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(reinterpret_cast<const uchar *>(image.constBits()), - int(image.sizeInBytes())); - //# 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() -{ - disconnect(); -} - -bool BaselineProtocol::disconnect() -{ - socket.close(); - return (socket.state() == QTcpSocket::UnconnectedState) ? true : socket.waitForDisconnected(Timeout); -} - - -bool BaselineProtocol::connect(const QString &testCase, bool *dryrun, const PlatformInfo& clientInfo) -{ - errMsg.clear(); - QByteArray serverName(qgetenv("QT_LANCELOT_SERVER")); - if (serverName.isNull()) - serverName = "lancelot.test.qt-project.org"; - - socket.connectToHost(serverName, ServerPort); - if (!socket.waitForConnected(Timeout)) { - sysSleep(3000); // Wait a bit and try again, the server might just be restarting - if (!socket.waitForConnected(Timeout)) { - errMsg += QLS("TCP connectToHost failed. Host:") + QLS(serverName) + QLS(" port:") + QString::number(ServerPort); - return false; - } - } - - PlatformInfo pi = clientInfo.isEmpty() ? PlatformInfo::localHostInfo() : clientInfo; - 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::submitMatch(const ImageItem &item, QByteArray *serverMsg) -{ - Command cmd; - ImageItem smallItem = item; - smallItem.image = QImage(); // No need to waste bandwith sending image (identical to baseline) to server - return (sendItem(AcceptMatch, smallItem) && receiveBlock(&cmd, serverMsg) && cmd == Ack); -} - - -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, bool *fuzzyMatch) -{ - Command cmd; - if (sendItem(AcceptMismatch, item) && receiveBlock(&cmd, serverMsg) && (cmd == Ack || cmd == FuzzyMatch)) { - if (fuzzyMatch) - *fuzzyMatch = (cmd == FuzzyMatch); - return true; - } - return false; -} - - -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 deleted file mode 100644 index 510762e304..0000000000 --- a/tests/baselineserver/shared/baselineprotocol.h +++ /dev/null @@ -1,187 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#ifndef BASELINEPROTOCOL_H -#define BASELINEPROTOCOL_H - -#include <QDataStream> -#include <QTcpSocket> -#include <QImage> -#include <QList> -#include <QMap> -#include <QPointer> -#include <QStringList> - -#define QLS QLatin1String -#define QLC QLatin1Char - -#define FileFormat "png" - -extern const QString PI_Project; -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_QtBuildMode; -extern const QString PI_GitCommit; -extern const QString PI_QMakeSpec; -extern const QString PI_PulseGitBranch; -extern const QString PI_PulseTestrBranch; - -class PlatformInfo : public QMap<QString, QString> -{ -public: - PlatformInfo(); - PlatformInfo(const PlatformInfo &other); - ~PlatformInfo() - {} - PlatformInfo &operator=(const PlatformInfo &other); - - static PlatformInfo localHostInfo(); - - void addOverride(const QString& key, const QString& value); - QStringList overrides() const; - bool isAdHocRun() const; - void setAdHocRun(bool isAdHoc); - -private: - QStringList orides; - bool adHoc; - 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, - FuzzyMatch = 4, - Error = 5 - }; - - QString testFunction; - QString itemName; - ItemStatus status; - QImage image; - QList<quint64> imageChecksums; - quint16 itemChecksum; - QByteArray misc; - - void writeImageToStream(QDataStream &stream) const; - void readImageFromStream(QDataStream &stream); -}; -QDataStream & operator<< (QDataStream &stream, const ImageItem &ii); -QDataStream & operator>> (QDataStream &stream, ImageItem& ii); - -Q_DECLARE_METATYPE(ImageItem); - -typedef QList<ImageItem> ImageItemList; - -class BaselineProtocol -{ -public: - BaselineProtocol(); - ~BaselineProtocol(); - - static BaselineProtocol *instance(QObject *parent = nullptr); - - // **************************************************** - // Important constants here - // **************************************************** - enum Constant { - ProtocolVersion = 5, - ServerPort = 54129, - Timeout = 15000 - }; - - enum Command { - UnknownError = 0, - // Queries - AcceptPlatformInfo = 1, - RequestBaselineChecksums = 2, - AcceptMatch = 3, - AcceptNewBaseline = 4, - AcceptMismatch = 5, - // Responses - Ack = 128, - Abort = 129, - DoDryRun = 130, - FuzzyMatch = 131 - }; - - // For client: - - // For advanced client: - bool connect(const QString &testCase, bool *dryrun = nullptr, const PlatformInfo& clientInfo = PlatformInfo()); - bool disconnect(); - bool requestBaselineChecksums(const QString &testFunction, ImageItemList *itemList); - bool submitMatch(const ImageItem &item, QByteArray *serverMsg); - bool submitNewBaseline(const ImageItem &item, QByteArray *serverMsg); - bool submitMismatch(const ImageItem &item, QByteArray *serverMsg, bool *fuzzyMatch = nullptr); - - // 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 deleted file mode 100644 index 996f9d5a1f..0000000000 --- a/tests/baselineserver/shared/baselineprotocol.pri +++ /dev/null @@ -1,10 +0,0 @@ -INCLUDEPATH += $$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 deleted file mode 100644 index 3d8d763bb7..0000000000 --- a/tests/baselineserver/shared/lookup3.cpp +++ /dev/null @@ -1,846 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - - -/* -These functions are based on: - -------------------------------------------------------------------------------- -lookup3.c, by Bob Jenkins, May 2006, Public Domain. - -These are functions for producing 32-bit hashes for hash table lookup. -hashword(), hashlittle(), hashlittle2(), hashbig(), mix(), and final() -are externally useful functions. Routines to test the hash are included -if SELF_TEST is defined. You can use this free for any purpose. It's in -the public domain. It has no warranty. - -You probably want to use hashlittle(). hashlittle() and hashbig() -hash byte arrays. hashlittle() is is faster than hashbig() on -little-endian machines. Intel and AMD are little-endian machines. -On second thought, you probably want hashlittle2(), which is identical to -hashlittle() except it returns two 32-bit hashes for the price of one. -You could implement hashbig2() if you wanted but I haven't bothered here. - -If you want to find a hash of, say, exactly 7 integers, do - a = i1; b = i2; c = i3; - mix(a,b,c); - a += i4; b += i5; c += i6; - mix(a,b,c); - a += i7; - final(a,b,c); -then use c as the hash value. If you have a variable length array of -4-byte integers to hash, use hashword(). If you have a byte array (like -a character string), use hashlittle(). If you have several byte arrays, or -a mix of things, see the comments above hashlittle(). - -Why is this so big? I read 12 bytes at a time into 3 4-byte integers, -then mix those integers. This is fast (you can do a lot more thorough -mixing with 12*3 instructions on 3 integers than you can with 3 instructions -on 1 byte), but shoehorning those bytes into integers efficiently is messy. -------------------------------------------------------------------------------- -*/ - -#include <QtGlobal> - -#if Q_BYTE_ORDER == Q_BIG_ENDIAN -# define HASH_LITTLE_ENDIAN 0 -# define HASH_BIG_ENDIAN 1 -#else -# define HASH_LITTLE_ENDIAN 1 -# define HASH_BIG_ENDIAN 0 -#endif - -#define hashsize(n) ((quint32)1<<(n)) -#define hashmask(n) (hashsize(n)-1) -#define rot(x,k) (((x)<<(k)) | ((x)>>(32-(k)))) - -/* -------------------------------------------------------------------------------- -mix -- mix 3 32-bit values reversibly. - -This is reversible, so any information in (a,b,c) before mix() is -still in (a,b,c) after mix(). - -If four pairs of (a,b,c) inputs are run through mix(), or through -mix() in reverse, there are at least 32 bits of the output that -are sometimes the same for one pair and different for another pair. -This was tested for: -* pairs that differed by one bit, by two bits, in any combination - of top bits of (a,b,c), or in any combination of bottom bits of - (a,b,c). -* "differ" is defined as +, -, ^, or ~^. For + and -, I transformed - the output delta to a Gray code (a^(a>>1)) so a string of 1's (as - is commonly produced by subtraction) look like a single 1-bit - difference. -* the base values were pseudorandom, all zero but one bit set, or - all zero plus a counter that starts at zero. - -Some k values for my "a-=c; a^=rot(c,k); c+=b;" arrangement that -satisfy this are - 4 6 8 16 19 4 - 9 15 3 18 27 15 - 14 9 3 7 17 3 -Well, "9 15 3 18 27 15" didn't quite get 32 bits diffing -for "differ" defined as + with a one-bit base and a two-bit delta. I -used http://burtleburtle.net/bob/hash/avalanche.html to choose -the operations, constants, and arrangements of the variables. - -This does not achieve avalanche. There are input bits of (a,b,c) -that fail to affect some output bits of (a,b,c), especially of a. The -most thoroughly mixed value is c, but it doesn't really even achieve -avalanche in c. - -This allows some parallelism. Read-after-writes are good at doubling -the number of bits affected, so the goal of mixing pulls in the opposite -direction as the goal of parallelism. I did what I could. Rotates -seem to cost as much as shifts on every machine I could lay my hands -on, and rotates are much kinder to the top and bottom bits, so I used -rotates. -------------------------------------------------------------------------------- -*/ -#define mix(a,b,c) \ -{ \ - a -= c; a ^= rot(c, 4); c += b; \ - b -= a; b ^= rot(a, 6); a += c; \ - c -= b; c ^= rot(b, 8); b += a; \ - a -= c; a ^= rot(c,16); c += b; \ - b -= a; b ^= rot(a,19); a += c; \ - c -= b; c ^= rot(b, 4); b += a; \ -} - -/* -------------------------------------------------------------------------------- -final -- final mixing of 3 32-bit values (a,b,c) into c - -Pairs of (a,b,c) values differing in only a few bits will usually -produce values of c that look totally different. This was tested for -* pairs that differed by one bit, by two bits, in any combination - of top bits of (a,b,c), or in any combination of bottom bits of - (a,b,c). -* "differ" is defined as +, -, ^, or ~^. For + and -, I transformed - the output delta to a Gray code (a^(a>>1)) so a string of 1's (as - is commonly produced by subtraction) look like a single 1-bit - difference. -* the base values were pseudorandom, all zero but one bit set, or - all zero plus a counter that starts at zero. - -These constants passed: - 14 11 25 16 4 14 24 - 12 14 25 16 4 14 24 -and these came close: - 4 8 15 26 3 22 24 - 10 8 15 26 3 22 24 - 11 8 15 26 3 22 24 -------------------------------------------------------------------------------- -*/ -#define final(a,b,c) \ -{ \ - c ^= b; c -= rot(b,14); \ - a ^= c; a -= rot(c,11); \ - b ^= a; b -= rot(a,25); \ - c ^= b; c -= rot(b,16); \ - a ^= c; a -= rot(c,4); \ - b ^= a; b -= rot(a,14); \ - c ^= b; c -= rot(b,24); \ -} - -/* --------------------------------------------------------------------- - This works on all machines. To be useful, it requires - -- that the key be an array of quint32's, and - -- that the length be the number of quint32's in the key - - The function hashword() is identical to hashlittle() on little-endian - machines, and identical to hashbig() on big-endian machines, - except that the length has to be measured in quint32s rather than in - bytes. hashlittle() is more complicated than hashword() only because - hashlittle() has to dance around fitting the key bytes into registers. --------------------------------------------------------------------- -*/ -quint32 hashword( -const quint32 *k, /* the key, an array of quint32 values */ -size_t length, /* the length of the key, in quint32s */ -quint32 initval) /* the previous hash, or an arbitrary value */ -{ - quint32 a,b,c; - - /* Set up the internal state */ - a = b = c = 0xdeadbeef + (((quint32)length)<<2) + initval; - - /*------------------------------------------------- handle most of the key */ - while (length > 3) - { - a += k[0]; - b += k[1]; - c += k[2]; - mix(a,b,c); - length -= 3; - k += 3; - } - - /*------------------------------------------- handle the last 3 quint32's */ - switch(length) /* all the case statements fall through */ - { - case 3 : c+=k[2]; - Q_FALLTHROUGH(); - case 2 : b+=k[1]; - Q_FALLTHROUGH(); - case 1 : a+=k[0]; - final(a,b,c); - Q_FALLTHROUGH(); - 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]; - Q_FALLTHROUGH(); - case 2 : b+=k[1]; - Q_FALLTHROUGH(); - case 1 : a+=k[0]; - final(a,b,c); - Q_FALLTHROUGH(); - case 0: /* case 0: nothing left to add */ - break; - } - /*------------------------------------------------------ report the result */ - *pc=c; *pb=b; -} - - -/* -------------------------------------------------------------------------------- -hashlittle() -- hash a variable-length key into a 32-bit value - k : the key (the unaligned variable-length array of bytes) - length : the length of the key, counting by bytes - initval : can be any 4-byte value -Returns a 32-bit value. Every bit of the key affects every bit of -the return value. Two keys differing by one or two bits will have -totally different hash values. - -The best hash table sizes are powers of 2. There is no need to do -mod a prime (mod is sooo slow!). If you need less than 32 bits, -use a bitmask. For example, if you need only 10 bits, do - h = (h & hashmask(10)); -In which case, the hash table should have hashsize(10) elements. - -If you are hashing n strings (quint8 **)k, do it like this: - for (i=0, h=0; i<n; ++i) h = hashlittle( k[i], len[i], h); - -By Bob Jenkins, 2006. bob_jenkins@burtleburtle.net. You may use this -code any way you wish, private, educational, or commercial. It's free. - -Use for hash table lookup, or anything where one collision in 2^^32 is -acceptable. Do NOT use for cryptographic purposes. -------------------------------------------------------------------------------- -*/ - -quint32 hashlittle( const void *key, size_t length, quint32 initval) -{ - quint32 a,b,c; /* internal state */ - union { const void *ptr; size_t i; } u; /* needed for Mac Powerbook G4 */ - - /* Set up the internal state */ - a = b = c = 0xdeadbeef + ((quint32)length) + initval; - - u.ptr = key; - if (HASH_LITTLE_ENDIAN && ((u.i & 0x3) == 0)) { - const quint32 *k = (const quint32 *)key; /* read 32-bit chunks */ - - /*------ all but last block: aligned reads and affect 32 bits of (a,b,c) */ - while (length > 12) - { - a += k[0]; - b += k[1]; - c += k[2]; - mix(a,b,c); - length -= 12; - k += 3; - } - - /*----------------------------- handle the last (probably partial) block */ - /* - * "k[2]&0xffffff" actually reads beyond the end of the string, but - * then masks off the part it's not allowed to read. Because the - * string is aligned, the masked-off tail is in the same word as the - * rest of the string. Every machine with memory protection I've seen - * does it on word boundaries, so is OK with this. But VALGRIND will - * still catch it and complain. The masking trick does make the hash - * noticably faster for short strings (like English words). - */ -#ifndef VALGRIND - - switch(length) - { - case 12: c+=k[2]; b+=k[1]; a+=k[0]; break; - case 11: c+=k[2]&0xffffff; b+=k[1]; a+=k[0]; break; - case 10: c+=k[2]&0xffff; b+=k[1]; a+=k[0]; break; - case 9 : c+=k[2]&0xff; b+=k[1]; a+=k[0]; break; - case 8 : b+=k[1]; a+=k[0]; break; - case 7 : b+=k[1]&0xffffff; a+=k[0]; break; - case 6 : b+=k[1]&0xffff; a+=k[0]; break; - case 5 : b+=k[1]&0xff; a+=k[0]; break; - case 4 : a+=k[0]; break; - case 3 : a+=k[0]&0xffffff; break; - case 2 : a+=k[0]&0xffff; break; - case 1 : a+=k[0]&0xff; break; - case 0 : return c; /* zero length strings require no mixing */ - } - -#else /* make valgrind happy */ - - const quint8 *k8 = (const quint8 *)k; - switch(length) - { - case 12: c+=k[2]; b+=k[1]; a+=k[0]; break; - case 11: c+=((quint32)k8[10])<<16; - Q_FALLTHROUGH(); - case 10: c+=((quint32)k8[9])<<8; - Q_FALLTHROUGH(); - case 9 : c+=k8[8]; - Q_FALLTHROUGH(); - case 8 : b+=k[1]; a+=k[0]; break; - case 7 : b+=((quint32)k8[6])<<16; - Q_FALLTHROUGH(); - case 6 : b+=((quint32)k8[5])<<8; - Q_FALLTHROUGH(); - case 5 : b+=k8[4]; - Q_FALLTHROUGH(); - case 4 : a+=k[0]; break; - case 3 : a+=((quint32)k8[2])<<16; - Q_FALLTHROUGH(); - case 2 : a+=((quint32)k8[1])<<8; - Q_FALLTHROUGH(); - 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; - Q_FALLTHROUGH(); - 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]; - Q_FALLTHROUGH(); - case 8 : b+=k[2]+(((quint32)k[3])<<16); - a+=k[0]+(((quint32)k[1])<<16); - break; - case 7 : b+=((quint32)k8[6])<<16; - Q_FALLTHROUGH(); - case 6 : b+=k[2]; - a+=k[0]+(((quint32)k[1])<<16); - break; - case 5 : b+=k8[4]; - Q_FALLTHROUGH(); - case 4 : a+=k[0]+(((quint32)k[1])<<16); - break; - case 3 : a+=((quint32)k8[2])<<16; - Q_FALLTHROUGH(); - 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; - Q_FALLTHROUGH(); - case 11: c+=((quint32)k[10])<<16; - Q_FALLTHROUGH(); - case 10: c+=((quint32)k[9])<<8; - Q_FALLTHROUGH(); - case 9 : c+=k[8]; - Q_FALLTHROUGH(); - case 8 : b+=((quint32)k[7])<<24; - Q_FALLTHROUGH(); - case 7 : b+=((quint32)k[6])<<16; - Q_FALLTHROUGH(); - case 6 : b+=((quint32)k[5])<<8; - Q_FALLTHROUGH(); - case 5 : b+=k[4]; - Q_FALLTHROUGH(); - case 4 : a+=((quint32)k[3])<<24; - Q_FALLTHROUGH(); - case 3 : a+=((quint32)k[2])<<16; - Q_FALLTHROUGH(); - case 2 : a+=((quint32)k[1])<<8; - Q_FALLTHROUGH(); - 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; - Q_FALLTHROUGH(); - case 10: c+=((quint32)k8[9])<<8; - Q_FALLTHROUGH(); - case 9 : c+=k8[8]; - Q_FALLTHROUGH(); - case 8 : b+=k[1]; a+=k[0]; break; - case 7 : b+=((quint32)k8[6])<<16; - Q_FALLTHROUGH(); - case 6 : b+=((quint32)k8[5])<<8; - Q_FALLTHROUGH(); - case 5 : b+=k8[4]; - Q_FALLTHROUGH(); - case 4 : a+=k[0]; break; - case 3 : a+=((quint32)k8[2])<<16; - Q_FALLTHROUGH(); - case 2 : a+=((quint32)k8[1])<<8; - Q_FALLTHROUGH(); - 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; - Q_FALLTHROUGH(); - 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]; - Q_FALLTHROUGH(); - case 8 : b+=k[2]+(((quint32)k[3])<<16); - a+=k[0]+(((quint32)k[1])<<16); - break; - case 7 : b+=((quint32)k8[6])<<16; - Q_FALLTHROUGH(); - case 6 : b+=k[2]; - a+=k[0]+(((quint32)k[1])<<16); - break; - case 5 : b+=k8[4]; - Q_FALLTHROUGH(); - case 4 : a+=k[0]+(((quint32)k[1])<<16); - break; - case 3 : a+=((quint32)k8[2])<<16; - Q_FALLTHROUGH(); - 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; - Q_FALLTHROUGH(); - case 11: c+=((quint32)k[10])<<16; - Q_FALLTHROUGH(); - case 10: c+=((quint32)k[9])<<8; - Q_FALLTHROUGH(); - case 9 : c+=k[8]; - Q_FALLTHROUGH(); - case 8 : b+=((quint32)k[7])<<24; - Q_FALLTHROUGH(); - case 7 : b+=((quint32)k[6])<<16; - Q_FALLTHROUGH(); - case 6 : b+=((quint32)k[5])<<8; - Q_FALLTHROUGH(); - case 5 : b+=k[4]; - Q_FALLTHROUGH(); - case 4 : a+=((quint32)k[3])<<24; - Q_FALLTHROUGH(); - case 3 : a+=((quint32)k[2])<<16; - Q_FALLTHROUGH(); - case 2 : a+=((quint32)k[1])<<8; - Q_FALLTHROUGH(); - 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; - Q_FALLTHROUGH(); - case 10: c+=((quint32)k8[9])<<16; - Q_FALLTHROUGH(); - case 9 : c+=((quint32)k8[8])<<24; - Q_FALLTHROUGH(); - case 8 : b+=k[1]; a+=k[0]; break; - case 7 : b+=((quint32)k8[6])<<8; - Q_FALLTHROUGH(); - case 6 : b+=((quint32)k8[5])<<16; - Q_FALLTHROUGH(); - case 5 : b+=((quint32)k8[4])<<24; - Q_FALLTHROUGH(); - case 4 : a+=k[0]; break; - case 3 : a+=((quint32)k8[2])<<8; - Q_FALLTHROUGH(); - case 2 : a+=((quint32)k8[1])<<16; - Q_FALLTHROUGH(); - 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]; - Q_FALLTHROUGH(); - case 11: c+=((quint32)k[10])<<8; - Q_FALLTHROUGH(); - case 10: c+=((quint32)k[9])<<16; - Q_FALLTHROUGH(); - case 9 : c+=((quint32)k[8])<<24; - Q_FALLTHROUGH(); - case 8 : b+=k[7]; - Q_FALLTHROUGH(); - case 7 : b+=((quint32)k[6])<<8; - Q_FALLTHROUGH(); - case 6 : b+=((quint32)k[5])<<16; - Q_FALLTHROUGH(); - case 5 : b+=((quint32)k[4])<<24; - Q_FALLTHROUGH(); - case 4 : a+=k[3]; - Q_FALLTHROUGH(); - case 3 : a+=((quint32)k[2])<<8; - Q_FALLTHROUGH(); - case 2 : a+=((quint32)k[1])<<16; - Q_FALLTHROUGH(); - 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 deleted file mode 100644 index 3587cd01ea..0000000000 --- a/tests/baselineserver/shared/qbaselinetest.cpp +++ /dev/null @@ -1,423 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "qbaselinetest.h" -#include "baselineprotocol.h" -#include <QtCore/QDir> -#include <QFile> - -#define MAXCMDLINEARGS 128 - -namespace QBaselineTest { - -static char *fargv[MAXCMDLINEARGS]; -static bool simfail = false; -static PlatformInfo customInfo; -static bool customAutoModeSet = false; - -static BaselineProtocol proto; -static bool connected = false; -static bool triedConnecting = false; -static bool dryRunMode = false; -static enum { UploadMissing, UploadAll, UploadNone } baselinePolicy = UploadMissing; - -static QByteArray curFunction; -static ImageItemList itemList; -static bool gotBaselines; - -static QString definedTestProject; -static QString definedTestCase; - - -void handleCmdLineArgs(int *argcp, char ***argvp) -{ - if (!argcp || !argvp) - return; - - bool showHelp = false; - - int fargc = 0; - int numArgs = *argcp; - - for (int i = 0; i < numArgs; i++) { - QByteArray arg = (*argvp)[i]; - QByteArray nextArg = (i+1 < numArgs) ? (*argvp)[i+1] : 0; - - if (arg == "-simfail") { - simfail = true; - } else if (arg == "-fuzzlevel") { - i++; - bool ok = false; - (void)nextArg.toInt(&ok); - if (!ok) { - qWarning() << "-fuzzlevel requires integer parameter"; - showHelp = true; - break; - } - customInfo.insert("FuzzLevel", QString::fromLatin1(nextArg)); - } else if (arg == "-auto") { - customAutoModeSet = true; - customInfo.setAdHocRun(false); - } else if (arg == "-adhoc") { - customAutoModeSet = true; - customInfo.setAdHocRun(true); - } else if (arg == "-setbaselines") { - baselinePolicy = UploadAll; - } else if (arg == "-nosetbaselines") { - baselinePolicy = UploadNone; - } else if (arg == "-compareto") { - i++; - int split = qMax(0, nextArg.indexOf('=')); - QByteArray key = nextArg.left(split).trimmed(); - QByteArray value = nextArg.mid(split+1).trimmed(); - if (key.isEmpty() || value.isEmpty()) { - qWarning() << "-compareto requires parameter of the form <key>=<value>"; - showHelp = true; - break; - } - customInfo.addOverride(key, value); - } else { - if ( (arg == "-help") || (arg == "--help") ) - showHelp = true; - if (fargc >= MAXCMDLINEARGS) { - qWarning() << "Too many command line arguments!"; - break; - } - fargv[fargc++] = (*argvp)[i]; - } - } - *argcp = fargc; - *argvp = fargv; - - if (showHelp) { - // TBD: arrange for this to be printed *after* QTest's help - QTextStream out(stdout); - out << "\n Baseline testing (lancelot) options:\n"; - out << " -simfail : Force an image comparison mismatch. For testing purposes.\n"; - out << " -fuzzlevel <int> : Specify the percentage of fuzziness in comparison. Overrides server default. 0 means exact match.\n"; - out << " -auto : Inform server that this run is done by a daemon, CI system or similar.\n"; - out << " -adhoc (default) : The inverse of -auto; this run is done by human, e.g. for testing.\n"; - out << " -setbaselines : Store ALL rendered images as new baselines. Forces replacement of previous baselines.\n"; - out << " -nosetbaselines : Do not store rendered images as new baselines when previous baselines are missing.\n"; - out << " -compareto KEY=VAL : Force comparison to baselines from a different client,\n"; - out << " for example: -compareto QtVersion=4.8.0\n"; - out << " Multiple -compareto client specifications may be given.\n"; - out << "\n"; - } -} - - -void addClientProperty(const QString& key, const QString& value) -{ - customInfo.insert(key, value); -} - - -/* - If a client property script is present, run it and accept its output - in the form of one 'key: value' property per line -*/ -void fetchCustomClientProperties() -{ - QFile file("hostinfo.txt"); - if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) - return; - QTextStream in(&file); - - while (!in.atEnd()) { - QString line = in.readLine().trimmed(); // ###local8bit? utf8? - if (line.startsWith(QLatin1Char('#'))) // Ignore comments in file - continue; - QString key, val; - int colonPos = line.indexOf(':'); - if (colonPos > 0) { - key = line.left(colonPos).simplified().replace(' ', '_'); - val = line.mid(colonPos+1).trimmed(); - } - if (!key.isEmpty() && key.length() < 64 && val.length() < 256) // ###TBD: maximum 256 chars in value? - addClientProperty(key, val); - else - qDebug() << "Unparseable script output ignored:" << line; - } -} - - -bool connect(QByteArray *msg, bool *error) -{ - if (connected) { - return true; - } - else if (triedConnecting) { - // Avoid repeated connection attempts, to avoid the program using Timeout * #testItems seconds before giving up - *msg = "Not connected to baseline server."; - *error = true; - return false; - } - - triedConnecting = true; - fetchCustomClientProperties(); - // Merge the platform info set by the program with the protocols default info - PlatformInfo clientInfo = customInfo; - PlatformInfo defaultInfo = PlatformInfo::localHostInfo(); - foreach (QString key, defaultInfo.keys()) { - if (!clientInfo.contains(key)) - clientInfo.insert(key, defaultInfo.value(key)); - } - if (!customAutoModeSet) - clientInfo.setAdHocRun(defaultInfo.isAdHocRun()); - - if (!definedTestProject.isEmpty()) - clientInfo.insert(PI_Project, definedTestProject); - - QString testCase = definedTestCase; - if (testCase.isEmpty() && QTest::testObject() && QTest::testObject()->metaObject()) { - //qDebug() << "Trying to Read TestCaseName from Testlib!"; - testCase = QTest::testObject()->metaObject()->className(); - } - if (testCase.isEmpty()) { - qWarning("QBaselineTest::connect: No test case name specified, cannot connect."); - return false; - } - - if (!proto.connect(testCase, &dryRunMode, clientInfo)) { - *msg += "Failed to connect to baseline server: " + proto.errorMessage().toLatin1(); - *error = true; - return false; - } - connected = true; - return true; -} - -bool disconnectFromBaselineServer() -{ - if (proto.disconnect()) { - connected = false; - triedConnecting = false; - return true; - } - - return false; -} - -bool connectToBaselineServer(QByteArray *msg, const QString &testProject, const QString &testCase) -{ - bool dummy; - QByteArray dummyMsg; - - definedTestProject = testProject; - definedTestCase = testCase; - - return connect(msg ? msg : &dummyMsg, &dummy); -} - -void setAutoMode(bool mode) -{ - customInfo.setAdHocRun(!mode); - customAutoModeSet = true; -} - -void setSimFail(bool fail) -{ - simfail = fail; -} - - -void modifyImage(QImage *img) -{ - uint c0 = 0x0000ff00; - uint c1 = 0x0080ff00; - img->setPixel(1,1,c0); - img->setPixel(2,1,c1); - img->setPixel(3,1,c0); - img->setPixel(1,2,c1); - img->setPixel(1,3,c0); - img->setPixel(2,3,c1); - img->setPixel(3,3,c0); - img->setPixel(1,4,c1); - img->setPixel(1,5,c0); -} - - -bool compareItem(const ImageItem &baseline, const QImage &img, QByteArray *msg, bool *error) -{ - ImageItem item = baseline; - if (simfail) { - // Simulate test failure by forcing image mismatch; for testing purposes - QImage misImg = img; - modifyImage(&misImg); - item.image = misImg; - simfail = false; // One failure is typically enough - } else { - item.image = img; - } - item.imageChecksums.clear(); - item.imageChecksums.prepend(ImageItem::computeChecksum(item.image)); - 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 (!customInfo.overrides().isEmpty() || baselinePolicy == UploadNone) { - qWarning() << "Cannot compare to baseline: No such baseline found on server."; - return true; - } - 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))) { - if (!proto.submitMatch(item, &srvMsg)) - qWarning() << "Failed to report image match to server:" << srvMsg; - return true; - } - // At this point, we have established a legitimate mismatch - if (baselinePolicy == UploadAll) { - if (proto.submitNewBaseline(item, &srvMsg)) - qDebug() << msg->constData() << "Forcing new baseline; uploaded ok."; - else - qDebug() << msg->constData() << "Forcing new baseline; uploading failed:" << srvMsg; - return true; - } - bool fuzzyMatch = false; - bool res = proto.submitMismatch(item, &srvMsg, &fuzzyMatch); - if (res && fuzzyMatch) { - *error = true; // To force a QSKIP/debug output; somewhat kludgy - *msg += srvMsg; - return true; // The server decides: a fuzzy match means no mismatch - } - *msg += "Mismatch. See report:\n " + srvMsg; - if (dryRunMode) { - qDebug() << "Dryrun, so ignoring" << *msg; - return true; - } - return false; -} - -bool checkImage(const QImage &img, const char *name, quint16 checksum, QByteArray *msg, bool *error, int manualdatatag) -{ - 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"; - } - - if (manualdatatag > 0) - { - itemName.prepend("_"); - itemName.prepend(QByteArray::number(manualdatatag)); - } - - *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 deleted file mode 100644 index ede0fe42e3..0000000000 --- a/tests/baselineserver/shared/qbaselinetest.h +++ /dev/null @@ -1,70 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#ifndef BASELINETEST_H -#define BASELINETEST_H - -#include <QTest> - -namespace QBaselineTest { -void setAutoMode(bool mode); -void setSimFail(bool fail); -void handleCmdLineArgs(int *argcp, char ***argvp); -void addClientProperty(const QString& key, const QString& value); -bool connectToBaselineServer(QByteArray *msg = nullptr, const QString &testProject = QString(), const QString &testCase = QString()); -bool checkImage(const QImage& img, const char *name, quint16 checksum, QByteArray *msg, bool *error, int manualdatatag = 0); -bool testImage(const QImage& img, QByteArray *msg, bool *error); -QTestData &newRow(const char *dataTag, quint16 checksum = 0); -bool disconnectFromBaselineServer(); -} - -#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());\ - }\ -} 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());\ - }\ -} while (0) - -#endif // BASELINETEST_H diff --git a/tests/baselineserver/shared/qbaselinetest.pri b/tests/baselineserver/shared/qbaselinetest.pri deleted file mode 100644 index 921871b189..0000000000 --- a/tests/baselineserver/shared/qbaselinetest.pri +++ /dev/null @@ -1,13 +0,0 @@ -QT *= testlib - -SOURCES += \ - $$PWD/qbaselinetest.cpp - -HEADERS += \ - $$PWD/qbaselinetest.h - -win32: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 deleted file mode 100644 index c6e12dc09c..0000000000 --- a/tests/baselineserver/src/baselineserver.cpp +++ /dev/null @@ -1,853 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#define QT_USE_FAST_CONCATENATION -#define QT_USE_FAST_OPERATOR_PLUS - -#include "baselineserver.h" -#include <QBuffer> -#include <QFile> -#include <QDir> -#include <QCoreApplication> -#include <QFileInfo> -#include <QHostInfo> -#include <QTextStream> -#include <QProcess> -#include <QDirIterator> -#include <QUrl> -#include <QRegularExpression> - -// 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; -QStringList BaselineServer::pathKeys; - -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; -} - -QStringList BaselineServer::defaultPathKeys() -{ - if (pathKeys.isEmpty()) - pathKeys << PI_QtVersion << PI_QMakeSpec << PI_HostName; - return pathKeys; -} - -void BaselineServer::incomingConnection(qintptr socketDescriptor) -{ - QString runId = QDateTime::currentDateTime().toString(QLS("MMMdd-hhmmss")); - if (runId == lastRunId) { - runId += QLC('-') + QString::number(++lastRunIdIdx); - } else { - lastRunId = runId; - lastRunIdIdx = 0; - } - qDebug() << "Server: New connection! RunId:" << runId; - BaselineThread *thread = new BaselineThread(runId, socketDescriptor, this); - connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); - thread->start(); -} - -void BaselineServer::heartbeat() -{ - // The idea is to exit to be restarted when modified, as soon as not actually serving - QFileInfo me(QCoreApplication::applicationFilePath()); - if (me.lastModified() == meLastMod) - return; - if (!me.exists() || !me.isExecutable()) - return; - - //# (could close() here to avoid accepting new connections, to avoid livelock) - //# also, could check for a timeout to force exit, to avoid hung threads blocking - bool isServing = false; - foreach(BaselineThread *thread, findChildren<BaselineThread *>()) { - if (thread->isRunning()) { - isServing = true; - break; - } - } - - if (!isServing) - QCoreApplication::exit(); -} - -BaselineThread::BaselineThread(const QString &runId, int socketDescriptor, QObject *parent) - : QThread(parent), runId(runId), socketDescriptor(socketDescriptor) -{ -} - -void BaselineThread::run() -{ - BaselineHandler handler(runId, socketDescriptor); - exec(); -} - - -BaselineHandler::BaselineHandler(const QString &runId, int socketDescriptor) - : QObject(), runId(runId), connectionEstablished(false), settings(0), fuzzLevel(0) -{ - idleTimer = new QTimer(this); - idleTimer->setSingleShot(true); - idleTimer->setInterval(IDLE_CLIENT_TIMEOUT * 1000); - connect(idleTimer, SIGNAL(timeout()), this, SLOT(idleClientTimeout())); - idleTimer->start(); - - if (socketDescriptor == -1) - return; - - connect(&proto.socket, SIGNAL(readyRead()), this, SLOT(receiveRequest())); - connect(&proto.socket, SIGNAL(disconnected()), this, SLOT(receiveDisconnect())); - proto.socket.setSocketDescriptor(socketDescriptor); - proto.socket.setSocketOption(QAbstractSocket::KeepAliveOption, 1); -} - -const char *BaselineHandler::logtime() -{ - return 0; - //return QTime::currentTime().toString(QLS("mm:ss.zzz")); -} - -QString BaselineHandler::projectPath(bool absolute) const -{ - QString p = clientInfo.value(PI_Project); - return absolute ? BaselineServer::storagePath() + QLC('/') + p : p; -} - -bool BaselineHandler::checkClient(QByteArray *errMsg, bool *dryRunMode) -{ - if (!errMsg) - return false; - if (clientInfo.value(PI_Project).isEmpty() || clientInfo.value(PI_TestCase).isEmpty()) { - *errMsg = "No Project and/or TestCase specified in client info."; - return false; - } - - // Determine ad-hoc state ### hardcoded for now - if (clientInfo.value(PI_TestCase) == QLS("tst_Lancelot")) { - //### Todo: push this stuff out in a script - if (!clientInfo.isAdHocRun()) { - // ### comp. with earlier versions still running (4.8) (?) - clientInfo.setAdHocRun(clientInfo.value(PI_PulseGitBranch).isEmpty() && clientInfo.value(PI_PulseTestrBranch).isEmpty()); - } - } - else { - // TBD - } - - if (clientInfo.isAdHocRun()) { - if (dryRunMode) - *dryRunMode = false; - return true; - } - - // Not ad hoc: filter the client - settings->beginGroup("ClientFilters"); - bool matched = false; - bool dryRunReq = false; - foreach (const QString &rule, settings->childKeys()) { - //qDebug() << " > RULE" << rule; - dryRunReq = false; - QString ruleMode = settings->value(rule).toString().toLower(); - if (ruleMode == QLS("dryrun")) - dryRunReq = true; - else if (ruleMode != QLS("enabled")) - continue; - settings->beginGroup(rule); - bool ruleMatched = true; - foreach (const QString &filterKey, settings->childKeys()) { - //qDebug() << " > FILTER" << filterKey; - QString filter = settings->value(filterKey).toString(); - if (filter.isEmpty()) - continue; - QString platVal = clientInfo.value(filterKey); - if (!platVal.contains(filter)) { - ruleMatched = false; - break; - } - } - if (ruleMatched) { - ruleName = rule; - matched = true; - break; - } - settings->endGroup(); - } - - if (!matched && errMsg) - *errMsg = "Non-adhoc client did not match any filter rule in " + settings->fileName().toLatin1(); - - if (matched && dryRunMode) - *dryRunMode = dryRunReq; - - // NB! Must reset the settings object before returning - while (!settings->group().isEmpty()) - settings->endGroup(); - - return matched; -} - -bool BaselineHandler::establishConnection() -{ - if (!proto.acceptConnection(&clientInfo)) { - 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, clientInfo.keys()) { - if (key != PI_HostName && key != PI_HostAddress) - logMsg += key + QLS(": '") + clientInfo.value(key) + QLS("', "); - } - qDebug() << runId << logtime() << "Connection established with" << clientInfo.value(PI_HostName) - << '[' << qPrintable(clientInfo.value(PI_HostAddress)) << ']' << logMsg - << "Overrides:" << clientInfo.overrides() << "AdHoc-Run:" << clientInfo.isAdHocRun(); - - // ### Hardcoded backwards compatibility: add project field for certain existing clients that lack it - if (clientInfo.value(PI_Project).isEmpty()) { - QString tc = clientInfo.value(PI_TestCase); - if (tc == QLS("tst_Lancelot")) - clientInfo.insert(PI_Project, QLS("Raster")); - else if (tc == QLS("tst_Scenegraph")) - clientInfo.insert(PI_Project, QLS("SceneGraph")); - else - clientInfo.insert(PI_Project, QLS("Other")); - } - - QString settingsFile = projectPath() + QLS("/config.ini"); - settings = new QSettings(settingsFile, QSettings::IniFormat, this); - - QByteArray errMsg; - bool dryRunMode = false; - if (!checkClient(&errMsg, &dryRunMode)) { - qDebug() << runId << logtime() << "Rejecting connection:" << errMsg; - proto.sendBlock(BaselineProtocol::Abort, errMsg); - proto.socket.disconnectFromHost(); - return false; - } - - fuzzLevel = qBound(0, settings->value("FuzzLevel").toInt(), 100); - if (!clientInfo.isAdHocRun()) { - qDebug() << runId << logtime() << "Client matches filter rule" << ruleName - << "Dryrun:" << dryRunMode - << "FuzzLevel:" << fuzzLevel - << "ReportMissingResults:" << settings->value("ReportMissingResults").toBool(); - } - - proto.sendBlock(dryRunMode ? BaselineProtocol::DoDryRun : BaselineProtocol::Ack, QByteArray()); - report.init(this, runId, clientInfo, settings); - return true; -} - -void BaselineHandler::receiveRequest() -{ - idleTimer->start(); // Restart idle client timeout - - 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::AcceptMatch: - recordMatch(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::recordMatch(const QByteArray &itemBlock) -{ - QDataStream ds(itemBlock); - ImageItem item; - ds >> item; - report.addResult(item); - proto.sendBlock(BaselineProtocol::Ack, QByteArray()); -} - - -void BaselineHandler::storeImage(const QByteArray &itemBlock, bool isBaseline) -{ - QDataStream ds(itemBlock); - ImageItem item; - ds >> item; - - if (isBaseline && !clientInfo.overrides().isEmpty()) { - qDebug() << runId << logtime() << "Received baseline from client with override info, ignoring. Item:" << item.itemName; - proto.sendBlock(BaselineProtocol::UnknownError, "New baselines not accepted from client with override info."); - return; - } - - QString blPrefix = pathForItem(item, true); - QString mmPrefix = pathForItem(item, false); - QString prefix = isBaseline ? blPrefix : mmPrefix; - - qDebug() << runId << logtime() << "Received" << (isBaseline ? "baseline" : "mismatched") << "image for:" << item.itemName << "Storing in" << prefix; - - // Reply to the client - QString msg; - if (isBaseline) - msg = QLS("New baseline image stored: ") + blPrefix + QLS(FileFormat); - else - msg = BaselineServer::baseUrl() + report.filePath(); - - if (isBaseline || !fuzzLevel) - proto.sendBlock(BaselineProtocol::Ack, msg.toLatin1()); // Do early reply if possible: don't make the client wait longer than necessary - - // Store the image - 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 = clientInfo; - 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) { - // Do fuzzy matching - bool fuzzyMatch = false; - if (fuzzLevel) { - BaselineProtocol::Command cmd = BaselineProtocol::Ack; - fuzzyMatch = fuzzyCompare(blPrefix, mmPrefix); - if (fuzzyMatch) { - msg.prepend(QString("Fuzzy match at fuzzlevel %1%. Report: ").arg(fuzzLevel)); - cmd = BaselineProtocol::FuzzyMatch; - } - proto.sendBlock(cmd, msg.toLatin1()); // We didn't reply earlier - } - - // Add to report - item.status = fuzzyMatch ? ImageItem::FuzzyMatch : ImageItem::Mismatch; - report.addResult(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) || !QFile::exists(path + QLS(FileFormat))) - 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::idleClientTimeout() -{ - qWarning() << runId << logtime() << "Idle client timeout: no request received for" << IDLE_CLIENT_TIMEOUT << "seconds, terminating connection."; - proto.socket.disconnectFromHost(); -} - - -void BaselineHandler::receiveDisconnect() -{ - qDebug() << runId << logtime() << "Client disconnected."; - report.end(); - if (report.reportProduced() && !clientInfo.isAdHocRun()) - issueMismatchNotification(); - if (settings && settings->value("ProcessXmlResults").toBool() && !clientInfo.isAdHocRun()) { - // ### TBD: actually execute the processing command. For now, just generate the xml files. - QString xmlDir = report.writeResultsXmlFiles(); - } - QThread::currentThread()->exit(0); -} - - -PlatformInfo BaselineHandler::mapPlatformInfo(const PlatformInfo& orig) const -{ - PlatformInfo mapped; - foreach (const QString &key, orig.uniqueKeys()) { - QString val = orig.value(key).simplified(); - val.replace(QLC('/'), QLC('_')); - val.replace(QLC(' '), QLC('_')); - mapped.insert(key, QUrl::toPercentEncoding(val, "+")); - //qDebug() << "MAPPED" << key << "FROM" << orig.value(key) << "TO" << mapped.value(key); - } - - // Special fixup for OS version - if (mapped.value(PI_OSName) == QLS("MacOS")) { - int ver = mapped.value(PI_OSVersion).toInt(); - if (ver > 1) - mapped.insert(PI_OSVersion, QString("MV_10_%1").arg(ver-2)); - } - else if (mapped.value(PI_OSName) == QLS("Windows")) { - // TBD: map windows version numbers to names - } - - // Special fixup for hostname - QString host = mapped.value(PI_HostName).section(QLC('.'), 0, 0); // Filter away domain, if any - if (host.isEmpty() || host == QLS("localhost")) { - host = orig.value(PI_HostAddress); - } else { - if (!orig.isAdHocRun()) { // i.e. CI system run, so remove index postfix typical of vm hostnames - host.remove(QRegularExpression(QLS("\\d+$"))); - if (host.endsWith(QLC('-'))) - host.chop(1); - } - } - if (host.isEmpty()) - host = QLS("UNKNOWN-HOST"); - if (mapped.value(PI_OSName) == QLS("MacOS")) // handle multiple os versions on same host - host += QLC('-') + mapped.value(PI_OSVersion); - mapped.insert(PI_HostName, host); - - // Special fixup for Qt version - QString ver = mapped.value(PI_QtVersion); - if (!ver.isEmpty()) - mapped.insert(PI_QtVersion, ver.prepend(QLS("Qt-"))); - - return mapped; -} - - -QString BaselineHandler::pathForItem(const ImageItem &item, bool isBaseline, bool absolute) const -{ - if (mappedClientInfo.isEmpty()) { - mappedClientInfo = mapPlatformInfo(clientInfo); - PlatformInfo oraw = clientInfo; - // ### simplify: don't map if no overrides! - for (int i = 0; i < clientInfo.overrides().size()-1; i+=2) - oraw.insert(clientInfo.overrides().at(i), clientInfo.overrides().at(i+1)); - overriddenMappedClientInfo = mapPlatformInfo(oraw); - } - - const PlatformInfo& mapped = isBaseline ? overriddenMappedClientInfo : mappedClientInfo; - - QString itemName = safeName(item.itemName); - itemName.append(QLC('_') + QString::number(item.itemChecksum, 16).rightJustified(4, QLC('0'))); - - QStringList path; - path += projectPath(absolute); - path += mapped.value(PI_TestCase); - path += QLS(isBaseline ? "baselines" : "mismatches"); - path += item.testFunction; - QStringList itemPathKeys; - if (settings) - itemPathKeys = settings->value("ItemPathKeys").toStringList(); - if (itemPathKeys.isEmpty()) - itemPathKeys = BaselineServer::defaultPathKeys(); - foreach (const QString &key, itemPathKeys) - path += mapped.value(key, QLS("UNSET-")+key); - 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, diffstats(baseline, rendered)); -} - -QString BaselineHandler::diffstats(const QString &baseline, const QString &rendered) -{ - QImage blImg(BaselineServer::storagePath() + QLC('/') + baseline); - QImage mmImg(BaselineServer::storagePath() + QLC('/') + rendered); - if (blImg.isNull() || mmImg.isNull()) - return QLS("Could not compute diffstats: image loading failed."); - - // ### TBD: cache the results - return computeMismatchScore(blImg, mmImg); -} - -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) - << QLS("*.") + QLS(ThumbnailExt)); - while (it.hasNext()) { - bool counting = !it.next().endsWith(QLS(ThumbnailExt)); - if (counting) - tot++; - if (!QFile::remove(it.filePath()) && counting) - 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) - << filter + QLS(ThumbnailExt)); - while (it.hasNext()) { - bool counting = !it.next().endsWith(QLS(ThumbnailExt)); - if (counting) - tot++; - QString oldFile = storagePrefix + context + QLC('/') + it.fileName(); - QFile::remove(oldFile); // Remove existing baseline file - if (!QFile::copy(it.filePath(), oldFile) && counting) // 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-project.org") - << QLS("osl-mac-master-6.test.qt-project.org") - << 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; - - clientInfo.insert(PI_QtVersion, QLS("5.0.0")); - clientInfo.insert(PI_QMakeSpec, QLS("linux-g++")); - clientInfo.insert(PI_PulseGitBranch, QLS("somebranch")); - clientInfo.setAdHocRun(false); - foreach(const QString& host, hosts) { - mappedClientInfo.clear(); - clientInfo.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 diffstats, incomparable images.]"); - if (baseline.depth() != 32) - return QLS("[Diffstats 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 - uint mind = 0; // minimum difference - uint maxd = 0; // maximum difference - - for (int y=0; y<h; ++y) { - const QRgb *bl = (const QRgb *) baseline.constScanLine(y); - const QRgb *rl = (const QRgb *) rendered.constScanLine(y); - for (int x=0; x<w; ++x) { - QRgb b = bl[x]; - QRgb r = rl[x]; - if (r != b) { - uint dr = qAbs(qRed(b) - qRed(r)); - uint dg = qAbs(qGreen(b) - qGreen(r)); - uint db = qAbs(qBlue(b) - qBlue(r)); - uint ds = (dr + dg + db) / 3; - uint da = qAbs(qAlpha(b) - qAlpha(r)); - if (ds) { - ncd++; - scd += ds; - if (!mind || ds < mind) - mind = ds; - if (ds > maxd) - maxd = ds; - } - if (da) { - nad++; - sad += da; - } - } - } - } - - - double pcd = 100.0 * ncd / (w*h); // percent of pixels that differ - double acd = ncd ? double(scd) / (ncd) : 0; // avg. difference -/* - if (baseline.hasAlphaChannel()) { - double pad = 100.0 * nad / (w*h); // percent of pixels that differ - double aad = nad ? double(sad) / (3*nad) : 0; // avg. difference - } -*/ - QString res = "<table>\n"; - QString item = "<tr><td>%1</td><td align=right>%2</td></tr>\n"; - res += item.arg("Number of mismatching pixels").arg(ncd); - res += item.arg("Percentage mismatching pixels").arg(pcd, 0, 'g', 2); - res += item.arg("Minimum pixel distance").arg(mind); - res += item.arg("Maximum pixel distance").arg(maxd); - if (acd >= 10.0) - res += item.arg("Average pixel distance").arg(qRound(acd)); - else - res += item.arg("Average pixel distance").arg(acd, 0, 'g', 2); - - if (baseline.hasAlphaChannel()) - res += item.arg("Number of mismatching alpha values").arg(nad); - - res += "</table>\n"; - res += "<p>(Distances are normalized to the range 0-255)</p>\n"; - return res; -} - - -bool BaselineHandler::fuzzyCompare(const QString &baselinePath, const QString &mismatchPath) -{ - QProcess compareProc; - QStringList args; - args << "-fuzz" << QString("%1%").arg(fuzzLevel) << "-metric" << "AE"; - args << baselinePath + QLS(FileFormat) << mismatchPath + QLS(FileFormat) << "/dev/null"; // TBD: Should save output image, so report won't have to regenerate it - - compareProc.setProcessChannelMode(QProcess::MergedChannels); - compareProc.start("compare", args, QIODevice::ReadOnly); - if (compareProc.waitForFinished(3000) && compareProc.error() == QProcess::UnknownError) { - bool ok = false; - int metric = compareProc.readAll().trimmed().toInt(&ok); - if (ok && metric == 0) - return true; - } - return false; -} - - -void BaselineHandler::issueMismatchNotification() -{ - // KISS: hardcoded use of the "sendemail" utility. Make this configurable if and when demand arises. - if (!settings) - return; - - settings->beginGroup("Notification"); - QStringList receivers = settings->value("Receivers").toStringList(); - QString sender = settings->value("Sender").toString(); - QString server = settings->value("SMTPserver").toString(); - settings->endGroup(); - if (receivers.isEmpty() || sender.isEmpty() || server.isEmpty()) - return; - - QString msg = QString("\nResult summary for test run %1:\n").arg(runId); - msg += report.summary(); - msg += "\nReport: " + BaselineServer::baseUrl() + report.filePath() + "\n"; - - msg += "\nTest run platform properties:\n------------------\n"; - foreach (const QString &key, clientInfo.keys()) - msg += key + ": " + clientInfo.value(key) + '\n'; - msg += "\nCheers,\n- Your friendly Lancelot Baseline Server\n"; - - QProcess proc; - QString cmd = "sendemail"; - QStringList args; - args << "-s" << server << "-f" << sender << "-t" << receivers; - args << "-u" << "[Lancelot] Mismatch report for project " + clientInfo.value(PI_Project) + ", test case " + clientInfo.value(PI_TestCase); - args << "-m" << msg; - - //proc.setProcessChannelMode(QProcess::MergedChannels); - proc.start(cmd, args); - if (!proc.waitForFinished(10 * 1000) || (proc.exitStatus() != QProcess::NormalExit) || proc.exitCode()) { - qWarning() << "FAILED to issue notification. Command:" << cmd << args.mid(0, args.size()-2); - qWarning() << " Command standard output:" << proc.readAllStandardOutput(); - qWarning() << " Command error output:" << proc.readAllStandardError(); - } -} - - -// Make an identifer safer for use as filename and URL -QString safeName(const QString& name) -{ - QString res = name.simplified(); - res.replace(QLC(' '), QLC('_')); - res.replace(QLC('.'), QLC('_')); - res.replace(QLC('/'), QLC('^')); - return res; -} diff --git a/tests/baselineserver/src/baselineserver.h b/tests/baselineserver/src/baselineserver.h deleted file mode 100644 index 25ef17f023..0000000000 --- a/tests/baselineserver/src/baselineserver.h +++ /dev/null @@ -1,151 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ -#ifndef BASELINESERVER_H -#define BASELINESERVER_H - -#include <QStringList> -#include <QTcpServer> -#include <QThread> -#include <QTcpSocket> -#include <QScopedPointer> -#include <QTimer> -#include <QDateTime> -#include <QSettings> - -#include "baselineprotocol.h" -#include "report.h" - -// #seconds between checks for update of the executable -#define HEARTBEAT 10 -// Timeout if no activity received from client, #seconds -#define IDLE_CLIENT_TIMEOUT 3*60 - -#define MetadataFileExt "metadata" -#define ThumbnailExt "thumbnail.jpg" - - -class BaselineServer : public QTcpServer -{ - Q_OBJECT - -public: - BaselineServer(QObject *parent = nullptr); - - static QString storagePath(); - static QString baseUrl(); - static QStringList defaultPathKeys(); - -protected: - void incomingConnection(qintptr socketDescriptor); - -private slots: - void heartbeat(); - -private: - QTimer *heartbeatTimer; - QDateTime meLastMod; - QString lastRunId; - int lastRunIdIdx; - static QString storage; - static QString url; - static QStringList pathKeys; -}; - - - -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); - QString projectPath(bool absolute = true) const; - 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 diffstats(const QString &baseline, const QString &rendered); - 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); - - // for debugging - void testPathMapping(); - -private slots: - void receiveRequest(); - void receiveDisconnect(); - void idleClientTimeout(); - -private: - bool checkClient(QByteArray *errMsg, bool *dryRunMode = nullptr); - bool establishConnection(); - void provideBaselineChecksums(const QByteArray &itemListBlock); - void recordMatch(const QByteArray &itemBlock); - void storeImage(const QByteArray &itemBlock, bool isBaseline); - void storeItemMetadata(const PlatformInfo &metadata, const QString &path); - PlatformInfo fetchItemMetadata(const QString &path); - PlatformInfo mapPlatformInfo(const PlatformInfo& orig) const; - const char *logtime(); - void issueMismatchNotification(); - bool fuzzyCompare(const QString& baselinePath, const QString& mismatchPath); - - static QString computeMismatchScore(const QImage& baseline, const QImage& rendered); - - BaselineProtocol proto; - PlatformInfo clientInfo; - mutable PlatformInfo mappedClientInfo; - mutable PlatformInfo overriddenMappedClientInfo; - QString runId; - bool connectionEstablished; - Report report; - QSettings *settings; - QString ruleName; - int fuzzLevel; - QTimer *idleTimer; -}; - - -// Make an identifer safer for use as filename and URL -QString safeName(const QString& name); - -#endif // BASELINESERVER_H diff --git a/tests/baselineserver/src/baselineserver.pro b/tests/baselineserver/src/baselineserver.pro deleted file mode 100644 index 2d8438cb51..0000000000 --- a/tests/baselineserver/src/baselineserver.pro +++ /dev/null @@ -1,24 +0,0 @@ -QT += core network - -# gui needed for QImage -# QT -= gui - -TARGET = baselineserver -DESTDIR = ../bin -CONFIG += cmdline - -TEMPLATE = app - -include(../shared/baselineprotocol.pri) - -SOURCES += main.cpp \ - baselineserver.cpp \ - report.cpp - -HEADERS += \ - baselineserver.h \ - report.h - -RESOURCES += \ - baselineserver.qrc -DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0 diff --git a/tests/baselineserver/src/baselineserver.qrc b/tests/baselineserver/src/baselineserver.qrc deleted file mode 100644 index b5cd6afadb..0000000000 --- a/tests/baselineserver/src/baselineserver.qrc +++ /dev/null @@ -1,5 +0,0 @@ -<RCC> - <qresource prefix="/"> - <file>templates/view.html</file> - </qresource> -</RCC> diff --git a/tests/baselineserver/src/main.cpp b/tests/baselineserver/src/main.cpp deleted file mode 100644 index dfc9b83da8..0000000000 --- a/tests/baselineserver/src/main.cpp +++ /dev/null @@ -1,57 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ -#include <QtCore/QCoreApplication> -#include "baselineserver.h" - -int main(int argc, char *argv[]) -{ - QCoreApplication a(argc, argv); - - QString queryString(qgetenv("QUERY_STRING")); - if (!queryString.isEmpty()) { - // run as CGI script - Report::handleCGIQuery(queryString); - return 0; - } - - if (a.arguments().contains(QLatin1String("-testmapping"))) { - BaselineHandler h(QLS("SomeRunId")); - h.testPathMapping(); - return 0; - } - - BaselineServer server; - if (!server.listen(QHostAddress::Any, BaselineProtocol::ServerPort)) { - qWarning("Failed to listen!"); - return 1; - } - - qDebug() << "\n*****" << argv[0] << "started, ready to serve on port" << BaselineProtocol::ServerPort - << "with baseline protocol version" << BaselineProtocol::ProtocolVersion << "*****\n"; - return a.exec(); -} diff --git a/tests/baselineserver/src/report.cpp b/tests/baselineserver/src/report.cpp deleted file mode 100644 index 748d76ebfe..0000000000 --- a/tests/baselineserver/src/report.cpp +++ /dev/null @@ -1,503 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ -#include "report.h" -#include "baselineprotocol.h" -#include "baselineserver.h" -#include <QDir> -#include <QProcess> -#include <QUrl> -#include <QXmlStreamWriter> -#include <QRegularExpression> -#include <unistd.h> - -Report::Report() - : initialized(false), handler(0), written(false), numItems(0), numMismatches(0), settings(0), - hasStats(false) -{ -} - -Report::~Report() -{ - end(); -} - -QString Report::filePath() -{ - return path; -} - -int Report::numberOfMismatches() -{ - return numMismatches; -} - -bool Report::reportProduced() -{ - return written; -} - -void Report::init(const BaselineHandler *h, const QString &r, const PlatformInfo &p, const QSettings *s) -{ - handler = h; - runId = r; - plat = p; - settings = s; - rootDir = BaselineServer::storagePath() + QLC('/'); - baseDir = handler->pathForItem(ImageItem(), true, false).remove(QRegularExpression("/baselines/.*$")); - QString dir = baseDir + (plat.isAdHocRun() ? QLS("/adhoc-reports") : QLS("/auto-reports")); - QDir cwd; - if (!cwd.exists(rootDir + dir)) - cwd.mkpath(rootDir + dir); - path = dir + QLS("/Report_") + runId + QLS(".html"); - hasOverride = !plat.overrides().isEmpty(); - initialized = true; -} - -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); - ImageItemList list = items; - if (settings->value("ReportMissingResults").toBool()) { - for (ImageItemList::iterator it = list.begin(); it != list.end(); ++it) { - if (it->status == ImageItem::Ok) - it->status = ImageItem::Error; // Status should be set by report from client, else report as error - } - } - itemLists[func] += list; -} - -void Report::addResult(const ImageItem &item) -{ - if (!testFunctions.contains(item.testFunction)) { - qWarning() << "Report::addResult: 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 = item.status; - found = true; - break; - } - } - if (found) { - if (item.status == ImageItem::Mismatch) - numMismatches++; - } else { - qWarning() << "Report::addResult: unknown item" << item.itemName << "in testfunction" << item.testFunction; - } -} - -void Report::end() -{ - if (!initialized || written) - return; - // Make report iff (#mismatches>0) || (#fuzzymatches>0) || (#errors>0 && settings say report errors) - bool doReport = (numMismatches > 0); - if (!doReport) { - bool reportErrors = settings->value("ReportMissingResults").toBool(); - computeStats(); - foreach (const QString &func, itemLists.keys()) { - FuncStats stat = stats.value(func); - if (stat.value(ImageItem::FuzzyMatch) > 0) { - doReport = true; - break; - } - foreach (const ImageItem &item, itemLists.value(func)) { - if (reportErrors && item.status == ImageItem::Error) { - doReport = true; - break; - } - } - if (doReport) - break; - } - } - if (!doReport) - return; - write(); - written = true; -} - -void Report::computeStats() -{ - if (hasStats) - return; - foreach (const QString &func, itemLists.keys()) { - FuncStats funcStat; - funcStat[ImageItem::Ok] = 0; - funcStat[ImageItem::BaselineNotFound] = 0; - funcStat[ImageItem::IgnoreItem] = 0; - funcStat[ImageItem::Mismatch] = 0; - funcStat[ImageItem::FuzzyMatch] = 0; - funcStat[ImageItem::Error] = 0; - foreach (const ImageItem &item, itemLists.value(func)) { - funcStat[item.status]++; - } - stats[func] = funcStat; - } - hasStats = true; -} - -QString Report::summary() -{ - computeStats(); - QString res; - foreach (const QString &func, itemLists.keys()) { - FuncStats stat = stats.value(func); - QString s = QString("%1 %3 mismatch(es), %4 error(s), %5 fuzzy match(es)\n"); - s = s.arg(QString("%1() [%2 items]:").arg(func).arg(itemLists.value(func).size()).leftJustified(40)); - s = s.arg(stat.value(ImageItem::Mismatch)); - s = s.arg(stat.value(ImageItem::Error)); - s = s.arg(stat.value(ImageItem::FuzzyMatch)); - res += s; - } -#if 0 - qDebug() << "***************************** Summary *************************"; - qDebug() << res; - qDebug() << "***************************************************************"; -#endif - return res; -} - -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(); - updateLatestPointer(); -} - - -void Report::writeHeader() -{ - QString title = plat.value(PI_Project) + QLC(':') + plat.value(PI_TestCase) + QLS(" Lancelot Test Report"); - out << "<!DOCTYPE html>\n" - << "<html><head><title>" << title << "</title></head>\n" - << "<body bgcolor=""#ddeeff""><h1>" << title << "</h1>\n" - << "<p>Note: This is a <i>static</i> page, generated at " << QDateTime::currentDateTime().toString() - << " for the test run with id " << runId << "</p>\n" - << "<p>Summary: <b><span style=\"color:red\">" << numMismatches << " of " << numItems << "</span></b> items reported mismatching</p>\n"; - out << "<pre>\n" << summary() << "</pre>\n\n"; - out << "<h3>Testing Client Platform Info:</h3>\n" - << "<table>\n"; - foreach (QString key, plat.keys()) - out << "<tr><td>" << key << ":</td><td>" << plat.value(key) << "</td></tr>\n"; - out << "</table>\n\n"; - if (hasOverride) { - out << "<span style=\"color:red\"><h4>Note! Override Platform Info:</h4></span>\n" - << "<p>The client's output has been compared to baselines created on a different platform. Differences:</p>\n" - << "<table>\n"; - for (int i = 0; i < plat.overrides().size()-1; i+=2) - out << "<tr><td>" << plat.overrides().at(i) << ":</td><td>" << plat.overrides().at(i+1) << "</td></tr>\n"; - out << "</table>\n\n"; - } -} - - -void Report::writeFunctionResults(const ImageItemList &list) -{ - QString testFunction = list.at(0).testFunction; - QString pageUrl = BaselineServer::baseUrl() + path; - QString ctx = handler->pathForItem(list.at(0), true, false).section(QLC('/'), 0, -2); - QString misCtx = handler->pathForItem(list.at(0), false, false).section(QLC('/'), 0, -2); - - - out << "\n<p> </p><h3>Test function: " << testFunction << "</h3>\n"; - if (!hasOverride) { - out << "<p><a href=\"/cgi-bin/server.cgi?cmd=clearAllBaselines&context=" << ctx << "&url=" << pageUrl - << "\"><b>Clear all baselines</b></a> for this testfunction (They will be recreated by the next run)</p>\n"; - out << "<p><a href=\"/cgi-bin/server.cgi?cmd=updateAllBaselines&context=" << ctx << "&mismatchContext=" << misCtx << "&url=" << pageUrl - << "\"><b>Let these mismatching images be the new baselines</b></a> for this testfunction</p>\n\n"; - } - - out << "<table border=\"2\">\n" - "<tr>\n" - "<th width=123>Item</th>\n" - "<th width=246>Baseline</th>\n" - "<th width=246>Rendered</th>\n" - "<th width=246>Comparison (diffs are <span style=\"color:red\">RED</span>)</th>\n" - "<th width=246>Info/Action</th>\n" - "</tr>\n\n"; - - foreach (const ImageItem &item, list) { - QString mmPrefix = handler->pathForItem(item, false, false); - QString blPrefix = handler->pathForItem(item, true, false); - - // Make hard links to the current baseline, so that the report is static even if the baseline changes - generateThumbnail(blPrefix + QLS(FileFormat), rootDir); // Make sure baseline thumbnail is up to date - QString lnPrefix = mmPrefix + QLS("baseline."); - QByteArray blPrefixBa = (rootDir + blPrefix).toLatin1(); - QByteArray lnPrefixBa = (rootDir + lnPrefix).toLatin1(); - ::link((blPrefixBa + FileFormat).constData(), (lnPrefixBa + FileFormat).constData()); - ::link((blPrefixBa + MetadataFileExt).constData(), (lnPrefixBa + MetadataFileExt).constData()); - ::link((blPrefixBa + ThumbnailExt).constData(), (lnPrefixBa + ThumbnailExt).constData()); - - QString baseline = lnPrefix + QLS(FileFormat); - QString metadata = lnPrefix + QLS(MetadataFileExt); - out << "<tr>\n"; - out << "<td>" << item.itemName << "</td>\n"; - if (item.status == ImageItem::Mismatch || item.status == ImageItem::FuzzyMatch) { - QString rendered = mmPrefix + QLS(FileFormat); - QString itemFile = mmPrefix.section(QLC('/'), -1); - writeItem(baseline, rendered, item, itemFile, ctx, misCtx, metadata); - } - else { - out << "<td align=center><a href=\"/" << baseline << "\">image</a> <a href=\"/" << metadata << "\">info</a></td>\n" - << "<td align=center colspan=2><small>n/a</small></td>\n" - << "<td align=center>"; - switch (item.status) { - case ImageItem::BaselineNotFound: - out << "Baseline not found/regenerated"; - break; - case ImageItem::IgnoreItem: - out << "<span style=\"background-color:yellow\">Blacklisted</span> "; - if (!hasOverride) { - out << "<a href=\"/cgi-bin/server.cgi?cmd=whitelist&context=" << ctx - << "&itemId=" << item.itemName << "&url=" << pageUrl - << "\">Whitelist this item</a>"; - } - break; - case ImageItem::Error: - out << "<span style=\"background-color:red\">Error: No result reported!</span>"; - break; - case ImageItem::Ok: - out << "<span style=\"color:green\"><small>No mismatch reported</small></span>"; - break; - default: - out << '?'; - break; - } - out << "</td>\n"; - } - out << "</tr>\n\n"; - } - - out << "</table>\n"; -} - -void Report::writeItem(const QString &baseline, const QString &rendered, const ImageItem &item, - const QString &itemFile, const QString &ctx, const QString &misCtx, const QString &metadata) -{ - QString compared = generateCompared(baseline, rendered); - QString pageUrl = BaselineServer::baseUrl() + path; - - QStringList images = QStringList() << baseline << rendered << compared; - foreach (const QString& img, images) - out << "<td height=246 align=center><a href=\"/" << img << "\"><img src=\"/" << generateThumbnail(img, rootDir) << "\"></a></td>\n"; - - out << "<td align=center>\n"; - if (item.status == ImageItem::FuzzyMatch) - out << "<p><span style=\"color:orange\">Fuzzy match</span></p>\n"; - else - out << "<p><span style=\"color:red\">Mismatch reported</span></p>\n"; - out << "<p><a href=\"/" << metadata << "\">Baseline Info</a>\n"; - if (!hasOverride) { - out << "<p><a href=\"/cgi-bin/server.cgi?cmd=updateSingleBaseline&context=" << ctx << "&mismatchContext=" << misCtx - << "&itemFile=" << itemFile << "&url=" << pageUrl << "\">Let this be the new baseline</a></p>\n" - << "<p><a href=\"/cgi-bin/server.cgi?cmd=blacklist&context=" << ctx - << "&itemId=" << item.itemName << "&url=" << pageUrl << "\">Blacklist this item</a></p>\n"; - } - out << "<p><a href=\"/cgi-bin/server.cgi?cmd=view&baseline=" << baseline << "&rendered=" << rendered - << "&compared=" << compared << "&url=" << pageUrl << "\">Inspect</a></p>\n"; - -#if 0 - out << "<p><a href=\"/cgi-bin/server.cgi?cmd=diffstats&baseline=" << baseline << "&rendered=" << rendered - << "&url=" << pageUrl << "\">Diffstats</a></p>\n"; -#endif - - out << "</td>\n"; -} - -void Report::writeFooter() -{ - out << "\n</body></html>\n"; -} - - -QString Report::generateCompared(const QString &baseline, const QString &rendered, bool fuzzy) -{ - QString res = rendered; - QFileInfo fi(res); - res.chop(fi.suffix().length()); - 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, const QString &rootDir) -{ - QString res = image; - QFileInfo imgFI(rootDir+image); - if (!imgFI.exists()) - return res; - res.chop(imgFI.suffix().length()); - res += ThumbnailExt; - 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; -} - - -QString Report::writeResultsXmlFiles() -{ - if (!itemLists.size()) - return QString(); - QString dir = rootDir + baseDir + QLS("/xml-reports/") + runId; - QDir cwd; - if (!cwd.exists(dir)) - cwd.mkpath(dir); - foreach (const QString &func, itemLists.keys()) { - QFile f(dir + QLatin1Char('/') + func + "-results.xml"); - if (!f.open(QIODevice::WriteOnly)) - continue; - QXmlStreamWriter s(&f); - s.setAutoFormatting(true); - s.writeStartDocument(); - foreach (QString key, plat.keys()) { - QString cmt = QLatin1Char(' ') + key + "=\"" + plat.value(key) +"\" "; - s.writeComment(cmt.replace("--", "[-]")); - } - s.writeStartElement("testsuite"); - s.writeAttribute("name", func); - foreach (const ImageItem &item, itemLists.value(func)) { - QString res; - switch (item.status) { - case ImageItem::Ok: - case ImageItem::FuzzyMatch: - res = "pass"; - break; - case ImageItem::Mismatch: - case ImageItem::Error: - res = "fail"; - break; - case ImageItem::BaselineNotFound: - case ImageItem::IgnoreItem: - default: - res = "skip"; - } - s.writeStartElement("testcase"); - s.writeAttribute("name", item.itemName); - s.writeAttribute("result", res); - s.writeEndElement(); - } - s.writeEndElement(); - s.writeEndDocument(); - } - return dir; -} - - - -void Report::updateLatestPointer() -{ - QString linkPath = rootDir + baseDir + QLS("/latest_report.html"); - QString reportPath = path.mid(baseDir.size()+1); - QFile::remove(linkPath); // possible race with another thread, yada yada yada - QFile::link(reportPath, linkPath); - -#if 0 - QByteArray fwd = "<!DOCTYPE html><html><head><meta HTTP-EQUIV=\"refresh\" CONTENT=\"0;URL=%1\"></meta></head><body></body></html>\n"; - fwd.replace("%1", filePath().prepend(QLC('/')).toLatin1()); - - QFile file(rootDir + baseDir + "/latest_report.html"); - if (file.open(QIODevice::WriteOnly | QIODevice::Truncate)) - file.write(fwd); -#endif -} - - -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" - << "<!DOCTYPE html>\n<HTML>\n<body bgcolor=""#ddeeff"">\n"; // Lancelot blue - - QString command(cgiUrl.queryItemValue("cmd")); - - if (command == QLS("view")) { - s << BaselineHandler::view(cgiUrl.queryItemValue(QLS("baseline")), - cgiUrl.queryItemValue(QLS("rendered")), - cgiUrl.queryItemValue(QLS("compared"))); - } -#if 0 - else if (command == QLS("diffstats")) { - s << BaselineHandler::diffstats(cgiUrl.queryItemValue(QLS("baseline")), - cgiUrl.queryItemValue(QLS("rendered"))); - } -#endif - else if (command == QLS("updateSingleBaseline")) { - s << BaselineHandler::updateBaselines(cgiUrl.queryItemValue(QLS("context")), - cgiUrl.queryItemValue(QLS("mismatchContext")), - cgiUrl.queryItemValue(QLS("itemFile"))); - } else if (command == QLS("updateAllBaselines")) { - s << BaselineHandler::updateBaselines(cgiUrl.queryItemValue(QLS("context")), - cgiUrl.queryItemValue(QLS("mismatchContext")), - QString()); - } else if (command == QLS("clearAllBaselines")) { - s << BaselineHandler::clearAllBaselines(cgiUrl.queryItemValue(QLS("context"))); - } else if (command == QLS("blacklist")) { - // blacklist a test - s << BaselineHandler::blacklistTest(cgiUrl.queryItemValue(QLS("context")), - cgiUrl.queryItemValue(QLS("itemId"))); - } else if (command == QLS("whitelist")) { - // whitelist a test - s << BaselineHandler::blacklistTest(cgiUrl.queryItemValue(QLS("context")), - cgiUrl.queryItemValue(QLS("itemId")), true); - } else { - s << "Unknown query:<br>" << query << "<br>"; - } - s << "<p><a href=\"" << cgiUrl.queryItemValue(QLS("url")) << "\">Back to report</a>\n"; - s << "</body>\n</HTML>"; -} diff --git a/tests/baselineserver/src/report.h b/tests/baselineserver/src/report.h deleted file mode 100644 index c568e7ab8d..0000000000 --- a/tests/baselineserver/src/report.h +++ /dev/null @@ -1,98 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ -#ifndef REPORT_H -#define REPORT_H - -#include "baselineprotocol.h" -#include <QFile> -#include <QTextStream> -#include <QMap> -#include <QStringList> -#include <QSettings> - -class BaselineHandler; - -class Report -{ -public: - Report(); - ~Report(); - - void init(const BaselineHandler *h, const QString &r, const PlatformInfo &p, const QSettings *s); - void addItems(const ImageItemList& items); - void addResult(const ImageItem& item); - void end(); - - bool reportProduced(); - - int numberOfMismatches(); - QString summary(); - - QString filePath(); - - QString writeResultsXmlFiles(); - - static void handleCGIQuery(const QString &query); - - static QString generateThumbnail(const QString &image, const QString &rootDir = QString()); - -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); - - void updateLatestPointer(); - - void computeStats(); - - bool initialized; - const BaselineHandler *handler; - QString runId; - PlatformInfo plat; - QString rootDir; - QString baseDir; - QString path; - QStringList testFunctions; - QMap<QString, ImageItemList> itemLists; - bool written; - int numItems; - int numMismatches; - QTextStream out; - bool hasOverride; - const QSettings *settings; - - typedef QMap<ImageItem::ItemStatus, int> FuncStats; - QMap<QString, FuncStats> stats; - bool hasStats; -}; - -#endif // REPORT_H diff --git a/tests/baselineserver/src/templates/view.html b/tests/baselineserver/src/templates/view.html deleted file mode 100644 index f0971010f2..0000000000 --- a/tests/baselineserver/src/templates/view.html +++ /dev/null @@ -1,84 +0,0 @@ -<h3>Lancelot Viewer</h3> - -<p> -Zoom: -<input name="zoom" id="z1" type="radio" checked="Checked">1x</input> -<input name="zoom" id="z2" type="radio">2x</input> -<input name="zoom" id="z4" type="radio">4x</input> -</p> - -<p><table> -<tr> -<td><input name="imgselect" id="baseline" type="radio" checked="Checked">Baseline</input></td> -<td>%1</td> -</tr> -<tr> -<td><input name="imgselect" id="rendered" type="radio">Rendered</input></td> -<td>%2</td> -</tr> -<tr> -<td><input name="imgselect" id="compared" type="radio">Differences</input></td> -<td></td> -</tr> -</table></p> - - -<p><table cellspacing="25"><tr> -<td valign="top"> -<canvas id="c" width="800" height="800"></canvas> -</td> -<td valign="top"> -%4 -</td> -</tr></table></p> - -<script> - var canvas = document.getElementById("c"); - var context = canvas.getContext("2d"); - var cat = new Image(); - cat.src = "%1"; - var z = 1; - cat.onload = function() { - context.mozImageSmoothingEnabled = false; - context.drawImage(cat, 0, 0, z*cat.width, z*cat.height); - }; - - var bbut = document.getElementById("baseline"); - bbut.onclick = function() { - cat.src = "%1"; - }; - - var rbut = document.getElementById("rendered"); - rbut.onclick = function() { - cat.src = "%2"; - }; - - var cbut = document.getElementById("compared"); - cbut.onclick = function() { - cat.src = "%3"; - }; - - function setZoom(zoom) - { - z = zoom; - canvas.width = z*800; - canvas.height = z*800; - context.mozImageSmoothingEnabled = false; - context.drawImage(cat, 0, 0, z*cat.width, z*cat.height); - } - - var z1but = document.getElementById("z1"); - z1but.onclick = function() { - setZoom(1); - }; - - var z2but = document.getElementById("z2"); - z2but.onclick = function() { - setZoom(2); - }; - - var z4but = document.getElementById("z4"); - z4but.onclick = function() { - setZoom(4); - }; -</script> |