blob: 73a15752aee6c1ce7e0ec98ad1bbd5e41356fddd [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2018 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:GPL-EXCEPT$
** 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 General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** 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-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "abstracttestsuite.h"
#include <QtTest/QtTest>
#include <QtScript>
struct TestRecord
{
TestRecord() : lineNumber(-1) { }
TestRecord(const QString &desc,
bool pass,
const QString &act,
const QString &exp,
const QString &fn, int ln)
: description(desc), passed(pass),
actual(act), expected(exp),
fileName(fn), lineNumber(ln)
{ }
TestRecord(const QString &skipReason, const QString &fn)
: description(skipReason), actual("QSKIP"),
fileName(fn), lineNumber(-1)
{ }
QString description;
bool passed;
QString actual;
QString expected;
QString fileName;
int lineNumber;
};
Q_DECLARE_METATYPE(TestRecord)
struct FailureItem
{
enum Action {
ExpectFail,
Skip
};
FailureItem(Action act, const QRegExp &rx, const QString &desc, const QString &msg)
: action(act), pathRegExp(rx), description(desc), message(msg)
{ }
Action action;
QRegExp pathRegExp;
QString description;
QString message;
};
class tst_QScriptJSTestSuite : public AbstractTestSuite
{
public:
tst_QScriptJSTestSuite();
virtual ~tst_QScriptJSTestSuite();
protected:
virtual void configData(TestConfig::Mode mode, const QStringList &parts);
virtual void writeSkipConfigFile(QTextStream &);
virtual void writeExpectFailConfigFile(QTextStream &);
virtual void runTestFunction(int testIndex);
private:
void addExpectedFailure(const QString &fileName, const QString &description, const QString &message);
void addExpectedFailure(const QRegExp &path, const QString &description, const QString &message);
void addSkip(const QString &fileName, const QString &description, const QString &message);
void addSkip(const QRegExp &path, const QString &description, const QString &message);
bool isExpectedFailure(const QString &fileName, const QString &description,
QString *message, FailureItem::Action *action) const;
void addFileExclusion(const QString &fileName, const QString &message);
void addFileExclusion(const QRegExp &rx, const QString &message);
bool isExcludedFile(const QString &fileName, QString *message) const;
QList<QString> subSuitePaths;
QList<FailureItem> expectedFailures;
QList<QPair<QRegExp, QString> > fileExclusions;
};
static QScriptValue qscript_void(QScriptContext *, QScriptEngine *eng)
{
return eng->undefinedValue();
}
static QScriptValue qscript_quit(QScriptContext *ctx, QScriptEngine *)
{
return ctx->throwError("Test quit");
}
static QString optionsToString(int options)
{
QSet<QString> set;
if (options & 1)
set.insert("strict");
if (options & 2)
set.insert("werror");
if (options & 4)
set.insert("atline");
if (options & 8)
set.insert("xml");
return QStringList(set.values()).join(",");
}
static QScriptValue qscript_options(QScriptContext *ctx, QScriptEngine *)
{
static QHash<QString, int> stringToFlagHash;
if (stringToFlagHash.isEmpty()) {
stringToFlagHash["strict"] = 1;
stringToFlagHash["werror"] = 2;
stringToFlagHash["atline"] = 4;
stringToFlagHash["xml"] = 8;
}
QScriptValue callee = ctx->callee();
int opts = callee.data().toInt32();
QString result = optionsToString(opts);
for (int i = 0; i < ctx->argumentCount(); ++i)
opts |= stringToFlagHash.value(ctx->argument(0).toString());
callee.setData(opts);
return result;
}
static QScriptValue qscript_TestCase(QScriptContext *ctx, QScriptEngine *eng)
{
QScriptValue origTestCaseCtor = ctx->callee().data();
QScriptValue kase = ctx->thisObject();
QScriptValue ret = origTestCaseCtor.call(kase, ctx->argumentsObject());
QScriptContextInfo info(ctx->parentContext());
kase.setProperty("__lineNumber__", QScriptValue(eng, info.lineNumber()));
return ret;
}
void tst_QScriptJSTestSuite::runTestFunction(int testIndex)
{
if (!(testIndex & 1)) {
// data
QTest::addColumn<TestRecord>("record");
bool hasData = false;
QString testsShellPath = testsDir.absoluteFilePath("shell.js");
QString testsShellContents = readFile(testsShellPath);
QDir subSuiteDir(subSuitePaths.at(testIndex / 2));
QString subSuiteShellPath = subSuiteDir.absoluteFilePath("shell.js");
QString subSuiteShellContents = readFile(subSuiteShellPath);
QDir testSuiteDir(subSuiteDir);
testSuiteDir.cdUp();
QString suiteJsrefPath = testSuiteDir.absoluteFilePath("jsref.js");
QString suiteJsrefContents = readFile(suiteJsrefPath);
QString suiteShellPath = testSuiteDir.absoluteFilePath("shell.js");
QString suiteShellContents = readFile(suiteShellPath);
const QFileInfoList testFileInfos = subSuiteDir.entryInfoList(QStringList() << "*.js", QDir::Files);
for (const QFileInfo &tfi : testFileInfos) {
if ((tfi.fileName() == "shell.js") || (tfi.fileName() == "browser.js"))
continue;
QString abspath = tfi.absoluteFilePath();
QString relpath = testsDir.relativeFilePath(abspath);
QString excludeMessage;
if (isExcludedFile(relpath, &excludeMessage)) {
QTest::newRow(relpath.toLatin1()) << TestRecord(excludeMessage, relpath);
continue;
}
QScriptEngine eng;
QScriptValue global = eng.globalObject();
global.setProperty("print", eng.newFunction(qscript_void));
global.setProperty("quit", eng.newFunction(qscript_quit));
global.setProperty("options", eng.newFunction(qscript_options));
eng.evaluate(testsShellContents, testsShellPath);
if (eng.hasUncaughtException()) {
QStringList bt = eng.uncaughtExceptionBacktrace();
QString err = eng.uncaughtException().toString();
qWarning("%s\n%s", qPrintable(err), qPrintable(bt.join("\n")));
break;
}
eng.evaluate(suiteJsrefContents, suiteJsrefPath);
if (eng.hasUncaughtException()) {
QStringList bt = eng.uncaughtExceptionBacktrace();
QString err = eng.uncaughtException().toString();
qWarning("%s\n%s", qPrintable(err), qPrintable(bt.join("\n")));
break;
}
eng.evaluate(suiteShellContents, suiteShellPath);
if (eng.hasUncaughtException()) {
QStringList bt = eng.uncaughtExceptionBacktrace();
QString err = eng.uncaughtException().toString();
qWarning("%s\n%s", qPrintable(err), qPrintable(bt.join("\n")));
break;
}
eng.evaluate(subSuiteShellContents, subSuiteShellPath);
if (eng.hasUncaughtException()) {
QStringList bt = eng.uncaughtExceptionBacktrace();
QString err = eng.uncaughtException().toString();
qWarning("%s\n%s", qPrintable(err), qPrintable(bt.join("\n")));
break;
}
QScriptValue origTestCaseCtor = global.property("TestCase");
QScriptValue myTestCaseCtor = eng.newFunction(qscript_TestCase);
myTestCaseCtor.setData(origTestCaseCtor);
global.setProperty("TestCase", myTestCaseCtor);
global.setProperty("gTestfile", tfi.fileName());
global.setProperty("gTestsuite", testSuiteDir.dirName());
global.setProperty("gTestsubsuite", subSuiteDir.dirName());
QString testFileContents = readFile(abspath);
// qDebug() << relpath;
eng.evaluate(testFileContents, abspath);
if (eng.hasUncaughtException() && !relpath.endsWith("-n.js")) {
QStringList bt = eng.uncaughtExceptionBacktrace();
QString err = eng.uncaughtException().toString();
qWarning("%s\n%s\n", qPrintable(err), qPrintable(bt.join("\n")));
continue;
}
QScriptValue testcases = global.property("testcases");
if (!testcases.isArray())
testcases = global.property("gTestcases");
int count = testcases.property("length").toInt32();
if (count == 0)
continue;
hasData = true;
QString title = global.property("TITLE").toString();
for (int i = 0; i < count; ++i) {
QScriptValue kase = testcases.property(i);
QString description = kase.property("description").toString();
QScriptValue expect = kase.property("expect");
QScriptValue actual = kase.property("actual");
bool passed = kase.property("passed").toBoolean();
int lineNumber = kase.property("__lineNumber__").toInt32();
TestRecord rec(description, passed,
actual.toString(), expect.toString(),
relpath, lineNumber);
QTest::newRow(description.toUtf8()) << rec;
}
}
if (!hasData)
QTest::newRow("") << TestRecord(); // dummy
} else {
QFETCH(TestRecord, record);
if ((record.lineNumber == -1) && (record.actual == "QSKIP")) {
QTest::qSkip(record.description.toLatin1(), record.fileName.toLatin1(), -1);
} else {
QString msg;
FailureItem::Action failAct;
bool expectFail = isExpectedFailure(record.fileName, record.description, &msg, &failAct);
if (expectFail) {
switch (failAct) {
case FailureItem::ExpectFail:
QTest::qExpectFail("", msg.toLatin1(),
QTest::Continue, record.fileName.toLatin1(),
record.lineNumber);
break;
case FailureItem::Skip:
QTest::qSkip(msg.toLatin1(), record.fileName.toLatin1(), record.lineNumber);
break;
}
}
if (!expectFail || (failAct == FailureItem::ExpectFail)) {
if (!record.passed) {
if (!expectFail && shouldGenerateExpectedFailures) {
addExpectedFailure(record.fileName,
record.description,
QString());
}
QTest::qCompare(record.actual, record.expected, "actual", "expect",
record.fileName.toLatin1(), record.lineNumber);
} else {
QTest::qCompare(record.actual, record.actual, "actual", "expect",
record.fileName.toLatin1(), record.lineNumber);
}
}
}
}
}
tst_QScriptJSTestSuite::tst_QScriptJSTestSuite()
: AbstractTestSuite("tst_QScriptJsTestSuite",
QFINDTESTDATA("tests"),
":/")
{
// don't execute any tests on slow machines
#if !defined(Q_OS_IRIX)
// do all the test suites
const QFileInfoList testSuiteDirInfos = testsDir.entryInfoList(QDir::AllDirs | QDir::NoDotAndDotDot);
for (const QFileInfo &tsdi : testSuiteDirInfos) {
QDir testSuiteDir(tsdi.absoluteFilePath());
// do all the dirs in the test suite
const QFileInfoList subSuiteDirInfos = testSuiteDir.entryInfoList(QDir::AllDirs | QDir::NoDotAndDotDot);
for (const QFileInfo &ssdi : subSuiteDirInfos) {
subSuitePaths.append(ssdi.absoluteFilePath());
QString function = QString::fromLatin1("%0/%1")
.arg(testSuiteDir.dirName()).arg(ssdi.fileName());
addTestFunction(function, CreateDataFunction);
}
}
#endif
finalizeMetaObject();
}
tst_QScriptJSTestSuite::~tst_QScriptJSTestSuite()
{
}
void tst_QScriptJSTestSuite::configData(TestConfig::Mode mode, const QStringList &parts)
{
switch (mode) {
case TestConfig::Skip:
addFileExclusion(parts.at(0), parts.value(1));
break;
case TestConfig::ExpectFail:
addExpectedFailure(parts.at(0), parts.value(1), parts.value(2));
break;
}
}
void tst_QScriptJSTestSuite::writeSkipConfigFile(QTextStream &stream)
{
stream << QString::fromLatin1("# testcase | message") << endl;
}
void tst_QScriptJSTestSuite::writeExpectFailConfigFile(QTextStream &stream)
{
stream << QString::fromLatin1("# testcase | description | message") << endl;
for (int i = 0; i < expectedFailures.size(); ++i) {
const FailureItem &fail = expectedFailures.at(i);
if (fail.pathRegExp.pattern().isEmpty())
continue;
stream << QString::fromLatin1("%0 | %1")
.arg(fail.pathRegExp.pattern())
.arg(escape(fail.description));
if (!fail.message.isEmpty())
stream << QString::fromLatin1(" | %0").arg(escape(fail.message));
stream << endl;
}
}
void tst_QScriptJSTestSuite::addExpectedFailure(const QRegExp &path, const QString &description, const QString &message)
{
expectedFailures.append(FailureItem(FailureItem::ExpectFail, path, description, message));
}
void tst_QScriptJSTestSuite::addExpectedFailure(const QString &fileName, const QString &description, const QString &message)
{
expectedFailures.append(FailureItem(FailureItem::ExpectFail, QRegExp(fileName), description, message));
}
void tst_QScriptJSTestSuite::addSkip(const QRegExp &path, const QString &description, const QString &message)
{
expectedFailures.append(FailureItem(FailureItem::Skip, path, description, message));
}
void tst_QScriptJSTestSuite::addSkip(const QString &fileName, const QString &description, const QString &message)
{
expectedFailures.append(FailureItem(FailureItem::Skip, QRegExp(fileName), description, message));
}
bool tst_QScriptJSTestSuite::isExpectedFailure(const QString &fileName, const QString &description,
QString *message, FailureItem::Action *action) const
{
for (int i = 0; i < expectedFailures.size(); ++i) {
QRegExp pathRegExp = expectedFailures.at(i).pathRegExp;
if (pathRegExp.indexIn(fileName) != -1) {
if (description == expectedFailures.at(i).description) {
if (message)
*message = expectedFailures.at(i).message;
if (action)
*action = expectedFailures.at(i).action;
return true;
}
}
}
return false;
}
void tst_QScriptJSTestSuite::addFileExclusion(const QString &fileName, const QString &message)
{
fileExclusions.append(qMakePair(QRegExp(fileName), message));
}
void tst_QScriptJSTestSuite::addFileExclusion(const QRegExp &rx, const QString &message)
{
fileExclusions.append(qMakePair(rx, message));
}
bool tst_QScriptJSTestSuite::isExcludedFile(const QString &fileName, QString *message) const
{
for (int i = 0; i < fileExclusions.size(); ++i) {
QRegExp copy = fileExclusions.at(i).first;
if (copy.indexIn(fileName) != -1) {
if (message)
*message = fileExclusions.at(i).second;
return true;
}
}
return false;
}
QTEST_MAIN(tst_QScriptJSTestSuite)