1
/****************************************************************************
3
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
4
** Contact: http://www.qt-project.org/legal
6
** This file is part of Qt Creator.
8
** Commercial License Usage
9
** Licensees holding valid commercial Qt licenses may use this file in
10
** accordance with the commercial license agreement provided with the
11
** Software or, alternatively, in accordance with the terms contained in
12
** a written agreement between you and Digia. For licensing terms and
13
** conditions see http://qt.digia.com/licensing. For further information
14
** use the contact form at http://qt.digia.com/contact-us.
16
** GNU Lesser General Public License Usage
17
** Alternatively, this file may be used under the terms of the GNU Lesser
18
** General Public License version 2.1 as published by the Free Software
19
** Foundation and appearing in the file LICENSE.LGPL included in the
20
** packaging of this file. Please review the following information to
21
** ensure the GNU Lesser General Public License version 2.1 requirements
22
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24
** In addition, as a special exception, Digia gives you certain additional
25
** rights. These rights are described in the Digia Qt LGPL Exception
26
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28
****************************************************************************/
30
#include <qdeclarativetester.h>
31
#include <qdeclarativeview.h>
32
#include <QDeclarativeComponent>
33
#include <QGraphicsObject>
34
#include <QApplication>
38
#include <QCryptographicHash>
40
#ifndef NO_PRIVATE_HEADERS
41
#include <private/qabstractanimation_p.h>
42
#include <private/qdeclarativeitem_p.h>
47
extern Q_GUI_EXPORT bool qt_applefontsmoothing_enabled;
49
QDeclarativeTester::QDeclarativeTester(const QString &script, QDeclarativeViewer::ScriptOptions opts,
50
QDeclarativeView *parent)
51
: QAbstractAnimation(parent), m_script(script), m_view(parent), filterEvents(true), options(opts),
52
testscript(0), hasCompleted(false), hasFailed(false)
54
parent->viewport()->installEventFilter(this);
55
parent->installEventFilter(this);
56
#ifndef NO_PRIVATE_HEADERS
57
QUnifiedTimer::instance()->setConsistentTiming(true);
60
//Font antialiasing makes tests system-specific, so disable it
61
QFont noAA = QApplication::font();
62
noAA.setStyleStrategy(QFont::NoAntialias);
63
QApplication::setFont(noAA);
65
if (options & QDeclarativeViewer::Play)
70
QDeclarativeTester::~QDeclarativeTester()
73
options & QDeclarativeViewer::Record &&
74
options & QDeclarativeViewer::SaveOnExit)
78
int QDeclarativeTester::duration() const
83
void QDeclarativeTester::addMouseEvent(Destination dest, QMouseEvent *me)
90
void QDeclarativeTester::addKeyEvent(Destination dest, QKeyEvent *ke)
97
bool QDeclarativeTester::eventFilter(QObject *o, QEvent *e)
102
Destination destination;
105
} else if (o == m_view->viewport()) {
106
destination = ViewPort;
112
case QEvent::KeyPress:
113
case QEvent::KeyRelease:
114
addKeyEvent(destination, (QKeyEvent *)e);
116
case QEvent::MouseButtonPress:
117
case QEvent::MouseButtonRelease:
118
case QEvent::MouseMove:
119
case QEvent::MouseButtonDblClick:
120
addMouseEvent(destination, (QMouseEvent *)e);
128
void QDeclarativeTester::executefailure()
132
if (options & QDeclarativeViewer::ExitOnFailure)
136
void QDeclarativeTester::imagefailure()
140
if (options & QDeclarativeViewer::ExitOnFailure){
142
exit(hasFailed?-1:0);
146
void QDeclarativeTester::testSkip()
148
if (options & QDeclarativeViewer::TestSkipProperty){
149
QString e = m_view->rootObject()->property("skip").toString();
152
qWarning() << "Test failed, but skipping it: " << e;
154
qWarning() << "Test skipped: " << e;
161
void QDeclarativeTester::complete()
163
if ((options & QDeclarativeViewer::TestErrorProperty) && !hasFailed) {
164
QString e = m_view->rootObject()->property("error").toString();
166
qWarning() << "Test failed:" << e;
173
if (options & QDeclarativeViewer::ExitOnComplete)
174
QApplication::exit(hasFailed?-1:0);
180
if (options & QDeclarativeViewer::Play)
181
qWarning("Script playback complete");
184
void QDeclarativeTester::run()
186
QDeclarativeComponent c(m_view->engine(), m_script + QLatin1String(".qml"));
188
testscript = qobject_cast<QDeclarativeVisualTest *>(c.create());
189
if (testscript) testscript->setParent(this);
190
else { executefailure(); exit(-1); }
194
void QDeclarativeTester::save()
196
QString filename = m_script + QLatin1String(".qml");
197
QFileInfo filenameInfo(filename);
198
QDir saveDir = filenameInfo.absoluteDir();
201
QFile file(filename);
202
file.open(QIODevice::WriteOnly);
203
QTextStream ts(&file);
205
ts << "import Qt.VisualTest 4.7\n\n";
206
ts << "VisualTest {\n";
209
QList<KeyEvent> keyevents = m_savedKeyEvents;
210
QList<MouseEvent> mouseevents = m_savedMouseEvents;
211
for (int ii = 0; ii < m_savedFrameEvents.count(); ++ii) {
212
const FrameEvent &fe = m_savedFrameEvents.at(ii);
214
ts << " msec: " << fe.msec << "\n";
215
if (!fe.hash.isEmpty()) {
216
ts << " hash: \"" << fe.hash.toHex() << "\"\n";
217
} else if (!fe.image.isNull()) {
218
QString filename = filenameInfo.baseName() + QLatin1Char('.')
219
+ QString::number(imgCount) + ".png";
220
fe.image.save(m_script + QLatin1Char('.') + QString::number(imgCount) + ".png");
222
ts << " image: \"" << filename << "\"\n";
226
while (!mouseevents.isEmpty() &&
227
mouseevents.first().msec == fe.msec) {
228
MouseEvent me = mouseevents.takeFirst();
231
ts << " type: " << me.type << "\n";
232
ts << " button: " << me.button << "\n";
233
ts << " buttons: " << me.buttons << "\n";
234
ts << " x: " << me.pos.x() << "; y: " << me.pos.y() << "\n";
235
ts << " modifiers: " << me.modifiers << "\n";
236
if (me.destination == ViewPort)
237
ts << " sendToViewport: true\n";
241
while (!keyevents.isEmpty() &&
242
keyevents.first().msec == fe.msec) {
243
KeyEvent ke = keyevents.takeFirst();
246
ts << " type: " << ke.type << "\n";
247
ts << " key: " << ke.key << "\n";
248
ts << " modifiers: " << ke.modifiers << "\n";
249
ts << " text: \"" << ke.text.toUtf8().toHex() << "\"\n";
250
ts << " autorep: " << (ke.autorep?"true":"false") << "\n";
251
ts << " count: " << ke.count << "\n";
252
if (ke.destination == ViewPort)
253
ts << " sendToViewport: true\n";
262
void QDeclarativeTester::updateCurrentTime(int msec)
264
#ifndef NO_PRIVATE_HEADERS
265
QDeclarativeItemPrivate::setConsistentTime(msec);
267
if (!testscript && msec > 16 && options & QDeclarativeViewer::Snapshot)
270
QImage img(m_view->width(), m_view->height(), QImage::Format_RGB32);
272
if (options & QDeclarativeViewer::TestImages) {
273
img.fill(qRgb(255,255,255));
276
bool oldSmooth = qt_applefontsmoothing_enabled;
277
qt_applefontsmoothing_enabled = false;
281
qt_applefontsmoothing_enabled = oldSmooth;
287
bool snapshot = msec == 16 && (options & QDeclarativeViewer::Snapshot
288
|| (testscript && testscript->count() == 2));
292
if (msec == 0 || !(options & QDeclarativeViewer::TestImages)) {
293
// Skip first frame, skip if not doing images
294
} else if (0 == ((m_savedFrameEvents.count()-1) % 60) || snapshot) {
297
QCryptographicHash hash(QCryptographicHash::Md5);
298
hash.addData((const char *)img.bits(), img.bytesPerLine() * img.height());
299
fe.hash = hash.result();
301
m_savedFrameEvents.append(fe);
303
// Deliver mouse events
304
filterEvents = false;
307
for (int ii = 0; ii < m_mouseEvents.count(); ++ii) {
308
MouseEvent &me = m_mouseEvents[ii];
310
QMouseEvent event(me.type, me.pos, me.button, me.buttons, me.modifiers);
312
if (me.destination == View) {
313
QCoreApplication::sendEvent(m_view, &event);
315
QCoreApplication::sendEvent(m_view->viewport(), &event);
319
for (int ii = 0; ii < m_keyEvents.count(); ++ii) {
320
KeyEvent &ke = m_keyEvents[ii];
322
QKeyEvent event(ke.type, ke.key, ke.modifiers, ke.text, ke.autorep, ke.count);
324
if (ke.destination == View) {
325
QCoreApplication::sendEvent(m_view, &event);
327
QCoreApplication::sendEvent(m_view->viewport(), &event);
330
m_savedMouseEvents.append(m_mouseEvents);
331
m_savedKeyEvents.append(m_keyEvents);
334
m_mouseEvents.clear();
337
// Advance test script
338
while (testscript && testscript->count() > testscriptidx) {
340
QObject *event = testscript->event(testscriptidx);
342
if (QDeclarativeVisualTestFrame *frame = qobject_cast<QDeclarativeVisualTestFrame *>(event)) {
343
if (frame->msec() < msec) {
344
if (options & QDeclarativeViewer::TestImages && !(options & QDeclarativeViewer::Record)) {
345
qWarning() << "QDeclarativeTester(" << m_script << "): Extra frame. Seen:"
346
<< msec << "Expected:" << frame->msec();
349
} else if (frame->msec() == msec) {
350
if (!frame->hash().isEmpty() && frame->hash().toUtf8() != fe.hash.toHex()) {
351
if (options & QDeclarativeViewer::TestImages && !(options & QDeclarativeViewer::Record)) {
352
qWarning() << "QDeclarativeTester(" << m_script << "): Mismatched frame hash at" << msec
353
<< ". Seen:" << fe.hash.toHex()
354
<< "Expected:" << frame->hash().toUtf8();
358
} else if (frame->msec() > msec) {
362
if (options & QDeclarativeViewer::TestImages && !(options & QDeclarativeViewer::Record) && !frame->image().isEmpty()) {
363
QImage goodImage(frame->image().toLocalFile());
364
if (frame->msec() == 16 && goodImage.size() != img.size()){
365
//Also an image mismatch, but this warning is more informative. Only checked at start though.
366
qWarning() << "QDeclarativeTester(" << m_script << "): Size mismatch. This test must be run at " << goodImage.size();
369
if (goodImage != img) {
370
QString reject(frame->image().toLocalFile() + ".reject.png");
371
qWarning() << "QDeclarativeTester(" << m_script << "): Image mismatch. Reject saved to:"
374
bool doDiff = (goodImage.size() == img.size());
376
QImage diffimg(m_view->width(), m_view->height(), QImage::Format_RGB32);
377
diffimg.fill(qRgb(255,255,255));
378
QPainter p(&diffimg);
380
for (int x = 0; x < img.width(); ++x) {
381
for (int y = 0; y < img.height(); ++y) {
382
if (goodImage.pixel(x,y) != img.pixel(x,y)) {
388
QString diff(frame->image().toLocalFile() + ".diff.png");
390
qWarning().nospace() << " Diff (" << diffCount << " pixels differed) saved to: " << diff;
395
} else if (QDeclarativeVisualTestMouse *mouse = qobject_cast<QDeclarativeVisualTestMouse *>(event)) {
396
QPoint pos(mouse->x(), mouse->y());
397
QPoint globalPos = m_view->mapToGlobal(QPoint(0, 0)) + pos;
398
QMouseEvent event((QEvent::Type)mouse->type(), pos, globalPos, (Qt::MouseButton)mouse->button(), (Qt::MouseButtons)mouse->buttons(), (Qt::KeyboardModifiers)mouse->modifiers());
400
MouseEvent me(&event);
402
if (!mouse->sendToViewport()) {
403
QCoreApplication::sendEvent(m_view, &event);
404
me.destination = View;
406
QCoreApplication::sendEvent(m_view->viewport(), &event);
407
me.destination = ViewPort;
409
m_savedMouseEvents.append(me);
410
} else if (QDeclarativeVisualTestKey *key = qobject_cast<QDeclarativeVisualTestKey *>(event)) {
412
QKeyEvent event((QEvent::Type)key->type(), key->key(), (Qt::KeyboardModifiers)key->modifiers(), QString::fromUtf8(QByteArray::fromHex(key->text().toUtf8())), key->autorep(), key->count());
416
if (!key->sendToViewport()) {
417
QCoreApplication::sendEvent(m_view, &event);
418
ke.destination = View;
420
QCoreApplication::sendEvent(m_view->viewport(), &event);
421
ke.destination = ViewPort;
423
m_savedKeyEvents.append(ke);
430
if (testscript && testscript->count() <= testscriptidx) {
431
//if (msec == 16) //for a snapshot, leave it up long enough to see
437
void QDeclarativeTester::registerTypes()
439
qmlRegisterType<QDeclarativeVisualTest>("Qt.VisualTest", 4,7, "VisualTest");
440
qmlRegisterType<QDeclarativeVisualTestFrame>("Qt.VisualTest", 4,7, "Frame");
441
qmlRegisterType<QDeclarativeVisualTestMouse>("Qt.VisualTest", 4,7, "Mouse");
442
qmlRegisterType<QDeclarativeVisualTestKey>("Qt.VisualTest", 4,7, "Key");