343 lines · 10.4 KB · C++
RawDownload
////  Copyright 2023 Overte e.V.////  Distributed under the Apache License, Version 2.0.//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html//  SPDX-License-Identifier: Apache-2.0// #include <QSignalSpy>#include <QDebug>#include <QFile>#include <QTextStream>  #include "ScriptEngineTests.h"#include "DependencyManager.h" #include "ScriptEngines.h"#include "ScriptEngine.h"#include "ScriptCache.h"#include "ScriptManager.h" #include "ResourceManager.h"#include "ResourceRequestObserver.h"#include "StatTracker.h" #include "NodeList.h"#include "../../../libraries/entities/src/EntityScriptingInterface.h"//#include "../../../libraries/entities/src/EntityScriptingInterface.h" QTEST_MAIN(ScriptEngineTests) void ScriptEngineTests::initTestCase() {    // AudioClient starts networking, but for the purposes of the tests here we don't care,    // so just got to use some port.    //int listenPort = 10000;     //DependencyManager::registerInheritance<LimitedNodeList, NodeList>();    //DependencyManager::set<NodeList>(NodeType::Agent, listenPort);    DependencyManager::set<ScriptEngines>(ScriptManager::NETWORKLESS_TEST_SCRIPT, QUrl(""));    DependencyManager::set<ScriptCache>();   // DependencyManager::set<ResourceManager>();   // DependencyManager::set<ResourceRequestObserver>();    DependencyManager::set<StatTracker>();    DependencyManager::set<ScriptInitializers>();   // DependencyManager::set<EntityScriptingInterface>(true);  } ScriptManagerPointer ScriptEngineTests::makeManager(const QString &scriptSource, const QString &scriptFilename) {    ScriptManagerPointer sm = newScriptManager(ScriptManager::NETWORKLESS_TEST_SCRIPT, scriptSource, scriptFilename);      sm->setAbortOnUncaughtException(true);     connect(sm.get(), &ScriptManager::scriptLoaded, [](const QString& filename){        qWarning() << "Loaded script" << filename;    });      connect(sm.get(), &ScriptManager::errorLoadingScript, [](const QString& filename){        qWarning() << "Failed to load script" << filename;    });     connect(sm.get(), &ScriptManager::printedMessage, [](const QString& message, const QString& engineName){        qDebug() << "Printed message from engine" << engineName << ": " << message;    });     connect(sm.get(), &ScriptManager::infoMessage, [](const QString& message, const QString& engineName){        qInfo() << "Info message from engine" << engineName << ": " << message;    });     connect(sm.get(), &ScriptManager::warningMessage, [](const QString& message, const QString& engineName){        qWarning() << "Warning from engine" << engineName << ": " << message;    });     connect(sm.get(), &ScriptManager::errorMessage, [](const QString& message, const QString& engineName){        qCritical() << "Error from engine" << engineName << ": " << message;    });     connect(sm.get(), &ScriptManager::finished, [](const QString& fileNameString, ScriptManagerPointer smp){        qInfo() << "Finished running script" << fileNameString;    });     connect(sm.get(), &ScriptManager::runningStateChanged, [sm](){        qInfo() << "Running state changed. Running = " << sm->isRunning() << "; Stopped = " << sm->isStopped() << "; Finished = " << sm->isFinished();    });     connect(sm.get(), &ScriptManager::unhandledException, [](std::shared_ptr<ScriptException> exception){        qWarning() << "Exception from engine: " << exception;    });      return sm;} void ScriptEngineTests::testTrivial() {    auto sm = makeManager("print(\"script works!\"); Script.stop(true);", "testTrivial.js");    auto scopeGuard = sm->engine()->getScopeGuard();     QString printed;     QVERIFY(!sm->isRunning());    QVERIFY(!sm->isStopped());    QVERIFY(!sm->isFinished());      connect(sm.get(), &ScriptManager::printedMessage, [&printed](const QString& message, const QString& engineName){        printed.append(message);    });      sm->run();     QVERIFY(!sm->isRunning());    QVERIFY(!sm->isStopped());    QVERIFY(sm->isFinished());    QVERIFY(printed == "script works!"); } void ScriptEngineTests::testSyntaxError() {    auto sm = makeManager("this is not good syntax", "testSyntaxError.js");    auto scopeGuard = sm->engine()->getScopeGuard();     bool exceptionHappened = false;     connect(sm.get(), &ScriptManager::unhandledException, [&exceptionHappened](std::shared_ptr<ScriptException> exception){        exceptionHappened = true;    });      sm->run();     std::shared_ptr<ScriptException> ex = sm->getUncaughtException();     qDebug() << "Exception:" << ex;     QVERIFY(exceptionHappened);    QVERIFY(ex);    QVERIFY(ex && ex->errorMessage.contains("SyntaxError"));}  void ScriptEngineTests::testRuntimeError() {    auto sm = makeManager("nonexisting();", "testRuntimeError.js");    auto scopeGuard = sm->engine()->getScopeGuard();    bool exceptionHappened = false;     connect(sm.get(), &ScriptManager::unhandledException, [&exceptionHappened](std::shared_ptr<ScriptException> exception){        exceptionHappened = true;    });      sm->run();     std::shared_ptr<ScriptException> ex = sm->getUncaughtException();     qDebug() << "Exception:" << ex;     QVERIFY(exceptionHappened);    QVERIFY(ex);    QVERIFY(ex && ex->errorMessage.contains("ReferenceError")); } void ScriptEngineTests::testJSThrow() {    auto sm = makeManager("throw(42);", "testThrow.js");    auto scopeGuard = sm->engine()->getScopeGuard();    sm->run();     std::shared_ptr<ScriptException> ex = sm->getUncaughtException();     qDebug() << "Exception:" << ex;     auto runtime_ex = std::dynamic_pointer_cast<ScriptRuntimeException>(ex);     QVERIFY(ex);    QVERIFY(runtime_ex);    QVERIFY(runtime_ex && runtime_ex->thrownValue.toInt32() == 42);} void ScriptEngineTests::testRegisterClass() {    QString printed;    auto sm = makeManager("print(testClass.invokableFunc(4)); Script.stop(true);", "testClass.js");    connect(sm.get(), &ScriptManager::printedMessage, [&printed](const QString& message, const QString& engineName){        printed.append(message);    });     auto scopeGuard = sm->engine()->getScopeGuard();    sm->engine()->registerGlobalObject(scopeGuard.get(), "testClass", new TestClass());     sm->run();     auto ex = sm->getUncaughtException();     QVERIFY(!ex);    QVERIFY(printed == "14"); } void ScriptEngineTests::testInvokeNonInvokable() {    auto sm = makeManager("print(testClass.nonInvokableFunc(4)); Script.stop(true);", "testClass.js");    auto scopeGuard = sm->engine()->getScopeGuard();    sm->engine()->registerGlobalObject(scopeGuard.get(), "testClass", new TestClass());     sm->run();    auto ex = sm->getUncaughtException();     QVERIFY(ex);    QVERIFY(ex && ex->errorMessage.contains("TypeError"));} void ScriptEngineTests::testRaiseException() {    auto sm = makeManager("testClass.doRaiseTest(); Script.stop(true);", "testRaise.js");    auto scopeGuard = sm->engine()->getScopeGuard();    sm->engine()->registerGlobalObject(scopeGuard.get(), "testClass", new TestClass(sm->engine()));     sm->run();    auto ex = sm->getUncaughtException();     QVERIFY(ex);    QVERIFY(ex && ex->errorMessage.contains("Exception test"));} void ScriptEngineTests::testRaiseExceptionAndCatch() {    QString script =        "try {"        "    testClass.doRaiseTest();"        "} catch (err) {"        "    if (err.message.includes(\"Exception test!\")) {"        "        print(\"Caught!\");"        "    }"        "}"        "Script.stop(true);";     QString printed;    auto sm = makeManager(script, "testRaiseCatch.js");     connect(sm.get(), &ScriptManager::printedMessage, [&printed](const QString& message, const QString& engineName){        printed.append(message);    });      auto scopeGuard = sm->engine()->getScopeGuard();    sm->engine()->registerGlobalObject(scopeGuard.get(), "testClass", new TestClass(sm->engine()));     sm->run();    auto ex = sm->getUncaughtException();     QVERIFY(!ex);    QVERIFY(printed == "Caught!");}  void ScriptEngineTests::testSignal() {    QString script =        "var count = 0;"        "Script.update.connect(function(deltaTime) {"        "    count++;"        "    print(deltaTime);"        "    if (count >= 10) {"        "        Script.stop(true);"        "    }"        "});";     QStringList printed;    auto sm = makeManager(script, "testSignal.js");    auto scopeGuard = sm->engine()->getScopeGuard();     connect(sm.get(), &ScriptManager::printedMessage, [&printed](const QString& message, const QString& engineName){        printed.append(message);    });     sm->run();    QVERIFY(printed.length() >= 10);} void ScriptEngineTests::testSignalWithException() {    QString script =        "var count = 0;"        "Script.update.connect(function(deltaTime) {"        "    count++;"        "    print(deltaTime);"        "    if (count >= 3) {"        "        Script.stop(true);"        "    }"        "    nonExist();"        "});";     QStringList printed;    int exceptionCount = 0;     auto sm = makeManager(script, "testSignalWithException.js");    auto scopeGuard = sm->engine()->getScopeGuard();     connect(sm.get(), &ScriptManager::printedMessage, [&printed](const QString& message, const QString& engineName){        printed.append(message);    });     connect(sm.get(), &ScriptManager::unhandledException, [&exceptionCount](std::shared_ptr<ScriptException> exception){        exceptionCount++;    });      sm->run();    QVERIFY(printed.length() >= 3);    QVERIFY(exceptionCount >= 3);} void ScriptEngineTests::testQuat() {    QString script =        "var x;\n"        "print(JSON.stringify(Quat.IDENTITY));\n"        "print(JSON.stringify(Quat.safeEulerAngles(Quat.IDENTITY)));\n"        "print(JSON.stringify(Quat.getUp(Quat.IDENTITY)));\n"        "print(JSON.stringify(Quat.getUp(x)));\n"        "Script.stop(true);\n";     int printCount = 0;    QStringList answers{        "{\"x\":0,\"y\":0,\"z\":0,\"w\":1}",        "{\"x\":0,\"y\":0,\"z\":0}",        "{\"x\":0,\"y\":1,\"z\":0}",        ""    };     auto sm = makeManager(script, "testQuat.js");    auto scopeGuard = sm->engine()->getScopeGuard();     connect(sm.get(), &ScriptManager::printedMessage, [&printCount, answers](const QString& message, const QString& engineName){        QCOMPARE(message, answers[printCount++]);    });     connect(sm.get(), &ScriptManager::unhandledException, [](std::shared_ptr<ScriptException> exception){        QVERIFY(exception->errorMessage.contains("undefined to glm::quat"));    });      sm->run();}