summaryrefslogtreecommitdiffstats
path: root/tests/arthur
diff options
context:
space:
mode:
authoraavit <qt-info@nokia.com>2010-08-25 11:49:34 (GMT)
committeraavit <qt-info@nokia.com>2010-08-25 11:49:34 (GMT)
commitdeef2e16776c171d4b033257cd4fc3ab2bd005a3 (patch)
tree85ac87094fc690f71d20a6bd2989879b166992bf /tests/arthur
parentdae23694cb89b853785a5772cc7e0477f65ac5bf (diff)
downloadQt-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-xtests/arthur/baselineserver/bin/runserver6
-rw-r--r--tests/arthur/baselineserver/src/baselineserver.cpp188
-rw-r--r--tests/arthur/baselineserver/src/baselineserver.h74
-rw-r--r--tests/arthur/baselineserver/src/baselineserver.pro25
-rw-r--r--tests/arthur/baselineserver/src/main.cpp16
-rw-r--r--tests/arthur/common/baselineprotocol.cpp219
-rw-r--r--tests/arthur/common/baselineprotocol.h76
-rw-r--r--tests/arthur/common/baselineprotocol.pri9
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