summaryrefslogtreecommitdiffstats
path: root/tests/arthur/baselineserver/src/baselineserver.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tests/arthur/baselineserver/src/baselineserver.cpp')
-rw-r--r--tests/arthur/baselineserver/src/baselineserver.cpp188
1 files changed, 188 insertions, 0 deletions
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