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