mirror of
https://github.com/crystalidea/qt6windows7.git
synced 2025-07-04 16:25:27 +08:00
qt 6.5.1 original
This commit is contained in:
456
tests/baseline/shared/baselineprotocol.cpp
Normal file
456
tests/baseline/shared/baselineprotocol.cpp
Normal file
@ -0,0 +1,456 @@
|
||||
// Copyright (C) 2021 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
#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 <QThread>
|
||||
#include <QTime>
|
||||
#include <QPointer>
|
||||
#include <QRegularExpression>
|
||||
|
||||
const QString PI_Project(QLS("Project"));
|
||||
const QString PI_ProjectImageKeys(QLS("ProjectImageKeys"));
|
||||
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_GitBranch(QLS("GitBranch"));
|
||||
|
||||
PlatformInfo PlatformInfo::localHostInfo()
|
||||
{
|
||||
PlatformInfo pi;
|
||||
pi.insert(PI_HostName, QHostInfo::localHostName());
|
||||
pi.insert(PI_QtVersion, QLS(qVersion()));
|
||||
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());
|
||||
|
||||
QString gc = qEnvironmentVariable("BASELINE_GIT_COMMIT");
|
||||
#if QT_CONFIG(process)
|
||||
if (gc.isEmpty()) {
|
||||
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())
|
||||
gc = QString::fromLocal8Bit(git.readAllStandardOutput().constData()).simplified();
|
||||
}
|
||||
#endif // QT_CONFIG(process)
|
||||
pi.insert(PI_GitCommit, gc.isEmpty() ? QLS("Unknown") : gc);
|
||||
|
||||
if (qEnvironmentVariableIsSet("JENKINS_HOME"))
|
||||
pi.setAdHocRun(false);
|
||||
|
||||
QString gb = qEnvironmentVariable("GIT_BRANCH");
|
||||
if (!gb.isEmpty())
|
||||
pi.insert(PI_GitBranch, gb);
|
||||
|
||||
return pi;
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
// 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()), 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)) {
|
||||
QThread::msleep(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 bandwidth 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;
|
||||
}
|
||||
|
144
tests/baseline/shared/baselineprotocol.h
Normal file
144
tests/baseline/shared/baselineprotocol.h
Normal file
@ -0,0 +1,144 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
#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_ProjectImageKeys;
|
||||
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_GitBranch;
|
||||
|
||||
class PlatformInfo : public QMap<QString, QString>
|
||||
{
|
||||
public:
|
||||
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 = true;
|
||||
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
|
||||
{
|
||||
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 = Ok;
|
||||
QImage image;
|
||||
QList<quint64> imageChecksums;
|
||||
quint16 itemChecksum = 0;
|
||||
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);
|
||||
|
||||
QString errMsg;
|
||||
QTcpSocket socket;
|
||||
|
||||
friend class BaselineThread;
|
||||
friend class BaselineHandler;
|
||||
};
|
||||
|
||||
|
||||
#endif // BASELINEPROTOCOL_H
|
10
tests/baseline/shared/baselineprotocol.pri
Normal file
10
tests/baseline/shared/baselineprotocol.pri
Normal file
@ -0,0 +1,10 @@
|
||||
INCLUDEPATH += $$PWD
|
||||
|
||||
QT *= network
|
||||
|
||||
SOURCES += \
|
||||
$$PWD/baselineprotocol.cpp \
|
||||
$$PWD/lookup3.cpp
|
||||
|
||||
HEADERS += \
|
||||
$$PWD/baselineprotocol.h
|
821
tests/baseline/shared/lookup3.cpp
Normal file
821
tests/baseline/shared/lookup3.cpp
Normal file
@ -0,0 +1,821 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
|
||||
/*
|
||||
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
|
||||
* noticeably 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
|
||||
* noticeably 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
|
||||
* noticeably 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;
|
||||
}
|
2950
tests/baseline/shared/paintcommands.cpp
Normal file
2950
tests/baseline/shared/paintcommands.cpp
Normal file
File diff suppressed because it is too large
Load Diff
323
tests/baseline/shared/paintcommands.h
Normal file
323
tests/baseline/shared/paintcommands.h
Normal file
@ -0,0 +1,323 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
#ifndef PAINTCOMMANDS_H
|
||||
#define PAINTCOMMANDS_H
|
||||
|
||||
#include <qcolor.h>
|
||||
#include <qmap.h>
|
||||
#include <qpainterpath.h>
|
||||
#include <qregion.h>
|
||||
#include <qregularexpression.h>
|
||||
#include <qstringlist.h>
|
||||
#include <qpixmap.h>
|
||||
#include <qbrush.h>
|
||||
#include <qhash.h>
|
||||
|
||||
QT_FORWARD_DECLARE_CLASS(QPainter)
|
||||
#ifndef QT_NO_OPENGL
|
||||
QT_FORWARD_DECLARE_CLASS(QOpenGLFramebufferObject)
|
||||
QT_FORWARD_DECLARE_CLASS(QOpenGLPaintDevice)
|
||||
QT_FORWARD_DECLARE_CLASS(QOpenGLContext)
|
||||
#endif
|
||||
|
||||
enum DeviceType {
|
||||
WidgetType,
|
||||
BitmapType,
|
||||
PixmapType,
|
||||
ImageType,
|
||||
ImageMonoType,
|
||||
OpenGLType,
|
||||
OpenGLBufferType,
|
||||
PictureType,
|
||||
PrinterType,
|
||||
PdfType,
|
||||
PsType,
|
||||
GrabType,
|
||||
CustomDeviceType,
|
||||
CustomWidgetType,
|
||||
ImageWidgetType
|
||||
};
|
||||
|
||||
/************************************************************************/
|
||||
class PaintCommands
|
||||
{
|
||||
public:
|
||||
// construction / initialization
|
||||
PaintCommands(const QStringList &cmds, int /*w*/, int /*h*/, QImage::Format format)
|
||||
: m_painter(0)
|
||||
, m_surface_painter(0)
|
||||
, m_format(format)
|
||||
, m_commands(cmds)
|
||||
, m_gradientSpread(QGradient::PadSpread)
|
||||
, m_gradientCoordinate(QGradient::LogicalMode)
|
||||
, m_verboseMode(false)
|
||||
, m_type(WidgetType)
|
||||
, m_checkers_background(true)
|
||||
, m_shouldDrawText(true)
|
||||
#ifndef QT_NO_OPENGL
|
||||
, m_default_glcontext(0)
|
||||
, m_surface_glcontext(0)
|
||||
, m_surface_glbuffer(0)
|
||||
, m_surface_glpaintdevice(0)
|
||||
#endif
|
||||
{
|
||||
staticInit();
|
||||
}
|
||||
|
||||
public:
|
||||
void setCheckersBackground(bool b) { staticInit(); m_checkers_background = b; }
|
||||
void setContents(const QStringList &cmds) {
|
||||
staticInit();
|
||||
m_blockMap.clear();
|
||||
m_pathMap.clear();
|
||||
m_pixmapMap.clear();
|
||||
m_imageMap.clear();
|
||||
m_regionMap.clear();
|
||||
m_gradientStops.clear();
|
||||
m_controlPoints.clear();
|
||||
m_gradientSpread = QGradient::PadSpread;
|
||||
m_gradientCoordinate = QGradient::LogicalMode;
|
||||
m_commands = cmds;
|
||||
|
||||
|
||||
}
|
||||
void setPainter(QPainter *pt) { staticInit(); m_painter = pt; }
|
||||
void setType(DeviceType t) { staticInit(); m_type = t; }
|
||||
void setFilePath(const QString &path) { staticInit(); m_filepath = path; }
|
||||
void setControlPoints(const QList<QPointF> &points)
|
||||
{
|
||||
staticInit();
|
||||
m_controlPoints = points;
|
||||
}
|
||||
void setVerboseMode(bool v) { staticInit(); m_verboseMode = v; }
|
||||
void insertAt(int commandIndex, const QStringList &newCommands);
|
||||
void setShouldDrawText(bool drawText) { m_shouldDrawText = drawText; }
|
||||
|
||||
// run
|
||||
void runCommands();
|
||||
|
||||
private:
|
||||
// run
|
||||
void runCommand(const QString &scriptLine);
|
||||
|
||||
// conversion methods
|
||||
int convertToInt(const QString &str);
|
||||
double convertToDouble(const QString &str);
|
||||
float convertToFloat(const QString &str);
|
||||
QColor convertToColor(const QString &str);
|
||||
|
||||
// commands: comments
|
||||
void command_comment(QRegularExpressionMatch re);
|
||||
|
||||
// commands: importer
|
||||
void command_import(QRegularExpressionMatch re);
|
||||
|
||||
// commands: blocks
|
||||
void command_begin_block(QRegularExpressionMatch re);
|
||||
void command_end_block(QRegularExpressionMatch re);
|
||||
void command_repeat_block(QRegularExpressionMatch re);
|
||||
|
||||
// commands: misc
|
||||
void command_textlayout_draw(QRegularExpressionMatch re);
|
||||
void command_abort(QRegularExpressionMatch re);
|
||||
|
||||
// commands: noops
|
||||
void command_noop(QRegularExpressionMatch re);
|
||||
|
||||
// commands: setters
|
||||
void command_setBgMode(QRegularExpressionMatch re);
|
||||
void command_setBackground(QRegularExpressionMatch re);
|
||||
void command_setOpacity(QRegularExpressionMatch re);
|
||||
void command_path_setFillRule(QRegularExpressionMatch re);
|
||||
void command_setBrush(QRegularExpressionMatch re);
|
||||
void command_setBrushOrigin(QRegularExpressionMatch re);
|
||||
void command_brushTranslate(QRegularExpressionMatch re);
|
||||
void command_brushRotate(QRegularExpressionMatch re);
|
||||
void command_brushScale(QRegularExpressionMatch re);
|
||||
void command_brushShear(QRegularExpressionMatch re);
|
||||
void command_setClipPath(QRegularExpressionMatch re);
|
||||
void command_setClipRect(QRegularExpressionMatch re);
|
||||
void command_setClipRectF(QRegularExpressionMatch re);
|
||||
void command_setClipRegion(QRegularExpressionMatch re);
|
||||
void command_setClipping(QRegularExpressionMatch re);
|
||||
void command_setCompositionMode(QRegularExpressionMatch re);
|
||||
void command_setFont(QRegularExpressionMatch re);
|
||||
void command_setPen(QRegularExpressionMatch re);
|
||||
void command_setPen2(QRegularExpressionMatch re);
|
||||
void command_pen_setDashOffset(QRegularExpressionMatch re);
|
||||
void command_pen_setDashPattern(QRegularExpressionMatch re);
|
||||
void command_pen_setCosmetic(QRegularExpressionMatch re);
|
||||
void command_setRenderHint(QRegularExpressionMatch re);
|
||||
void command_clearRenderHint(QRegularExpressionMatch re);
|
||||
void command_gradient_appendStop(QRegularExpressionMatch re);
|
||||
void command_gradient_clearStops(QRegularExpressionMatch re);
|
||||
void command_gradient_setConical(QRegularExpressionMatch re);
|
||||
void command_gradient_setLinear(QRegularExpressionMatch re);
|
||||
void command_gradient_setRadial(QRegularExpressionMatch re);
|
||||
void command_gradient_setRadialExtended(QRegularExpressionMatch re);
|
||||
void command_gradient_setLinearPen(QRegularExpressionMatch re);
|
||||
void command_gradient_setSpread(QRegularExpressionMatch re);
|
||||
void command_gradient_setCoordinateMode(QRegularExpressionMatch re);
|
||||
|
||||
// commands: drawing ops
|
||||
void command_drawArc(QRegularExpressionMatch re);
|
||||
void command_drawChord(QRegularExpressionMatch re);
|
||||
void command_drawConvexPolygon(QRegularExpressionMatch re);
|
||||
void command_drawEllipse(QRegularExpressionMatch re);
|
||||
void command_drawImage(QRegularExpressionMatch re);
|
||||
void command_drawLine(QRegularExpressionMatch re);
|
||||
void command_drawLines(QRegularExpressionMatch re);
|
||||
void command_drawPath(QRegularExpressionMatch re);
|
||||
void command_drawPie(QRegularExpressionMatch re);
|
||||
void command_drawPixmap(QRegularExpressionMatch re);
|
||||
void command_drawPoint(QRegularExpressionMatch re);
|
||||
void command_drawPolygon(QRegularExpressionMatch re);
|
||||
void command_drawPolyline(QRegularExpressionMatch re);
|
||||
void command_drawRect(QRegularExpressionMatch re);
|
||||
void command_drawRoundedRect(QRegularExpressionMatch re);
|
||||
void command_drawRoundRect(QRegularExpressionMatch re);
|
||||
void command_drawText(QRegularExpressionMatch re);
|
||||
void command_drawStaticText(QRegularExpressionMatch re);
|
||||
void command_drawGlyphRun(QRegularExpressionMatch re);
|
||||
void command_drawTextDocument(QRegularExpressionMatch re);
|
||||
void command_drawTiledPixmap(QRegularExpressionMatch re);
|
||||
void command_fillRect(QRegularExpressionMatch re);
|
||||
void command_fillRectF(QRegularExpressionMatch re);
|
||||
void command_drawPixmapFragments(QRegularExpressionMatch re);
|
||||
|
||||
// paths
|
||||
void command_path_addEllipse(QRegularExpressionMatch re);
|
||||
void command_path_addPolygon(QRegularExpressionMatch re);
|
||||
void command_path_addRect(QRegularExpressionMatch re);
|
||||
void command_path_addText(QRegularExpressionMatch re);
|
||||
void command_path_arcTo(QRegularExpressionMatch re);
|
||||
void command_path_closeSubpath(QRegularExpressionMatch re);
|
||||
void command_path_createOutline(QRegularExpressionMatch re);
|
||||
void command_path_cubicTo(QRegularExpressionMatch re);
|
||||
void command_path_debugPrint(QRegularExpressionMatch re);
|
||||
void command_path_lineTo(QRegularExpressionMatch re);
|
||||
void command_path_moveTo(QRegularExpressionMatch re);
|
||||
void command_region_addEllipse(QRegularExpressionMatch re);
|
||||
void command_region_addRect(QRegularExpressionMatch re);
|
||||
|
||||
// getters
|
||||
void command_region_getClipRegion(QRegularExpressionMatch re);
|
||||
void command_path_getClipPath(QRegularExpressionMatch re);
|
||||
|
||||
// commands: surface begin/end
|
||||
void command_surface_begin(QRegularExpressionMatch re);
|
||||
void command_surface_end(QRegularExpressionMatch re);
|
||||
|
||||
// commands: save/restore painter state
|
||||
void command_restore(QRegularExpressionMatch re);
|
||||
void command_save(QRegularExpressionMatch re);
|
||||
|
||||
// commands: pixmap/image
|
||||
void command_pixmap_load(QRegularExpressionMatch re);
|
||||
void command_pixmap_setMask(QRegularExpressionMatch re);
|
||||
void command_bitmap_load(QRegularExpressionMatch re);
|
||||
void command_pixmap_setDevicePixelRatio(QRegularExpressionMatch re);
|
||||
void command_image_convertToFormat(QRegularExpressionMatch re);
|
||||
void command_image_load(QRegularExpressionMatch re);
|
||||
void command_image_setColor(QRegularExpressionMatch re);
|
||||
void command_image_setColorCount(QRegularExpressionMatch re);
|
||||
void command_image_setDevicePixelRatio(QRegularExpressionMatch re);
|
||||
|
||||
// commands: transformation
|
||||
void command_resetMatrix(QRegularExpressionMatch re);
|
||||
void command_translate(QRegularExpressionMatch re);
|
||||
void command_rotate(QRegularExpressionMatch re);
|
||||
void command_rotate_x(QRegularExpressionMatch re);
|
||||
void command_rotate_y(QRegularExpressionMatch re);
|
||||
void command_scale(QRegularExpressionMatch re);
|
||||
void command_mapQuadToQuad(QRegularExpressionMatch re);
|
||||
void command_setMatrix(QRegularExpressionMatch re);
|
||||
|
||||
// attributes
|
||||
QPainter *m_painter;
|
||||
QPainter *m_surface_painter;
|
||||
QImage::Format m_format;
|
||||
QImage m_surface_image;
|
||||
QRectF m_surface_rect;
|
||||
QStringList m_commands;
|
||||
QString m_currentCommand;
|
||||
int m_currentCommandIndex;
|
||||
QString m_filepath;
|
||||
QMap<QString, QStringList> m_blockMap;
|
||||
QMap<QString, QPainterPath> m_pathMap;
|
||||
QMap<QString, QPixmap> m_pixmapMap;
|
||||
QMap<QString, QImage> m_imageMap;
|
||||
QMap<QString, QRegion> m_regionMap;
|
||||
QGradientStops m_gradientStops;
|
||||
QGradient::Spread m_gradientSpread;
|
||||
QGradient::CoordinateMode m_gradientCoordinate;
|
||||
bool m_abort;
|
||||
|
||||
bool m_verboseMode;
|
||||
DeviceType m_type;
|
||||
bool m_checkers_background;
|
||||
bool m_shouldDrawText;
|
||||
|
||||
QList<QPointF> m_controlPoints;
|
||||
|
||||
#ifndef QT_NO_OPENGL
|
||||
QOpenGLContext *m_default_glcontext;
|
||||
QOpenGLContext *m_surface_glcontext;
|
||||
QOpenGLFramebufferObject *m_surface_glbuffer;
|
||||
QOpenGLPaintDevice *m_surface_glpaintdevice;
|
||||
#endif
|
||||
|
||||
// painter functionalities string tables
|
||||
static const char *brushStyleTable[];
|
||||
static const char *penStyleTable[];
|
||||
static const char *fontWeightTable[];
|
||||
static const char *fontHintingTable[];
|
||||
static const char *fontCapitalizationTable[];
|
||||
static const char *clipOperationTable[];
|
||||
static const char *spreadMethodTable[];
|
||||
static const char *coordinateMethodTable[];
|
||||
static const char *compositionModeTable[];
|
||||
static const char *imageFormatTable[];
|
||||
static const char *sizeModeTable[];
|
||||
static const char *renderHintTable[];
|
||||
static int translateEnum(const char *table[], const QString &pattern, int limit);
|
||||
|
||||
// utility
|
||||
template <typename T> T image_load(const QString &filepath);
|
||||
|
||||
// commands dictionary management
|
||||
static void staticInit();
|
||||
|
||||
public:
|
||||
struct PaintCommandInfos
|
||||
{
|
||||
PaintCommandInfos(QString id, void (PaintCommands::*p)(QRegularExpressionMatch), QRegularExpression r, QString sy, QString sa)
|
||||
: identifier(id)
|
||||
, regExp(r)
|
||||
, syntax(sy)
|
||||
, sample(sa)
|
||||
, paintMethod(p)
|
||||
{}
|
||||
PaintCommandInfos(QString title)
|
||||
: identifier(title), paintMethod(0) {}
|
||||
bool isSectionHeader() const { return paintMethod == 0; }
|
||||
QString identifier;
|
||||
QRegularExpression regExp;
|
||||
QString syntax;
|
||||
QString sample;
|
||||
void (PaintCommands::*paintMethod)(QRegularExpressionMatch);
|
||||
};
|
||||
|
||||
static PaintCommandInfos *findCommandById(const QString &identifier) {
|
||||
for (int i=0; i<s_commandInfoTable.size(); i++)
|
||||
if (s_commandInfoTable[i].identifier == identifier)
|
||||
return &s_commandInfoTable[i];
|
||||
return 0;
|
||||
}
|
||||
|
||||
static QList<PaintCommandInfos> s_commandInfoTable;
|
||||
static QList<QPair<QString,QStringList> > s_enumsTable;
|
||||
static QMultiHash<QString, int> s_commandHash;
|
||||
};
|
||||
|
||||
#endif // PAINTCOMMANDS_H
|
406
tests/baseline/shared/qbaselinetest.cpp
Normal file
406
tests/baseline/shared/qbaselinetest.cpp
Normal file
@ -0,0 +1,406 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
#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 bool abortIfUnstable = true;
|
||||
|
||||
static QByteArray curFunction;
|
||||
static ImageItemList itemList;
|
||||
static bool gotBaselines;
|
||||
|
||||
|
||||
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] : nullptr;
|
||||
|
||||
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 == "-keeprunning") {
|
||||
abortIfUnstable = false;
|
||||
} 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 << " -keeprunning : Run all tests even if the system is unstable \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";
|
||||
}
|
||||
}
|
||||
|
||||
bool shouldAbortIfUnstable()
|
||||
{
|
||||
return abortIfUnstable;
|
||||
}
|
||||
|
||||
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.size() < 64 && val.size() < 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();
|
||||
const auto &defaultInfoKeys = defaultInfo.keys();
|
||||
for (const QString &key : defaultInfoKeys) {
|
||||
if (!clientInfo.contains(key))
|
||||
clientInfo.insert(key, defaultInfo.value(key));
|
||||
}
|
||||
if (!customAutoModeSet)
|
||||
clientInfo.setAdHocRun(defaultInfo.isAdHocRun());
|
||||
|
||||
QString testCase = clientInfo.value(PI_TestCase);
|
||||
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)
|
||||
{
|
||||
bool dummy;
|
||||
QByteArray dummyMsg;
|
||||
return connect(msg ? msg : &dummyMsg, &dummy);
|
||||
}
|
||||
|
||||
void setAutoMode(bool mode)
|
||||
{
|
||||
customInfo.setAdHocRun(!mode);
|
||||
customAutoModeSet = true;
|
||||
}
|
||||
|
||||
void setSimFail(bool fail)
|
||||
{
|
||||
simfail = fail;
|
||||
}
|
||||
|
||||
void setProject(const QString &projectName)
|
||||
{
|
||||
addClientProperty(PI_Project, projectName);
|
||||
}
|
||||
|
||||
void setProjectImageKeys(const QStringList &keys)
|
||||
{
|
||||
QString keyList = keys.join(QLC(','));
|
||||
addClientProperty(PI_ProjectImageKeys, keyList);
|
||||
}
|
||||
|
||||
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) {
|
||||
qInfo() << "Baseline server reports:" << 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);
|
||||
}
|
||||
|
||||
}
|
62
tests/baseline/shared/qbaselinetest.h
Normal file
62
tests/baseline/shared/qbaselinetest.h
Normal file
@ -0,0 +1,62 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
#ifndef BASELINETEST_H
|
||||
#define BASELINETEST_H
|
||||
|
||||
#include <QTest>
|
||||
#include <QString>
|
||||
|
||||
namespace QBaselineTest {
|
||||
void setAutoMode(bool mode);
|
||||
void setSimFail(bool fail);
|
||||
void handleCmdLineArgs(int *argcp, char ***argvp);
|
||||
void setProject(const QString &projectName); // Selects server config settings and top level dir
|
||||
void setProjectImageKeys(const QStringList &keys); // Overrides the ItemPathKeys config setting
|
||||
void addClientProperty(const QString& key, const QString& value);
|
||||
bool connectToBaselineServer(QByteArray *msg = nullptr);
|
||||
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();
|
||||
bool shouldAbortIfUnstable();
|
||||
}
|
||||
|
||||
#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_SUM_DEFERRED(image, name, checksum)\
|
||||
do {\
|
||||
QByteArray _msg;\
|
||||
bool _err = false;\
|
||||
if (!QBaselineTest::checkImage((image), (name), (checksum), &_msg, &_err)) {\
|
||||
QTest::qFail(_msg.constData(), __FILE__, __LINE__);\
|
||||
} else if (_err) {\
|
||||
QSKIP(_msg.constData());\
|
||||
}\
|
||||
} while (0)
|
||||
|
||||
#define QBASELINE_CHECK(image, name) QBASELINE_CHECK_SUM(image, name, 0)
|
||||
|
||||
#define QBASELINE_CHECK_DEFERRED(image, name) QBASELINE_CHECK_SUM_DEFERRED(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
|
14
tests/baseline/shared/qbaselinetest.pri
Normal file
14
tests/baseline/shared/qbaselinetest.pri
Normal file
@ -0,0 +1,14 @@
|
||||
QT *= testlib
|
||||
|
||||
SOURCES += \
|
||||
$$PWD/qbaselinetest.cpp
|
||||
|
||||
HEADERS += \
|
||||
$$PWD/qbaselinetest.h
|
||||
|
||||
qtHaveModule(widgets) {
|
||||
SOURCES += $$PWD/qwidgetbaselinetest.cpp
|
||||
HEADERS += $$PWD/qwidgetbaselinetest.h
|
||||
}
|
||||
|
||||
include($$PWD/baselineprotocol.pri)
|
170
tests/baseline/shared/qwidgetbaselinetest.cpp
Normal file
170
tests/baseline/shared/qwidgetbaselinetest.cpp
Normal file
@ -0,0 +1,170 @@
|
||||
// Copyright (C) 2021 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
#include "qwidgetbaselinetest.h"
|
||||
|
||||
#include <qbaselinetest.h>
|
||||
#include <QApplication>
|
||||
#include <QStyle>
|
||||
#include <QStyleHints>
|
||||
#include <QScreen>
|
||||
|
||||
#include <QtWidgets/private/qapplication_p.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
QWidgetBaselineTest::QWidgetBaselineTest()
|
||||
{
|
||||
QBaselineTest::setProject("Widgets");
|
||||
|
||||
// Set key platform properties that are relevant for the appearance of widgets
|
||||
const QString platformName = QGuiApplication::platformName() + "-" + QSysInfo::productType();
|
||||
QBaselineTest::addClientProperty("PlatformName", platformName);
|
||||
QBaselineTest::addClientProperty("OSVersion", QSysInfo::productVersion());
|
||||
|
||||
// Encode a number of parameters that impact the UI
|
||||
QPalette palette;
|
||||
QFont font;
|
||||
const QString styleName =
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||
QApplication::style()->metaObject()->className();
|
||||
#else
|
||||
QApplication::style()->name();
|
||||
#endif
|
||||
// turn off animations and make the cursor flash time really long to avoid blinking
|
||||
QApplication::style()->setProperty("_qt_animation_time", QTime());
|
||||
QGuiApplication::styleHints()->setCursorFlashTime(50000);
|
||||
|
||||
QByteArray appearanceBytes;
|
||||
{
|
||||
QDataStream appearanceStream(&appearanceBytes, QIODevice::WriteOnly);
|
||||
appearanceStream << palette << font;
|
||||
const qreal screenDpr = QApplication::primaryScreen()->devicePixelRatio();
|
||||
if (screenDpr != 1.0) {
|
||||
qWarning() << "DPR is" << screenDpr << "- images will not be compared to 1.0 baseline!";
|
||||
appearanceStream << screenDpr;
|
||||
}
|
||||
}
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||
const quint16 appearanceId = qChecksum(appearanceBytes, appearanceBytes.size());
|
||||
#else
|
||||
const quint16 appearanceId = qChecksum(appearanceBytes);
|
||||
#endif
|
||||
|
||||
// Assume that text that's darker than the background means we run in light mode
|
||||
// This results in a more meaningful appearance ID between different runs than
|
||||
// just the checksum of the various attributes.
|
||||
const QColor windowColor = palette.window().color();
|
||||
const QColor textColor = palette.text().color();
|
||||
const QString appearanceIdString = (windowColor.value() > textColor.value()
|
||||
? QString("light-%1-%2") : QString("dark-%1-%2"))
|
||||
.arg(styleName).arg(appearanceId, 0, 16);
|
||||
QBaselineTest::addClientProperty("AppearanceID", appearanceIdString);
|
||||
|
||||
// let users know where they can find the results
|
||||
qDebug() << "PlatformName computed to be:" << platformName;
|
||||
qDebug() << "Appearance ID computed as:" << appearanceIdString;
|
||||
}
|
||||
|
||||
void QWidgetBaselineTest::initTestCase()
|
||||
{
|
||||
// Check and setup the environment. Failure to do so skips the test.
|
||||
QByteArray msg;
|
||||
if (!QBaselineTest::connectToBaselineServer(&msg))
|
||||
QSKIP(msg);
|
||||
}
|
||||
|
||||
void QWidgetBaselineTest::init()
|
||||
{
|
||||
QVERIFY(!window);
|
||||
window = new QWidget;
|
||||
window->setWindowTitle(QTest::currentDataTag());
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
window->setScreen(QGuiApplication::primaryScreen());
|
||||
#endif
|
||||
window->move(QGuiApplication::primaryScreen()->availableGeometry().topLeft());
|
||||
|
||||
doInit();
|
||||
}
|
||||
|
||||
void QWidgetBaselineTest::cleanup()
|
||||
{
|
||||
doCleanup();
|
||||
|
||||
delete window;
|
||||
window = nullptr;
|
||||
}
|
||||
|
||||
void QWidgetBaselineTest::makeVisible()
|
||||
{
|
||||
Q_ASSERT(window);
|
||||
window->show();
|
||||
QApplicationPrivate::setActiveWindow(window);
|
||||
QVERIFY(QTest::qWaitForWindowActive(window));
|
||||
// explicitly unset focus, the test needs to control when focus is shown
|
||||
if (window->focusWidget())
|
||||
window->focusWidget()->clearFocus();
|
||||
}
|
||||
|
||||
/*
|
||||
Grabs the test window and returns the resulting QImage, without
|
||||
compensating for DPR differences.
|
||||
*/
|
||||
QImage QWidgetBaselineTest::takeSnapshot()
|
||||
{
|
||||
// make sure all effects are done
|
||||
QTest::qWait(250);
|
||||
return window->grab().toImage();
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets standard widget properties on the test window and its children,
|
||||
and uploads snapshots. The widgets are returned in the same state
|
||||
that they had before.
|
||||
|
||||
Call this helper after setting up the test window.
|
||||
*/
|
||||
void QWidgetBaselineTest::takeStandardSnapshots()
|
||||
{
|
||||
makeVisible();
|
||||
struct PublicWidget : QWidget {
|
||||
bool focusNextPrevChild(bool next) override { return QWidget::focusNextPrevChild(next); }
|
||||
};
|
||||
|
||||
QBASELINE_CHECK_DEFERRED(takeSnapshot(), "default");
|
||||
|
||||
// try hard to set focus
|
||||
static_cast<PublicWidget*>(window)->focusNextPrevChild(true);
|
||||
if (!window->focusWidget()) {
|
||||
QWidget *firstChild = window->findChild<QWidget*>();
|
||||
if (firstChild)
|
||||
firstChild->setFocus();
|
||||
}
|
||||
|
||||
if (testWindow()->focusWidget()) {
|
||||
QBASELINE_CHECK_DEFERRED(takeSnapshot(), "focused");
|
||||
testWindow()->focusWidget()->clearFocus();
|
||||
}
|
||||
|
||||
// this disables all children
|
||||
window->setEnabled(false);
|
||||
QBASELINE_CHECK_DEFERRED(takeSnapshot(), "disabled");
|
||||
window->setEnabled(true);
|
||||
|
||||
// show and activate another window so that our test window becomes inactive
|
||||
QWidget otherWindow;
|
||||
otherWindow.move(window->geometry().bottomRight() + QPoint(10, 10));
|
||||
otherWindow.resize(50, 50);
|
||||
otherWindow.setWindowFlags(Qt::CustomizeWindowHint | Qt::FramelessWindowHint);
|
||||
otherWindow.show();
|
||||
otherWindow.windowHandle()->requestActivate();
|
||||
QVERIFY(QTest::qWaitForWindowActive(&otherWindow));
|
||||
QBASELINE_CHECK_DEFERRED(takeSnapshot(), "inactive");
|
||||
|
||||
window->windowHandle()->requestActivate();
|
||||
QVERIFY(QTest::qWaitForWindowActive(window));
|
||||
if (window->focusWidget())
|
||||
window->focusWidget()->clearFocus();
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
40
tests/baseline/shared/qwidgetbaselinetest.h
Normal file
40
tests/baseline/shared/qwidgetbaselinetest.h
Normal file
@ -0,0 +1,40 @@
|
||||
// Copyright (C) 2021 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QImage>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
class QWidget;
|
||||
|
||||
class QWidgetBaselineTest : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
QWidgetBaselineTest();
|
||||
|
||||
void takeStandardSnapshots();
|
||||
QWidget *testWindow() const { return window; }
|
||||
|
||||
protected:
|
||||
virtual void doInit() {}
|
||||
virtual void doCleanup() {}
|
||||
|
||||
private slots:
|
||||
void initTestCase();
|
||||
void init();
|
||||
void cleanup();
|
||||
|
||||
protected:
|
||||
void makeVisible();
|
||||
QImage takeSnapshot();
|
||||
|
||||
private:
|
||||
QWidget *window = nullptr;
|
||||
};
|
||||
|
||||
QT_END_NAMESPACE
|
Reference in New Issue
Block a user