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