/**************************************************************************** ** ** 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 #include 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 subSuitePaths; QList expectedFailures; QList > 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 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 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("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.toLatin1()) << 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)