From d349c879e1d55e4cd2b7d31c08e2796c0fec4020 Mon Sep 17 00:00:00 2001 From: Kai Koehne Date: Tue, 21 Jun 2011 16:38:44 +0200 Subject: QDeclarativeDebug: Add code coverage information Add messages to track the lines of JavaScript that are executed. Patch done by Janne Virranmaki Reviewed-by: kkoehne --- src/declarative/debugger/qjsdebuggeragent.cpp | 42 +++++++-- src/declarative/debugger/qjsdebuggeragent_p.h | 12 +++ src/declarative/debugger/qjsdebugservice.cpp | 53 ++++++++++- src/declarative/debugger/qjsdebugservice_p.h | 24 +++++ .../tst_qdeclarativedebugjs.cpp | 101 +++++++++++++++++++++ 5 files changed, 225 insertions(+), 7 deletions(-) diff --git a/src/declarative/debugger/qjsdebuggeragent.cpp b/src/declarative/debugger/qjsdebuggeragent.cpp index dff637b..683032b 100644 --- a/src/declarative/debugger/qjsdebuggeragent.cpp +++ b/src/declarative/debugger/qjsdebuggeragent.cpp @@ -41,6 +41,7 @@ #include "private/qjsdebuggeragent_p.h" #include "private/qdeclarativedebughelper_p.h" +#include "private/qjsdebugservice_p.h" #include #include @@ -56,7 +57,7 @@ class QJSDebuggerAgentPrivate { public: QJSDebuggerAgentPrivate(QJSDebuggerAgent *q) - : q(q), state(NoState), isInitialized(false) + : q(q), state(NoState), isInitialized(false), coverageEnabled(false) {} void continueExec(); @@ -80,6 +81,7 @@ public: QStringList watchExpressions; QSet knownObjectIds; bool isInitialized; + bool coverageEnabled; }; namespace { @@ -261,6 +263,12 @@ bool QJSDebuggerAgent::isInitialized() const return d->isInitialized; } +void QJSDebuggerAgent::setCoverageEnabled(bool enabled) +{ + d->isInitialized = true; + d->coverageEnabled = enabled; +} + void QJSDebuggerAgent::setBreakpoints(const JSAgentBreakpoints &breakpoints) { d->breakpoints = breakpoints; @@ -417,10 +425,16 @@ void QJSDebuggerAgent::setProperty(qint64 objectId, \reimp */ void QJSDebuggerAgent::scriptLoad(qint64 id, const QString &program, - const QString &fileName, int) + const QString &fileName, int baseLineNumber) { - Q_UNUSED(program); d->filenames.insert(id, fileName); + + if (d->coverageEnabled) { + JSAgentCoverageData rd = {"COVERAGE", QJSDebugService::instance()->m_timer.elapsed(), (int)CoverageScriptLoad, + id, program, fileName, baseLineNumber, + 0, 0, QString()}; + QJSDebugService::instance()->processMessage(rd); + } } /*! @@ -450,8 +464,14 @@ void QJSDebuggerAgent::contextPop() */ void QJSDebuggerAgent::functionEntry(qint64 scriptId) { - Q_UNUSED(scriptId); d->stepDepth++; + + if (d->coverageEnabled) { + JSAgentCoverageData rd = {"COVERAGE", QJSDebugService::instance()->m_timer.elapsed(), (int)CoverageFuncEntry, + scriptId, QString(), QString(), 0, 0, 0, QString()}; + QJSDebugService::instance()->processMessage(rd); + QJSDebugService::instance()->m_timer.restart(); + } } /*! @@ -459,9 +479,13 @@ void QJSDebuggerAgent::functionEntry(qint64 scriptId) */ void QJSDebuggerAgent::functionExit(qint64 scriptId, const QScriptValue &returnValue) { - Q_UNUSED(scriptId); - Q_UNUSED(returnValue); d->stepDepth--; + + if (d->coverageEnabled) { + JSAgentCoverageData rd = {"COVERAGE", QJSDebugService::instance()->m_timer.elapsed(), (int)CoverageFuncExit, + scriptId, QString(), QString(), 0, 0, 0, returnValue.toString()}; + QJSDebugService::instance()->processMessage(rd); + } } /*! @@ -470,6 +494,12 @@ void QJSDebuggerAgent::functionExit(qint64 scriptId, const QScriptValue &returnV void QJSDebuggerAgent::positionChange(qint64 scriptId, int lineNumber, int columnNumber) { d->positionChange(scriptId, lineNumber, columnNumber); + + if (d->coverageEnabled) { + JSAgentCoverageData rd = {"COVERAGE", QJSDebugService::instance()->m_timer.elapsed(), (int)CoveragePosChange, + scriptId, QString(), QString(), 0, lineNumber, columnNumber, QString()}; + QJSDebugService::instance()->processMessage(rd); + } } void QJSDebuggerAgentPrivate::positionChange(qint64 scriptId, int lineNumber, int columnNumber) diff --git a/src/declarative/debugger/qjsdebuggeragent_p.h b/src/declarative/debugger/qjsdebuggeragent_p.h index 4b27fc8..e999080 100644 --- a/src/declarative/debugger/qjsdebuggeragent_p.h +++ b/src/declarative/debugger/qjsdebuggeragent_p.h @@ -78,6 +78,17 @@ enum JSDebuggerState StoppedState }; +enum JSCoverageMessage { + CoverageLocation, + CoverageScriptLoad, + CoveragePosChange, + CoverageFuncEntry, + CoverageFuncExit, + CoverageComplete, + + CoverageMaximumMessage +}; + struct JSAgentWatchData { QByteArray exp; @@ -165,6 +176,7 @@ public: void stepInto(); void stepOut(); void continueExecution(); + void setCoverageEnabled(bool enabled); JSAgentWatchData executeExpression(const QString &expr); QList expandObjectById(quint64 objectId); diff --git a/src/declarative/debugger/qjsdebugservice.cpp b/src/declarative/debugger/qjsdebugservice.cpp index ad84f65..03d1f76 100644 --- a/src/declarative/debugger/qjsdebugservice.cpp +++ b/src/declarative/debugger/qjsdebugservice.cpp @@ -49,10 +49,22 @@ Q_GLOBAL_STATIC(QJSDebugService, serviceInstance) +// convert to a QByteArray that can be sent to the debug client +QByteArray JSAgentCoverageData::toByteArray() const +{ + QByteArray data; + //### using QDataStream is relatively expensive + QDataStream ds(&data, QIODevice::WriteOnly); + ds << prefix << time << messageType << scriptId << program << fileName << baseLineNumber + << lineNumber << columnNumber << returnValue; + return data; +} + QJSDebugService::QJSDebugService(QObject *parent) : QDeclarativeDebugService(QLatin1String("JSDebugger"), parent) - , m_agent(0) + , m_agent(0), m_deferredSend(true) { + m_timer.start(); } QJSDebugService::~QJSDebugService() @@ -186,6 +198,13 @@ void QJSDebugService::messageReceived(const QByteArray &message) QDataStream rs(&reply, QIODevice::WriteOnly); rs << QByteArray("PONG") << ping; sendMessage(reply); + } else if (command == "COVERAGE") { + bool enabled; + ds >> enabled; + m_agent->setCoverageEnabled(enabled); + if (!enabled) { + sendMessages(); + } } else { qDebug() << Q_FUNC_INFO << "Unknown command" << command; } @@ -206,3 +225,35 @@ void QJSDebugService::executionStopped(bool becauseOfException, << becauseOfException << exception; sendMessage(reply); } + +/* + Either send the message directly, or queue up + a list of messages to send later (via sendMessages) +*/ +void QJSDebugService::processMessage(const JSAgentCoverageData &message) +{ + if (m_deferredSend) + m_data.append(message); + else + sendMessage(message.toByteArray()); +} + +/* + Send the messages queued up by processMessage +*/ +void QJSDebugService::sendMessages() +{ + if (m_deferredSend) { + //### this is a suboptimal way to send batched messages + for (int i = 0; i < m_data.count(); ++i) + sendMessage(m_data.at(i).toByteArray()); + m_data.clear(); + + //indicate completion + QByteArray data; + QDataStream ds(&data, QIODevice::WriteOnly); + ds << QByteArray("COVERAGE") << (qint64)-1 << (int)CoverageComplete; + sendMessage(data); + } +} + diff --git a/src/declarative/debugger/qjsdebugservice_p.h b/src/declarative/debugger/qjsdebugservice_p.h index 7e7642e..4f99043 100644 --- a/src/declarative/debugger/qjsdebugservice_p.h +++ b/src/declarative/debugger/qjsdebugservice_p.h @@ -54,6 +54,7 @@ // #include +#include #include "private/qdeclarativedebugservice_p.h" @@ -66,6 +67,23 @@ QT_MODULE(Declarative) class QDeclarativeEngine; class QJSDebuggerAgent; +struct JSAgentCoverageData +{ + QByteArray prefix; + qint64 time; + int messageType; + + qint64 scriptId; + QString program; + QString fileName; + int baseLineNumber; + int lineNumber; + int columnNumber; + QString returnValue; + + QByteArray toByteArray() const; +}; + class QJSDebugService : public QDeclarativeDebugService { Q_OBJECT @@ -78,6 +96,9 @@ public: void addEngine(QDeclarativeEngine *); void removeEngine(QDeclarativeEngine *); + void processMessage(const JSAgentCoverageData &message); + + QElapsedTimer m_timer; protected: void statusChanged(Status status); @@ -88,8 +109,11 @@ private Q_SLOTS: const QString &exception); private: + void sendMessages(); QList m_engines; QPointer m_agent; + bool m_deferredSend; + QList m_data; }; QT_END_NAMESPACE diff --git a/tests/auto/declarative/qdeclarativedebugjs/tst_qdeclarativedebugjs.cpp b/tests/auto/declarative/qdeclarativedebugjs/tst_qdeclarativedebugjs.cpp index a40bcc0..1990c0d 100644 --- a/tests/auto/declarative/qdeclarativedebugjs/tst_qdeclarativedebugjs.cpp +++ b/tests/auto/declarative/qdeclarativedebugjs/tst_qdeclarativedebugjs.cpp @@ -69,6 +69,8 @@ public: void expandObjectById(const QByteArray& objectName, quint64 objectId); void setProperty(const QByteArray& id, qint64 objectId, const QString &property, const QString &value); void activateFrame(int frameId); + void startCoverageCompleted(); + void startCoverageRun(); // info from last exec JSAgentWatchData exec_data; @@ -94,6 +96,11 @@ signals: void stopped(); void expanded(); void watchTriggered(); + void coverageScriptLoaded(); + void coverageFuncEntered(); + void coverageFuncExited(); + void coveragePosChanged(); + void coverageCompleted(); protected: virtual void statusChanged(Status status); @@ -156,6 +163,9 @@ private slots: void setProperty4(); void activateFrame2(); void verifyQMLOptimizerDisabled(); + void testCoverageCompleted(); + void testCoverageRun(); + }; @@ -280,6 +290,28 @@ void QJSDebugClient::activateFrame(int frameId) sendMessage(reply); } +void QJSDebugClient::startCoverageRun() +{ + QByteArray reply; + QDataStream rs(&reply, QIODevice::WriteOnly); + QByteArray cmd = "COVERAGE"; + bool enabled = true; + rs << cmd + << enabled; + sendMessage(reply); +} + +void QJSDebugClient::startCoverageCompleted() +{ + QByteArray reply; + QDataStream rs(&reply, QIODevice::WriteOnly); + QByteArray cmd = "COVERAGE"; + bool enabled = false; + rs << cmd + << enabled; + sendMessage(reply); +} + void QJSDebugClient::statusChanged(Status /*status*/) { emit statusHasChanged(); @@ -317,6 +349,30 @@ void QJSDebugClient::messageReceived(const QByteArray &data) stream >> ping; QCOMPARE(ping, m_ping); emit pong(); + } else if (command == "COVERAGE") { + qint64 time; + int messageType; + qint64 scriptId; + QString program; + QString fileName; + int baseLineNumber; + int lineNumber; + int columnNumber; + QString returnValue; + + stream >> time >> messageType >> scriptId >> program >> fileName >> baseLineNumber + >> lineNumber >> columnNumber >> returnValue; + if (messageType == CoverageComplete) { + emit coverageCompleted(); + } else if (messageType == CoverageScriptLoad) { + emit coverageScriptLoaded(); + } else if (messageType == CoveragePosChange) { + emit coveragePosChanged(); + } else if (messageType == CoverageFuncEntry) { + emit coverageFuncEntered(); + } else if (messageType == CoverageFuncExit) { + emit coverageFuncExited(); + } } else { QFAIL("Unknown message :" + command); } @@ -1336,6 +1392,51 @@ void tst_QDeclarativeDebugJS::verifyQMLOptimizerDisabled() QVERIFY(QDeclarativeDebugTest::waitForSignal(&client, SIGNAL(stopped()))); } + +void tst_QDeclarativeDebugJS::testCoverageCompleted() +{ + QJSDebugProcess process; + process.start(QStringList() << "-qmljsdebugger=port:3771,block" << TEST_FILE("backtrace1.qml")); + QVERIFY(process.waitForStarted()); + + QDeclarativeDebugConnection connection; + connection.connectToHost("127.0.0.1", 3771); + QVERIFY(connection.waitForConnected()); + + QJSDebugClient client(&connection); + QVERIFY(QDeclarativeDebugTest::waitForSignal(&client, SIGNAL(statusHasChanged()))); + if (client.status() == QJSDebugClient::Unavailable) + QVERIFY(QDeclarativeDebugTest::waitForSignal(&client, SIGNAL(statusHasChanged()))); + QCOMPARE(client.status(), QJSDebugClient::Enabled); + + client.startCoverageCompleted(); + QVERIFY(QDeclarativeDebugTest::waitForSignal(&client, SIGNAL(coverageCompleted()))); +} + +void tst_QDeclarativeDebugJS::testCoverageRun() +{ + QJSDebugProcess process; + process.start(QStringList() << "-qmljsdebugger=port:3771,block" << TEST_FILE("backtrace1.qml")); + QVERIFY(process.waitForStarted()); + + QDeclarativeDebugConnection connection; + connection.connectToHost("127.0.0.1", 3771); + QVERIFY(connection.waitForConnected()); + + QJSDebugClient client(&connection); + QVERIFY(QDeclarativeDebugTest::waitForSignal(&client, SIGNAL(statusHasChanged()))); + if (client.status() == QJSDebugClient::Unavailable) + QVERIFY(QDeclarativeDebugTest::waitForSignal(&client, SIGNAL(statusHasChanged()))); + QCOMPARE(client.status(), QJSDebugClient::Enabled); + + client.startCoverageRun(); + client.startCoverageCompleted(); + QVERIFY(QDeclarativeDebugTest::waitForSignal(&client, SIGNAL(coverageScriptLoaded()))); + QVERIFY(QDeclarativeDebugTest::waitForSignal(&client, SIGNAL(coveragePosChanged()))); + //QVERIFY(QDeclarativeDebugTest::waitForSignal(&client, SIGNAL(coverageFuncEntered()))); + //QVERIFY(QDeclarativeDebugTest::waitForSignal(&client, SIGNAL(coverageFuncExited()))); +} + QTEST_MAIN(tst_QDeclarativeDebugJS) #include "tst_qdeclarativedebugjs.moc" -- cgit v0.12