X-Git-Url: https://git.saurik.com/apple/javascriptcore.git/blobdiff_plain/b80e619319b1def83d1e8b4f84042b661be1be7f..14957cd040308e3eeec43d26bae5d76da13fcd85:/qt/tests/qscriptengine/tst_qscriptengine.cpp diff --git a/qt/tests/qscriptengine/tst_qscriptengine.cpp b/qt/tests/qscriptengine/tst_qscriptengine.cpp index 1ec9ad3..dabcfb2 100644 --- a/qt/tests/qscriptengine/tst_qscriptengine.cpp +++ b/qt/tests/qscriptengine/tst_qscriptengine.cpp @@ -21,6 +21,7 @@ #include "qscriptprogram.h" #include "qscriptsyntaxcheckresult.h" #include "qscriptvalue.h" +#include #include class tst_QScriptEngine : public QObject { @@ -35,6 +36,8 @@ public slots: void cleanup() {} private slots: + void newFunction(); + void newObject(); void globalObject(); void evaluate(); void collectGarbage(); @@ -44,6 +47,11 @@ private slots: void evaluateProgram(); void checkSyntax_data(); void checkSyntax(); + void toObject(); + void toObjectTwoEngines(); + void newArray(); + void uncaughtException(); + void newDate(); }; /* Evaluating a script that throw an unhandled exception should return an invalid value. */ @@ -54,6 +62,158 @@ void tst_QScriptEngine::evaluate() QVERIFY2(engine.evaluate("ping").isValid(), "Script throwing an unhandled exception should return an exception value"); } +static QScriptValue myFunction(QScriptContext*, QScriptEngine* eng) +{ + return eng->nullValue(); +} + +static QScriptValue myFunctionWithArg(QScriptContext*, QScriptEngine* eng, void* arg) +{ + int* result = reinterpret_cast(arg); + return QScriptValue(eng, *result); +} + +static QScriptValue myFunctionThatReturns(QScriptContext*, QScriptEngine* eng) +{ + return QScriptValue(eng, 42); +} + +static QScriptValue myFunctionThatReturnsWithoutEngine(QScriptContext*, QScriptEngine*) +{ + return QScriptValue(1024); +} + +static QScriptValue myFunctionThatReturnsWrongEngine(QScriptContext*, QScriptEngine*, void* arg) +{ + QScriptEngine* wrongEngine = reinterpret_cast(arg); + return QScriptValue(wrongEngine, 42); +} + +void tst_QScriptEngine::newFunction() +{ + QScriptEngine eng; + { + QScriptValue fun = eng.newFunction(myFunction); + QCOMPARE(fun.isValid(), true); + QCOMPARE(fun.isFunction(), true); + QCOMPARE(fun.isObject(), true); + // QCOMPARE(fun.scriptClass(), (QScriptClass*)0); + // a prototype property is automatically constructed + { + QScriptValue prot = fun.property("prototype", QScriptValue::ResolveLocal); + QVERIFY(prot.isObject()); + QVERIFY(prot.property("constructor").strictlyEquals(fun)); + QEXPECT_FAIL("", "JSCallbackObject::getOwnPropertyDescriptor() doesn't return correct information yet", Continue); + QCOMPARE(fun.propertyFlags("prototype"), QScriptValue::Undeletable); + QEXPECT_FAIL("", "WebKit bug: 40613 (The JSObjectSetProperty doesn't overwrite property flags)", Continue); + QCOMPARE(prot.propertyFlags("constructor"), QScriptValue::PropertyFlags(QScriptValue::Undeletable | QScriptValue::SkipInEnumeration)); + } + // prototype should be Function.prototype + QCOMPARE(fun.prototype().isValid(), true); + QCOMPARE(fun.prototype().isFunction(), true); + QCOMPARE(fun.prototype().strictlyEquals(eng.evaluate("Function.prototype")), true); + + QCOMPARE(fun.call().isNull(), true); + // QCOMPARE(fun.construct().isObject(), true); + } + // the overload that takes an extra argument + { + int expectedResult = 42; + QScriptValue fun = eng.newFunction(myFunctionWithArg, reinterpret_cast(&expectedResult)); + QVERIFY(fun.isFunction()); + // QCOMPARE(fun.scriptClass(), (QScriptClass*)0); + // a prototype property is automatically constructed + { + QScriptValue prot = fun.property("prototype", QScriptValue::ResolveLocal); + QVERIFY(prot.isObject()); + QVERIFY(prot.property("constructor").strictlyEquals(fun)); + QEXPECT_FAIL("", "JSCallbackObject::getOwnPropertyDescriptor() doesn't return correct information yet", Continue); + QCOMPARE(fun.propertyFlags("prototype"), QScriptValue::Undeletable); + QEXPECT_FAIL("", "WebKit bug: 40613 (The JSObjectSetProperty doesn't overwrite property flags)", Continue); + QCOMPARE(prot.propertyFlags("constructor"), QScriptValue::PropertyFlags(QScriptValue::Undeletable | QScriptValue::SkipInEnumeration)); + } + // prototype should be Function.prototype + QCOMPARE(fun.prototype().isValid(), true); + QCOMPARE(fun.prototype().isFunction(), true); + QCOMPARE(fun.prototype().strictlyEquals(eng.evaluate("Function.prototype")), true); + + QScriptValue result = fun.call(); + QCOMPARE(result.isNumber(), true); + QCOMPARE(result.toInt32(), expectedResult); + } + // the overload that takes a prototype + { + QScriptValue proto = eng.newObject(); + QScriptValue fun = eng.newFunction(myFunction, proto); + QCOMPARE(fun.isValid(), true); + QCOMPARE(fun.isFunction(), true); + QCOMPARE(fun.isObject(), true); + // internal prototype should be Function.prototype + QCOMPARE(fun.prototype().isValid(), true); + QCOMPARE(fun.prototype().isFunction(), true); + QCOMPARE(fun.prototype().strictlyEquals(eng.evaluate("Function.prototype")), true); + // public prototype should be the one we passed + QCOMPARE(fun.property("prototype").strictlyEquals(proto), true); + QEXPECT_FAIL("", "JSCallbackObject::getOwnPropertyDescriptor() doesn't return correct information yet", Continue); + QCOMPARE(fun.propertyFlags("prototype"), QScriptValue::Undeletable); + QCOMPARE(proto.property("constructor").strictlyEquals(fun), true); + QEXPECT_FAIL("", "WebKit bug: 40613 (The JSObjectSetProperty doesn't overwrite property flags)", Continue); + QCOMPARE(proto.propertyFlags("constructor"), QScriptValue::PropertyFlags(QScriptValue::Undeletable | QScriptValue::SkipInEnumeration)); + + QCOMPARE(fun.call().isNull(), true); + // QCOMPARE(fun.construct().isObject(), true); + } + // whether the return value is correct + { + QScriptValue fun = eng.newFunction(myFunctionThatReturns); + QCOMPARE(fun.isValid(), true); + QCOMPARE(fun.isFunction(), true); + QCOMPARE(fun.isObject(), true); + + QScriptValue result = fun.call(); + QCOMPARE(result.isNumber(), true); + QCOMPARE(result.toInt32(), 42); + } + // whether the return value is assigned to the correct engine + { + QScriptValue fun = eng.newFunction(myFunctionThatReturnsWithoutEngine); + QCOMPARE(fun.isValid(), true); + QCOMPARE(fun.isFunction(), true); + QCOMPARE(fun.isObject(), true); + + QScriptValue result = fun.call(); + QCOMPARE(result.engine(), &eng); + QCOMPARE(result.isNumber(), true); + QCOMPARE(result.toInt32(), 1024); + } + // whether the return value is undefined when returning a value with wrong engine + { + QScriptEngine wrongEngine; + + QScriptValue fun = eng.newFunction(myFunctionThatReturnsWrongEngine, reinterpret_cast(&wrongEngine)); + QCOMPARE(fun.isValid(), true); + QCOMPARE(fun.isFunction(), true); + QCOMPARE(fun.isObject(), true); + + QTest::ignoreMessage(QtWarningMsg, "Value from different engine returned from native function, returning undefined value instead."); + QScriptValue result = fun.call(); + QCOMPARE(result.isValid(), true); + QCOMPARE(result.isUndefined(), true); + } +} + +void tst_QScriptEngine::newObject() +{ + QScriptEngine engine; + QScriptValue object = engine.newObject(); + QVERIFY(object.isObject()); + QVERIFY(object.engine() == &engine); + QVERIFY(!object.isError()); + QVERIFY(!object.equals(engine.newObject())); + QVERIFY(!object.strictlyEquals(engine.newObject())); + QCOMPARE(object.toString(), QString::fromAscii("[object Object]")); +} + void tst_QScriptEngine::globalObject() { QScriptEngine engine; @@ -61,7 +221,6 @@ void tst_QScriptEngine::globalObject() QScriptValue self = engine.evaluate("this"); QVERIFY(global.isObject()); QVERIFY(engine.globalObject().equals(engine.evaluate("this"))); - QEXPECT_FAIL("", "strictlyEquals is broken - bug 36600 in bugs.webkit.org", Continue); QVERIFY(engine.globalObject().strictlyEquals(self)); } @@ -303,6 +462,273 @@ void tst_QScriptEngine::checkSyntax() QCOMPARE(result.errorMessage(), errorMessage); } +void tst_QScriptEngine::toObject() +{ + QScriptEngine eng; + QVERIFY(!eng.toObject(eng.undefinedValue()).isValid()); + QVERIFY(!eng.toObject(eng.nullValue()).isValid()); + QVERIFY(!eng.toObject(QScriptValue()).isValid()); + + QScriptValue falskt(false); + { + QScriptValue tmp = eng.toObject(falskt); + QVERIFY(tmp.isObject()); + QVERIFY(!falskt.isObject()); + QVERIFY(!falskt.engine()); + QCOMPARE(tmp.toNumber(), falskt.toNumber()); + } + + QScriptValue sant(true); + { + QScriptValue tmp = eng.toObject(sant); + QVERIFY(tmp.isObject()); + QVERIFY(!sant.isObject()); + QVERIFY(!sant.engine()); + QCOMPARE(tmp.toNumber(), sant.toNumber()); + } + + QScriptValue number(123.0); + { + QScriptValue tmp = eng.toObject(number); + QVERIFY(tmp.isObject()); + QVERIFY(!number.isObject()); + QVERIFY(!number.engine()); + QCOMPARE(tmp.toNumber(), number.toNumber()); + } + + QScriptValue str = QScriptValue(&eng, QString("ciao")); + { + QScriptValue tmp = eng.toObject(str); + QVERIFY(tmp.isObject()); + QVERIFY(!str.isObject()); + QCOMPARE(tmp.toString(), str.toString()); + } + + QScriptValue object = eng.evaluate("new Object"); + { + QScriptValue tmp = eng.toObject(object); + QVERIFY(tmp.isObject()); + QVERIFY(object.isObject()); + QVERIFY(tmp.strictlyEquals(object)); + } +} + +void tst_QScriptEngine::toObjectTwoEngines() +{ + QScriptEngine engine1; + QScriptEngine engine2; + + { + QScriptValue null = engine1.nullValue(); + QTest::ignoreMessage(QtWarningMsg, "QScriptEngine::toObject: cannot convert value created in a different engine"); + QVERIFY(!engine2.toObject(null).isValid()); + QVERIFY(null.isValid()); + QTest::ignoreMessage(QtWarningMsg, "QScriptEngine::toObject: cannot convert value created in a different engine"); + QVERIFY(engine2.toObject(null).engine() != &engine2); + } + { + QScriptValue undefined = engine1.undefinedValue(); + QTest::ignoreMessage(QtWarningMsg, "QScriptEngine::toObject: cannot convert value created in a different engine"); + QVERIFY(!engine2.toObject(undefined).isValid()); + QVERIFY(undefined.isValid()); + QTest::ignoreMessage(QtWarningMsg, "QScriptEngine::toObject: cannot convert value created in a different engine"); + QVERIFY(engine2.toObject(undefined).engine() != &engine2); + } + { + QScriptValue value = engine1.evaluate("1"); + QTest::ignoreMessage(QtWarningMsg, "QScriptEngine::toObject: cannot convert value created in a different engine"); + QVERIFY(engine2.toObject(value).engine() != &engine2); + QVERIFY(!value.isObject()); + } + { + QScriptValue string = engine1.evaluate("'Qt'"); + QTest::ignoreMessage(QtWarningMsg, "QScriptEngine::toObject: cannot convert value created in a different engine"); + QVERIFY(engine2.toObject(string).engine() != &engine2); + QVERIFY(!string.isObject()); + } + { + QScriptValue object = engine1.evaluate("new Object"); + QTest::ignoreMessage(QtWarningMsg, "QScriptEngine::toObject: cannot convert value created in a different engine"); + QVERIFY(engine2.toObject(object).engine() != &engine2); + QVERIFY(object.isObject()); + } +} + +void tst_QScriptEngine::newArray() +{ + QScriptEngine eng; + QScriptValue array = eng.newArray(); + QCOMPARE(array.isValid(), true); + QCOMPARE(array.isArray(), true); + QCOMPARE(array.isObject(), true); + QVERIFY(!array.isFunction()); + // QCOMPARE(array.scriptClass(), (QScriptClass*)0); + + // Prototype should be Array.prototype. + QCOMPARE(array.prototype().isValid(), true); + QCOMPARE(array.prototype().isArray(), true); + QCOMPARE(array.prototype().strictlyEquals(eng.evaluate("Array.prototype")), true); + + QScriptValue arrayWithSize = eng.newArray(42); + QCOMPARE(arrayWithSize.isValid(), true); + QCOMPARE(arrayWithSize.isArray(), true); + QCOMPARE(arrayWithSize.isObject(), true); + QCOMPARE(arrayWithSize.property("length").toInt32(), 42); + + // task 218092 + { + QScriptValue ret = eng.evaluate("[].splice(0, 0, 'a')"); + QVERIFY(ret.isArray()); + QCOMPARE(ret.property("length").toInt32(), 0); + } + { + QScriptValue ret = eng.evaluate("['a'].splice(0, 1, 'b')"); + QVERIFY(ret.isArray()); + QCOMPARE(ret.property("length").toInt32(), 1); + } + { + QScriptValue ret = eng.evaluate("['a', 'b'].splice(0, 1, 'c')"); + QVERIFY(ret.isArray()); + QCOMPARE(ret.property("length").toInt32(), 1); + } + { + QScriptValue ret = eng.evaluate("['a', 'b', 'c'].splice(0, 2, 'd')"); + QVERIFY(ret.isArray()); + QCOMPARE(ret.property("length").toInt32(), 2); + } + { + QScriptValue ret = eng.evaluate("['a', 'b', 'c'].splice(1, 2, 'd', 'e', 'f')"); + QVERIFY(ret.isArray()); + QCOMPARE(ret.property("length").toInt32(), 2); + } +} + +void tst_QScriptEngine::uncaughtException() +{ + QScriptEngine eng; + QScriptValue fun = eng.evaluate("(function foo () { return null; });"); + QVERIFY(!eng.uncaughtException().isValid()); + QVERIFY(fun.isFunction()); + QScriptValue throwFun = eng.evaluate("( function() { throw new Error('Pong'); });"); + QVERIFY(throwFun.isFunction()); + { + eng.evaluate("a = 10"); + QVERIFY(!eng.hasUncaughtException()); + QVERIFY(!eng.uncaughtException().isValid()); + } + { + eng.evaluate("1 = 2"); + QVERIFY(eng.hasUncaughtException()); + eng.clearExceptions(); + QVERIFY(!eng.hasUncaughtException()); + } + { + // Check if the call or toString functions can remove the last exception. + QVERIFY(throwFun.call().isError()); + QVERIFY(eng.hasUncaughtException()); + QScriptValue exception = eng.uncaughtException(); + fun.call(); + exception.toString(); + QVERIFY(eng.hasUncaughtException()); + QVERIFY(eng.uncaughtException().strictlyEquals(exception)); + } + eng.clearExceptions(); + { + // Check if in the call function a new exception can override an existing one. + throwFun.call(); + QVERIFY(eng.hasUncaughtException()); + QScriptValue exception = eng.uncaughtException(); + throwFun.call(); + QVERIFY(eng.hasUncaughtException()); + QVERIFY(!exception.strictlyEquals(eng.uncaughtException())); + } + { + eng.evaluate("throwFun = (function foo () { throw new Error('bla') });"); + eng.evaluate("1;\nthrowFun();"); + QVERIFY(eng.hasUncaughtException()); + QCOMPARE(eng.uncaughtExceptionLineNumber(), 1); + eng.clearExceptions(); + QVERIFY(!eng.hasUncaughtException()); + } + for (int x = 1; x < 4; ++x) { + QScriptValue ret = eng.evaluate("a = 10;\nb = 20;\n0 = 0;\n", + QString::fromLatin1("FooScript") + QString::number(x), + /* lineNumber */ x); + QVERIFY(eng.hasUncaughtException()); + QCOMPARE(eng.uncaughtExceptionLineNumber(), x + 2); + QVERIFY(eng.uncaughtException().strictlyEquals(ret)); + QVERIFY(eng.hasUncaughtException()); + QVERIFY(eng.uncaughtException().strictlyEquals(ret)); + QString backtrace = QString::fromLatin1("()@FooScript") + QString::number(x) + ":" + QString::number(x + 2); + QCOMPARE(eng.uncaughtExceptionBacktrace().join(""), backtrace); + QVERIFY(fun.call().isNull()); + QVERIFY(eng.hasUncaughtException()); + QCOMPARE(eng.uncaughtExceptionLineNumber(), x + 2); + QVERIFY(eng.uncaughtException().strictlyEquals(ret)); + eng.clearExceptions(); + QVERIFY(!eng.hasUncaughtException()); + QCOMPARE(eng.uncaughtExceptionLineNumber(), -1); + QVERIFY(!eng.uncaughtException().isValid()); + eng.evaluate("2 = 3"); + QVERIFY(eng.hasUncaughtException()); + QScriptValue ret2 = throwFun.call(); + QVERIFY(ret2.isError()); + QVERIFY(eng.hasUncaughtException()); + QVERIFY(eng.uncaughtException().strictlyEquals(ret2)); + QCOMPARE(eng.uncaughtExceptionLineNumber(), 1); + eng.clearExceptions(); + QVERIFY(!eng.hasUncaughtException()); + eng.evaluate("1 + 2"); + QVERIFY(!eng.hasUncaughtException()); + } +} + +void tst_QScriptEngine::newDate() +{ + QScriptEngine eng; + { + QScriptValue date = eng.newDate(0); + QCOMPARE(date.isValid(), true); + QCOMPARE(date.isDate(), true); + QCOMPARE(date.isObject(), true); + QVERIFY(!date.isFunction()); + // prototype should be Date.prototype + QCOMPARE(date.prototype().isValid(), true); + QCOMPARE(date.prototype().isDate(), true); + QCOMPARE(date.prototype().strictlyEquals(eng.evaluate("Date.prototype")), true); + } + { + QDateTime dt = QDateTime(QDate(1, 2, 3), QTime(4, 5, 6, 7), Qt::LocalTime); + QScriptValue date = eng.newDate(dt); + QCOMPARE(date.isValid(), true); + QCOMPARE(date.isDate(), true); + QCOMPARE(date.isObject(), true); + // prototype should be Date.prototype + QCOMPARE(date.prototype().isValid(), true); + QCOMPARE(date.prototype().isDate(), true); + QCOMPARE(date.prototype().strictlyEquals(eng.evaluate("Date.prototype")), true); + + QCOMPARE(date.toDateTime(), dt); + } + { + QDateTime dt = QDateTime(QDate(1, 2, 3), QTime(4, 5, 6, 7), Qt::UTC); + QScriptValue date = eng.newDate(dt); + // toDateTime() result should be in local time + QCOMPARE(date.toDateTime(), dt.toLocalTime()); + } + // Date.parse() should return NaN when it fails + { + QScriptValue ret = eng.evaluate("Date.parse()"); + QVERIFY(ret.isNumber()); + QVERIFY(qIsNaN(ret.toNumber())); + } + // Date.parse() should be able to parse the output of Date().toString() + { + QScriptValue ret = eng.evaluate("var x = new Date(); var s = x.toString(); s == new Date(Date.parse(s)).toString()"); + QVERIFY(ret.isBoolean()); + QCOMPARE(ret.toBoolean(), true); + } +} QTEST_MAIN(tst_QScriptEngine) #include "tst_qscriptengine.moc"