1/*
2    Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies)
3
4    This library is free software; you can redistribute it and/or
5    modify it under the terms of the GNU Library General Public
6    License as published by the Free Software Foundation; either
7    version 2 of the License, or (at your option) any later version.
8
9    This library is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12    Library General Public License for more details.
13
14    You should have received a copy of the GNU Library General Public License
15    along with this library; see the file COPYING.LIB.  If not, write to
16    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17    Boston, MA 02110-1301, USA.
18*/
19
20#include "qscriptengine.h"
21#include "qscriptprogram.h"
22#include "qscriptsyntaxcheckresult.h"
23#include "qscriptvalue.h"
24#include <QtCore/qnumeric.h>
25#include <QtTest/qtest.h>
26
27class tst_QScriptEngine : public QObject {
28    Q_OBJECT
29
30public:
31    tst_QScriptEngine() {}
32    virtual ~tst_QScriptEngine() {}
33
34public slots:
35    void init() {}
36    void cleanup() {}
37
38private slots:
39    void newFunction();
40    void newObject();
41    void globalObject();
42    void evaluate();
43    void collectGarbage();
44    void reportAdditionalMemoryCost();
45    void nullValue();
46    void undefinedValue();
47    void evaluateProgram();
48    void checkSyntax_data();
49    void checkSyntax();
50    void toObject();
51    void toObjectTwoEngines();
52    void newArray();
53    void uncaughtException();
54    void newDate();
55};
56
57/* Evaluating a script that throw an unhandled exception should return an invalid value. */
58void tst_QScriptEngine::evaluate()
59{
60    QScriptEngine engine;
61    QVERIFY2(engine.evaluate("1+1").isValid(), "the expression should be evaluated and an valid result should be returned");
62    QVERIFY2(engine.evaluate("ping").isValid(), "Script throwing an unhandled exception should return an exception value");
63}
64
65static QScriptValue myFunction(QScriptContext*, QScriptEngine* eng)
66{
67    return eng->nullValue();
68}
69
70static QScriptValue myFunctionWithArg(QScriptContext*, QScriptEngine* eng, void* arg)
71{
72    int* result = reinterpret_cast<int*>(arg);
73    return QScriptValue(eng, *result);
74}
75
76static QScriptValue myFunctionThatReturns(QScriptContext*, QScriptEngine* eng)
77{
78    return QScriptValue(eng, 42);
79}
80
81static QScriptValue myFunctionThatReturnsWithoutEngine(QScriptContext*, QScriptEngine*)
82{
83    return QScriptValue(1024);
84}
85
86static QScriptValue myFunctionThatReturnsWrongEngine(QScriptContext*, QScriptEngine*, void* arg)
87{
88    QScriptEngine* wrongEngine = reinterpret_cast<QScriptEngine*>(arg);
89    return QScriptValue(wrongEngine, 42);
90}
91
92void tst_QScriptEngine::newFunction()
93{
94    QScriptEngine eng;
95    {
96        QScriptValue fun = eng.newFunction(myFunction);
97        QCOMPARE(fun.isValid(), true);
98        QCOMPARE(fun.isFunction(), true);
99        QCOMPARE(fun.isObject(), true);
100        // QCOMPARE(fun.scriptClass(), (QScriptClass*)0);
101        // a prototype property is automatically constructed
102        {
103            QScriptValue prot = fun.property("prototype", QScriptValue::ResolveLocal);
104            QVERIFY(prot.isObject());
105            QVERIFY(prot.property("constructor").strictlyEquals(fun));
106            QEXPECT_FAIL("", "JSCallbackObject::getOwnPropertyDescriptor() doesn't return correct information yet", Continue);
107            QCOMPARE(fun.propertyFlags("prototype"), QScriptValue::Undeletable);
108            QEXPECT_FAIL("", "WebKit bug: 40613 (The JSObjectSetProperty doesn't overwrite property flags)", Continue);
109            QCOMPARE(prot.propertyFlags("constructor"), QScriptValue::PropertyFlags(QScriptValue::Undeletable | QScriptValue::SkipInEnumeration));
110        }
111        // prototype should be Function.prototype
112        QCOMPARE(fun.prototype().isValid(), true);
113        QCOMPARE(fun.prototype().isFunction(), true);
114        QCOMPARE(fun.prototype().strictlyEquals(eng.evaluate("Function.prototype")), true);
115
116        QCOMPARE(fun.call().isNull(), true);
117        // QCOMPARE(fun.construct().isObject(), true);
118    }
119    // the overload that takes an extra argument
120    {
121        int expectedResult = 42;
122        QScriptValue fun = eng.newFunction(myFunctionWithArg, reinterpret_cast<void*>(&expectedResult));
123        QVERIFY(fun.isFunction());
124        // QCOMPARE(fun.scriptClass(), (QScriptClass*)0);
125        // a prototype property is automatically constructed
126        {
127            QScriptValue prot = fun.property("prototype", QScriptValue::ResolveLocal);
128            QVERIFY(prot.isObject());
129            QVERIFY(prot.property("constructor").strictlyEquals(fun));
130            QEXPECT_FAIL("", "JSCallbackObject::getOwnPropertyDescriptor() doesn't return correct information yet", Continue);
131            QCOMPARE(fun.propertyFlags("prototype"), QScriptValue::Undeletable);
132            QEXPECT_FAIL("", "WebKit bug: 40613 (The JSObjectSetProperty doesn't overwrite property flags)", Continue);
133            QCOMPARE(prot.propertyFlags("constructor"), QScriptValue::PropertyFlags(QScriptValue::Undeletable | QScriptValue::SkipInEnumeration));
134        }
135        // prototype should be Function.prototype
136        QCOMPARE(fun.prototype().isValid(), true);
137        QCOMPARE(fun.prototype().isFunction(), true);
138        QCOMPARE(fun.prototype().strictlyEquals(eng.evaluate("Function.prototype")), true);
139
140        QScriptValue result = fun.call();
141        QCOMPARE(result.isNumber(), true);
142        QCOMPARE(result.toInt32(), expectedResult);
143    }
144    // the overload that takes a prototype
145    {
146        QScriptValue proto = eng.newObject();
147        QScriptValue fun = eng.newFunction(myFunction, proto);
148        QCOMPARE(fun.isValid(), true);
149        QCOMPARE(fun.isFunction(), true);
150        QCOMPARE(fun.isObject(), true);
151        // internal prototype should be Function.prototype
152        QCOMPARE(fun.prototype().isValid(), true);
153        QCOMPARE(fun.prototype().isFunction(), true);
154        QCOMPARE(fun.prototype().strictlyEquals(eng.evaluate("Function.prototype")), true);
155        // public prototype should be the one we passed
156        QCOMPARE(fun.property("prototype").strictlyEquals(proto), true);
157        QEXPECT_FAIL("", "JSCallbackObject::getOwnPropertyDescriptor() doesn't return correct information yet", Continue);
158        QCOMPARE(fun.propertyFlags("prototype"), QScriptValue::Undeletable);
159        QCOMPARE(proto.property("constructor").strictlyEquals(fun), true);
160        QEXPECT_FAIL("", "WebKit bug: 40613 (The JSObjectSetProperty doesn't overwrite property flags)", Continue);
161        QCOMPARE(proto.propertyFlags("constructor"), QScriptValue::PropertyFlags(QScriptValue::Undeletable | QScriptValue::SkipInEnumeration));
162
163        QCOMPARE(fun.call().isNull(), true);
164        // QCOMPARE(fun.construct().isObject(), true);
165    }
166    // whether the return value is correct
167    {
168        QScriptValue fun = eng.newFunction(myFunctionThatReturns);
169        QCOMPARE(fun.isValid(), true);
170        QCOMPARE(fun.isFunction(), true);
171        QCOMPARE(fun.isObject(), true);
172
173        QScriptValue result = fun.call();
174        QCOMPARE(result.isNumber(), true);
175        QCOMPARE(result.toInt32(), 42);
176    }
177    // whether the return value is assigned to the correct engine
178    {
179        QScriptValue fun = eng.newFunction(myFunctionThatReturnsWithoutEngine);
180        QCOMPARE(fun.isValid(), true);
181        QCOMPARE(fun.isFunction(), true);
182        QCOMPARE(fun.isObject(), true);
183
184        QScriptValue result = fun.call();
185        QCOMPARE(result.engine(), &eng);
186        QCOMPARE(result.isNumber(), true);
187        QCOMPARE(result.toInt32(), 1024);
188    }
189    // whether the return value is undefined when returning a value with wrong engine
190    {
191        QScriptEngine wrongEngine;
192
193        QScriptValue fun = eng.newFunction(myFunctionThatReturnsWrongEngine, reinterpret_cast<void*>(&wrongEngine));
194        QCOMPARE(fun.isValid(), true);
195        QCOMPARE(fun.isFunction(), true);
196        QCOMPARE(fun.isObject(), true);
197
198        QTest::ignoreMessage(QtWarningMsg, "Value from different engine returned from native function, returning undefined value instead.");
199        QScriptValue result = fun.call();
200        QCOMPARE(result.isValid(), true);
201        QCOMPARE(result.isUndefined(), true);
202    }
203}
204
205void tst_QScriptEngine::newObject()
206{
207    QScriptEngine engine;
208    QScriptValue object = engine.newObject();
209    QVERIFY(object.isObject());
210    QVERIFY(object.engine() == &engine);
211    QVERIFY(!object.isError());
212    QVERIFY(!object.equals(engine.newObject()));
213    QVERIFY(!object.strictlyEquals(engine.newObject()));
214    QCOMPARE(object.toString(), QString::fromAscii("[object Object]"));
215}
216
217void tst_QScriptEngine::globalObject()
218{
219    QScriptEngine engine;
220    QScriptValue global = engine.globalObject();
221    QScriptValue self = engine.evaluate("this");
222    QVERIFY(global.isObject());
223    QVERIFY(engine.globalObject().equals(engine.evaluate("this")));
224    QVERIFY(engine.globalObject().strictlyEquals(self));
225}
226
227/* Test garbage collection, at least try to not crash. */
228void tst_QScriptEngine::collectGarbage()
229{
230    QScriptEngine engine;
231    QScriptValue foo = engine.evaluate("( function foo() {return 'pong';} )");
232    QVERIFY(foo.isFunction());
233    engine.collectGarbage();
234    QCOMPARE(foo.call().toString(), QString::fromAscii("pong"));
235}
236
237void tst_QScriptEngine::reportAdditionalMemoryCost()
238{
239    // There isn't any easy way to test the responsiveness of the GC;
240    // just try to call the function a few times with various sizes.
241    QScriptEngine eng;
242    for (int i = 0; i < 100; ++i) {
243        eng.reportAdditionalMemoryCost(0);
244        eng.reportAdditionalMemoryCost(10);
245        eng.reportAdditionalMemoryCost(1000);
246        eng.reportAdditionalMemoryCost(10000);
247        eng.reportAdditionalMemoryCost(100000);
248        eng.reportAdditionalMemoryCost(1000000);
249        eng.reportAdditionalMemoryCost(10000000);
250        eng.reportAdditionalMemoryCost(-1);
251        eng.reportAdditionalMemoryCost(-1000);
252        QScriptValue obj = eng.evaluate("new Object");
253        eng.collectGarbage();
254    }
255}
256
257void tst_QScriptEngine::nullValue()
258{
259    QScriptEngine engine;
260    QScriptValue value = engine.nullValue();
261    QVERIFY(value.isValid());
262    QVERIFY(value.isNull());
263}
264
265void tst_QScriptEngine::undefinedValue()
266{
267    QScriptEngine engine;
268    QScriptValue value = engine.undefinedValue();
269    QVERIFY(value.isValid());
270    QVERIFY(value.isUndefined());
271}
272
273void tst_QScriptEngine::evaluateProgram()
274{
275    QScriptEngine eng;
276    {
277        QString code("1 + 2");
278        QString fileName("hello.js");
279        int lineNumber = 123;
280        QScriptProgram program(code, fileName, lineNumber);
281        QVERIFY(!program.isNull());
282        QCOMPARE(program.sourceCode(), code);
283        QCOMPARE(program.fileName(), fileName);
284        QCOMPARE(program.firstLineNumber(), lineNumber);
285
286        QScriptValue expected = eng.evaluate(code);
287        for (int x = 0; x < 10; ++x) {
288            QScriptValue ret = eng.evaluate(program);
289            QVERIFY(ret.equals(expected));
290        }
291
292        // operator=
293        QScriptProgram sameProgram = program;
294        QVERIFY(sameProgram == program);
295        QVERIFY(eng.evaluate(sameProgram).equals(expected));
296
297        // copy constructor
298        QScriptProgram sameProgram2(program);
299        QVERIFY(sameProgram2 == program);
300        QVERIFY(eng.evaluate(sameProgram2).equals(expected));
301
302        QScriptProgram differentProgram("2 + 3");
303        QVERIFY(differentProgram != program);
304        QVERIFY(!eng.evaluate(differentProgram).equals(expected));
305    }
306
307    // Program that accesses variable in the scope
308    {
309        QScriptProgram program("a");
310        QVERIFY(!program.isNull());
311        {
312            QScriptValue ret = eng.evaluate(program);
313            QVERIFY(ret.isError());
314            QCOMPARE(ret.toString(), QString::fromLatin1("ReferenceError: Can't find variable: a"));
315        }
316        {
317            QScriptValue ret = eng.evaluate(program);
318            QVERIFY(ret.isError());
319        }
320        eng.evaluate("a = 456");
321        {
322            QScriptValue ret = eng.evaluate(program);
323            QVERIFY(!ret.isError());
324            QCOMPARE(ret.toNumber(), 456.0);
325        }
326    }
327
328    // Program that creates closure
329    {
330        QScriptProgram program("(function() { var count = 0; return function() { return count++; }; })");
331        QVERIFY(!program.isNull());
332        QScriptValue createCounter = eng.evaluate(program);
333        QVERIFY(createCounter.isFunction());
334        QScriptValue counter = createCounter.call();
335        QVERIFY(counter.isFunction());
336        {
337            QScriptValue ret = counter.call();
338            QVERIFY(ret.isNumber());
339        }
340        QScriptValue counter2 = createCounter.call();
341        QVERIFY(counter2.isFunction());
342        QVERIFY(!counter2.equals(counter));
343        {
344            QScriptValue ret = counter2.call();
345            QVERIFY(ret.isNumber());
346        }
347    }
348
349    // Same program run in different engines
350    {
351        QString code("1 + 2");
352        QScriptProgram program(code);
353        QVERIFY(!program.isNull());
354        double expected = eng.evaluate(program).toNumber();
355        for (int x = 0; x < 2; ++x) {
356            QScriptEngine eng2;
357            for (int y = 0; y < 2; ++y) {
358                double ret = eng2.evaluate(program).toNumber();
359                QCOMPARE(ret, expected);
360            }
361        }
362    }
363
364    // No program
365    {
366        QScriptProgram program;
367        QVERIFY(program.isNull());
368        QScriptValue ret = eng.evaluate(program);
369        QVERIFY(!ret.isValid());
370    }
371}
372
373void tst_QScriptEngine::checkSyntax_data()
374{
375    QTest::addColumn<QString>("code");
376    QTest::addColumn<int>("expectedState");
377    QTest::addColumn<int>("errorLineNumber");
378    QTest::addColumn<int>("errorColumnNumber");
379    QTest::addColumn<QString>("errorMessage");
380
381    QTest::newRow("0")
382        << QString("0") << int(QScriptSyntaxCheckResult::Valid)
383        << -1 << -1 << "";
384    QTest::newRow("if (")
385        << QString("if (\n") << int(QScriptSyntaxCheckResult::Intermediate)
386        << 1 << 4 << "";
387    QTest::newRow("if else")
388        << QString("\nif else") << int(QScriptSyntaxCheckResult::Error)
389        << 2 << 4 << "SyntaxError: Parse error";
390    QTest::newRow("{if}")
391            << QString("{\n{\nif\n}\n") << int(QScriptSyntaxCheckResult::Error)
392        << 4 << 1 << "SyntaxError: Parse error";
393    QTest::newRow("foo[")
394        << QString("foo[") << int(QScriptSyntaxCheckResult::Error)
395        << 1 << 4 << "SyntaxError: Parse error";
396    QTest::newRow("foo['bar']")
397        << QString("foo['bar']") << int(QScriptSyntaxCheckResult::Valid)
398        << -1 << -1 << "";
399
400    QTest::newRow("/*")
401        << QString("/*") << int(QScriptSyntaxCheckResult::Intermediate)
402        << 1 << 1 << "Unclosed comment at end of file";
403    QTest::newRow("/*\nMy comment")
404        << QString("/*\nMy comment") << int(QScriptSyntaxCheckResult::Intermediate)
405        << 1 << 1 << "Unclosed comment at end of file";
406    QTest::newRow("/*\nMy comment */\nfoo = 10")
407        << QString("/*\nMy comment */\nfoo = 10") << int(QScriptSyntaxCheckResult::Valid)
408        << -1 << -1 << "";
409    QTest::newRow("foo = 10 /*")
410        << QString("foo = 10 /*") << int(QScriptSyntaxCheckResult::Intermediate)
411        << -1 << -1 << "";
412    QTest::newRow("foo = 10; /*")
413        << QString("foo = 10; /*") << int(QScriptSyntaxCheckResult::Intermediate)
414        << 1 << 11 << "Expected `end of file'";
415    QTest::newRow("foo = 10 /* My comment */")
416        << QString("foo = 10 /* My comment */") << int(QScriptSyntaxCheckResult::Valid)
417        << -1 << -1 << "";
418
419    QTest::newRow("/=/")
420        << QString("/=/") << int(QScriptSyntaxCheckResult::Valid) << -1 << -1 << "";
421    QTest::newRow("/=/g")
422        << QString("/=/g") << int(QScriptSyntaxCheckResult::Valid) << -1 << -1 << "";
423    QTest::newRow("/a/")
424        << QString("/a/") << int(QScriptSyntaxCheckResult::Valid) << -1 << -1 << "";
425    QTest::newRow("/a/g")
426        << QString("/a/g") << int(QScriptSyntaxCheckResult::Valid) << -1 << -1 << "";
427}
428
429void tst_QScriptEngine::checkSyntax()
430{
431    QFETCH(QString, code);
432    QFETCH(int, expectedState);
433    QFETCH(int, errorLineNumber);
434    QFETCH(int, errorColumnNumber);
435    QFETCH(QString, errorMessage);
436
437    QScriptSyntaxCheckResult result = QScriptEngine::checkSyntax(code);
438
439    // assignment
440    {
441        QScriptSyntaxCheckResult copy = result;
442        QCOMPARE(copy.state(), result.state());
443        QCOMPARE(copy.errorLineNumber(), result.errorLineNumber());
444        QCOMPARE(copy.errorColumnNumber(), result.errorColumnNumber());
445        QCOMPARE(copy.errorMessage(), result.errorMessage());
446    }
447    {
448        QScriptSyntaxCheckResult copy(result);
449        QCOMPARE(copy.state(), result.state());
450        QCOMPARE(copy.errorLineNumber(), result.errorLineNumber());
451        QCOMPARE(copy.errorColumnNumber(), result.errorColumnNumber());
452        QCOMPARE(copy.errorMessage(), result.errorMessage());
453    }
454
455    if (expectedState == QScriptSyntaxCheckResult::Intermediate)
456        QEXPECT_FAIL("", "QScriptSyntaxCheckResult::state() doesn't return the Intermediate state", Abort);
457    QCOMPARE(result.state(), QScriptSyntaxCheckResult::State(expectedState));
458    QCOMPARE(result.errorLineNumber(), errorLineNumber);
459    if (expectedState != QScriptSyntaxCheckResult::Valid && errorColumnNumber != 1)
460            QEXPECT_FAIL("", "QScriptSyntaxCheckResult::errorColumnNumber() doesn't return correct value", Continue);
461    QCOMPARE(result.errorColumnNumber(), errorColumnNumber);
462    QCOMPARE(result.errorMessage(), errorMessage);
463}
464
465void tst_QScriptEngine::toObject()
466{
467    QScriptEngine eng;
468    QVERIFY(!eng.toObject(eng.undefinedValue()).isValid());
469    QVERIFY(!eng.toObject(eng.nullValue()).isValid());
470    QVERIFY(!eng.toObject(QScriptValue()).isValid());
471
472    QScriptValue falskt(false);
473    {
474        QScriptValue tmp = eng.toObject(falskt);
475        QVERIFY(tmp.isObject());
476        QVERIFY(!falskt.isObject());
477        QVERIFY(!falskt.engine());
478        QCOMPARE(tmp.toNumber(), falskt.toNumber());
479    }
480
481    QScriptValue sant(true);
482    {
483        QScriptValue tmp = eng.toObject(sant);
484        QVERIFY(tmp.isObject());
485        QVERIFY(!sant.isObject());
486        QVERIFY(!sant.engine());
487        QCOMPARE(tmp.toNumber(), sant.toNumber());
488    }
489
490    QScriptValue number(123.0);
491    {
492        QScriptValue tmp = eng.toObject(number);
493        QVERIFY(tmp.isObject());
494        QVERIFY(!number.isObject());
495        QVERIFY(!number.engine());
496        QCOMPARE(tmp.toNumber(), number.toNumber());
497    }
498
499    QScriptValue str = QScriptValue(&eng, QString("ciao"));
500    {
501        QScriptValue tmp = eng.toObject(str);
502        QVERIFY(tmp.isObject());
503        QVERIFY(!str.isObject());
504        QCOMPARE(tmp.toString(), str.toString());
505    }
506
507    QScriptValue object = eng.evaluate("new Object");
508    {
509        QScriptValue tmp = eng.toObject(object);
510        QVERIFY(tmp.isObject());
511        QVERIFY(object.isObject());
512        QVERIFY(tmp.strictlyEquals(object));
513    }
514}
515
516void tst_QScriptEngine::toObjectTwoEngines()
517{
518    QScriptEngine engine1;
519    QScriptEngine engine2;
520
521    {
522        QScriptValue null = engine1.nullValue();
523        QTest::ignoreMessage(QtWarningMsg, "QScriptEngine::toObject: cannot convert value created in a different engine");
524        QVERIFY(!engine2.toObject(null).isValid());
525        QVERIFY(null.isValid());
526        QTest::ignoreMessage(QtWarningMsg, "QScriptEngine::toObject: cannot convert value created in a different engine");
527        QVERIFY(engine2.toObject(null).engine() != &engine2);
528    }
529    {
530        QScriptValue undefined = engine1.undefinedValue();
531        QTest::ignoreMessage(QtWarningMsg, "QScriptEngine::toObject: cannot convert value created in a different engine");
532        QVERIFY(!engine2.toObject(undefined).isValid());
533        QVERIFY(undefined.isValid());
534        QTest::ignoreMessage(QtWarningMsg, "QScriptEngine::toObject: cannot convert value created in a different engine");
535        QVERIFY(engine2.toObject(undefined).engine() != &engine2);
536    }
537    {
538        QScriptValue value = engine1.evaluate("1");
539        QTest::ignoreMessage(QtWarningMsg, "QScriptEngine::toObject: cannot convert value created in a different engine");
540        QVERIFY(engine2.toObject(value).engine() != &engine2);
541        QVERIFY(!value.isObject());
542    }
543    {
544        QScriptValue string = engine1.evaluate("'Qt'");
545        QTest::ignoreMessage(QtWarningMsg, "QScriptEngine::toObject: cannot convert value created in a different engine");
546        QVERIFY(engine2.toObject(string).engine() != &engine2);
547        QVERIFY(!string.isObject());
548    }
549    {
550        QScriptValue object = engine1.evaluate("new Object");
551        QTest::ignoreMessage(QtWarningMsg, "QScriptEngine::toObject: cannot convert value created in a different engine");
552        QVERIFY(engine2.toObject(object).engine() != &engine2);
553        QVERIFY(object.isObject());
554    }
555}
556
557void tst_QScriptEngine::newArray()
558{
559    QScriptEngine eng;
560    QScriptValue array = eng.newArray();
561    QCOMPARE(array.isValid(), true);
562    QCOMPARE(array.isArray(), true);
563    QCOMPARE(array.isObject(), true);
564    QVERIFY(!array.isFunction());
565    // QCOMPARE(array.scriptClass(), (QScriptClass*)0);
566
567    // Prototype should be Array.prototype.
568    QCOMPARE(array.prototype().isValid(), true);
569    QCOMPARE(array.prototype().isArray(), true);
570    QCOMPARE(array.prototype().strictlyEquals(eng.evaluate("Array.prototype")), true);
571
572    QScriptValue arrayWithSize = eng.newArray(42);
573    QCOMPARE(arrayWithSize.isValid(), true);
574    QCOMPARE(arrayWithSize.isArray(), true);
575    QCOMPARE(arrayWithSize.isObject(), true);
576    QCOMPARE(arrayWithSize.property("length").toInt32(), 42);
577
578    // task 218092
579    {
580        QScriptValue ret = eng.evaluate("[].splice(0, 0, 'a')");
581        QVERIFY(ret.isArray());
582        QCOMPARE(ret.property("length").toInt32(), 0);
583    }
584    {
585        QScriptValue ret = eng.evaluate("['a'].splice(0, 1, 'b')");
586        QVERIFY(ret.isArray());
587        QCOMPARE(ret.property("length").toInt32(), 1);
588    }
589    {
590        QScriptValue ret = eng.evaluate("['a', 'b'].splice(0, 1, 'c')");
591        QVERIFY(ret.isArray());
592        QCOMPARE(ret.property("length").toInt32(), 1);
593    }
594    {
595        QScriptValue ret = eng.evaluate("['a', 'b', 'c'].splice(0, 2, 'd')");
596        QVERIFY(ret.isArray());
597        QCOMPARE(ret.property("length").toInt32(), 2);
598    }
599    {
600        QScriptValue ret = eng.evaluate("['a', 'b', 'c'].splice(1, 2, 'd', 'e', 'f')");
601        QVERIFY(ret.isArray());
602        QCOMPARE(ret.property("length").toInt32(), 2);
603    }
604}
605
606void tst_QScriptEngine::uncaughtException()
607{
608    QScriptEngine eng;
609    QScriptValue fun = eng.evaluate("(function foo () { return null; });");
610    QVERIFY(!eng.uncaughtException().isValid());
611    QVERIFY(fun.isFunction());
612    QScriptValue throwFun = eng.evaluate("( function() { throw new Error('Pong'); });");
613    QVERIFY(throwFun.isFunction());
614    {
615        eng.evaluate("a = 10");
616        QVERIFY(!eng.hasUncaughtException());
617        QVERIFY(!eng.uncaughtException().isValid());
618    }
619    {
620        eng.evaluate("1 = 2");
621        QVERIFY(eng.hasUncaughtException());
622        eng.clearExceptions();
623        QVERIFY(!eng.hasUncaughtException());
624    }
625    {
626        // Check if the call or toString functions can remove the last exception.
627        QVERIFY(throwFun.call().isError());
628        QVERIFY(eng.hasUncaughtException());
629        QScriptValue exception = eng.uncaughtException();
630        fun.call();
631        exception.toString();
632        QVERIFY(eng.hasUncaughtException());
633        QVERIFY(eng.uncaughtException().strictlyEquals(exception));
634    }
635    eng.clearExceptions();
636    {
637        // Check if in the call function a new exception can override an existing one.
638        throwFun.call();
639        QVERIFY(eng.hasUncaughtException());
640        QScriptValue exception = eng.uncaughtException();
641        throwFun.call();
642        QVERIFY(eng.hasUncaughtException());
643        QVERIFY(!exception.strictlyEquals(eng.uncaughtException()));
644    }
645    {
646        eng.evaluate("throwFun = (function foo () { throw new Error('bla') });");
647        eng.evaluate("1;\nthrowFun();");
648        QVERIFY(eng.hasUncaughtException());
649        QCOMPARE(eng.uncaughtExceptionLineNumber(), 1);
650        eng.clearExceptions();
651        QVERIFY(!eng.hasUncaughtException());
652    }
653    for (int x = 1; x < 4; ++x) {
654        QScriptValue ret = eng.evaluate("a = 10;\nb = 20;\n0 = 0;\n",
655                                        QString::fromLatin1("FooScript") + QString::number(x),
656                                        /* lineNumber */ x);
657        QVERIFY(eng.hasUncaughtException());
658        QCOMPARE(eng.uncaughtExceptionLineNumber(), x + 2);
659        QVERIFY(eng.uncaughtException().strictlyEquals(ret));
660        QVERIFY(eng.hasUncaughtException());
661        QVERIFY(eng.uncaughtException().strictlyEquals(ret));
662        QString backtrace = QString::fromLatin1("<anonymous>()@FooScript") + QString::number(x) + ":" + QString::number(x + 2);
663        QCOMPARE(eng.uncaughtExceptionBacktrace().join(""), backtrace);
664        QVERIFY(fun.call().isNull());
665        QVERIFY(eng.hasUncaughtException());
666        QCOMPARE(eng.uncaughtExceptionLineNumber(), x + 2);
667        QVERIFY(eng.uncaughtException().strictlyEquals(ret));
668        eng.clearExceptions();
669        QVERIFY(!eng.hasUncaughtException());
670        QCOMPARE(eng.uncaughtExceptionLineNumber(), -1);
671        QVERIFY(!eng.uncaughtException().isValid());
672        eng.evaluate("2 = 3");
673        QVERIFY(eng.hasUncaughtException());
674        QScriptValue ret2 = throwFun.call();
675        QVERIFY(ret2.isError());
676        QVERIFY(eng.hasUncaughtException());
677        QVERIFY(eng.uncaughtException().strictlyEquals(ret2));
678        QCOMPARE(eng.uncaughtExceptionLineNumber(), 1);
679        eng.clearExceptions();
680        QVERIFY(!eng.hasUncaughtException());
681        eng.evaluate("1 + 2");
682        QVERIFY(!eng.hasUncaughtException());
683    }
684}
685
686void tst_QScriptEngine::newDate()
687{
688    QScriptEngine eng;
689    {
690        QScriptValue date = eng.newDate(0);
691        QCOMPARE(date.isValid(), true);
692        QCOMPARE(date.isDate(), true);
693        QCOMPARE(date.isObject(), true);
694        QVERIFY(!date.isFunction());
695        // prototype should be Date.prototype
696        QCOMPARE(date.prototype().isValid(), true);
697        QCOMPARE(date.prototype().isDate(), true);
698        QCOMPARE(date.prototype().strictlyEquals(eng.evaluate("Date.prototype")), true);
699    }
700    {
701        QDateTime dt = QDateTime(QDate(1, 2, 3), QTime(4, 5, 6, 7), Qt::LocalTime);
702        QScriptValue date = eng.newDate(dt);
703        QCOMPARE(date.isValid(), true);
704        QCOMPARE(date.isDate(), true);
705        QCOMPARE(date.isObject(), true);
706        // prototype should be Date.prototype
707        QCOMPARE(date.prototype().isValid(), true);
708        QCOMPARE(date.prototype().isDate(), true);
709        QCOMPARE(date.prototype().strictlyEquals(eng.evaluate("Date.prototype")), true);
710
711        QCOMPARE(date.toDateTime(), dt);
712    }
713    {
714        QDateTime dt = QDateTime(QDate(1, 2, 3), QTime(4, 5, 6, 7), Qt::UTC);
715        QScriptValue date = eng.newDate(dt);
716        // toDateTime() result should be in local time
717        QCOMPARE(date.toDateTime(), dt.toLocalTime());
718    }
719    // Date.parse() should return NaN when it fails
720    {
721        QScriptValue ret = eng.evaluate("Date.parse()");
722        QVERIFY(ret.isNumber());
723        QVERIFY(qIsNaN(ret.toNumber()));
724    }
725    // Date.parse() should be able to parse the output of Date().toString()
726    {
727        QScriptValue ret = eng.evaluate("var x = new Date(); var s = x.toString(); s == new Date(Date.parse(s)).toString()");
728        QVERIFY(ret.isBoolean());
729        QCOMPARE(ret.toBoolean(), true);
730    }
731}
732
733QTEST_MAIN(tst_QScriptEngine)
734#include "tst_qscriptengine.moc"
735