1/****************************************************************************
2**
3** Copyright (C) 2018 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the test suite of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
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 https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28
29
30#include "abstracttestsuite.h"
31#include <QtTest/QtTest>
32#include <QtScript>
33
34class tst_QScriptV8TestSuite : public AbstractTestSuite
35{
36public:
37 tst_QScriptV8TestSuite();
38 virtual ~tst_QScriptV8TestSuite();
39
40protected:
41 struct ExpectedFailure
42 {
43 ExpectedFailure(const QString &name, const QString &act,
44 const QString &exp, const QString &msg)
45 : testName(name), actual(act), expected(exp), message(msg)
46 { }
47
48 QString testName;
49 QString actual;
50 QString expected;
51 QString message;
52 };
53
54 void addExpectedFailure(const QString &testName, const QString &actual,
55 const QString &expected, const QString &message);
56 bool isExpectedFailure(const QString &testName, const QString &actual,
57 const QString &expected, QString *message) const;
58 void addTestExclusion(const QString &testName, const QString &message);
59 void addTestExclusion(const QRegExp &rx, const QString &message);
60 bool isExcludedTest(const QString &testName, QString *message) const;
61
62 virtual void initTestCase();
63 virtual void configData(TestConfig::Mode mode, const QStringList &parts);
64 virtual void writeSkipConfigFile(QTextStream &);
65 virtual void writeExpectFailConfigFile(QTextStream &);
66 virtual void runTestFunction(int testIndex);
67
68 QStringList testNames;
69 QList<ExpectedFailure> expectedFailures;
70 QList<QPair<QRegExp, QString> > testExclusions;
71 QString mjsunitContents;
72};
73
74// We expect failing tests to call the fail() function (defined in
75// mjsunit.js) with arguments expected, actual, message_opt. This
76// function intercepts the call, calls the real fail() function (which
77// will throw an exception), and sets the original arguments on the
78// exception object so that we can process them later.
79static QScriptValue qscript_fail(QScriptContext *ctx, QScriptEngine *eng)
80{
81 QScriptValue realFail = ctx->callee().data();
82 if (!realFail.isFunction())
83 qFatal(msg: "%s: realFail must be a function", Q_FUNC_INFO);
84 QScriptValue ret = realFail.call(thisObject: ctx->thisObject(), arguments: ctx->argumentsObject());
85 if (!eng->hasUncaughtException())
86 qFatal(msg: "%s: realFail function did not throw an exception", Q_FUNC_INFO);
87 ret.setProperty(name: "expected", value: ctx->argument(index: 0));
88 ret.setProperty(name: "actual", value: ctx->argument(index: 1));
89 ret.setProperty(name: "message", value: ctx->argument(index: 2));
90 QScriptContextInfo info(ctx->parentContext()->parentContext());
91 ret.setProperty(name: "lineNumber", value: info.lineNumber());
92 return ret;
93}
94
95void tst_QScriptV8TestSuite::writeSkipConfigFile(QTextStream &stream)
96{
97 stream << QString::fromLatin1(str: "# testcase | message") << endl;
98}
99
100void tst_QScriptV8TestSuite::writeExpectFailConfigFile(QTextStream &stream)
101{
102 stream << QString::fromLatin1(str: "# testcase | actual | expected | message") << endl;
103 for (int i = 0; i < expectedFailures.size(); ++i) {
104 const ExpectedFailure &fail = expectedFailures.at(i);
105 stream << QString::fromLatin1(str: "%0 | %1 | %2")
106 .arg(a: fail.testName)
107 .arg(a: escape(fail.actual))
108 .arg(a: escape(fail.expected));
109 if (!fail.message.isEmpty())
110 stream << QString::fromLatin1(str: " | %0").arg(a: escape(fail.message));
111 stream << endl;
112 }
113}
114
115void tst_QScriptV8TestSuite::runTestFunction(int testIndex)
116{
117 QString name = testNames.at(i: testIndex);
118 QString path = testsDir.absoluteFilePath(fileName: name + ".js");
119
120 QString excludeMessage;
121 if (isExcludedTest(testName: name, message: &excludeMessage)) {
122 QTest::qSkip(message: excludeMessage.toLatin1(), file: path.toLatin1(), line: -1);
123 return;
124 }
125
126 QScriptEngine engine;
127 engine.evaluate(program: mjsunitContents);
128 if (engine.hasUncaughtException()) {
129 QStringList bt = engine.uncaughtExceptionBacktrace();
130 QString err = engine.uncaughtException().toString();
131 qWarning(msg: "%s\n%s", qPrintable(err), qPrintable(bt.join("\n")));
132 } else {
133 // Prepare to intercept calls to mjsunit's fail() function.
134 QScriptValue fakeFail = engine.newFunction(signature: qscript_fail);
135 fakeFail.setData(engine.globalObject().property(name: "fail"));
136 engine.globalObject().setProperty(name: "fail", value: fakeFail);
137
138 QString contents = readFile(path);
139 QScriptValue ret = engine.evaluate(program: contents);
140 if (engine.hasUncaughtException()) {
141 if (!ret.isError()) {
142 int lineNumber = ret.property(name: "lineNumber").toInt32();
143 QTest::qVerify(statement: ret.instanceOf(other: engine.globalObject().property(name: "MjsUnitAssertionError")),
144 statementStr: ret.toString().toLatin1(),
145 description: "",
146 file: path.toLatin1(),
147 line: lineNumber);
148 QString actual = ret.property(name: "actual").toString();
149 QString expected = ret.property(name: "expected").toString();
150 QString failMessage;
151 if (shouldGenerateExpectedFailures) {
152 if (ret.property(name: "message").isString())
153 failMessage = ret.property(name: "message").toString();
154 addExpectedFailure(testName: name, actual, expected, message: failMessage);
155 } else if (isExpectedFailure(testName: name, actual, expected, message: &failMessage)) {
156 QTest::qExpectFail(dataIndex: "", comment: failMessage.toLatin1(),
157 mode: QTest::Continue, file: path.toLatin1(),
158 line: lineNumber);
159 }
160 QTest::qCompare(t1: actual, t2: expected, actual: "actual", expected: "expect",
161 file: path.toLatin1(), line: lineNumber);
162 } else {
163 int lineNumber = ret.property(name: "lineNumber").toInt32();
164 QTest::qExpectFail(dataIndex: "", comment: ret.toString().toLatin1(),
165 mode: QTest::Continue, file: path.toLatin1(), line: lineNumber);
166 QTest::qVerify(statement: false, statementStr: ret.toString().toLatin1(), description: "", file: path.toLatin1(), line: lineNumber);
167 }
168 }
169 }
170}
171
172tst_QScriptV8TestSuite::tst_QScriptV8TestSuite()
173 : AbstractTestSuite("tst_QScriptV8TestSuite",
174 ":/tests", ":/")
175{
176 // One test function per test file.
177 const QFileInfoList testFileInfos = testsDir.entryInfoList(nameFilters: QStringList() << "*.js", filters: QDir::Files);
178 for (const QFileInfo &tfi : testFileInfos) {
179 QString name = tfi.baseName();
180 addTestFunction(name);
181 testNames.append(t: name);
182 }
183
184 finalizeMetaObject();
185}
186
187tst_QScriptV8TestSuite::~tst_QScriptV8TestSuite()
188{
189}
190
191void tst_QScriptV8TestSuite::initTestCase()
192{
193 AbstractTestSuite::initTestCase();
194
195 // FIXME: These warnings should be QFAIL, but that would make the
196 // test fail right now.
197 if (!testsDir.exists(name: "mjsunit.js"))
198 qWarning(msg: "*** no tests/mjsunit.js file!");
199 else {
200 mjsunitContents = readFile(testsDir.absoluteFilePath(fileName: "mjsunit.js"));
201 if (mjsunitContents.isEmpty())
202 qWarning(msg: "*** tests/mjsunit.js is empty!");
203 }
204}
205
206void tst_QScriptV8TestSuite::configData(TestConfig::Mode mode, const QStringList &parts)
207{
208 switch (mode) {
209 case TestConfig::Skip:
210 addTestExclusion(testName: parts.at(i: 0), message: parts.value(i: 1));
211 break;
212
213 case TestConfig::ExpectFail:
214 addExpectedFailure(testName: parts.at(i: 0), actual: parts.value(i: 1),
215 expected: parts.value(i: 2), message: parts.value(i: 3));
216 break;
217 }
218}
219
220void tst_QScriptV8TestSuite::addExpectedFailure(const QString &testName, const QString &actual,
221 const QString &expected, const QString &message)
222{
223 expectedFailures.append(t: ExpectedFailure(testName, actual, expected, message));
224}
225
226bool tst_QScriptV8TestSuite::isExpectedFailure(const QString &testName, const QString &actual,
227 const QString &expected, QString *message) const
228{
229 for (int i = 0; i < expectedFailures.size(); ++i) {
230 const ExpectedFailure &ef = expectedFailures.at(i);
231 if ((testName == ef.testName) && (actual == ef.actual) && (expected == ef.expected)) {
232 if (message)
233 *message = ef.message;
234 return true;
235 }
236 }
237 return false;
238}
239
240void tst_QScriptV8TestSuite::addTestExclusion(const QString &testName, const QString &message)
241{
242 testExclusions.append(t: qMakePair(x: QRegExp(testName), y: message));
243}
244
245void tst_QScriptV8TestSuite::addTestExclusion(const QRegExp &rx, const QString &message)
246{
247 testExclusions.append(t: qMakePair(x: rx, y: message));
248}
249
250bool tst_QScriptV8TestSuite::isExcludedTest(const QString &testName, QString *message) const
251{
252 for (int i = 0; i < testExclusions.size(); ++i) {
253 if (QRegExp(testExclusions.at(i).first).indexIn(str: testName) != -1) {
254 if (message)
255 *message = testExclusions.at(i).second;
256 return true;
257 }
258 }
259 return false;
260}
261
262QTEST_MAIN(tst_QScriptV8TestSuite)
263

source code of qtscript/tests/auto/qscriptv8testsuite/tst_qscriptv8testsuite.cpp