File indexing completed on 2024-05-05 16:15:13
0001 /* 0002 * This file is part of the KDE libraries 0003 * 0004 * Copyright (C) 2012 Rolf Eike Beer <kde@opensource.sf-tec.de> 0005 * 0006 * This library is free software; you can redistribute it and/or 0007 * modify it under the terms of the GNU Lesser General Public 0008 * License as published by the Free Software Foundation; either 0009 * version 2 of the License, or (at your option) any later version. 0010 */ 0011 0012 #include "ecmatest.h" 0013 #include <QTest> 0014 0015 #include <wtf/HashTraits.h> 0016 #include "JSLock.h" 0017 #include "object.h" 0018 #include "JSVariableObject.h" 0019 #include "Parser.h" 0020 0021 #include <QMap> 0022 #include <QDebug> 0023 #include <QRegularExpression> 0024 #include <QDirIterator> 0025 0026 // Let the interpreter create its own global Object instead of using our selfcreated 0027 #define USE_KJS_GLOBAL 1 0028 0029 // from khtml/ecma/kjs_binding.cpp" 0030 KJS::UString::UString(const QString &d) 0031 { 0032 unsigned int len = d.length(); 0033 KJS::UChar *dat = static_cast<KJS::UChar *>(fastMalloc(sizeof(KJS::UChar) * len)); 0034 memcpy(dat, reinterpret_cast<const void*>(d.unicode()), len * sizeof(KJS::UChar)); 0035 m_rep = KJS::UString::Rep::create(dat, len); 0036 } 0037 0038 QString KJS::UString::qstring() const 0039 { 0040 return QString((QChar *) data(), size()); 0041 } 0042 0043 // from khtml/ecma/debugger/value2string.cpp 0044 QString valueToString(KJS::JSValue *value) 0045 { 0046 switch (KJS::JSValue::type(value)) { 0047 case KJS::NumberType: { 0048 double v = 0.0; 0049 KJS::JSValue::getNumber(value, v); 0050 return QString::number(v); 0051 } 0052 case KJS::BooleanType: 0053 return KJS::JSValue::getBoolean(value) ? "true" : "false"; 0054 case KJS::StringType: { 0055 KJS::UString s; 0056 KJS::JSValue::getString(value, s); 0057 return '"' + s.qstring() + '"'; 0058 } 0059 case KJS::UndefinedType: 0060 return "undefined"; 0061 case KJS::NullType: 0062 return "null"; 0063 case KJS::ObjectType: 0064 return "[object " + static_cast<KJS::JSObject *>(value)->className().qstring() + "]"; 0065 case KJS::GetterSetterType: 0066 case KJS::UnspecifiedType: 0067 default: 0068 return QString(); 0069 } 0070 } 0071 0072 // from khtml/ecma/debugger/debugwindow.cpp 0073 static QString exceptionToString(KJS::ExecState *exec, KJS::JSValue *exceptionObj) 0074 { 0075 QString exceptionMsg = valueToString(exceptionObj); 0076 0077 // Since we purposefully bypass toString, we need to figure out 0078 // string serialization ourselves. 0079 //### might be easier to export class info for ErrorInstance --- 0080 0081 KJS::JSObject *valueObj = KJS::JSValue::getObject(exceptionObj); 0082 KJS::JSValue *protoObj = valueObj ? valueObj->prototype() : nullptr; 0083 0084 bool exception = false; 0085 bool syntaxError = false; 0086 if (protoObj == exec->lexicalInterpreter()->builtinSyntaxErrorPrototype()) { 0087 exception = true; 0088 syntaxError = true; 0089 } 0090 0091 if (protoObj == exec->lexicalInterpreter()->builtinErrorPrototype() || 0092 protoObj == exec->lexicalInterpreter()->builtinEvalErrorPrototype() || 0093 protoObj == exec->lexicalInterpreter()->builtinReferenceErrorPrototype() || 0094 protoObj == exec->lexicalInterpreter()->builtinRangeErrorPrototype() || 0095 protoObj == exec->lexicalInterpreter()->builtinTypeErrorPrototype() || 0096 protoObj == exec->lexicalInterpreter()->builtinURIErrorPrototype()) { 0097 exception = true; 0098 } 0099 0100 if (!exception) { 0101 return exceptionMsg; 0102 } 0103 0104 // Clear exceptions temporarily so we can get/call a few things. 0105 // We memorize the old exception first, of course. Note that 0106 // This is not always the same as exceptionObj since we may be 0107 // asked to translate a non-active exception 0108 KJS::JSValue *oldExceptionObj = exec->exception(); 0109 exec->clearException(); 0110 0111 // We want to serialize the syntax errors ourselves, to provide the line number. 0112 // The URL is in "sourceURL" and the line is in "line" 0113 // ### TODO: Perhaps we want to use 'sourceId' in case of eval contexts. 0114 if (syntaxError) { 0115 KJS::JSValue *lineValue = valueObj->get(exec, "line"); 0116 KJS::JSValue *urlValue = valueObj->get(exec, "sourceURL"); 0117 0118 int line = KJS::JSValue::toNumber(lineValue, exec); 0119 QString url = KJS::JSValue::toString(urlValue, exec).qstring(); 0120 exceptionMsg = QString::fromLatin1("Parse error at %1 line %2").arg(url).arg(line + 1); 0121 } else { 0122 // ### it's still not 100% safe to call toString here, even on 0123 // native exception objects, since someone might have changed the toString property 0124 // of the exception prototype, but I'll punt on this case for now. 0125 exceptionMsg = KJS::JSValue::toString(exceptionObj, exec).qstring(); 0126 } 0127 exec->setException(oldExceptionObj); 0128 return exceptionMsg; 0129 } 0130 0131 #ifndef USE_KJS_GLOBAL 0132 class GlobalImp : public KJS::JSGlobalObject 0133 { 0134 public: 0135 virtual KJS::UString className() const 0136 { 0137 return "global"; 0138 } 0139 }; 0140 0141 static GlobalImp *global; 0142 #endif 0143 static QString basedir(""); 0144 static QByteArray testrunner; 0145 static QMap<QByteArray, QByteArray> includes; 0146 static QStringList expectedBroken; // list of tests we know that will fail 0147 0148 /** 0149 * load the given file from the harness directory 0150 * @param fn filename 0151 * @return if operation succeeded 0152 * 0153 * Will load the given file into the "includes" map 0154 */ 0155 static bool loadInclude(const QByteArray &fn) 0156 { 0157 QFile runnerfile(basedir + QLatin1String("test/harness/") + QString::fromLatin1(fn.constData())); 0158 0159 if (!runnerfile.open(QIODevice::ReadOnly)) { 0160 return false; 0161 } 0162 0163 includes[ fn ] = runnerfile.readAll(); 0164 0165 return true; 0166 } 0167 0168 QTEST_MAIN(ECMAscriptTest) 0169 0170 void ECMAscriptTest::initTestCase() 0171 { 0172 basedir = QString::fromUtf8(qgetenv("ECMATEST_BASEDIR").constData()); 0173 0174 if (basedir.isEmpty()) { 0175 qFatal("ECMATEST_BASEDIR not set"); 0176 } 0177 0178 if (!basedir.endsWith(QLatin1Char('/'))) { 0179 basedir += QLatin1Char('/'); 0180 } 0181 0182 QVERIFY(loadInclude("sta.js")); 0183 QVERIFY(loadInclude("ed.js")); 0184 0185 testrunner = includes[ "sta.js" ] + includes[ "ed.js" ] + '\n'; 0186 0187 const QString brokenFn = QString::fromLatin1(qgetenv("ECMATEST_BROKEN").constData()); 0188 if (!brokenFn.isEmpty()) { 0189 QFile brokenF(brokenFn); 0190 if (!brokenF.open(QIODevice::ReadOnly)) { 0191 const QByteArray errmsg = QByteArray("cannot open ") + QFile::encodeName(brokenFn); 0192 QWARN(errmsg.constData()); 0193 } else { 0194 expectedBroken = QString::fromLatin1(brokenF.readAll().constData()).split(QLatin1Char('\n')) 0195 .filter(QRegularExpression(QStringLiteral("^[^#].*"))); 0196 } 0197 } 0198 0199 m_passed = 0; 0200 m_failed = 0; 0201 } 0202 0203 static QByteArray getTextProperty(const QByteArray &property, const QByteArray &code) 0204 { 0205 int from = code.indexOf(property); 0206 if (from == -1) { 0207 return QByteArray(); 0208 } 0209 0210 from += property.length(); 0211 while (code[ from ] == ' ') { 0212 from++; 0213 } 0214 0215 int to = code.indexOf('\n', from); 0216 if (code[to - 1] == '\r') { 0217 to--; 0218 } 0219 // poor mans escaping 0220 return code.mid(from, to - from).replace("\\", "\\\\").replace("\"", "\\\""); 0221 } 0222 0223 #define ECMATEST_VERIFY( expr ) \ 0224 do { \ 0225 const bool tmp_result = ( expr ); \ 0226 if ( tmp_result ) \ 0227 m_passed++; \ 0228 else \ 0229 m_failed++; \ 0230 if ( knownBroken ) \ 0231 QEXPECT_FAIL(QTest::currentDataTag(), "It is known that KJS doesn't pass this test", Abort); \ 0232 QVERIFY( tmp_result ); \ 0233 } while (0) 0234 0235 static QMap< QByteArray, QByteArray > skips; 0236 0237 void ECMAscriptTest::runAllTests() 0238 { 0239 static const QByteArray include = "$INCLUDE(\""; 0240 0241 QFETCH(QString, filename); 0242 QByteArray expectedError; 0243 0244 QFile input(filename); 0245 0246 Q_FOREACH (const QByteArray &skip, skips.keys()) { 0247 if (skip == QTest::currentDataTag()) { 0248 QSKIP(skips[ skip ].constData()); 0249 } 0250 } 0251 0252 QVERIFY(input.open(QIODevice::ReadOnly)); 0253 0254 const QByteArray testdata = input.readAll(); 0255 0256 QVERIFY(! testdata.isEmpty()); 0257 0258 #ifdef USE_KJS_GLOBAL 0259 RefPtr<KJS::Interpreter> interp = new KJS::Interpreter(); 0260 #else 0261 RefPtr<KJS::Interpreter> interp = new KJS::Interpreter(global); 0262 #endif 0263 0264 KJS::Interpreter::setShouldPrintExceptions(true); 0265 0266 QByteArray testscript; 0267 0268 // test is expected to fail 0269 if (testdata.indexOf("@negative") >= 0) { 0270 expectedError = getTextProperty("@negative", testdata); 0271 if (expectedError.isEmpty()) { 0272 expectedError = "."; 0273 } 0274 } 0275 0276 int from = 0; 0277 while ((from = testdata.indexOf(include, from)) >= 0) { 0278 int endq = testdata.indexOf("\"", from + include.length()); 0279 QVERIFY(endq >= 0); 0280 0281 const QByteArray includeFile = testdata.mid(from + include.length(), endq - from - include.length()); 0282 0283 if (! includes.contains(includeFile)) { 0284 QVERIFY(loadInclude(includeFile)); 0285 } 0286 0287 testscript += includes[ includeFile ]; 0288 from = endq; 0289 } 0290 0291 testscript += testrunner; 0292 0293 testscript += testdata; 0294 0295 const QFileInfo info(input); 0296 0297 const QString scriptutf = QString::fromUtf8(testscript.constData()); 0298 0299 // QWARN(filename.toAscii().data()); 0300 KJS::Completion completion = interp->evaluate(info.fileName().toLatin1().constData(), 0, scriptutf); 0301 0302 const bool knownBroken = expectedBroken.contains(QString::fromLatin1(QTest::currentDataTag())); 0303 0304 if (expectedError.isEmpty()) { 0305 ECMATEST_VERIFY(completion.complType() != KJS::Throw); 0306 } else { 0307 if (knownBroken && completion.complType() != KJS::Throw) { 0308 QEXPECT_FAIL(QTest::currentDataTag(), "It is known that KJS doesn't pass this test", Abort); 0309 m_failed++; 0310 } 0311 0312 QCOMPARE(completion.complType(), KJS::Throw); 0313 QVERIFY(completion.value() != nullptr); 0314 0315 const QString eMsg = exceptionToString(interp->execState(), completion.value()); 0316 0317 if (expectedError == "^((?!NotEarlyError).)*$") { 0318 ECMATEST_VERIFY(eMsg.indexOf("NotEarlyError") == -1); 0319 } else if (expectedError == ".") { 0320 // means "every exception passes 0321 } else { 0322 ECMATEST_VERIFY(eMsg.indexOf(expectedError) >= 0); 0323 } 0324 } 0325 } 0326 0327 void ECMAscriptTest::runAllTests_data() 0328 { 0329 #ifndef USE_KJS_GLOBAL 0330 global = new GlobalImp(); 0331 #endif 0332 0333 QTest::addColumn<QString>("filename"); 0334 0335 const QStringList js(QLatin1String("*.js")); 0336 const QStringList all(QLatin1String("*")); 0337 const QString chapter = QString::fromLatin1(qgetenv("ECMATEST_CHAPTER").constData()); 0338 0339 if (!chapter.isEmpty()) { 0340 QWARN(QByteArray("===> Testing chapter " + chapter.toLatin1()).constData()); 0341 } 0342 0343 if (chapter.isEmpty() || chapter.startsWith("ch15")) { 0344 const QByteArray timeZoneDepend = "this test depends on the timezone and may or may not fail, avoid it for the moment"; 0345 // The tests are timezone dependent because of the Date implementation in kjs. 0346 // It only affects the limit by +/- 24h (or less, depending on your timezone), 0347 // the "normal" use is not affected. 0348 // It requires a complete Date rewrite to fix this, see ECMA Edition 5.1r6 15.9.1.1 0349 skips[ "15.9.5.43-0-8" ] = timeZoneDepend; 0350 skips[ "15.9.5.43-0-9" ] = timeZoneDepend; 0351 skips[ "15.9.5.43-0-10" ] = timeZoneDepend; 0352 skips[ "15.9.5.43-0-11" ] = timeZoneDepend; 0353 skips[ "15.9.5.43-0-12" ] = timeZoneDepend; 0354 } 0355 0356 #ifndef USE_KJS_GLOBAL 0357 // some tests fail when the suite is run as a whole 0358 if (chapter.isEmpty() || chapter.startsWith("ch15") || chapter.startsWith("ch12")) { 0359 const QByteArray endlessLoop = "this test causes an endless loop, avoid it for the moment"; 0360 const QByteArray crashTest = "this test causes a crash when run as part of the whole suite"; 0361 skips[ "S12.7_A9_T1" ] = endlessLoop; 0362 skips[ "S12.7_A9_T2" ] = endlessLoop; 0363 skips[ "S15.1.2.3_A6" ] = endlessLoop; 0364 skips[ "S15.1.3.1_A2.5_T1" ] = endlessLoop; 0365 skips[ "S15.1.3.2_A2.4_T1" ] = endlessLoop; 0366 skips[ "S15.1.3.2_A2.5_T1" ] = endlessLoop; 0367 skips[ "15.2.3.4-4-1" ] = crashTest; 0368 skips[ "15.2.3.11-4-1" ] = crashTest; 0369 skips[ "15.2.3.12-3-1" ] = crashTest; 0370 } 0371 #endif 0372 0373 QDirIterator it(basedir + QLatin1String("test/suite/") + chapter, QDirIterator::Subdirectories); 0374 while (it.hasNext()) { 0375 it.next(); 0376 0377 const QFileInfo info = it.fileInfo(); 0378 0379 if (!info.isFile()) { 0380 continue; 0381 } 0382 0383 QString filename = info.fileName(); 0384 0385 filename.chop(3); // .js 0386 0387 QTest::newRow(filename.toLatin1().constData()) << info.absoluteFilePath(); 0388 } 0389 } 0390 0391 void ECMAscriptTest::cleanup() 0392 { 0393 #ifndef USE_KJS_GLOBAL 0394 global->clearProperties(); 0395 #endif 0396 } 0397 0398 void ECMAscriptTest::cleanupTestCase() 0399 { 0400 qDebug() << "passed testcases:" << m_passed << "failed testcases:" << m_failed; 0401 } 0402 0403 #include "moc_ecmatest.cpp"