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 
42 #include <QtTest/QtTest>
43 #include <paintcommands.h>
44 #include <QPainter>
45 #include <QLibraryInfo>
46 #include <baselineprotocol.h>
47 #include <QHash>
48 
49 #ifndef QT_NO_OPENGL
50 #include <QtOpenGL>
51 #endif
52 
53 #ifndef SRCDIR
54 #define SRCDIR "."
55 #endif
56 
57 class tst_Lancelot : public QObject
58 {
59 Q_OBJECT
60 
61 public:
62     tst_Lancelot();
63 
64     static bool simfail;
65     static PlatformInfo clientInfo;
66 
67 private:
68     enum GraphicsEngine {
69         Raster = 0,
70         OpenGL = 1
71     };
72 
73     bool setupTestSuite(const QStringList& blacklist);
74     void runTestSuite(GraphicsEngine engine, QImage::Format format);
75     ImageItem render(const ImageItem &item, GraphicsEngine engine, QImage::Format format);
76     void paint(QPaintDevice *device, const QStringList &script, const QString &filePath);
77 
78     BaselineProtocol proto;
79     ImageItemList baseList;
80     QHash<QString, QStringList> scripts;
81     bool dryRunMode;
82     QString scriptsDir;
83 
84 private slots:
85     void initTestCase();
cleanupTestCase()86     void cleanupTestCase() {}
87 
88     void testRasterARGB32PM_data();
89     void testRasterARGB32PM();
90     void testRasterRGB32_data();
91     void testRasterRGB32();
92     void testRasterRGB16_data();
93     void testRasterRGB16();
94 
95 #ifndef QT_NO_OPENGL
96     void testOpenGL_data();
97     void testOpenGL();
98 #endif
99 };
100 
101 bool tst_Lancelot::simfail = false;
102 PlatformInfo tst_Lancelot::clientInfo;
103 
tst_Lancelot()104 tst_Lancelot::tst_Lancelot()
105 {
106 }
107 
initTestCase()108 void tst_Lancelot::initTestCase()
109 {
110     // Check and setup the environment. We treat failures because of test environment
111     // (e.g. script files not found) as just warnings, and not QFAILs, to avoid false negatives
112     // caused by environment or server instability
113 
114 #if defined(Q_OS_SOMEPLATFORM)
115     QSKIP("This test is not supported on this platform.", SkipAll);
116 #endif
117     if (!proto.connect(QLatin1String("tst_Lancelot"), &dryRunMode, clientInfo))
118         QSKIP(qPrintable(proto.errorMessage()), SkipAll);
119 
120 #if defined(USE_RUNTIME_DIR)
121     scriptsDir = QCoreApplication::applicationDirPath() + "/scripts/";
122 #else
123     scriptsDir = SRCDIR "/scripts/";
124 #endif
125     QDir qpsDir(scriptsDir);
126     QStringList files = qpsDir.entryList(QStringList() << QLatin1String("*.qps"), QDir::Files | QDir::Readable);
127     if (files.isEmpty()) {
128         QWARN("No qps script files found in " + qpsDir.path().toLatin1());
129         QSKIP("Aborted due to errors.", SkipAll);
130     }
131 
132     baseList.resize(files.count());
133     ImageItemList::iterator it = baseList.begin();
134     foreach(const QString& fileName, files) {
135         QFile file(scriptsDir + fileName);
136         file.open(QFile::ReadOnly);
137         QByteArray cont = file.readAll();
138         scripts.insert(fileName, QString::fromLatin1(cont).split(QLatin1Char('\n'), QString::SkipEmptyParts));
139         it->itemName = fileName;
140         it->itemChecksum = qChecksum(cont.constData(), cont.size());
141         it++;
142     }
143 }
144 
145 
testRasterARGB32PM_data()146 void tst_Lancelot::testRasterARGB32PM_data()
147 {
148     QStringList localBlacklist;
149     if (!setupTestSuite(localBlacklist))
150         QSKIP("Communication with baseline image server failed.", SkipAll);
151 }
152 
153 
testRasterARGB32PM()154 void tst_Lancelot::testRasterARGB32PM()
155 {
156     runTestSuite(Raster, QImage::Format_ARGB32_Premultiplied);
157 }
158 
159 
testRasterRGB32_data()160 void tst_Lancelot::testRasterRGB32_data()
161 {
162     QStringList localBlacklist;
163     if (!setupTestSuite(localBlacklist))
164         QSKIP("Communication with baseline image server failed.", SkipAll);
165 }
166 
167 
testRasterRGB32()168 void tst_Lancelot::testRasterRGB32()
169 {
170     runTestSuite(Raster, QImage::Format_RGB32);
171 }
172 
173 
testRasterRGB16_data()174 void tst_Lancelot::testRasterRGB16_data()
175 {
176     QStringList localBlacklist;
177     if (!setupTestSuite(localBlacklist))
178         QSKIP("Communication with baseline image server failed.", SkipAll);
179 }
180 
181 
testRasterRGB16()182 void tst_Lancelot::testRasterRGB16()
183 {
184     runTestSuite(Raster, QImage::Format_RGB16);
185 }
186 
187 
188 #ifndef QT_NO_OPENGL
testOpenGL_data()189 void tst_Lancelot::testOpenGL_data()
190 {
191     QStringList localBlacklist = QStringList() << QLatin1String("rasterops.qps");
192     if (!setupTestSuite(localBlacklist))
193         QSKIP("Communication with baseline image server failed.", SkipAll);
194 }
195 
196 
testOpenGL()197 void tst_Lancelot::testOpenGL()
198 {
199     bool ok = false;
200     QGLWidget glWidget;
201     if (glWidget.isValid() && glWidget.format().directRendering()
202         && ((QGLFormat::openGLVersionFlags() & QGLFormat::OpenGL_Version_2_0)
203             || (QGLFormat::openGLVersionFlags() & QGLFormat::OpenGL_ES_Version_2_0))
204         && QGLFramebufferObject::hasOpenGLFramebufferObjects())
205     {
206         glWidget.makeCurrent();
207         if (!QByteArray((const char *)glGetString(GL_VERSION)).contains("Mesa"))
208             ok = true;
209     }
210     if (ok)
211         runTestSuite(OpenGL, QImage::Format_RGB32);
212     else
213         QSKIP("System under test does not meet preconditions for GL testing. Skipping.", SkipAll);
214 }
215 #endif
216 
217 
setupTestSuite(const QStringList & blacklist)218 bool tst_Lancelot::setupTestSuite(const QStringList& blacklist)
219 {
220     QTest::addColumn<ImageItem>("baseline");
221 
222     ImageItemList itemList(baseList);
223     if (!proto.requestBaselineChecksums(QTest::currentTestFunction(), &itemList)) {
224         QWARN(qPrintable(proto.errorMessage()));
225         return false;
226     }
227 
228     foreach(const ImageItem& item, itemList) {
229         if (!blacklist.contains(item.itemName))
230             QTest::newRow(item.itemName.toLatin1()) << item;
231     }
232     return true;
233 }
234 
235 
runTestSuite(GraphicsEngine engine,QImage::Format format)236 void tst_Lancelot::runTestSuite(GraphicsEngine engine, QImage::Format format)
237 {
238     QFETCH(ImageItem, baseline);
239 
240     if (baseline.status == ImageItem::IgnoreItem)
241         QSKIP("Blacklisted by baseline server.", SkipSingle);
242 
243     ImageItem rendered = render(baseline, engine, format);
244     static int consecutiveErrs = 0;
245     if (rendered.image.isNull()) {    // Assume an error in the test environment, not Qt
246         QWARN("Error: Failed to render image.");
247         if (++consecutiveErrs < 3) {
248             QSKIP("Aborted due to errors.", SkipSingle);
249         } else {
250             consecutiveErrs = 0;
251             QSKIP("Too many errors, skipping rest of testfunction.", SkipAll);
252         }
253     } else {
254         consecutiveErrs = 0;
255     }
256 
257 
258     if (baseline.status == ImageItem::BaselineNotFound) {
259         if (!proto.submitNewBaseline(rendered, 0))
260             QWARN("Failed to submit new baseline: " + proto.errorMessage().toLatin1());
261         QSKIP("Baseline not found; new baseline created.", SkipSingle);
262     }
263 
264     if (!baseline.imageChecksums.contains(rendered.imageChecksums.at(0))) {
265             QByteArray serverMsg;
266             if (!proto.submitMismatch(rendered, &serverMsg))
267                 serverMsg = "Failed to submit mismatching image to server.";
268             if (dryRunMode)
269                 qDebug() << "Dryrun mode, ignoring detected mismatch." << serverMsg;
270             else
271                 QFAIL("Rendered image differs from baseline. Report:\n   " + serverMsg);
272     }
273 }
274 
275 
render(const ImageItem & item,GraphicsEngine engine,QImage::Format format)276 ImageItem tst_Lancelot::render(const ImageItem &item, GraphicsEngine engine, QImage::Format format)
277 {
278     ImageItem res = item;
279     res.imageChecksums.clear();
280     res.image = QImage();
281     QString filePath = scriptsDir + item.itemName;
282     QStringList script = scripts.value(item.itemName);
283 
284     if (engine == Raster) {
285         QImage img(800, 800, format);
286         paint(&img, script, QFileInfo(filePath).absoluteFilePath()); // eh yuck (filePath stuff)
287         res.image = img;
288         res.imageChecksums.append(ImageItem::computeChecksum(img));
289 #ifndef QT_NO_OPENGL
290     } else if (engine == OpenGL) {
291         QGLWidget glWidget;
292         if (glWidget.isValid()) {
293             glWidget.makeCurrent();
294             QGLFramebufferObjectFormat fboFormat;
295             fboFormat.setSamples(16);
296             fboFormat.setAttachment(QGLFramebufferObject::CombinedDepthStencil);
297             QGLFramebufferObject fbo(800, 800, fboFormat);
298             paint(&fbo, script, QFileInfo(filePath).absoluteFilePath()); // eh yuck (filePath stuff)
299             res.image = fbo.toImage().convertToFormat(format);
300             res.imageChecksums.append(ImageItem::computeChecksum(res.image));
301         }
302 #endif
303     }
304 
305     return res;
306 }
307 
paint(QPaintDevice * device,const QStringList & script,const QString & filePath)308 void tst_Lancelot::paint(QPaintDevice *device, const QStringList &script, const QString &filePath)
309 {
310     QPainter p(device);
311     PaintCommands pcmd(script, 800, 800);
312     //pcmd.setShouldDrawText(false);
313     pcmd.setType(ImageType);
314     pcmd.setPainter(&p);
315     pcmd.setFilePath(filePath);
316     pcmd.runCommands();
317     p.end();
318 
319     if (simfail) {
320         QPainter p2(device);
321         p2.setPen(QPen(QBrush(Qt::cyan), 3, Qt::DashLine));
322         p2.drawLine(200, 200, 600, 600);
323         p2.drawLine(600, 200, 200, 600);
324         simfail = false;
325     }
326 }
327 
328 #define main rmain
QTEST_MAIN(tst_Lancelot)329 QTEST_MAIN(tst_Lancelot)
330 #undef main
331 
332 int main(int argc, char *argv[])
333 {
334     tst_Lancelot::clientInfo = PlatformInfo::localHostInfo();
335 
336     char *fargv[20];
337     int fargc = 0;
338     for (int i = 0; i < qMin(argc, 19); i++) {
339         if (!qstrcmp(argv[i], "-simfail")) {
340             tst_Lancelot::simfail = true;
341         } else if (!qstrcmp(argv[i], "-compareto") && i < argc-1) {
342             QString arg = QString::fromLocal8Bit(argv[++i]);
343             int split = arg.indexOf(QLC('='));
344             if (split < 0)
345                 continue;
346             QString key = arg.left(split).trimmed();
347             QString value = arg.mid(split+1).trimmed();
348             if (key.isEmpty() || value.isEmpty())
349                 continue;
350             tst_Lancelot::clientInfo.addOverride(key, value);
351         } else {
352             fargv[fargc++] = argv[i];
353         }
354     }
355     fargv[fargc] = 0;
356     return rmain(fargc, fargv);
357 }
358 
359 #include "tst_lancelot.moc"
360