1 /****************************************************************************
2 **
3 ** Copyright (C) 2015 The Qt Company Ltd.
4 ** Contact: http://www.qt.io/licensing/
5 **
6 ** This file is part of the test suite of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see http://www.qt.io/terms-conditions. For further
15 ** information use the contact form at http://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 or version 3 as published by the Free
20 ** Software Foundation and appearing in the file LICENSE.LGPLv21 and
21 ** LICENSE.LGPLv3 included in the packaging of this file. Please review the
22 ** following information to ensure the GNU Lesser General Public License
23 ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
24 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 **
26 ** As a special exception, The Qt Company gives you certain additional
27 ** rights. These rights are described in The Qt Company LGPL Exception
28 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 **
30 ** GNU General Public License Usage
31 ** Alternatively, this file may be used under the terms of the GNU
32 ** General Public License version 3.0 as published by the Free Software
33 ** Foundation and appearing in the file LICENSE.GPL included in the
34 ** packaging of this file. Please review the following information to
35 ** ensure the GNU General Public License version 3.0 requirements will be
36 ** met: http://www.gnu.org/copyleft/gpl.html.
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41 #include "baselineprotocol.h"
42 #include <QLibraryInfo>
43 #include <QImage>
44 #include <QBuffer>
45 #include <QHostInfo>
46 #include <QSysInfo>
47 #include <QProcess>
48 #include <QFileInfo>
49 #include <QDir>
50 #include <QTime>
51 #include <QPointer>
52
53 const QString PI_TestCase(QLS("TestCase"));
54 const QString PI_HostName(QLS("HostName"));
55 const QString PI_HostAddress(QLS("HostAddress"));
56 const QString PI_OSName(QLS("OSName"));
57 const QString PI_OSVersion(QLS("OSVersion"));
58 const QString PI_QtVersion(QLS("QtVersion"));
59 const QString PI_BuildKey(QLS("BuildKey"));
60 const QString PI_GitCommit(QLS("GitCommit"));
61 const QString PI_QMakeSpec(QLS("QMakeSpec"));
62 const QString PI_PulseGitBranch(QLS("PulseGitBranch"));
63 const QString PI_PulseTestrBranch(QLS("PulseTestrBranch"));
64
65 #ifndef QMAKESPEC
66 #define QMAKESPEC "Unknown"
67 #endif
68
69 #if defined(Q_OS_WIN)
70 #include <QtCore/qt_windows.h>
71 #endif
72 #if defined(Q_OS_UNIX)
73 #include <time.h>
74 #endif
sysSleep(int ms)75 void BaselineProtocol::sysSleep(int ms)
76 {
77 #if defined(Q_OS_WIN)
78 Sleep(DWORD(ms));
79 #else
80 struct timespec ts = { ms / 1000, (ms % 1000) * 1000 * 1000 };
81 nanosleep(&ts, NULL);
82 #endif
83 }
84
PlatformInfo()85 PlatformInfo::PlatformInfo()
86 : QMap<QString, QString>(), adHoc(true)
87 {
88 }
89
localHostInfo()90 PlatformInfo PlatformInfo::localHostInfo()
91 {
92 PlatformInfo pi;
93 pi.insert(PI_HostName, QHostInfo::localHostName());
94 pi.insert(PI_QtVersion, QLS(qVersion()));
95 pi.insert(PI_QMakeSpec, QString(QLS(QMAKESPEC)).remove(QRegExp(QLS("^.*mkspecs/"))));
96 pi.insert(PI_BuildKey, QLibraryInfo::buildKey());
97 #if defined(Q_OS_LINUX)
98 pi.insert(PI_OSName, QLS("Linux"));
99 QProcess uname;
100 uname.start(QLS("uname"), QStringList() << QLS("-r"));
101 if (uname.waitForFinished(3000))
102 pi.insert(PI_OSVersion, QString::fromLocal8Bit(uname.readAllStandardOutput().constData()).simplified());
103 #elif defined(Q_OS_WINCE)
104 pi.insert(PI_OSName, QLS("WinCE"));
105 pi.insert(PI_OSVersion, QString::number(QSysInfo::windowsVersion()));
106 #elif defined(Q_OS_WIN)
107 pi.insert(PI_OSName, QLS("Windows"));
108 pi.insert(PI_OSVersion, QString::number(QSysInfo::windowsVersion()));
109 #elif defined(Q_OS_MAC)
110 pi.insert(PI_OSName, QLS("MacOS"));
111 pi.insert(PI_OSVersion, QString::number(qMacVersion()));
112 #elif defined(Q_OS_SYMBIAN)
113 pi.insert(PI_OSName, QLS("Symbian"));
114 pi.insert(PI_OSVersion, QString::number(QSysInfo::symbianVersion());
115 #else
116 pi.insert(PI_OSName, QLS("Other"));
117 #endif
118
119 QProcess git;
120 QString cmd;
121 QStringList args;
122 #if defined(Q_OS_WIN)
123 cmd = QLS("cmd.exe");
124 args << QLS("/c") << QLS("git");
125 #else
126 cmd = QLS("git");
127 #endif
128 args << QLS("log") << QLS("--max-count=1") << QLS("--pretty=%H [%an] [%ad] %s");
129 git.start(cmd, args);
130 git.waitForFinished(3000);
131 if (!git.exitCode())
132 pi.insert(PI_GitCommit, QString::fromLocal8Bit(git.readAllStandardOutput().constData()).simplified());
133 else
134 pi.insert(PI_GitCommit, QLS("Unknown"));
135
136 QByteArray gb = qgetenv("PULSE_GIT_BRANCH");
137 if (!gb.isEmpty()) {
138 pi.insert(PI_PulseGitBranch, QString::fromLatin1(gb));
139 pi.setAdHocRun(false);
140 }
141 QByteArray tb = qgetenv("PULSE_TESTR_BRANCH");
142 if (!tb.isEmpty()) {
143 pi.insert(PI_PulseTestrBranch, QString::fromLatin1(tb));
144 pi.setAdHocRun(false);
145 }
146 if (!qgetenv("JENKINS_HOME").isEmpty()) {
147 pi.setAdHocRun(false);
148 gb = qgetenv("GIT_BRANCH");
149 if (!gb.isEmpty()) {
150 // FIXME: the string "Pulse" should be eliminated, since that is not the used tool.
151 pi.insert(PI_PulseGitBranch, QString::fromLatin1(gb));
152 }
153 }
154
155 return pi;
156 }
157
158
159 PlatformInfo::PlatformInfo(const PlatformInfo &other)
160 : QMap<QString, QString>(other)
161 {
162 orides = other.orides;
163 adHoc = other.adHoc;
164 }
165
166
167 PlatformInfo &PlatformInfo::operator=(const PlatformInfo &other)
168 {
169 QMap<QString, QString>::operator=(other);
170 orides = other.orides;
171 adHoc = other.adHoc;
172 return *this;
173 }
174
175
176 void PlatformInfo::addOverride(const QString& key, const QString& value)
177 {
178 orides.append(key);
179 orides.append(value);
180 }
181
182
183 QStringList PlatformInfo::overrides() const
184 {
185 return orides;
186 }
187
188
189 void PlatformInfo::setAdHocRun(bool isAdHoc)
190 {
191 adHoc = isAdHoc;
192 }
193
194
195 bool PlatformInfo::isAdHocRun() const
196 {
197 return adHoc;
198 }
199
200
201 QDataStream & operator<< (QDataStream &stream, const PlatformInfo &pi)
202 {
203 stream << static_cast<const QMap<QString, QString>&>(pi);
204 stream << pi.orides << pi.adHoc;
205 return stream;
206 }
207
208
209 QDataStream & operator>> (QDataStream &stream, PlatformInfo &pi)
210 {
211 stream >> static_cast<QMap<QString, QString>&>(pi);
212 stream >> pi.orides >> pi.adHoc;
213 return stream;
214 }
215
216
217 ImageItem &ImageItem::operator=(const ImageItem &other)
218 {
219 testFunction = other.testFunction;
220 itemName = other.itemName;
221 itemChecksum = other.itemChecksum;
222 status = other.status;
223 image = other.image;
224 imageChecksums = other.imageChecksums;
225 return *this;
226 }
227
228 // Defined in lookup3.c:
229 void hashword2 (
230 const quint32 *k, /* the key, an array of quint32 values */
231 size_t length, /* the length of the key, in quint32s */
232 quint32 *pc, /* IN: seed OUT: primary hash value */
233 quint32 *pb); /* IN: more seed OUT: secondary hash value */
234
235 quint64 ImageItem::computeChecksum(const QImage &image)
236 {
237 QImage img(image);
238 const int bpl = img.bytesPerLine();
239 const int padBytes = bpl - (img.width() * img.depth() / 8);
240 if (padBytes) {
241 uchar *p = img.bits() + bpl - padBytes;
242 const int h = img.height();
243 for (int y = 0; y < h; ++y) {
244 qMemSet(p, 0, padBytes);
245 p += bpl;
246 }
247 }
248
249 quint32 h1 = 0xfeedbacc;
250 quint32 h2 = 0x21604894;
251 hashword2((const quint32 *)img.constBits(), img.byteCount()/4, &h1, &h2);
252 return (quint64(h1) << 32) | h2;
253 }
254
255 #if 0
256 QString ImageItem::engineAsString() const
257 {
258 switch (engine) {
259 case Raster:
260 return QLS("Raster");
261 break;
262 case OpenGL:
263 return QLS("OpenGL");
264 break;
265 default:
266 break;
267 }
268 return QLS("Unknown");
269 }
270
271 QString ImageItem::formatAsString() const
272 {
273 static const int numFormats = 16;
274 static const char *formatNames[numFormats] = {
275 "Invalid",
276 "Mono",
277 "MonoLSB",
278 "Indexed8",
279 "RGB32",
280 "ARGB32",
281 "ARGB32-Premult",
282 "RGB16",
283 "ARGB8565-Premult",
284 "RGB666",
285 "ARGB6666-Premult",
286 "RGB555",
287 "ARGB8555-Premult",
288 "RGB888",
289 "RGB444",
290 "ARGB4444-Premult"
291 };
292 if (renderFormat < 0 || renderFormat >= numFormats)
293 return QLS("UnknownFormat");
294 return QLS(formatNames[renderFormat]);
295 }
296 #endif
297
298 void ImageItem::writeImageToStream(QDataStream &out) const
299 {
300 if (image.isNull() || image.format() == QImage::Format_Invalid) {
301 out << quint8(0);
302 return;
303 }
304 out << quint8('Q') << quint8(image.format());
305 out << quint8(QSysInfo::ByteOrder) << quint8(0); // pad to multiple of 4 bytes
306 out << quint32(image.width()) << quint32(image.height()) << quint32(image.bytesPerLine());
307 out << qCompress((const uchar *)image.constBits(), image.byteCount());
308 //# can be followed by colormap for formats that use it
309 }
310
311 void ImageItem::readImageFromStream(QDataStream &in)
312 {
313 quint8 hdr, fmt, endian, pad;
314 quint32 width, height, bpl;
315 QByteArray data;
316
317 in >> hdr;
318 if (hdr != 'Q') {
319 image = QImage();
320 return;
321 }
322 in >> fmt >> endian >> pad;
323 if (!fmt || fmt >= QImage::NImageFormats) {
324 image = QImage();
325 return;
326 }
327 if (endian != QSysInfo::ByteOrder) {
328 qWarning("ImageItem cannot read streamed image with different endianness");
329 image = QImage();
330 return;
331 }
332 in >> width >> height >> bpl;
333 in >> data;
334 data = qUncompress(data);
335 QImage res((const uchar *)data.constData(), width, height, bpl, QImage::Format(fmt));
336 image = res.copy(); //# yuck, seems there is currently no way to avoid data copy
337 }
338
339 QDataStream & operator<< (QDataStream &stream, const ImageItem &ii)
340 {
341 stream << ii.testFunction << ii.itemName << ii.itemChecksum << quint8(ii.status) << ii.imageChecksums << ii.misc;
342 ii.writeImageToStream(stream);
343 return stream;
344 }
345
346 QDataStream & operator>> (QDataStream &stream, ImageItem &ii)
347 {
348 quint8 encStatus;
349 stream >> ii.testFunction >> ii.itemName >> ii.itemChecksum >> encStatus >> ii.imageChecksums >> ii.misc;
350 ii.status = ImageItem::ItemStatus(encStatus);
351 ii.readImageFromStream(stream);
352 return stream;
353 }
354
355 BaselineProtocol::BaselineProtocol()
356 {
357 }
358
359 BaselineProtocol::~BaselineProtocol()
360 {
361 socket.close();
362 if (socket.state() != QTcpSocket::UnconnectedState)
363 socket.waitForDisconnected(Timeout);
364 }
365
366
367 bool BaselineProtocol::connect(const QString &testCase, bool *dryrun, const PlatformInfo& clientInfo)
368 {
369 errMsg.clear();
370 QByteArray serverName(qgetenv("QT_LANCELOT_SERVER"));
371 if (serverName.isNull())
372 serverName = "lancelot.test.qt.nokia.com";
373
374 socket.connectToHost(serverName, ServerPort);
375 if (!socket.waitForConnected(Timeout)) {
376 sysSleep(Timeout); // Wait a bit and try again, the server might just be restarting
377 if (!socket.waitForConnected(Timeout)) {
378 errMsg += QLS("TCP connectToHost failed. Host:") + serverName + QLS(" port:") + QString::number(ServerPort);
379 return false;
380 }
381 }
382
383 PlatformInfo pi = clientInfo.isEmpty() ? PlatformInfo::localHostInfo() : clientInfo;
384 pi.insert(PI_TestCase, testCase);
385 QByteArray block;
386 QDataStream ds(&block, QIODevice::ReadWrite);
387 ds << pi;
388 if (!sendBlock(AcceptPlatformInfo, block)) {
389 errMsg += QLS("Failed to send data to server.");
390 return false;
391 }
392
393 Command cmd = UnknownError;
394 if (!receiveBlock(&cmd, &block)) {
395 errMsg.prepend(QLS("Failed to get response from server. "));
396 return false;
397 }
398
399 if (cmd == Abort) {
400 errMsg += QLS("Server rejected connection. Reason: ") + QString::fromLatin1(block);
401 return false;
402 }
403
404 if (dryrun)
405 *dryrun = (cmd == DoDryRun);
406
407 if (cmd != Ack && cmd != DoDryRun) {
408 errMsg += QLS("Unexpected response from server.");
409 return false;
410 }
411
412 return true;
413 }
414
415
416 bool BaselineProtocol::acceptConnection(PlatformInfo *pi)
417 {
418 errMsg.clear();
419
420 QByteArray block;
421 Command cmd = AcceptPlatformInfo;
422 if (!receiveBlock(&cmd, &block) || cmd != AcceptPlatformInfo)
423 return false;
424
425 if (pi) {
426 QDataStream ds(block);
427 ds >> *pi;
428 pi->insert(PI_HostAddress, socket.peerAddress().toString());
429 }
430
431 return true;
432 }
433
434
435 bool BaselineProtocol::requestBaselineChecksums(const QString &testFunction, ImageItemList *itemList)
436 {
437 errMsg.clear();
438 if (!itemList)
439 return false;
440
441 for(ImageItemList::iterator it = itemList->begin(); it != itemList->end(); it++)
442 it->testFunction = testFunction;
443
444 QByteArray block;
445 QDataStream ds(&block, QIODevice::WriteOnly);
446 ds << *itemList;
447 if (!sendBlock(RequestBaselineChecksums, block))
448 return false;
449
450 Command cmd;
451 QByteArray rcvBlock;
452 if (!receiveBlock(&cmd, &rcvBlock) || cmd != BaselineProtocol::Ack)
453 return false;
454 QDataStream rds(&rcvBlock, QIODevice::ReadOnly);
455 rds >> *itemList;
456 return true;
457 }
458
459
460 bool BaselineProtocol::submitNewBaseline(const ImageItem &item, QByteArray *serverMsg)
461 {
462 Command cmd;
463 return (sendItem(AcceptNewBaseline, item) && receiveBlock(&cmd, serverMsg) && cmd == Ack);
464 }
465
466
467 bool BaselineProtocol::submitMismatch(const ImageItem &item, QByteArray *serverMsg)
468 {
469 Command cmd;
470 return (sendItem(AcceptMismatch, item) && receiveBlock(&cmd, serverMsg) && cmd == Ack);
471 }
472
473
474 bool BaselineProtocol::sendItem(Command cmd, const ImageItem &item)
475 {
476 errMsg.clear();
477 QBuffer buf;
478 buf.open(QIODevice::WriteOnly);
479 QDataStream ds(&buf);
480 ds << item;
481 if (!sendBlock(cmd, buf.data())) {
482 errMsg.prepend(QLS("Failed to submit image to server. "));
483 return false;
484 }
485 return true;
486 }
487
488
489 bool BaselineProtocol::sendBlock(Command cmd, const QByteArray &block)
490 {
491 QDataStream s(&socket);
492 // TBD: set qds version as a constant
493 s << quint16(ProtocolVersion) << quint16(cmd);
494 s.writeBytes(block.constData(), block.size());
495 return true;
496 }
497
498
499 bool BaselineProtocol::receiveBlock(Command *cmd, QByteArray *block)
500 {
501 while (socket.bytesAvailable() < int(2*sizeof(quint16) + sizeof(quint32))) {
502 if (!socket.waitForReadyRead(Timeout))
503 return false;
504 }
505 QDataStream ds(&socket);
506 quint16 rcvProtocolVersion, rcvCmd;
507 ds >> rcvProtocolVersion >> rcvCmd;
508 if (rcvProtocolVersion != ProtocolVersion) {
509 errMsg = QLS("Baseline protocol version mismatch, received:") + QString::number(rcvProtocolVersion)
510 + QLS(" expected:") + QString::number(ProtocolVersion);
511 return false;
512 }
513 if (cmd)
514 *cmd = Command(rcvCmd);
515
516 QByteArray uMsg;
517 quint32 remaining;
518 ds >> remaining;
519 uMsg.resize(remaining);
520 int got = 0;
521 char* uMsgBuf = uMsg.data();
522 do {
523 got = ds.readRawData(uMsgBuf, remaining);
524 remaining -= got;
525 uMsgBuf += got;
526 } while (remaining && got >= 0 && socket.waitForReadyRead(Timeout));
527
528 if (got < 0)
529 return false;
530
531 if (block)
532 *block = uMsg;
533
534 return true;
535 }
536
537
538 QString BaselineProtocol::errorMessage()
539 {
540 QString ret = errMsg;
541 if (socket.error() >= 0)
542 ret += QLS(" Socket state: ") + socket.errorString();
543 return ret;
544 }
545
546