blob: f3a6fea1933c2be52eaf1240110cc957ccabdecc [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2017 Crimson AS <info@crimson.no>
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "quicktestresult_p.h"
#include "quicktest.h"
#include <QtTest/qtestcase.h>
#include <QtTest/qtestsystem.h>
#include <QtTest/private/qtestblacklist_p.h>
#include <QtTest/private/qtestresult_p.h>
#include <QtTest/private/qtesttable_p.h>
#include <QtTest/private/qtestlog_p.h>
#include "qtestoptions_p.h"
#include <QtTest/qbenchmark.h>
// qbenchmark_p.h pulls windows.h via 3rd party; prevent it from defining
// the min/max macros which would clash with qnumeric_p.h's usage of min()/max().
#if defined(Q_OS_WIN32) && !defined(NOMINMAX)
# define NOMINMAX
#endif
#include <QtTest/private/qbenchmark_p.h>
#include <QtCore/qset.h>
#include <QtCore/qmap.h>
#include <QtCore/qbytearray.h>
#include <QtCore/qcoreapplication.h>
#include <QtCore/qdatetime.h>
#include <QtCore/qdebug.h>
#include <QtCore/QUrl>
#include <QtCore/QDir>
#if QT_CONFIG(regularexpression)
#include <QtCore/qregularexpression.h>
#endif
#include <QtQuick/qquickwindow.h>
#include <QtGui/qvector3d.h>
#include <QtGui/qimagewriter.h>
#include <QtQml/private/qqmlglobal_p.h>
#include <QtQml/QQmlEngine>
#include <QtQml/QQmlContext>
#include <private/qv4qobjectwrapper_p.h>
#include <algorithm>
QT_BEGIN_NAMESPACE
static const char *globalProgramName = nullptr;
static bool loggingStarted = false;
static QBenchmarkGlobalData globalBenchmarkData;
extern bool qWaitForSignal(QObject *obj, const char* signal, int timeout = 5000);
class Q_QUICK_TEST_EXPORT QuickTestImageObject : public QObject
{
Q_OBJECT
Q_PROPERTY(int width READ width CONSTANT)
Q_PROPERTY(int height READ height CONSTANT)
Q_PROPERTY(QSize size READ size CONSTANT)
public:
QuickTestImageObject(const QImage& img, QObject *parent = nullptr)
: QObject(parent)
, m_image(img)
{
}
~QuickTestImageObject() {}
public Q_SLOTS:
int red(int x, int y) const
{
return pixel(x, y).value<QColor>().red();
}
int green(int x, int y) const
{
return pixel(x, y).value<QColor>().green();
}
int blue(int x, int y) const
{
return pixel(x, y).value<QColor>().blue();
}
int alpha(int x, int y) const
{
return pixel(x, y).value<QColor>().alpha();
}
QVariant pixel(int x, int y) const
{
if (m_image.isNull()
|| x >= m_image.width()
|| y >= m_image.height()
|| x < 0
|| y < 0
|| x * y >= m_image.width() * m_image.height())
return QVariant();
return QColor::fromRgba(m_image.pixel(QPoint(x, y)));
}
bool equals(QuickTestImageObject *other) const
{
if (!other)
return m_image.isNull();
return m_image == other->m_image;
}
void save(const QString &filePath)
{
QImageWriter writer(filePath);
if (!writer.write(m_image)) {
QQmlEngine *engine = qmlContext(this)->engine();
QV4::ExecutionEngine *v4 = engine->handle();
v4->throwError(QStringLiteral("Can't save to %1: %2").arg(filePath, writer.errorString()));
}
}
public:
int width() const
{
return m_image.width();
}
int height() const
{
return m_image.height();
}
QSize size() const
{
return m_image.size();
}
private:
QImage m_image;
};
class QuickTestResultPrivate
{
public:
QuickTestResultPrivate()
: table(nullptr)
, benchmarkIter(nullptr)
, benchmarkData(nullptr)
, iterCount(0)
{
}
~QuickTestResultPrivate()
{
delete table;
delete benchmarkIter;
delete benchmarkData;
}
QByteArray intern(const QString &str);
QString testCaseName;
QString functionName;
QSet<QByteArray> internedStrings;
QTestTable *table;
QTest::QBenchmarkIterationController *benchmarkIter;
QBenchmarkTestMethodData *benchmarkData;
int iterCount;
QList<QBenchmarkResult> results;
};
QByteArray QuickTestResultPrivate::intern(const QString &str)
{
QByteArray bstr = str.toUtf8();
return *(internedStrings.insert(bstr));
}
QuickTestResult::QuickTestResult(QObject *parent)
: QObject(parent), d_ptr(new QuickTestResultPrivate)
{
if (!QBenchmarkGlobalData::current)
QBenchmarkGlobalData::current = &globalBenchmarkData;
}
QuickTestResult::~QuickTestResult()
{
}
/*!
\qmlproperty string TestResult::testCaseName
This property defines the name of current TestCase element
that is running test cases.
\sa functionName
*/
QString QuickTestResult::testCaseName() const
{
Q_D(const QuickTestResult);
return d->testCaseName;
}
void QuickTestResult::setTestCaseName(const QString &name)
{
Q_D(QuickTestResult);
d->testCaseName = name;
emit testCaseNameChanged();
}
/*!
\qmlproperty string TestResult::functionName
This property defines the name of current test function
within a TestCase element that is running. If this string is
empty, then no function is currently running.
\sa testCaseName
*/
QString QuickTestResult::functionName() const
{
Q_D(const QuickTestResult);
return d->functionName;
}
void QuickTestResult::setFunctionName(const QString &name)
{
Q_D(QuickTestResult);
if (!name.isEmpty()) {
if (d->testCaseName.isEmpty()) {
QTestResult::setCurrentTestFunction
(d->intern(name).constData());
} else {
QString fullName = d->testCaseName + QLatin1String("::") + name;
QTestResult::setCurrentTestFunction
(d->intern(fullName).constData());
QTestPrivate::checkBlackLists(fullName.toUtf8().constData(), nullptr);
}
} else {
QTestResult::setCurrentTestFunction(nullptr);
}
d->functionName = name;
emit functionNameChanged();
}
/*!
\qmlproperty string TestResult::dataTag
This property defines the tag for the current row in a
data-driven test, or an empty string if not a data-driven test.
*/
QString QuickTestResult::dataTag() const
{
const char *tag = QTestResult::currentDataTag();
if (tag)
return QString::fromUtf8(tag);
else
return QString();
}
void QuickTestResult::setDataTag(const QString &tag)
{
if (!tag.isEmpty()) {
QTestData *data = &(QTest::newRow(tag.toUtf8().constData()));
QTestResult::setCurrentTestData(data);
QTestPrivate::checkBlackLists((testCaseName() + QLatin1String("::") + functionName()).toUtf8().constData(), tag.toUtf8().constData());
emit dataTagChanged();
} else {
QTestResult::setCurrentTestData(nullptr);
}
}
/*!
\qmlproperty bool TestResult::failed
This property returns true if the current test function (or
current test data row for a data-driven test) has failed;
false otherwise. The fail state is reset when functionName
is changed or finishTestDataCleanup() is called.
\sa skipped
*/
bool QuickTestResult::isFailed() const
{
return QTestResult::currentTestFailed();
}
/*!
\qmlproperty bool TestResult::skipped
This property returns true if the current test function was
marked as skipped; false otherwise.
\sa failed
*/
bool QuickTestResult::isSkipped() const
{
return QTestResult::skipCurrentTest();
}
void QuickTestResult::setSkipped(bool skip)
{
QTestResult::setSkipCurrentTest(skip);
if (!skip)
QTestResult::setBlacklistCurrentTest(false);
emit skippedChanged();
}
/*!
\qmlproperty int TestResult::passCount
This property returns the number of tests that have passed.
\sa failCount, skipCount
*/
int QuickTestResult::passCount() const
{
return QTestLog::passCount();
}
/*!
\qmlproperty int TestResult::failCount
This property returns the number of tests that have failed.
\sa passCount, skipCount
*/
int QuickTestResult::failCount() const
{
return QTestLog::failCount();
}
/*!
\qmlproperty int TestResult::skipCount
This property returns the number of tests that have been skipped.
\sa passCount, failCount
*/
int QuickTestResult::skipCount() const
{
return QTestLog::skipCount();
}
/*!
\qmlproperty list<string> TestResult::functionsToRun
This property returns the list of function names to be run.
*/
QStringList QuickTestResult::functionsToRun() const
{
return QTest::testFunctions;
}
/*!
\qmlproperty list<string> TestResult::tagsToRun
This property returns the list of test function's data tags to be run
*/
QStringList QuickTestResult::tagsToRun() const
{
return QTest::testTags;
}
/*!
\qmlmethod TestResult::reset()
Resets all pass/fail/skip counters and prepare for testing.
*/
void QuickTestResult::reset()
{
if (!globalProgramName) // Only if run via qmlviewer.
QTestResult::reset();
}
/*!
\qmlmethod TestResult::startLogging()
Starts logging to the test output stream and writes the
test header.
\sa stopLogging()
*/
void QuickTestResult::startLogging()
{
// The program name is used for logging headers and footers if it
// is set. Otherwise the test case name is used.
if (loggingStarted)
return;
QTestLog::startLogging();
loggingStarted = true;
}
/*!
\qmlmethod TestResult::stopLogging()
Writes the test footer to the test output stream and then stops logging.
\sa startLogging()
*/
void QuickTestResult::stopLogging()
{
Q_D(QuickTestResult);
if (globalProgramName)
return; // Logging will be stopped by setProgramName(0).
QTestResult::setCurrentTestObject(d->intern(d->testCaseName).constData());
QTestLog::stopLogging();
}
void QuickTestResult::initTestTable()
{
Q_D(QuickTestResult);
delete d->table;
d->table = new QTestTable;
//qmltest does not really need the column for data driven test
//add this to avoid warnings.
d->table->addColumn(qMetaTypeId<QString>(), "qmltest_dummy_data_column");
}
void QuickTestResult::clearTestTable()
{
Q_D(QuickTestResult);
delete d->table;
d->table = nullptr;
}
void QuickTestResult::finishTestData()
{
QTestResult::finishedCurrentTestData();
}
void QuickTestResult::finishTestDataCleanup()
{
QTestResult::finishedCurrentTestDataCleanup();
}
void QuickTestResult::finishTestFunction()
{
QTestResult::finishedCurrentTestFunction();
}
static QString qtestFixUrl(const QUrl &location)
{
if (location.isLocalFile()) // Use QUrl's logic for Windows drive letters.
return QDir::toNativeSeparators(location.toLocalFile());
return location.toString();
}
void QuickTestResult::fail
(const QString &message, const QUrl &location, int line)
{
QTestResult::addFailure(message.toUtf8().constData(),
qtestFixUrl(location).toLatin1().constData(), line);
}
bool QuickTestResult::verify
(bool success, const QString &message, const QUrl &location, int line)
{
if (!success && message.isEmpty()) {
return QTestResult::verify
(success, "verify()", "",
qtestFixUrl(location).toLatin1().constData(), line);
} else {
return QTestResult::verify
(success, message.toUtf8().constData(), "",
qtestFixUrl(location).toLatin1().constData(), line);
}
}
bool QuickTestResult::fuzzyCompare(const QVariant &actual, const QVariant &expected, qreal delta)
{
if (actual.userType() == QMetaType::QColor || expected.userType() == QMetaType::QColor) {
if (!actual.canConvert(QMetaType::QColor) || !expected.canConvert(QMetaType::QColor))
return false;
//fuzzy color comparison
QColor act;
QColor exp;
bool ok(false);
QVariant var = QQml_colorProvider()->colorFromString(actual.toString(), &ok);
if (!ok)
return false;
act = var.value<QColor>();
QQml_colorProvider()->colorFromString(expected.toString(), &ok);
if (!ok)
return false;
exp = var.value<QColor>();
return ( qAbs(act.red() - exp.red()) <= delta
&& qAbs(act.green() - exp.green()) <= delta
&& qAbs(act.blue() - exp.blue()) <= delta
&& qAbs(act.alpha() - exp.alpha()) <= delta);
} else {
//number comparison
bool ok = true;
qreal act = actual.toFloat(&ok);
if (!ok)
return false;
qreal exp = expected.toFloat(&ok);
if (!ok)
return false;
return (qAbs(act - exp) <= delta);
}
return false;
}
void QuickTestResult::stringify(QQmlV4Function *args)
{
if (args->length() < 1)
args->setReturnValue(QV4::Encode::null());
QV4::Scope scope(args->v4engine());
QV4::ScopedValue value(scope, (*args)[0]);
QString result;
//Check for Object Type
if (value->isObject()
&& !value->as<QV4::FunctionObject>()
&& !value->as<QV4::ArrayObject>()) {
QVariant v = scope.engine->toVariant(value, QMetaType::UnknownType);
if (v.isValid()) {
switch (v.userType()) {
case QMetaType::QVector3D:
{
QVector3D v3d = v.value<QVector3D>();
result = QString::fromLatin1("Qt.vector3d(%1, %2, %3)").arg(v3d.x()).arg(v3d.y()).arg(v3d.z());
break;
}
case QMetaType::QUrl:
{
QUrl url = v.value<QUrl>();
result = QString::fromLatin1("Qt.url(%1)").arg(url.toString());
break;
}
case QMetaType::QDateTime:
{
QDateTime dt = v.value<QDateTime>();
result = dt.toString(Qt::ISODateWithMs);
break;
}
default:
result = v.toString();
}
} else {
result = QLatin1String("Object");
}
}
if (result.isEmpty()) {
QString tmp = value->toQStringNoThrow();
if (value->as<QV4::ArrayObject>())
result += QLatin1Char('[') + tmp + QLatin1Char(']');
else
result.append(tmp);
}
args->setReturnValue(QV4::Encode(args->v4engine()->newString(result)));
}
bool QuickTestResult::compare
(bool success, const QString &message,
const QVariant &val1, const QVariant &val2,
const QUrl &location, int line)
{
return QTestResult::compare
(success, message.toUtf8().constData(),
QTest::toString(val1.toString().toLatin1().constData()),
QTest::toString(val2.toString().toLatin1().constData()),
"", "",
qtestFixUrl(location).toLatin1().constData(), line);
}
void QuickTestResult::skip
(const QString &message, const QUrl &location, int line)
{
QTestResult::addSkip(message.toUtf8().constData(),
qtestFixUrl(location).toLatin1().constData(), line);
QTestResult::setSkipCurrentTest(true);
}
bool QuickTestResult::expectFail
(const QString &tag, const QString &comment, const QUrl &location, int line)
{
return QTestResult::expectFail
(tag.toLatin1().constData(),
QTest::toString(comment.toLatin1().constData()),
QTest::Abort, qtestFixUrl(location).toLatin1().constData(), line);
}
bool QuickTestResult::expectFailContinue
(const QString &tag, const QString &comment, const QUrl &location, int line)
{
return QTestResult::expectFail
(tag.toLatin1().constData(),
QTest::toString(comment.toUtf8().constData()),
QTest::Continue, qtestFixUrl(location).toLatin1().constData(), line);
}
void QuickTestResult::warn(const QString &message, const QUrl &location, int line)
{
QTestLog::warn(message.toUtf8().constData(), qtestFixUrl(location).toLatin1().constData(), line);
}
void QuickTestResult::ignoreWarning(const QJSValue &message)
{
if (message.isRegExp()) {
#if QT_CONFIG(regularexpression)
QTestLog::ignoreMessage(QtWarningMsg, message.toVariant().toRegularExpression());
#endif
} else {
QTestLog::ignoreMessage(QtWarningMsg, message.toString().toUtf8());
}
}
void QuickTestResult::wait(int ms)
{
QTest::qWait(ms);
}
void QuickTestResult::sleep(int ms)
{
QTest::qSleep(ms);
}
bool QuickTestResult::waitForRendering(QQuickItem *item, int timeout)
{
Q_ASSERT(item);
return qWaitForSignal(item->window(), SIGNAL(frameSwapped()), timeout);
}
void QuickTestResult::startMeasurement()
{
Q_D(QuickTestResult);
delete d->benchmarkData;
d->benchmarkData = new QBenchmarkTestMethodData();
QBenchmarkTestMethodData::current = d->benchmarkData;
d->iterCount = (QBenchmarkGlobalData::current->measurer->needsWarmupIteration()) ? -1 : 0;
d->results.clear();
}
void QuickTestResult::beginDataRun()
{
QBenchmarkTestMethodData::current->beginDataRun();
}
void QuickTestResult::endDataRun()
{
Q_D(QuickTestResult);
QBenchmarkTestMethodData::current->endDataRun();
if (d->iterCount > -1) // iteration -1 is the warmup iteration.
d->results.append(QBenchmarkTestMethodData::current->result);
if (QBenchmarkGlobalData::current->verboseOutput) {
if (d->iterCount == -1) {
qDebug() << "warmup stage result :" << QBenchmarkTestMethodData::current->result.value;
} else {
qDebug() << "accumulation stage result:" << QBenchmarkTestMethodData::current->result.value;
}
}
}
bool QuickTestResult::measurementAccepted()
{
return QBenchmarkTestMethodData::current->resultsAccepted();
}
static QBenchmarkResult qMedian(const QList<QBenchmarkResult> &container)
{
const int count = container.count();
if (count == 0)
return QBenchmarkResult();
if (count == 1)
return container.at(0);
QList<QBenchmarkResult> containerCopy = container;
std::sort(containerCopy.begin(), containerCopy.end());
const int middle = count / 2;
// ### handle even-sized containers here by doing an aritmetic mean of the two middle items.
return containerCopy.at(middle);
}
bool QuickTestResult::needsMoreMeasurements()
{
Q_D(QuickTestResult);
++(d->iterCount);
if (d->iterCount < QBenchmarkGlobalData::current->adjustMedianIterationCount())
return true;
if (QBenchmarkTestMethodData::current->resultsAccepted())
QTestLog::addBenchmarkResult(qMedian(d->results));
return false;
}
void QuickTestResult::startBenchmark(RunMode runMode, const QString &tag)
{
QBenchmarkTestMethodData::current->result = QBenchmarkResult();
QBenchmarkTestMethodData::current->resultAccepted = false;
QBenchmarkGlobalData::current->context.tag = tag;
QBenchmarkGlobalData::current->context.slotName = functionName();
Q_D(QuickTestResult);
delete d->benchmarkIter;
d->benchmarkIter = new QTest::QBenchmarkIterationController
(QTest::QBenchmarkIterationController::RunMode(runMode));
}
bool QuickTestResult::isBenchmarkDone() const
{
Q_D(const QuickTestResult);
if (d->benchmarkIter)
return d->benchmarkIter->isDone();
else
return true;
}
void QuickTestResult::nextBenchmark()
{
Q_D(QuickTestResult);
if (d->benchmarkIter)
d->benchmarkIter->next();
}
void QuickTestResult::stopBenchmark()
{
Q_D(QuickTestResult);
delete d->benchmarkIter;
d->benchmarkIter = nullptr;
}
QObject *QuickTestResult::grabImage(QQuickItem *item)
{
if (item && item->window()) {
QQuickWindow *window = item->window();
QImage grabbed = window->grabWindow();
QRectF rf(item->x(), item->y(), item->width(), item->height());
rf = rf.intersected(QRectF(0, 0, grabbed.width(), grabbed.height()));
QObject *o = new QuickTestImageObject(grabbed.copy(rf.toAlignedRect()));
QQmlEngine::setContextForObject(o, qmlContext(this));
return o;
}
return nullptr;
}
QObject *QuickTestResult::findChild(QObject *parent, const QString &objectName)
{
return parent ? parent->findChild<QObject*>(objectName) : 0;
}
bool QuickTestResult::isPolishScheduled(QQuickItem *item) const
{
return QQuickTest::qIsPolishScheduled(item);
}
bool QuickTestResult::waitForItemPolished(QQuickItem *item, int timeout)
{
return QQuickTest::qWaitForItemPolished(item, timeout);
}
namespace QTest {
void qtest_qParseArgs(int argc, char *argv[], bool qml);
};
void QuickTestResult::parseArgs(int argc, char *argv[])
{
if (!QBenchmarkGlobalData::current)
QBenchmarkGlobalData::current = &globalBenchmarkData;
QTest::qtest_qParseArgs(argc, argv, true);
}
void QuickTestResult::setProgramName(const char *name)
{
if (name) {
QTestPrivate::parseBlackList();
QTestResult::reset();
} else if (!name && loggingStarted) {
QTestResult::setCurrentTestObject(globalProgramName);
QTestLog::stopLogging();
QTestResult::setCurrentTestObject(nullptr);
}
globalProgramName = name;
QTestResult::setCurrentTestObject(globalProgramName);
}
void QuickTestResult::setCurrentAppname(const char *appname)
{
QTestResult::setCurrentAppName(appname);
}
int QuickTestResult::exitCode()
{
#if defined(QTEST_NOEXITCODE)
return 0;
#else
// make sure our exit code is never going above 127
// since that could wrap and indicate 0 test fails
return qMin(QTestLog::failCount(), 127);
#endif
}
QT_END_NAMESPACE
#include "quicktestresult.moc"
#include "moc_quicktestresult_p.cpp"