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