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 | |
33 | #include <QtScript> |
34 | |
35 | struct TestRecord |
36 | { |
37 | TestRecord() : lineNumber(-1) { } |
38 | TestRecord(const QString &desc, |
39 | bool pass, |
40 | const QString &act, |
41 | const QString &exp, |
42 | const QString &fn, int ln) |
43 | : description(desc), passed(pass), |
44 | actual(act), expected(exp), |
45 | fileName(fn), lineNumber(ln) |
46 | { } |
47 | TestRecord(const QString &skipReason, const QString &fn) |
48 | : description(skipReason), actual("QSKIP" ), |
49 | fileName(fn), lineNumber(-1) |
50 | { } |
51 | QString description; |
52 | bool passed; |
53 | QString actual; |
54 | QString expected; |
55 | QString fileName; |
56 | int lineNumber; |
57 | }; |
58 | |
59 | Q_DECLARE_METATYPE(TestRecord) |
60 | |
61 | struct FailureItem |
62 | { |
63 | enum Action { |
64 | ExpectFail, |
65 | Skip |
66 | }; |
67 | FailureItem(Action act, const QRegExp &rx, const QString &desc, const QString &msg) |
68 | : action(act), pathRegExp(rx), description(desc), message(msg) |
69 | { } |
70 | |
71 | Action action; |
72 | QRegExp pathRegExp; |
73 | QString description; |
74 | QString message; |
75 | }; |
76 | |
77 | class tst_QScriptJSTestSuite : public AbstractTestSuite |
78 | { |
79 | |
80 | public: |
81 | tst_QScriptJSTestSuite(); |
82 | virtual ~tst_QScriptJSTestSuite(); |
83 | |
84 | protected: |
85 | virtual void configData(TestConfig::Mode mode, const QStringList &parts); |
86 | virtual void writeSkipConfigFile(QTextStream &); |
87 | virtual void writeExpectFailConfigFile(QTextStream &); |
88 | virtual void runTestFunction(int testIndex); |
89 | |
90 | private: |
91 | void addExpectedFailure(const QString &fileName, const QString &description, const QString &message); |
92 | void addExpectedFailure(const QRegExp &path, const QString &description, const QString &message); |
93 | void addSkip(const QString &fileName, const QString &description, const QString &message); |
94 | void addSkip(const QRegExp &path, const QString &description, const QString &message); |
95 | bool isExpectedFailure(const QString &fileName, const QString &description, |
96 | QString *message, FailureItem::Action *action) const; |
97 | void addFileExclusion(const QString &fileName, const QString &message); |
98 | void addFileExclusion(const QRegExp &rx, const QString &message); |
99 | bool isExcludedFile(const QString &fileName, QString *message) const; |
100 | |
101 | QList<QString> subSuitePaths; |
102 | QList<FailureItem> expectedFailures; |
103 | QList<QPair<QRegExp, QString> > fileExclusions; |
104 | }; |
105 | |
106 | static QScriptValue qscript_void(QScriptContext *, QScriptEngine *eng) |
107 | { |
108 | return eng->undefinedValue(); |
109 | } |
110 | |
111 | static QScriptValue qscript_quit(QScriptContext *ctx, QScriptEngine *) |
112 | { |
113 | return ctx->throwError(text: "Test quit" ); |
114 | } |
115 | |
116 | static QString optionsToString(int options) |
117 | { |
118 | QSet<QString> set; |
119 | if (options & 1) |
120 | set.insert(value: "strict" ); |
121 | if (options & 2) |
122 | set.insert(value: "werror" ); |
123 | if (options & 4) |
124 | set.insert(value: "atline" ); |
125 | if (options & 8) |
126 | set.insert(value: "xml" ); |
127 | return QStringList(set.values()).join(sep: "," ); |
128 | } |
129 | |
130 | static QScriptValue qscript_options(QScriptContext *ctx, QScriptEngine *) |
131 | { |
132 | static QHash<QString, int> stringToFlagHash; |
133 | if (stringToFlagHash.isEmpty()) { |
134 | stringToFlagHash["strict" ] = 1; |
135 | stringToFlagHash["werror" ] = 2; |
136 | stringToFlagHash["atline" ] = 4; |
137 | stringToFlagHash["xml" ] = 8; |
138 | } |
139 | QScriptValue callee = ctx->callee(); |
140 | int opts = callee.data().toInt32(); |
141 | QString result = optionsToString(options: opts); |
142 | for (int i = 0; i < ctx->argumentCount(); ++i) |
143 | opts |= stringToFlagHash.value(akey: ctx->argument(index: 0).toString()); |
144 | callee.setData(opts); |
145 | return result; |
146 | } |
147 | |
148 | static QScriptValue qscript_TestCase(QScriptContext *ctx, QScriptEngine *eng) |
149 | { |
150 | QScriptValue origTestCaseCtor = ctx->callee().data(); |
151 | QScriptValue kase = ctx->thisObject(); |
152 | QScriptValue ret = origTestCaseCtor.call(thisObject: kase, arguments: ctx->argumentsObject()); |
153 | QScriptContextInfo info(ctx->parentContext()); |
154 | kase.setProperty(name: "__lineNumber__" , value: QScriptValue(eng, info.lineNumber())); |
155 | return ret; |
156 | } |
157 | |
158 | void tst_QScriptJSTestSuite::runTestFunction(int testIndex) |
159 | { |
160 | if (!(testIndex & 1)) { |
161 | // data |
162 | QTest::addColumn<TestRecord>(name: "record" ); |
163 | bool hasData = false; |
164 | |
165 | QString testsShellPath = testsDir.absoluteFilePath(fileName: "shell.js" ); |
166 | QString testsShellContents = readFile(testsShellPath); |
167 | |
168 | QDir subSuiteDir(subSuitePaths.at(i: testIndex / 2)); |
169 | QString subSuiteShellPath = subSuiteDir.absoluteFilePath(fileName: "shell.js" ); |
170 | QString subSuiteShellContents = readFile(subSuiteShellPath); |
171 | |
172 | QDir testSuiteDir(subSuiteDir); |
173 | testSuiteDir.cdUp(); |
174 | QString suiteJsrefPath = testSuiteDir.absoluteFilePath(fileName: "jsref.js" ); |
175 | QString suiteJsrefContents = readFile(suiteJsrefPath); |
176 | QString suiteShellPath = testSuiteDir.absoluteFilePath(fileName: "shell.js" ); |
177 | QString suiteShellContents = readFile(suiteShellPath); |
178 | |
179 | const QFileInfoList testFileInfos = subSuiteDir.entryInfoList(nameFilters: QStringList() << "*.js" , filters: QDir::Files); |
180 | for (const QFileInfo &tfi : testFileInfos) { |
181 | if ((tfi.fileName() == "shell.js" ) || (tfi.fileName() == "browser.js" )) |
182 | continue; |
183 | |
184 | QString abspath = tfi.absoluteFilePath(); |
185 | QString relpath = testsDir.relativeFilePath(fileName: abspath); |
186 | QString excludeMessage; |
187 | if (isExcludedFile(fileName: relpath, message: &excludeMessage)) { |
188 | QTest::newRow(dataTag: relpath.toLatin1()) << TestRecord(excludeMessage, relpath); |
189 | continue; |
190 | } |
191 | |
192 | QScriptEngine eng; |
193 | QScriptValue global = eng.globalObject(); |
194 | global.setProperty(name: "print" , value: eng.newFunction(signature: qscript_void)); |
195 | global.setProperty(name: "quit" , value: eng.newFunction(signature: qscript_quit)); |
196 | global.setProperty(name: "options" , value: eng.newFunction(signature: qscript_options)); |
197 | |
198 | eng.evaluate(program: testsShellContents, fileName: testsShellPath); |
199 | if (eng.hasUncaughtException()) { |
200 | QStringList bt = eng.uncaughtExceptionBacktrace(); |
201 | QString err = eng.uncaughtException().toString(); |
202 | qWarning(msg: "%s\n%s" , qPrintable(err), qPrintable(bt.join("\n" ))); |
203 | break; |
204 | } |
205 | |
206 | eng.evaluate(program: suiteJsrefContents, fileName: suiteJsrefPath); |
207 | if (eng.hasUncaughtException()) { |
208 | QStringList bt = eng.uncaughtExceptionBacktrace(); |
209 | QString err = eng.uncaughtException().toString(); |
210 | qWarning(msg: "%s\n%s" , qPrintable(err), qPrintable(bt.join("\n" ))); |
211 | break; |
212 | } |
213 | |
214 | eng.evaluate(program: suiteShellContents, fileName: suiteShellPath); |
215 | if (eng.hasUncaughtException()) { |
216 | QStringList bt = eng.uncaughtExceptionBacktrace(); |
217 | QString err = eng.uncaughtException().toString(); |
218 | qWarning(msg: "%s\n%s" , qPrintable(err), qPrintable(bt.join("\n" ))); |
219 | break; |
220 | } |
221 | |
222 | eng.evaluate(program: subSuiteShellContents, fileName: subSuiteShellPath); |
223 | if (eng.hasUncaughtException()) { |
224 | QStringList bt = eng.uncaughtExceptionBacktrace(); |
225 | QString err = eng.uncaughtException().toString(); |
226 | qWarning(msg: "%s\n%s" , qPrintable(err), qPrintable(bt.join("\n" ))); |
227 | break; |
228 | } |
229 | |
230 | QScriptValue origTestCaseCtor = global.property(name: "TestCase" ); |
231 | QScriptValue myTestCaseCtor = eng.newFunction(signature: qscript_TestCase); |
232 | myTestCaseCtor.setData(origTestCaseCtor); |
233 | global.setProperty(name: "TestCase" , value: myTestCaseCtor); |
234 | |
235 | global.setProperty(name: "gTestfile" , value: tfi.fileName()); |
236 | global.setProperty(name: "gTestsuite" , value: testSuiteDir.dirName()); |
237 | global.setProperty(name: "gTestsubsuite" , value: subSuiteDir.dirName()); |
238 | QString testFileContents = readFile(abspath); |
239 | // qDebug() << relpath; |
240 | eng.evaluate(program: testFileContents, fileName: abspath); |
241 | if (eng.hasUncaughtException() && !relpath.endsWith(s: "-n.js" )) { |
242 | QStringList bt = eng.uncaughtExceptionBacktrace(); |
243 | QString err = eng.uncaughtException().toString(); |
244 | qWarning(msg: "%s\n%s\n" , qPrintable(err), qPrintable(bt.join("\n" ))); |
245 | continue; |
246 | } |
247 | |
248 | QScriptValue testcases = global.property(name: "testcases" ); |
249 | if (!testcases.isArray()) |
250 | testcases = global.property(name: "gTestcases" ); |
251 | int count = testcases.property(name: "length" ).toInt32(); |
252 | if (count == 0) |
253 | continue; |
254 | |
255 | hasData = true; |
256 | QString title = global.property(name: "TITLE" ).toString(); |
257 | for (int i = 0; i < count; ++i) { |
258 | QScriptValue kase = testcases.property(arrayIndex: i); |
259 | QString description = kase.property(name: "description" ).toString(); |
260 | QScriptValue expect = kase.property(name: "expect" ); |
261 | QScriptValue actual = kase.property(name: "actual" ); |
262 | bool passed = kase.property(name: "passed" ).toBoolean(); |
263 | int lineNumber = kase.property(name: "__lineNumber__" ).toInt32(); |
264 | |
265 | TestRecord rec(description, passed, |
266 | actual.toString(), expect.toString(), |
267 | relpath, lineNumber); |
268 | |
269 | QTest::newRow(dataTag: description.toUtf8()) << rec; |
270 | } |
271 | } |
272 | if (!hasData) |
273 | QTest::newRow(dataTag: "" ) << TestRecord(); // dummy |
274 | } else { |
275 | QFETCH(TestRecord, record); |
276 | if ((record.lineNumber == -1) && (record.actual == "QSKIP" )) { |
277 | QTest::qSkip(message: record.description.toLatin1(), file: record.fileName.toLatin1(), line: -1); |
278 | } else { |
279 | QString msg; |
280 | FailureItem::Action failAct; |
281 | bool expectFail = isExpectedFailure(fileName: record.fileName, description: record.description, message: &msg, action: &failAct); |
282 | if (expectFail) { |
283 | switch (failAct) { |
284 | case FailureItem::ExpectFail: |
285 | QTest::qExpectFail(dataIndex: "" , comment: msg.toLatin1(), |
286 | mode: QTest::Continue, file: record.fileName.toLatin1(), |
287 | line: record.lineNumber); |
288 | break; |
289 | case FailureItem::Skip: |
290 | QTest::qSkip(message: msg.toLatin1(), file: record.fileName.toLatin1(), line: record.lineNumber); |
291 | break; |
292 | } |
293 | } |
294 | if (!expectFail || (failAct == FailureItem::ExpectFail)) { |
295 | if (!record.passed) { |
296 | if (!expectFail && shouldGenerateExpectedFailures) { |
297 | addExpectedFailure(fileName: record.fileName, |
298 | description: record.description, |
299 | message: QString()); |
300 | } |
301 | QTest::qCompare(t1: record.actual, t2: record.expected, actual: "actual" , expected: "expect" , |
302 | file: record.fileName.toLatin1(), line: record.lineNumber); |
303 | } else { |
304 | QTest::qCompare(t1: record.actual, t2: record.actual, actual: "actual" , expected: "expect" , |
305 | file: record.fileName.toLatin1(), line: record.lineNumber); |
306 | } |
307 | } |
308 | } |
309 | } |
310 | } |
311 | |
312 | tst_QScriptJSTestSuite::tst_QScriptJSTestSuite() |
313 | : AbstractTestSuite("tst_QScriptJsTestSuite" , |
314 | QFINDTESTDATA("tests" ), |
315 | ":/" ) |
316 | { |
317 | // don't execute any tests on slow machines |
318 | #if !defined(Q_OS_IRIX) |
319 | // do all the test suites |
320 | const QFileInfoList testSuiteDirInfos = testsDir.entryInfoList(filters: QDir::AllDirs | QDir::NoDotAndDotDot); |
321 | for (const QFileInfo &tsdi : testSuiteDirInfos) { |
322 | QDir testSuiteDir(tsdi.absoluteFilePath()); |
323 | // do all the dirs in the test suite |
324 | const QFileInfoList subSuiteDirInfos = testSuiteDir.entryInfoList(filters: QDir::AllDirs | QDir::NoDotAndDotDot); |
325 | for (const QFileInfo &ssdi : subSuiteDirInfos) { |
326 | subSuitePaths.append(t: ssdi.absoluteFilePath()); |
327 | QString function = QString::fromLatin1(str: "%0/%1" ) |
328 | .arg(a: testSuiteDir.dirName()).arg(a: ssdi.fileName()); |
329 | addTestFunction(function, CreateDataFunction); |
330 | } |
331 | } |
332 | #endif |
333 | |
334 | finalizeMetaObject(); |
335 | } |
336 | |
337 | tst_QScriptJSTestSuite::~tst_QScriptJSTestSuite() |
338 | { |
339 | } |
340 | |
341 | void tst_QScriptJSTestSuite::configData(TestConfig::Mode mode, const QStringList &parts) |
342 | { |
343 | switch (mode) { |
344 | case TestConfig::Skip: |
345 | addFileExclusion(fileName: parts.at(i: 0), message: parts.value(i: 1)); |
346 | break; |
347 | |
348 | case TestConfig::ExpectFail: |
349 | addExpectedFailure(fileName: parts.at(i: 0), description: parts.value(i: 1), message: parts.value(i: 2)); |
350 | break; |
351 | } |
352 | } |
353 | |
354 | void tst_QScriptJSTestSuite::writeSkipConfigFile(QTextStream &stream) |
355 | { |
356 | stream << QString::fromLatin1(str: "# testcase | message" ) << endl; |
357 | } |
358 | |
359 | void tst_QScriptJSTestSuite::writeExpectFailConfigFile(QTextStream &stream) |
360 | { |
361 | stream << QString::fromLatin1(str: "# testcase | description | message" ) << endl; |
362 | for (int i = 0; i < expectedFailures.size(); ++i) { |
363 | const FailureItem &fail = expectedFailures.at(i); |
364 | if (fail.pathRegExp.pattern().isEmpty()) |
365 | continue; |
366 | stream << QString::fromLatin1(str: "%0 | %1" ) |
367 | .arg(a: fail.pathRegExp.pattern()) |
368 | .arg(a: escape(fail.description)); |
369 | if (!fail.message.isEmpty()) |
370 | stream << QString::fromLatin1(str: " | %0" ).arg(a: escape(fail.message)); |
371 | stream << endl; |
372 | } |
373 | } |
374 | |
375 | void tst_QScriptJSTestSuite::addExpectedFailure(const QRegExp &path, const QString &description, const QString &message) |
376 | { |
377 | expectedFailures.append(t: FailureItem(FailureItem::ExpectFail, path, description, message)); |
378 | } |
379 | |
380 | void tst_QScriptJSTestSuite::addExpectedFailure(const QString &fileName, const QString &description, const QString &message) |
381 | { |
382 | expectedFailures.append(t: FailureItem(FailureItem::ExpectFail, QRegExp(fileName), description, message)); |
383 | } |
384 | |
385 | void tst_QScriptJSTestSuite::addSkip(const QRegExp &path, const QString &description, const QString &message) |
386 | { |
387 | expectedFailures.append(t: FailureItem(FailureItem::Skip, path, description, message)); |
388 | } |
389 | |
390 | void tst_QScriptJSTestSuite::addSkip(const QString &fileName, const QString &description, const QString &message) |
391 | { |
392 | expectedFailures.append(t: FailureItem(FailureItem::Skip, QRegExp(fileName), description, message)); |
393 | } |
394 | |
395 | bool tst_QScriptJSTestSuite::isExpectedFailure(const QString &fileName, const QString &description, |
396 | QString *message, FailureItem::Action *action) const |
397 | { |
398 | for (int i = 0; i < expectedFailures.size(); ++i) { |
399 | QRegExp pathRegExp = expectedFailures.at(i).pathRegExp; |
400 | if (pathRegExp.indexIn(str: fileName) != -1) { |
401 | if (description == expectedFailures.at(i).description) { |
402 | if (message) |
403 | *message = expectedFailures.at(i).message; |
404 | if (action) |
405 | *action = expectedFailures.at(i).action; |
406 | return true; |
407 | } |
408 | } |
409 | } |
410 | return false; |
411 | } |
412 | |
413 | void tst_QScriptJSTestSuite::addFileExclusion(const QString &fileName, const QString &message) |
414 | { |
415 | fileExclusions.append(t: qMakePair(x: QRegExp(fileName), y: message)); |
416 | } |
417 | |
418 | void tst_QScriptJSTestSuite::addFileExclusion(const QRegExp &rx, const QString &message) |
419 | { |
420 | fileExclusions.append(t: qMakePair(x: rx, y: message)); |
421 | } |
422 | |
423 | bool tst_QScriptJSTestSuite::isExcludedFile(const QString &fileName, QString *message) const |
424 | { |
425 | for (int i = 0; i < fileExclusions.size(); ++i) { |
426 | QRegExp copy = fileExclusions.at(i).first; |
427 | if (copy.indexIn(str: fileName) != -1) { |
428 | if (message) |
429 | *message = fileExclusions.at(i).second; |
430 | return true; |
431 | } |
432 | } |
433 | return false; |
434 | } |
435 | |
436 | QTEST_MAIN(tst_QScriptJSTestSuite) |
437 | |