diff options
Diffstat (limited to 'tests/auto/qml/debugger')
-rw-r--r-- | tests/auto/qml/debugger/debugger.pro | 3 | ||||
-rw-r--r-- | tests/auto/qml/debugger/qv4debugger/qv4debugger.pro | 17 | ||||
-rw-r--r-- | tests/auto/qml/debugger/qv4debugger/tst_qv4debugger.cpp | 729 |
3 files changed, 748 insertions, 1 deletions
diff --git a/tests/auto/qml/debugger/debugger.pro b/tests/auto/qml/debugger/debugger.pro index 420510679f..ccb2d71c53 100644 --- a/tests/auto/qml/debugger/debugger.pro +++ b/tests/auto/qml/debugger/debugger.pro @@ -15,7 +15,8 @@ PUBLICTESTS += \ PRIVATETESTS += \ qqmldebugclient \ qqmldebuglocal \ - qqmldebugservice + qqmldebugservice \ + qv4debugger SUBDIRS += $$PUBLICTESTS diff --git a/tests/auto/qml/debugger/qv4debugger/qv4debugger.pro b/tests/auto/qml/debugger/qv4debugger/qv4debugger.pro new file mode 100644 index 0000000000..d11de56e43 --- /dev/null +++ b/tests/auto/qml/debugger/qv4debugger/qv4debugger.pro @@ -0,0 +1,17 @@ +CONFIG += testcase +TARGET = tst_qv4debugger +osx:CONFIG -= app_bundle + +SOURCES += \ + $$PWD/tst_qv4debugger.cpp \ + $$PWD/../../../../../src/plugins/qmltooling/qmldbg_debugger/qv4datacollector.cpp \ + $$PWD/../../../../../src/plugins/qmltooling/qmldbg_debugger/qv4debugger.cpp + +HEADERS += \ + $$PWD/../../../../../src/plugins/qmltooling/qmldbg_debugger/qv4datacollector.h \ + $$PWD/../../../../../src/plugins/qmltooling/qmldbg_debugger/qv4debugger.h + +INCLUDEPATH += \ + $$PWD/../../../../../src/plugins/qmltooling/qmldbg_debugger + +QT += core-private gui-private qml-private network testlib diff --git a/tests/auto/qml/debugger/qv4debugger/tst_qv4debugger.cpp b/tests/auto/qml/debugger/qv4debugger/tst_qv4debugger.cpp new file mode 100644 index 0000000000..c5fa6be7a0 --- /dev/null +++ b/tests/auto/qml/debugger/qv4debugger/tst_qv4debugger.cpp @@ -0,0 +1,729 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include <QtTest/QtTest> + +#include "qv4datacollector.h" +#include "qv4debugger.h" + +#include <QJSEngine> +#include <QQmlEngine> +#include <QQmlComponent> +#include <private/qv4engine_p.h> +#include <private/qv4debugging_p.h> +#include <private/qv8engine_p.h> +#include <private/qv4objectiterator_p.h> +#include <private/qv4isel_moth_p.h> + +using namespace QV4; +using namespace QV4::Debugging; + +typedef QV4::ReturnedValue (*InjectedFunction)(QV4::CallContext*); +Q_DECLARE_METATYPE(InjectedFunction) + +static bool waitForSignal(QObject* obj, const char* signal, int timeout = 10000) +{ + QEventLoop loop; + QObject::connect(obj, signal, &loop, SLOT(quit())); + QTimer timer; + QSignalSpy timeoutSpy(&timer, SIGNAL(timeout())); + if (timeout > 0) { + QObject::connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit())); + timer.setSingleShot(true); + timer.start(timeout); + } + loop.exec(); + return timeoutSpy.isEmpty(); +} + +class TestEngine : public QJSEngine +{ + Q_OBJECT +public: + TestEngine() + { + qMetaTypeId<InjectedFunction>(); + } + + Q_INVOKABLE void evaluate(const QString &script, const QString &fileName, int lineNumber = 1) + { + QJSEngine::evaluate(script, fileName, lineNumber); + emit evaluateFinished(); + } + + QV4::ExecutionEngine *v4Engine() { return QV8Engine::getV4(this); } + + Q_INVOKABLE void injectFunction(const QString &functionName, InjectedFunction injectedFunction) + { + QV4::ExecutionEngine *v4 = v4Engine(); + QV4::Scope scope(v4); + + QV4::ScopedString name(scope, v4->newString(functionName)); + QV4::ScopedContext ctx(scope, v4->rootContext()); + QV4::ScopedValue function(scope, BuiltinFunction::create(ctx, name, injectedFunction)); + v4->globalObject->put(name, function); + } + +signals: + void evaluateFinished(); +}; + +class TestAgent : public QObject +{ + Q_OBJECT +public: + typedef QV4DataCollector::Refs Refs; + typedef QV4DataCollector::Ref Ref; + struct NamedRefs { + NamedRefs(QV4DataCollector *collector = 0): collector(collector) {} + + QStringList names; + Refs refs; + QV4DataCollector *collector; + + int size() const { + Q_ASSERT(names.size() == refs.size()); + return names.size(); + } + + bool contains(const QString &name) const { + return names.contains(name); + } + +#define DUMP_JSON(x) {\ + QJsonDocument doc(x);\ + qDebug() << #x << "=" << doc.toJson(QJsonDocument::Indented);\ +} + + QJsonObject rawValue(const QString &name) const { + Q_ASSERT(contains(name)); + return collector->lookupRef(refs.at(names.indexOf(name))); + } + + QJsonValue value(const QString &name) const { + return rawValue(name).value(QStringLiteral("value")); + } + + QString type(const QString &name) const { + return rawValue(name).value(QStringLiteral("type")).toString(); + } + + void dump(const QString &name) const { + if (!contains(name)) { + qDebug() << "no" << name; + return; + } + + QJsonObject o = collector->lookupRef(refs.at(names.indexOf(name))); + QJsonDocument d; + d.setObject(o); + qDebug() << name << "=" << d.toJson(QJsonDocument::Indented); + } + }; + + TestAgent(QV4::ExecutionEngine *engine) + : m_wasPaused(false) + , m_captureContextInfo(false) + , m_thrownValue(-1) + , collector(engine) + , m_debugger(0) + { + } + +public slots: + void debuggerPaused(QV4Debugger *debugger, QV4Debugger::PauseReason reason) + { + Q_ASSERT(debugger == m_debugger); + Q_ASSERT(debugger->engine() == collector.engine()); + m_wasPaused = true; + m_pauseReason = reason; + m_statesWhenPaused << debugger->currentExecutionState(); + + if (debugger->state() == QV4Debugger::Paused && + debugger->engine()->hasException) { + Refs refs; + RefHolder holder(&collector, &refs); + ExceptionCollectJob job(debugger->engine(), &collector); + debugger->runInEngine(&job); + Q_ASSERT(refs.size() > 0); + m_thrownValue = refs.first(); + } + + foreach (const TestBreakPoint &bp, m_breakPointsToAddWhenPaused) + debugger->addBreakPoint(bp.fileName, bp.lineNumber); + m_breakPointsToAddWhenPaused.clear(); + + m_stackTrace = debugger->stackTrace(); + + while (!m_expressionRequests.isEmpty()) { + Q_ASSERT(debugger->state() == QV4Debugger::Paused); + ExpressionRequest request = m_expressionRequests.takeFirst(); + m_expressionResults << Refs(); + RefHolder holder(&collector, &m_expressionResults.last()); + ExpressionEvalJob job(debugger->engine(), request.frameNr, request.expression, + &collector); + debugger->runInEngine(&job); + } + + if (m_captureContextInfo) + captureContextInfo(debugger); + + debugger->resume(QV4Debugger::FullThrottle); + } + +public: + struct TestBreakPoint + { + TestBreakPoint() : lineNumber(-1) {} + TestBreakPoint(const QString &fileName, int lineNumber) + : fileName(fileName), lineNumber(lineNumber) {} + QString fileName; + int lineNumber; + }; + + void captureContextInfo(QV4Debugger *debugger) + { + for (int i = 0, ei = m_stackTrace.size(); i != ei; ++i) { + m_capturedArguments.append(NamedRefs(&collector)); + RefHolder argHolder(&collector, &m_capturedArguments.last().refs); + ArgumentCollectJob argumentsJob(debugger->engine(), &collector, + &m_capturedArguments.last().names, i, 0); + debugger->runInEngine(&argumentsJob); + + m_capturedLocals.append(NamedRefs(&collector)); + RefHolder localHolder(&collector, &m_capturedLocals.last().refs); + LocalCollectJob localsJob(debugger->engine(), &collector, + &m_capturedLocals.last().names, i, 0); + debugger->runInEngine(&localsJob); + } + } + + void addDebugger(QV4Debugger *debugger) + { + Q_ASSERT(!m_debugger); + m_debugger = debugger; + connect(m_debugger, &QV4Debugger::debuggerPaused, this, &TestAgent::debuggerPaused); + } + + bool m_wasPaused; + QV4Debugger::PauseReason m_pauseReason; + bool m_captureContextInfo; + QList<QV4Debugger::ExecutionState> m_statesWhenPaused; + QList<TestBreakPoint> m_breakPointsToAddWhenPaused; + QVector<QV4::StackFrame> m_stackTrace; + QVector<NamedRefs> m_capturedArguments; + QVector<NamedRefs> m_capturedLocals; + qint64 m_thrownValue; + QV4DataCollector collector; + + struct ExpressionRequest { + QString expression; + int frameNr; + }; + QVector<ExpressionRequest> m_expressionRequests; + QVector<Refs> m_expressionResults; + QV4Debugger *m_debugger; + + // Utility methods: + void dumpStackTrace() const + { + qDebug() << "Stack depth:" << m_stackTrace.size(); + foreach (const QV4::StackFrame &frame, m_stackTrace) + qDebug("\t%s (%s:%d:%d)", qPrintable(frame.function), qPrintable(frame.source), + frame.line, frame.column); + } +}; + +class tst_qv4debugger : public QObject +{ + Q_OBJECT + +private slots: + void init(); + void cleanup(); + + // breakpoints: + void breakAnywhere(); + void pendingBreakpoint(); + void liveBreakPoint(); + void removePendingBreakPoint(); + void addBreakPointWhilePaused(); + void removeBreakPointForNextInstruction(); + void conditionalBreakPoint(); + void conditionalBreakPointInQml(); + + // context access: + void readArguments(); + void readLocals(); + void readObject(); + void readContextInAllFrames(); + + // exceptions: + void pauseOnThrow(); + void breakInCatch(); + void breakInWith(); + + void evaluateExpression(); + +private: + QV4Debugger *debugger() const + { + return static_cast<QV4Debugger *>(m_v4->debugger); + } + void evaluateJavaScript(const QString &script, const QString &fileName, int lineNumber = 1) + { + QMetaObject::invokeMethod(m_engine, "evaluate", Qt::QueuedConnection, + Q_ARG(QString, script), Q_ARG(QString, fileName), + Q_ARG(int, lineNumber)); + waitForSignal(m_engine, SIGNAL(evaluateFinished()), /*timeout*/0); + } + + TestEngine *m_engine; + QV4::ExecutionEngine *m_v4; + TestAgent *m_debuggerAgent; + QThread *m_javaScriptThread; +}; + +void tst_qv4debugger::init() +{ + m_javaScriptThread = new QThread; + m_engine = new TestEngine; + m_v4 = m_engine->v4Engine(); + m_v4->iselFactory.reset(new QV4::Moth::ISelFactory); + m_v4->setDebugger(new QV4Debugger(m_v4)); + m_engine->moveToThread(m_javaScriptThread); + m_javaScriptThread->start(); + m_debuggerAgent = new TestAgent(m_v4); + m_debuggerAgent->addDebugger(debugger()); +} + +void tst_qv4debugger::cleanup() +{ + m_javaScriptThread->exit(); + m_javaScriptThread->wait(); + delete m_engine; + delete m_javaScriptThread; + m_engine = 0; + m_v4 = 0; + delete m_debuggerAgent; + m_debuggerAgent = 0; +} + +void tst_qv4debugger::breakAnywhere() +{ + QString script = + "var i = 42;\n" + "var j = i + 1\n" + "var k = i\n"; + debugger()->pause(); + evaluateJavaScript(script, "testFile"); + QVERIFY(m_debuggerAgent->m_wasPaused); +} + +void tst_qv4debugger::pendingBreakpoint() +{ + QString script = + "var i = 42;\n" + "var j = i + 1\n" + "var k = i\n"; + debugger()->addBreakPoint("testfile", 2); + evaluateJavaScript(script, "testfile"); + QVERIFY(m_debuggerAgent->m_wasPaused); + QCOMPARE(m_debuggerAgent->m_statesWhenPaused.count(), 1); + QV4Debugger::ExecutionState state = m_debuggerAgent->m_statesWhenPaused.first(); + QCOMPARE(state.fileName, QString("testfile")); + QCOMPARE(state.lineNumber, 2); +} + +void tst_qv4debugger::liveBreakPoint() +{ + QString script = + "var i = 42;\n" + "var j = i + 1\n" + "var k = i\n"; + m_debuggerAgent->m_breakPointsToAddWhenPaused << TestAgent::TestBreakPoint("liveBreakPoint", 3); + debugger()->pause(); + evaluateJavaScript(script, "liveBreakPoint"); + QVERIFY(m_debuggerAgent->m_wasPaused); + QCOMPARE(m_debuggerAgent->m_statesWhenPaused.count(), 2); + QV4Debugger::ExecutionState state = m_debuggerAgent->m_statesWhenPaused.at(1); + QCOMPARE(state.fileName, QString("liveBreakPoint")); + QCOMPARE(state.lineNumber, 3); +} + +void tst_qv4debugger::removePendingBreakPoint() +{ + QString script = + "var i = 42;\n" + "var j = i + 1\n" + "var k = i\n"; + debugger()->addBreakPoint("removePendingBreakPoint", 2); + debugger()->removeBreakPoint("removePendingBreakPoint", 2); + evaluateJavaScript(script, "removePendingBreakPoint"); + QVERIFY(!m_debuggerAgent->m_wasPaused); +} + +void tst_qv4debugger::addBreakPointWhilePaused() +{ + QString script = + "var i = 42;\n" + "var j = i + 1\n" + "var k = i\n"; + debugger()->addBreakPoint("addBreakPointWhilePaused", 1); + m_debuggerAgent->m_breakPointsToAddWhenPaused << TestAgent::TestBreakPoint("addBreakPointWhilePaused", 2); + evaluateJavaScript(script, "addBreakPointWhilePaused"); + QVERIFY(m_debuggerAgent->m_wasPaused); + QCOMPARE(m_debuggerAgent->m_statesWhenPaused.count(), 2); + + QV4Debugger::ExecutionState state = m_debuggerAgent->m_statesWhenPaused.at(0); + QCOMPARE(state.fileName, QString("addBreakPointWhilePaused")); + QCOMPARE(state.lineNumber, 1); + + state = m_debuggerAgent->m_statesWhenPaused.at(1); + QCOMPARE(state.fileName, QString("addBreakPointWhilePaused")); + QCOMPARE(state.lineNumber, 2); +} + +static QV4::ReturnedValue someCall(QV4::CallContext *ctx) +{ + static_cast<QV4Debugger *>(ctx->d()->engine->debugger) + ->removeBreakPoint("removeBreakPointForNextInstruction", 2); + return QV4::Encode::undefined(); +} + +void tst_qv4debugger::removeBreakPointForNextInstruction() +{ + QString script = + "someCall();\n" + "var i = 42;"; + + QMetaObject::invokeMethod(m_engine, "injectFunction", Qt::BlockingQueuedConnection, + Q_ARG(QString, "someCall"), Q_ARG(InjectedFunction, someCall)); + + debugger()->addBreakPoint("removeBreakPointForNextInstruction", 2); + + evaluateJavaScript(script, "removeBreakPointForNextInstruction"); + QVERIFY(!m_debuggerAgent->m_wasPaused); +} + +void tst_qv4debugger::conditionalBreakPoint() +{ + m_debuggerAgent->m_captureContextInfo = true; + QString script = + "function test() {\n" + " for (var i = 0; i < 15; ++i) {\n" + " var x = i;\n" + " }\n" + "}\n" + "test()\n"; + + debugger()->addBreakPoint("conditionalBreakPoint", 3, QStringLiteral("i > 10")); + evaluateJavaScript(script, "conditionalBreakPoint"); + QVERIFY(m_debuggerAgent->m_wasPaused); + QCOMPARE(m_debuggerAgent->m_statesWhenPaused.count(), 4); + QV4Debugger::ExecutionState state = m_debuggerAgent->m_statesWhenPaused.first(); + QCOMPARE(state.fileName, QString("conditionalBreakPoint")); + QCOMPARE(state.lineNumber, 3); + + QVERIFY(m_debuggerAgent->m_capturedLocals.size() > 1); + const TestAgent::NamedRefs &frame0 = m_debuggerAgent->m_capturedLocals.at(0); + QCOMPARE(frame0.size(), 2); + QVERIFY(frame0.contains("i")); + QCOMPARE(frame0.value("i").toInt(), 11); +} + +void tst_qv4debugger::conditionalBreakPointInQml() +{ + QQmlEngine engine; + QV4::ExecutionEngine *v4 = QV8Engine::getV4(&engine); + QV4Debugger *v4Debugger = new QV4Debugger(v4); + v4->iselFactory.reset(new QV4::Moth::ISelFactory); + v4->setDebugger(v4Debugger); + + QScopedPointer<QThread> debugThread(new QThread); + debugThread->start(); + QScopedPointer<TestAgent> debuggerAgent(new TestAgent(v4)); + debuggerAgent->addDebugger(v4Debugger); + debuggerAgent->moveToThread(debugThread.data()); + + QQmlComponent component(&engine); + component.setData("import QtQml 2.0\n" + "QtObject {\n" + " id: root\n" + " property int foo: 42\n" + " property bool success: false\n" + " Component.onCompleted: {\n" + " success = true;\n" // breakpoint here + " }\n" + "}\n", QUrl("test.qml")); + + v4Debugger->addBreakPoint("test.qml", 7, "root.foo == 42"); + + QScopedPointer<QObject> obj(component.create()); + QCOMPARE(obj->property("success").toBool(), true); + + QCOMPARE(debuggerAgent->m_statesWhenPaused.count(), 1); + QCOMPARE(debuggerAgent->m_statesWhenPaused.at(0).fileName, QStringLiteral("test.qml")); + QCOMPARE(debuggerAgent->m_statesWhenPaused.at(0).lineNumber, 7); + + debugThread->quit(); + debugThread->wait(); +} + +void tst_qv4debugger::readArguments() +{ + m_debuggerAgent->m_captureContextInfo = true; + QString script = + "function f(a, b, c, d) {\n" + " return a === b\n" + "}\n" + "var four;\n" + "f(1, 'two', null, four);\n"; + debugger()->addBreakPoint("readArguments", 2); + evaluateJavaScript(script, "readArguments"); + QVERIFY(m_debuggerAgent->m_wasPaused); + QVERIFY(m_debuggerAgent->m_capturedArguments.size() > 1); + const TestAgent::NamedRefs &frame0 = m_debuggerAgent->m_capturedArguments.at(0); + QCOMPARE(frame0.size(), 4); + QVERIFY(frame0.contains(QStringLiteral("a"))); + QCOMPARE(frame0.type(QStringLiteral("a")), QStringLiteral("number")); + QCOMPARE(frame0.value(QStringLiteral("a")).toDouble(), 1.0); + QVERIFY(frame0.names.contains("b")); + QCOMPARE(frame0.type(QStringLiteral("b")), QStringLiteral("string")); + QCOMPARE(frame0.value(QStringLiteral("b")).toString(), QStringLiteral("two")); +} + +void tst_qv4debugger::readLocals() +{ + m_debuggerAgent->m_captureContextInfo = true; + QString script = + "function f(a, b) {\n" + " var c = a + b\n" + " var d = a - b\n" // breakpoint, c should be set, d should be undefined + " return c === d\n" + "}\n" + "f(1, 2, 3);\n"; + debugger()->addBreakPoint("readLocals", 3); + evaluateJavaScript(script, "readLocals"); + QVERIFY(m_debuggerAgent->m_wasPaused); + QVERIFY(m_debuggerAgent->m_capturedLocals.size() > 1); + const TestAgent::NamedRefs &frame0 = m_debuggerAgent->m_capturedLocals.at(0); + QCOMPARE(frame0.size(), 2); + QVERIFY(frame0.contains("c")); + QCOMPARE(frame0.type("c"), QStringLiteral("number")); + QCOMPARE(frame0.value("c").toDouble(), 3.0); + QVERIFY(frame0.contains("d")); + QCOMPARE(frame0.type("d"), QStringLiteral("undefined")); +} + +void tst_qv4debugger::readObject() +{ + m_debuggerAgent->m_captureContextInfo = true; + QString script = + "function f(a) {\n" + " var b = a\n" + " return b\n" + "}\n" + "f({head: 1, tail: { head: 'asdf', tail: null }});\n"; + debugger()->addBreakPoint("readObject", 3); + evaluateJavaScript(script, "readObject"); + QVERIFY(m_debuggerAgent->m_wasPaused); + QVERIFY(m_debuggerAgent->m_capturedLocals.size() > 1); + const TestAgent::NamedRefs &frame0 = m_debuggerAgent->m_capturedLocals.at(0); + QCOMPARE(frame0.size(), 1); + QVERIFY(frame0.contains("b")); + QCOMPARE(frame0.type("b"), QStringLiteral("object")); + QJsonObject b = frame0.rawValue("b"); + QVERIFY(b.contains(QStringLiteral("properties"))); + QVERIFY(b.value("properties").isArray()); + QJsonArray b_props = b.value("properties").toArray(); + QCOMPARE(b_props.size(), 2); + + QVERIFY(b_props.at(0).isObject()); + QJsonObject b_head = b_props.at(0).toObject(); + QCOMPARE(b_head.value("name").toString(), QStringLiteral("head")); + QCOMPARE(b_head.value("type").toString(), QStringLiteral("number")); + QCOMPARE(b_head.value("value").toDouble(), 1.0); + QVERIFY(b_props.at(1).isObject()); + QJsonObject b_tail = b_props.at(1).toObject(); + QCOMPARE(b_tail.value("name").toString(), QStringLiteral("tail")); + QVERIFY(b_tail.contains("ref")); + + QJsonObject b_tail_value = frame0.collector->lookupRef(b_tail.value("ref").toInt()); + QCOMPARE(b_tail_value.value("type").toString(), QStringLiteral("object")); + QVERIFY(b_tail_value.contains("properties")); + QJsonArray b_tail_props = b_tail_value.value("properties").toArray(); + QCOMPARE(b_tail_props.size(), 2); + QJsonObject b_tail_head = b_tail_props.at(0).toObject(); + QCOMPARE(b_tail_head.value("name").toString(), QStringLiteral("head")); + QCOMPARE(b_tail_head.value("type").toString(), QStringLiteral("string")); + QCOMPARE(b_tail_head.value("value").toString(), QStringLiteral("asdf")); + QJsonObject b_tail_tail = b_tail_props.at(1).toObject(); + QCOMPARE(b_tail_tail.value("name").toString(), QStringLiteral("tail")); + QCOMPARE(b_tail_tail.value("type").toString(), QStringLiteral("null")); + QVERIFY(b_tail_tail.value("value").isNull()); +} + +void tst_qv4debugger::readContextInAllFrames() +{ + m_debuggerAgent->m_captureContextInfo = true; + QString script = + "function fact(n) {\n" + " if (n > 1) {\n" + " var n_1 = n - 1;\n" + " n_1 = fact(n_1);\n" + " return n * n_1;\n" + " } else\n" + " return 1;\n" // breakpoint + "}\n" + "fact(12);\n"; + debugger()->addBreakPoint("readFormalsInAllFrames", 7); + evaluateJavaScript(script, "readFormalsInAllFrames"); + QVERIFY(m_debuggerAgent->m_wasPaused); + QCOMPARE(m_debuggerAgent->m_stackTrace.size(), 13); + QCOMPARE(m_debuggerAgent->m_capturedArguments.size(), 13); + QCOMPARE(m_debuggerAgent->m_capturedLocals.size(), 13); + + for (int i = 0; i < 12; ++i) { + const TestAgent::NamedRefs &args = m_debuggerAgent->m_capturedArguments.at(i); + QCOMPARE(args.size(), 1); + QVERIFY(args.contains("n")); + QCOMPARE(args.type("n"), QStringLiteral("number")); + QCOMPARE(args.value("n").toDouble(), i + 1.0); + + const TestAgent::NamedRefs &locals = m_debuggerAgent->m_capturedLocals.at(i); + QCOMPARE(locals.size(), 1); + QVERIFY(locals.contains("n_1")); + if (i == 0) { + QCOMPARE(locals.type("n_1"), QStringLiteral("undefined")); + } else { + QCOMPARE(locals.type("n_1"), QStringLiteral("number")); + QCOMPARE(locals.value("n_1").toInt(), i); + } + } + QCOMPARE(m_debuggerAgent->m_capturedArguments[12].size(), 0); + QCOMPARE(m_debuggerAgent->m_capturedLocals[12].size(), 0); +} + +void tst_qv4debugger::pauseOnThrow() +{ + QString script = + "function die(n) {\n" + " throw n\n" + "}\n" + "die('hard');\n"; + debugger()->setBreakOnThrow(true); + evaluateJavaScript(script, "pauseOnThrow"); + QVERIFY(m_debuggerAgent->m_wasPaused); + QCOMPARE(m_debuggerAgent->m_pauseReason, QV4Debugger::Throwing); + QCOMPARE(m_debuggerAgent->m_stackTrace.size(), 2); + QVERIFY(m_debuggerAgent->m_thrownValue >= qint64(0)); + QJsonObject exception = m_debuggerAgent->collector.lookupRef(m_debuggerAgent->m_thrownValue); +// DUMP_JSON(exception); + QCOMPARE(exception.value("type").toString(), QStringLiteral("string")); + QCOMPARE(exception.value("value").toString(), QStringLiteral("hard")); +} + +void tst_qv4debugger::breakInCatch() +{ + QString script = + "try {\n" + " throw 'catch...'\n" + "} catch (e) {\n" + " console.log(e, 'me');\n" + "}\n"; + + debugger()->addBreakPoint("breakInCatch", 4); + evaluateJavaScript(script, "breakInCatch"); + QVERIFY(m_debuggerAgent->m_wasPaused); + QCOMPARE(m_debuggerAgent->m_pauseReason, QV4Debugger::BreakPointHit); + QCOMPARE(m_debuggerAgent->m_statesWhenPaused.count(), 1); + QV4Debugger::ExecutionState state = m_debuggerAgent->m_statesWhenPaused.first(); + QCOMPARE(state.fileName, QString("breakInCatch")); + QCOMPARE(state.lineNumber, 4); +} + +void tst_qv4debugger::breakInWith() +{ + QString script = + "with (42) {\n" + " console.log('give the answer');\n" + "}\n"; + + debugger()->addBreakPoint("breakInWith", 2); + evaluateJavaScript(script, "breakInWith"); + QVERIFY(m_debuggerAgent->m_wasPaused); + QCOMPARE(m_debuggerAgent->m_pauseReason, QV4Debugger::BreakPointHit); + QCOMPARE(m_debuggerAgent->m_statesWhenPaused.count(), 1); + QV4Debugger::ExecutionState state = m_debuggerAgent->m_statesWhenPaused.first(); + QCOMPARE(state.fileName, QString("breakInWith")); + QCOMPARE(state.lineNumber, 2); +} + +void tst_qv4debugger::evaluateExpression() +{ + QString script = + "function testFunction() {\n" + " var x = 10\n" + " return x\n" // breakpoint + "}\n" + "var x = 20\n" + "testFunction()\n"; + + TestAgent::ExpressionRequest request; + request.expression = "x"; + request.frameNr = 0; + m_debuggerAgent->m_expressionRequests << request; + request.expression = "x"; + request.frameNr = 1; + m_debuggerAgent->m_expressionRequests << request; + + debugger()->addBreakPoint("evaluateExpression", 3); + + evaluateJavaScript(script, "evaluateExpression"); + + QCOMPARE(m_debuggerAgent->m_expressionResults.count(), 2); + QCOMPARE(m_debuggerAgent->m_expressionResults[0].size(), 1); + QJsonObject result0 = + m_debuggerAgent->collector.lookupRef(m_debuggerAgent->m_expressionResults[0].first()); + QCOMPARE(result0.value("type").toString(), QStringLiteral("number")); + QCOMPARE(result0.value("value").toInt(), 10); + QCOMPARE(m_debuggerAgent->m_expressionResults[1].size(), 1); + QJsonObject result1 = + m_debuggerAgent->collector.lookupRef(m_debuggerAgent->m_expressionResults[1].first()); + QCOMPARE(result1.value("type").toString(), QStringLiteral("number")); + QCOMPARE(result1.value("value").toInt(), 20); +} + +QTEST_MAIN(tst_qv4debugger) + +#include "tst_qv4debugger.moc" |