diff options
author | aavit <qt-info@nokia.com> | 2010-08-25 11:49:34 (GMT) |
---|---|---|
committer | aavit <qt-info@nokia.com> | 2010-08-25 11:49:34 (GMT) |
commit | deef2e16776c171d4b033257cd4fc3ab2bd005a3 (patch) | |
tree | 85ac87094fc690f71d20a6bd2989879b166992bf /tests/arthur | |
parent | dae23694cb89b853785a5772cc7e0477f65ac5bf (diff) | |
download | Qt-deef2e16776c171d4b033257cd4fc3ab2bd005a3.zip Qt-deef2e16776c171d4b033257cd4fc3ab2bd005a3.tar.gz Qt-deef2e16776c171d4b033257cd4fc3ab2bd005a3.tar.bz2 |
First shot at a new client-server based autotest of the Arthur
2D rendering system, based on the lance test tool.
Hence dubbed lancelot.
Diffstat (limited to 'tests/arthur')
-rwxr-xr-x | tests/arthur/baselineserver/bin/runserver | 6 | ||||
-rw-r--r-- | tests/arthur/baselineserver/src/baselineserver.cpp | 188 | ||||
-rw-r--r-- | tests/arthur/baselineserver/src/baselineserver.h | 74 | ||||
-rw-r--r-- | tests/arthur/baselineserver/src/baselineserver.pro | 25 | ||||
-rw-r--r-- | tests/arthur/baselineserver/src/main.cpp | 16 | ||||
-rw-r--r-- | tests/arthur/common/baselineprotocol.cpp | 219 | ||||
-rw-r--r-- | tests/arthur/common/baselineprotocol.h | 76 | ||||
-rw-r--r-- | tests/arthur/common/baselineprotocol.pri | 9 |
8 files changed, 613 insertions, 0 deletions
diff --git a/tests/arthur/baselineserver/bin/runserver b/tests/arthur/baselineserver/bin/runserver new file mode 100755 index 0000000..52feb86 --- /dev/null +++ b/tests/arthur/baselineserver/bin/runserver @@ -0,0 +1,6 @@ +#!/bin/bash + +while true; do + ./baselineserver + sleep 5 +done diff --git a/tests/arthur/baselineserver/src/baselineserver.cpp b/tests/arthur/baselineserver/src/baselineserver.cpp new file mode 100644 index 0000000..bba2cd8 --- /dev/null +++ b/tests/arthur/baselineserver/src/baselineserver.cpp @@ -0,0 +1,188 @@ +#include "baselineserver.h" +#include <QBuffer> +#include <QFile> +#include <QDir> +#include <QCoreApplication> +#include <QFileInfo> +#include <QHostInfo> + +QString BaselineServer::storage; + +BaselineServer::BaselineServer(QObject *parent) + : QTcpServer(parent) +{ + 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()) { + QDir dir(QCoreApplication::applicationDirPath()); + dir.cdUp(); + storage = dir.path() + QLatin1String("/storage/"); + } + return storage; +} + +void BaselineServer::incomingConnection(int socketDescriptor) +{ + qDebug() << "Server: New connection!"; + BaselineThread *thread = new BaselineThread(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; + + // (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(int socketDescriptor, QObject *parent) + : QThread(parent), socketDescriptor(socketDescriptor) +{ +} + +void BaselineThread::run() +{ + BaselineHandler handler(socketDescriptor); + exec(); +} + + +BaselineHandler::BaselineHandler(int socketDescriptor) + : QObject(), connectionEstablished(false) +{ + runId = QDateTime::currentDateTime().toString(QLatin1String("MMMdd-hhmmss")); + + connect(&proto.socket, SIGNAL(readyRead()), this, SLOT(receiveRequest())); + connect(&proto.socket, SIGNAL(disconnected()), this, SLOT(receiveDisconnect())); + proto.socket.setSocketDescriptor(socketDescriptor); +} + + +void BaselineHandler::receiveRequest() +{ + if (!connectionEstablished) { + if (!proto.acceptConnection(&plat)) { + qWarning() << runId << "Accepting new connection failed. " << proto.errorMessage(); + QThread::currentThread()->exit(1); + return; + } + connectionEstablished = true; + qDebug() << runId << "Connection established with" << plat.hostname << "Qt version:" << plat.qtVersion << plat.buildKey; + return; + } + + QByteArray block; + BaselineProtocol::Command cmd; + if (!proto.receiveBlock(&cmd, &block)) { + qWarning() << runId << "Command reception failed. "<< proto.errorMessage(); + QThread::currentThread()->exit(1); + return; + } + + switch(cmd) { + case BaselineProtocol::RequestBaseline: + provideBaseline(block); + break; + case BaselineProtocol::AcceptNewBaseline: + storeImage(block, true); + break; + case BaselineProtocol::AcceptMismatch: + storeImage(block, false); + break; + default: + qWarning() << runId << "Unknown command received. " << proto.errorMessage(); + QThread::currentThread()->exit(1); + } +} + + +void BaselineHandler::provideBaseline(const QByteArray &caseId) +{ + qDebug() << runId << "Received request for baseline" << caseId; + + QFile file(pathForCaseId(caseId)); + if (!file.open(QIODevice::ReadOnly)) { + qDebug() << runId << "baseline not found."; + proto.sendBlock(BaselineProtocol::BaselineNotPresent, caseId); + return; + } + proto.sendBlock(BaselineProtocol::AcceptBaseline, file.readAll()); +} + + +void BaselineHandler::storeImage(const QByteArray &imageBlock, bool isBaseline) +{ + QBuffer buf; + buf.setData(imageBlock); + buf.open(QIODevice::ReadOnly); + QDataStream ds(&buf); + QByteArray caseId; + ds >> caseId; + //# For futuresafeness, should make caseId FS-safe - but revertable... + QFile file(pathForCaseId(caseId, isBaseline)); + qDebug() << runId << "Received" << (isBaseline ? "baseline" : "mismatched") << "image for:" << caseId << "Storing in" << file.fileName(); + QString path = file.fileName().section(QDir::separator(), 0, -2); + QDir cwd; + if (!cwd.exists(path)) + cwd.mkpath(path); + if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { + qWarning() << runId << "Failed to store" << file.fileName(); + return; + } + file.write(imageBlock.constData()+buf.pos(), imageBlock.size()-buf.pos()); + + QByteArray msg(isBaseline ? "Mismatching" : "Baseline"); + msg += " image stored in " + + QHostInfo::localHostName().toLatin1() + '.' + + QHostInfo::localDomainName().toLatin1() + ':' + + QFileInfo(file).absoluteFilePath().toLatin1(); + proto.sendBlock(BaselineProtocol::Ack, msg); + qDebug() << runId << "Storing done."; +} + + +void BaselineHandler::receiveDisconnect() +{ + qDebug() << runId << "Client disconnected."; + QThread::currentThread()->exit(0); +} + + +QString BaselineHandler::pathForCaseId(const QByteArray &caseId, bool isBaseline) +{ + QString storePath = BaselineServer::storagePath(); + storePath += plat.buildKey.section(QLatin1Char(' '), 1, 1) + QLatin1String("_Qt-") + + plat.qtVersion + QDir::separator(); + if (isBaseline) + storePath += QLatin1String("baselines") + QDir::separator(); + else + storePath += runId + QDir::separator(); + return storePath + caseId + QLatin1Char('.') + QLatin1String(FileFormat); +} + + +// - transferring and comparing checksums instead of images +// - then we could now if multiple error/imgs are really the same (and just store it once) +// - e.g. using db diff --git a/tests/arthur/baselineserver/src/baselineserver.h b/tests/arthur/baselineserver/src/baselineserver.h new file mode 100644 index 0000000..8cd9f56 --- /dev/null +++ b/tests/arthur/baselineserver/src/baselineserver.h @@ -0,0 +1,74 @@ +#ifndef BASELINESERVER_H +#define BASELINESERVER_H + +#include <QStringList> +#include <QTcpServer> +#include <QThread> +#include <QTcpSocket> +#include "baselineprotocol.h" +#include <QScopedPointer> +#include <QTimer> +#include <QDateTime> + +// #seconds between update checks +#define HEARTBEAT 10 + +class BaselineServer : public QTcpServer +{ + Q_OBJECT + +public: + BaselineServer(QObject *parent = 0); + + static QString storagePath(); + +protected: + void incomingConnection(int socketDescriptor); + +private slots: + void heartbeat(); + +private: + QTimer *heartbeatTimer; + QDateTime meLastMod; + static QString storage; +}; + + + +class BaselineThread : public QThread +{ + Q_OBJECT + +public: + BaselineThread(int socketDescriptor, QObject *parent); + void run(); + +private: + int socketDescriptor; +}; + + +class BaselineHandler : public QObject +{ + Q_OBJECT + +public: + BaselineHandler(int socketDescriptor); + +private slots: + void receiveRequest(); + void receiveDisconnect(); + +private: + void provideBaseline(const QByteArray &caseId); + void storeImage(const QByteArray &imageBlock, bool isBaseline); + QString pathForCaseId(const QByteArray &caseId, bool isBaseline = true); + + BaselineProtocol proto; + PlatformInfo plat; + bool connectionEstablished; + QString runId; +}; + +#endif // BASELINESERVER_H diff --git a/tests/arthur/baselineserver/src/baselineserver.pro b/tests/arthur/baselineserver/src/baselineserver.pro new file mode 100644 index 0000000..2065d4a --- /dev/null +++ b/tests/arthur/baselineserver/src/baselineserver.pro @@ -0,0 +1,25 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2010-08-11T11:51:09 +# +#------------------------------------------------- + +QT += core network + +# gui needed for QImage +# QT -= gui + +TARGET = baselineserver +DESTDIR = ../bin +CONFIG += console +CONFIG -= app_bundle + +TEMPLATE = app + +include(../../common/baselineprotocol.pri) + +SOURCES += main.cpp \ + baselineserver.cpp + +HEADERS += \ + baselineserver.h diff --git a/tests/arthur/baselineserver/src/main.cpp b/tests/arthur/baselineserver/src/main.cpp new file mode 100644 index 0000000..46048db --- /dev/null +++ b/tests/arthur/baselineserver/src/main.cpp @@ -0,0 +1,16 @@ +#include <QtCore/QCoreApplication> +#include "baselineserver.h" + +int main(int argc, char *argv[]) +{ + QCoreApplication a(argc, argv); + + BaselineServer server; + if (!server.listen(QHostAddress::Any, BaselineProtocol::ServerPort)) { + qWarning("Failed to listen!"); + return 1; + } + + qDebug() << "Listening for connections"; + return a.exec(); +} diff --git a/tests/arthur/common/baselineprotocol.cpp b/tests/arthur/common/baselineprotocol.cpp new file mode 100644 index 0000000..fb4f06d --- /dev/null +++ b/tests/arthur/common/baselineprotocol.cpp @@ -0,0 +1,219 @@ + +#include "baselineprotocol.h" +#include <QLibraryInfo> +#include <QImage> +#include <QBuffer> +#include <QHostInfo> + +PlatformInfo::PlatformInfo(bool useLocal) +{ + if (useLocal) { + buildKey = QLibraryInfo::buildKey(); + qtVersion = QLatin1String(qVersion()); + hostname = QHostInfo::localHostName(); + } +} + +QDataStream & operator<< (QDataStream &stream, const PlatformInfo &pinfo) +{ + stream << pinfo.buildKey << pinfo.qtVersion << pinfo.hostname; + return stream; +} + +QDataStream & operator>> (QDataStream& stream, PlatformInfo& pinfo) +{ + stream >> pinfo.buildKey >> pinfo.qtVersion >> pinfo.hostname; + return stream; +} + +BaselineProtocol::~BaselineProtocol() +{ + socket.close(); + if (socket.state() != QTcpSocket::UnconnectedState) + socket.waitForDisconnected(Timeout); +} + + +bool BaselineProtocol::connect() +{ + errMsg.clear(); + //TBD: determine server address; for now localhost + QLatin1String serverName("localhost"); + + socket.connectToHost(serverName, ServerPort); + + if (!socket.waitForConnected(Timeout)) { + errMsg += QLatin1String("TCP connectToHost failed. Host:") + serverName + QLatin1String(" port:") + QString::number(ServerPort); + return false; + } + + PlatformInfo pi(true); + QByteArray block; + QDataStream ds(&block, QIODevice::ReadWrite); + ds << pi; + if (!sendBlock(AcceptPlatformInfo, block)) { + errMsg += QLatin1String("Failed to send data to server."); + return false; + } + + Command cmd = Ack; + if (!receiveBlock(&cmd, &block) || cmd != Ack) { + errMsg += QLatin1String("Failed to get 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; + } + + if (!sendBlock(Ack, QByteArray())) + return false; + return true; +} + + +bool BaselineProtocol::requestBaseline(const QString &caseId, Command *response, QImage *baseline) +{ + errMsg.clear(); + if (!sendBlock(RequestBaseline, caseId.toLatin1())) + return false; + QByteArray imgData; + Command cmd; + if (!receiveBlock(&cmd, &imgData)) + return false; + if (response) + *response = cmd; + if (cmd == BaselineNotPresent || cmd == IgnoreCase) + return true; + else if (cmd == AcceptBaseline) { + if (baseline) { + // hm, get the name also, for checking? + *baseline = QImage::fromData(imgData); + if (baseline->isNull()) { + errMsg.prepend(QLatin1String("Invalid baseline image data received. ")); + return false; + } + } + return true; + } + errMsg.prepend(QLatin1String("Unexpected reply from server on baseline request. ")); + return false; +} + + +bool BaselineProtocol::submitNewBaseline(const QString &caseId, const QImage &baseline) +{ + errMsg.clear(); + QBuffer buf; + buf.open(QIODevice::WriteOnly); + QDataStream ds(&buf); + ds << caseId.toLatin1(); + if (!baseline.save(&buf, FileFormat)) { + errMsg = QLatin1String("Failed to convert new baseline image to ") + QLatin1String(FileFormat); + return false; + } + if (!sendBlock(AcceptNewBaseline, buf.data())) { + errMsg.prepend(QLatin1String("Failed to submit new baseline to server. ")); + return false; + } + Command cmd; + receiveBlock(&cmd, 0); // Just wait for the pong; ignore reply contents + return true; +} + + +bool BaselineProtocol::submitMismatch(const QString &caseId, const QImage &mismatch, QByteArray *failMsg) +{ + errMsg.clear(); + QBuffer buf; + buf.open(QIODevice::WriteOnly); + QDataStream ds(&buf); + ds << caseId.toLatin1(); + if (!mismatch.save(&buf, FileFormat)) { + errMsg = QLatin1String("Failed to convert mismatched image to ") + QLatin1String(FileFormat); + return false; + } + if (!sendBlock(AcceptMismatch, buf.data())) { + errMsg.prepend(QLatin1String("Failed to submit mismatched image to server. ")); + return false; + } + Command cmd; + if (!receiveBlock(&cmd, failMsg)) { + errMsg.prepend(QLatin1String("Failed to receive response on mismatched image from 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) { + // TBD: More resilient handling of this case; the server should accept client's version + errMsg = QLatin1String("Server protocol version mismatch, received:") + QString::number(rcvProtocolVersion); + + 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 += QLatin1String(" Socket state: ") + socket.errorString(); + return ret; +} diff --git a/tests/arthur/common/baselineprotocol.h b/tests/arthur/common/baselineprotocol.h new file mode 100644 index 0000000..8740413 --- /dev/null +++ b/tests/arthur/common/baselineprotocol.h @@ -0,0 +1,76 @@ +#ifndef BASELINEPROTOCOL_H +#define BASELINEPROTOCOL_H + +#include <QDataStream> +#include <QTcpSocket> + +#define FileFormat "png" + +struct PlatformInfo +{ +public: + PlatformInfo(bool useLocal = false); + + QString buildKey; + QString qtVersion; + QString hostname; +}; + + + +class BaselineProtocol +{ +public: + BaselineProtocol() {} + ~BaselineProtocol(); + + // **************************************************** + // Important constants here + // **************************************************** + enum Constant { + ProtocolVersion = 1, + ServerPort = 54129, + Timeout = 100000 + }; + + enum Command { + UnknownError = 0, + // Queries + AcceptPlatformInfo = 1, + RequestBaseline = 2, + AcceptNewBaseline = 3, + AcceptMismatch = 4, + // Responses + Ack = 128, + AcceptBaseline = 129, + BaselineNotPresent = 130, + IgnoreCase = 131 + }; + + // For client: + bool connect(); + bool requestBaseline(const QString &caseId, Command *response, QImage *baseline); + bool submitNewBaseline(const QString &caseId, const QImage &baseline); + bool submitMismatch(const QString &caseId, const QImage &mismatch, QByteArray *failMsg); + + // For server: + bool acceptConnection(PlatformInfo *pi); + + QString errorMessage(); + +private: + bool sendBlock(Command cmd, const QByteArray &block); + bool receiveBlock(Command *cmd, QByteArray *block); + QString errMsg; + QTcpSocket socket; + + friend class BaselineThread; + friend class BaselineHandler; +}; + + +QDataStream & operator<< (QDataStream &stream, const PlatformInfo &pinfo); + +QDataStream & operator>> (QDataStream& stream, PlatformInfo& pinfo); + +#endif // BASELINEPROTOCOL_H diff --git a/tests/arthur/common/baselineprotocol.pri b/tests/arthur/common/baselineprotocol.pri new file mode 100644 index 0000000..338339e --- /dev/null +++ b/tests/arthur/common/baselineprotocol.pri @@ -0,0 +1,9 @@ +INCLUDEPATH += $$PWD + +QT *= network + +SOURCES += \ + $$PWD/baselineprotocol.cpp + +HEADERS += \ + $$PWD/baselineprotocol.h |