| 1 | /**************************************************************************** | 
| 2 | ** | 
| 3 | ** Copyright (C) 2016 The Qt Company Ltd. | 
| 4 | ** Contact: https://www.qt.io/licensing/ | 
| 5 | ** | 
| 6 | ** This file is part of the test suite of the Qt Toolkit. | 
| 7 | ** | 
| 8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ | 
| 9 | ** Commercial License Usage | 
| 10 | ** Licensees holding valid commercial Qt licenses may use this file in | 
| 11 | ** accordance with the commercial license agreement provided with the | 
| 12 | ** Software or, alternatively, in accordance with the terms contained in | 
| 13 | ** a written agreement between you and The Qt Company. For licensing terms | 
| 14 | ** and conditions see https://www.qt.io/terms-conditions. For further | 
| 15 | ** information use the contact form at https://www.qt.io/contact-us. | 
| 16 | ** | 
| 17 | ** GNU General Public License Usage | 
| 18 | ** Alternatively, this file may be used under the terms of the GNU | 
| 19 | ** General Public License version 3 as published by the Free Software | 
| 20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT | 
| 21 | ** included in the packaging of this file. Please review the following | 
| 22 | ** information to ensure the GNU General Public License requirements will | 
| 23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. | 
| 24 | ** | 
| 25 | ** $QT_END_LICENSE$ | 
| 26 | ** | 
| 27 | ****************************************************************************/ | 
| 28 |  | 
| 29 | #include "debugutil_p.h" | 
| 30 | #include "qqmldebugprocess_p.h" | 
| 31 | #include "../../../shared/util.h" | 
| 32 |  | 
| 33 | #include <private/qqmlenginedebugclient_p.h> | 
| 34 | #include <private/qv4debugclient_p.h> | 
| 35 | #include <private/qqmldebugconnection_p.h> | 
| 36 | #include <private/qpacket_p.h> | 
| 37 |  | 
| 38 | #include <QtTest/qtest.h> | 
| 39 | #include <QtTest/qtestsystem.h> | 
| 40 | #include <QtCore/qprocess.h> | 
| 41 | #include <QtCore/qfileinfo.h> | 
| 42 | #include <QtCore/qdir.h> | 
| 43 | #include <QtCore/qmutex.h> | 
| 44 | #include <QtCore/qlibraryinfo.h> | 
| 45 | #include <QtCore/qjsonobject.h> | 
| 46 | #include <QtCore/qjsonarray.h> | 
| 47 |  | 
| 48 | const char *BLOCKMODE = "-qmljsdebugger=port:3771,3800,block" ; | 
| 49 | const char *NORMALMODE = "-qmljsdebugger=port:3771,3800" ; | 
| 50 | const char *BLOCKRESTRICTEDMODE = "-qmljsdebugger=port:3771,3800,block,services:V8Debugger" ; | 
| 51 | const char *NORMALRESTRICTEDMODE = "-qmljsdebugger=port:3771,3800,services:V8Debugger" ; | 
| 52 | const char *TEST_QMLFILE = "test.qml" ; | 
| 53 | const char *TEST_JSFILE = "test.js" ; | 
| 54 | const char *TIMER_QMLFILE = "timer.qml" ; | 
| 55 | const char *LOADJSFILE_QMLFILE = "loadjsfile.qml" ; | 
| 56 | const char *EXCEPTION_QMLFILE = "exception.qml" ; | 
| 57 | const char *ONCOMPLETED_QMLFILE = "oncompleted.qml" ; | 
| 58 | const char *CREATECOMPONENT_QMLFILE = "createComponent.qml" ; | 
| 59 | const char *CONDITION_QMLFILE = "condition.qml" ; | 
| 60 | const char *QUIT_QMLFILE = "quit.qml" ; | 
| 61 | const char *QUITINJS_QMLFILE = "quitInJS.qml" ; | 
| 62 | const char *QUIT_JSFILE = "quit.js" ; | 
| 63 | const char *CHANGEBREAKPOINT_QMLFILE = "changeBreakpoint.qml" ; | 
| 64 | const char *STEPACTION_QMLFILE = "stepAction.qml" ; | 
| 65 | const char *BREAKPOINTRELOCATION_QMLFILE = "breakpointRelocation.qml" ; | 
| 66 | const char *ENCODEQMLSCOPE_QMLFILE = "encodeQmlScope.qml" ; | 
| 67 | const char *BREAKONANCHOR_QMLFILE = "breakOnAnchor.qml" ; | 
| 68 | const char *BREAKPOINTIDS_QMLFILE = "breakPointIds.qml" ; | 
| 69 | const char *LETCONSTLOCALS_QMLFILE = "letConstLocals.qml" ; | 
| 70 |  | 
| 71 | #undef QVERIFY | 
| 72 | #define QVERIFY(statement) \ | 
| 73 | do {\ | 
| 74 |     if (!QTest::qVerify((statement), #statement, "", __FILE__, __LINE__)) {\ | 
| 75 |         if (QTest::currentTestFailed()) \ | 
| 76 |           qDebug().nospace() << "\nDEBUGGEE OUTPUT:\n" << m_process->output();\ | 
| 77 |         return;\ | 
| 78 |     }\ | 
| 79 | } while (0) | 
| 80 |  | 
| 81 | class tst_QQmlDebugJS : public QQmlDebugTest | 
| 82 | { | 
| 83 |     Q_OBJECT | 
| 84 |  | 
| 85 | private slots: | 
| 86 |     void initTestCase() override; | 
| 87 |  | 
| 88 |     void connect_data(); | 
| 89 |     void connect(); | 
| 90 |     void interrupt_data() { targetData(); } | 
| 91 |     void interrupt(); | 
| 92 |     void getVersion_data() { targetData(); } | 
| 93 |     void getVersion(); | 
| 94 |     void getVersionWhenAttaching_data() { targetData(); } | 
| 95 |     void getVersionWhenAttaching(); | 
| 96 |  | 
| 97 |     void disconnect_data() { targetData(); } | 
| 98 |     void disconnect(); | 
| 99 |  | 
| 100 |     void setBreakpointInScriptOnCompleted_data() { targetData(); } | 
| 101 |     void setBreakpointInScriptOnCompleted(); | 
| 102 |     void setBreakpointInScriptOnComponentCreated_data() { targetData(); } | 
| 103 |     void setBreakpointInScriptOnComponentCreated(); | 
| 104 |     void setBreakpointInScriptOnTimerCallback_data() { targetData(); } | 
| 105 |     void setBreakpointInScriptOnTimerCallback(); | 
| 106 |     void setBreakpointInScriptInDifferentFile_data() { targetData(); } | 
| 107 |     void setBreakpointInScriptInDifferentFile(); | 
| 108 |     void () { targetData(); } | 
| 109 |     void setBreakpointInScriptOnComment(); | 
| 110 |     void setBreakpointInScriptOnEmptyLine_data() { targetData(); } | 
| 111 |     void setBreakpointInScriptOnEmptyLine(); | 
| 112 |     void setBreakpointInScriptOnOptimizedBinding_data() { targetData(); } | 
| 113 |     void setBreakpointInScriptOnOptimizedBinding(); | 
| 114 |     void setBreakpointInScriptWithCondition_data() { targetData(); } | 
| 115 |     void setBreakpointInScriptWithCondition(); | 
| 116 |     void setBreakpointInScriptThatQuits_data() { targetData(); }; | 
| 117 |     void setBreakpointInScriptThatQuits(); | 
| 118 |     void setBreakpointInJavaScript_data(); | 
| 119 |     void setBreakpointInJavaScript(); | 
| 120 |     void setBreakpointWhenAttaching(); | 
| 121 |  | 
| 122 |     void clearBreakpoint_data() { targetData(); } | 
| 123 |     void clearBreakpoint(); | 
| 124 |  | 
| 125 |     void changeBreakpoint_data() { targetData(); } | 
| 126 |     void changeBreakpoint(); | 
| 127 |  | 
| 128 |     void setExceptionBreak_data() { targetData(); } | 
| 129 |     void setExceptionBreak(); | 
| 130 |  | 
| 131 |     void stepNext_data() { targetData(); } | 
| 132 |     void stepNext(); | 
| 133 |     void stepIn_data() { targetData(); } | 
| 134 |     void stepIn(); | 
| 135 |     void stepOut_data() { targetData(); } | 
| 136 |     void stepOut(); | 
| 137 |     void continueDebugging_data() { targetData(); } | 
| 138 |     void continueDebugging(); | 
| 139 |  | 
| 140 |     void backtrace_data() { targetData(); } | 
| 141 |     void backtrace(); | 
| 142 |  | 
| 143 |     void getFrameDetails_data() { targetData(); } | 
| 144 |     void getFrameDetails(); | 
| 145 |  | 
| 146 |     void getScopeDetails_data() { targetData(); } | 
| 147 |     void getScopeDetails(); | 
| 148 |  | 
| 149 |     void evaluateInGlobalScope(); | 
| 150 |     void evaluateInLocalScope_data() { targetData(); } | 
| 151 |     void evaluateInLocalScope(); | 
| 152 |  | 
| 153 |     void evaluateInContext(); | 
| 154 |  | 
| 155 |     void getScripts_data() { targetData(); } | 
| 156 |     void getScripts(); | 
| 157 |  | 
| 158 |     void encodeQmlScope(); | 
| 159 |     void breakOnAnchor(); | 
| 160 |  | 
| 161 |     void breakPointIds(); | 
| 162 |     void letConstLocals(); | 
| 163 |  | 
| 164 | private: | 
| 165 |     ConnectResult init(bool qmlscene, const QString &qmlFile = QString(TEST_QMLFILE), | 
| 166 |                     bool blockMode = true, bool restrictServices = false); | 
| 167 |     QList<QQmlDebugClient *> createClients() override; | 
| 168 |     QPointer<QV4DebugClient> m_client; | 
| 169 |  | 
| 170 |     void targetData(); | 
| 171 |     bool waitForClientSignal(const char *signal, int timeout = 30000); | 
| 172 |     void checkVersionParameters(); | 
| 173 |     int setBreakPoint(const QString &file, int sourceLine, bool enabled); | 
| 174 |     void clearBreakPoint(int id); | 
| 175 | }; | 
| 176 |  | 
| 177 |  | 
| 178 | void tst_QQmlDebugJS::initTestCase() | 
| 179 | { | 
| 180 |     QQmlDebugTest::initTestCase(); | 
| 181 | } | 
| 182 |  | 
| 183 | QQmlDebugTest::ConnectResult tst_QQmlDebugJS::init(bool qmlscene, const QString &qmlFile, | 
| 184 |                                                    bool blockMode, bool restrictServices) | 
| 185 | { | 
| 186 |     const QString executable = qmlscene | 
| 187 |             ? QLibraryInfo::location(QLibraryInfo::BinariesPath) + "/qmlscene"  | 
| 188 |             : debugJsServerPath(selfPath: "qqmldebugjs" ); | 
| 189 |     return QQmlDebugTest::connectTo( | 
| 190 |                 executable, services: restrictServices ? QStringLiteral("V8Debugger" ) : QString(), | 
| 191 |                 extraArgs: testFile(fileName: qmlFile), block: blockMode); | 
| 192 | } | 
| 193 |  | 
| 194 | void tst_QQmlDebugJS::connect_data() | 
| 195 | { | 
| 196 |     QTest::addColumn<bool>(name: "blockMode" ); | 
| 197 |     QTest::addColumn<bool>(name: "restrictMode" ); | 
| 198 |     QTest::addColumn<bool>(name: "qmlscene" ); | 
| 199 |     QTest::newRow(dataTag: "normal / unrestricted / custom" )   << false << false << false; | 
| 200 |     QTest::newRow(dataTag: "block  / unrestricted / custom" )   << true  << false << false; | 
| 201 |     QTest::newRow(dataTag: "normal / restricted   / custom" )   << false << true  << false; | 
| 202 |     QTest::newRow(dataTag: "block  / restricted   / custom" )   << true  << true  << false; | 
| 203 |     QTest::newRow(dataTag: "normal / unrestricted / qmlscene" ) << false << false << true; | 
| 204 |     QTest::newRow(dataTag: "block  / unrestricted / qmlscene" ) << true  << false << true; | 
| 205 |     QTest::newRow(dataTag: "normal / restricted   / qmlscene" ) << false << true  << true; | 
| 206 |     QTest::newRow(dataTag: "block  / restricted   / qmlscene" ) << true  << true  << true; | 
| 207 | } | 
| 208 |  | 
| 209 | void tst_QQmlDebugJS::connect() | 
| 210 | { | 
| 211 |     QFETCH(bool, blockMode); | 
| 212 |     QFETCH(bool, restrictMode); | 
| 213 |     QFETCH(bool, qmlscene); | 
| 214 |  | 
| 215 |     QCOMPARE(init(qmlscene, QString(TEST_QMLFILE), blockMode, restrictMode), ConnectSuccess); | 
| 216 |     m_client->connect(); | 
| 217 |     QVERIFY(waitForClientSignal(SIGNAL(connected()))); | 
| 218 | } | 
| 219 |  | 
| 220 | void tst_QQmlDebugJS::interrupt() | 
| 221 | { | 
| 222 |     //void connect() | 
| 223 |     QFETCH(bool, qmlscene); | 
| 224 |  | 
| 225 |     QCOMPARE(init(qmlscene), ConnectSuccess); | 
| 226 |     m_client->connect(); | 
| 227 |  | 
| 228 |     m_client->interrupt(); | 
| 229 |     QVERIFY(waitForClientSignal(SIGNAL(interrupted()))); | 
| 230 | } | 
| 231 |  | 
| 232 | void tst_QQmlDebugJS::getVersion() | 
| 233 | { | 
| 234 |     //void version() | 
| 235 |     QFETCH(bool, qmlscene); | 
| 236 |  | 
| 237 |     QCOMPARE(init(qmlscene), ConnectSuccess); | 
| 238 |     m_client->connect(); | 
| 239 |     QVERIFY(waitForClientSignal(SIGNAL(connected()))); | 
| 240 |  | 
| 241 |     m_client->version(); | 
| 242 |     QVERIFY(waitForClientSignal(SIGNAL(result()))); | 
| 243 |     checkVersionParameters(); | 
| 244 | } | 
| 245 |  | 
| 246 | void tst_QQmlDebugJS::getVersionWhenAttaching() | 
| 247 | { | 
| 248 |     //void version() | 
| 249 |     QFETCH(bool, qmlscene); | 
| 250 |  | 
| 251 |     QCOMPARE(init(qmlscene, QLatin1String(TIMER_QMLFILE), false), ConnectSuccess); | 
| 252 |     m_client->connect(); | 
| 253 |  | 
| 254 |     m_client->version(); | 
| 255 |     QVERIFY(waitForClientSignal(SIGNAL(result()))); | 
| 256 |     checkVersionParameters(); | 
| 257 | } | 
| 258 |  | 
| 259 | void tst_QQmlDebugJS::disconnect() | 
| 260 | { | 
| 261 |     //void disconnect() | 
| 262 |     QFETCH(bool, qmlscene); | 
| 263 |  | 
| 264 |     QCOMPARE(init(qmlscene), ConnectSuccess); | 
| 265 |     m_client->connect(); | 
| 266 |  | 
| 267 |     m_client->disconnect(); | 
| 268 |     QVERIFY(waitForClientSignal(SIGNAL(result()))); | 
| 269 | } | 
| 270 |  | 
| 271 | void tst_QQmlDebugJS::setBreakpointInScriptOnCompleted() | 
| 272 | { | 
| 273 |     //void setBreakpoint(QString type, QString target, int line = -1, int column = -1, bool enabled = false, QString condition = QString(), int ignoreCount = -1) | 
| 274 |     QFETCH(bool, qmlscene); | 
| 275 |  | 
| 276 |     int sourceLine = 34; | 
| 277 |     QCOMPARE(init(qmlscene, ONCOMPLETED_QMLFILE), ConnectSuccess); | 
| 278 |  | 
| 279 |     m_client->setBreakpoint(target: QLatin1String(ONCOMPLETED_QMLFILE), line: sourceLine, column: -1, enabled: true); | 
| 280 |     m_client->connect(); | 
| 281 |     QVERIFY(waitForClientSignal(SIGNAL(stopped()))); | 
| 282 |  | 
| 283 |     const QJsonObject body = m_client->response().body.toObject(); | 
| 284 |  | 
| 285 |     QCOMPARE(body.value("sourceLine" ).toInt(), sourceLine); | 
| 286 |     QCOMPARE(QFileInfo(body.value("script" ).toObject().value("name" ).toString()).fileName(), | 
| 287 |              QLatin1String(ONCOMPLETED_QMLFILE)); | 
| 288 | } | 
| 289 |  | 
| 290 | void tst_QQmlDebugJS::setBreakpointInScriptOnComponentCreated() | 
| 291 | { | 
| 292 |     //void setBreakpoint(QString type, QString target, int line = -1, int column = -1, bool enabled = false, QString condition = QString(), int ignoreCount = -1) | 
| 293 |     QFETCH(bool, qmlscene); | 
| 294 |  | 
| 295 |     int sourceLine = 34; | 
| 296 |     QCOMPARE(init(qmlscene, CREATECOMPONENT_QMLFILE), ConnectSuccess); | 
| 297 |  | 
| 298 |     m_client->setBreakpoint(target: QLatin1String(ONCOMPLETED_QMLFILE), line: sourceLine, column: -1, enabled: true); | 
| 299 |     m_client->connect(); | 
| 300 |     QVERIFY(waitForClientSignal(SIGNAL(stopped()))); | 
| 301 |  | 
| 302 |     const QJsonObject body = m_client->response().body.toObject(); | 
| 303 |  | 
| 304 |     QCOMPARE(body.value("sourceLine" ).toInt(), sourceLine); | 
| 305 |     QCOMPARE(QFileInfo(body.value("script" ).toObject().value("name" ).toString()).fileName(), | 
| 306 |              QLatin1String(ONCOMPLETED_QMLFILE)); | 
| 307 | } | 
| 308 |  | 
| 309 | void tst_QQmlDebugJS::setBreakpointInScriptOnTimerCallback() | 
| 310 | { | 
| 311 |     QFETCH(bool, qmlscene); | 
| 312 |  | 
| 313 |     int sourceLine = 35; | 
| 314 |     QCOMPARE(init(qmlscene, TIMER_QMLFILE), ConnectSuccess); | 
| 315 |  | 
| 316 |     m_client->connect(); | 
| 317 |     //We can set the breakpoint after connect() here because the timer is repeating and if we miss | 
| 318 |     //its first iteration we can still catch the second one. | 
| 319 |     m_client->setBreakpoint(target: QLatin1String(TIMER_QMLFILE), line: sourceLine, column: -1, enabled: true); | 
| 320 |     QVERIFY(waitForClientSignal(SIGNAL(stopped()))); | 
| 321 |  | 
| 322 |     const QJsonObject body = m_client->response().body.toObject(); | 
| 323 |  | 
| 324 |     QCOMPARE(body.value("sourceLine" ).toInt(), sourceLine); | 
| 325 |     QCOMPARE(QFileInfo(body.value("script" ).toObject().value("name" ).toString()).fileName(), | 
| 326 |              QLatin1String(TIMER_QMLFILE)); | 
| 327 | } | 
| 328 |  | 
| 329 | void tst_QQmlDebugJS::setBreakpointInScriptInDifferentFile() | 
| 330 | { | 
| 331 |     //void setBreakpoint(QString type, QString target, int line = -1, int column = -1, bool enabled = false, QString condition = QString(), int ignoreCount = -1) | 
| 332 |     QFETCH(bool, qmlscene); | 
| 333 |  | 
| 334 |     int sourceLine = 31; | 
| 335 |     QCOMPARE(init(qmlscene, LOADJSFILE_QMLFILE), ConnectSuccess); | 
| 336 |  | 
| 337 |     m_client->setBreakpoint(target: QLatin1String(TEST_JSFILE), line: sourceLine, column: -1, enabled: true); | 
| 338 |     m_client->connect(); | 
| 339 |     QVERIFY(waitForClientSignal(SIGNAL(stopped()))); | 
| 340 |  | 
| 341 |     const QJsonObject body = m_client->response().body.toObject(); | 
| 342 |  | 
| 343 |     QCOMPARE(body.value("sourceLine" ).toInt(), sourceLine); | 
| 344 |     QCOMPARE(QFileInfo(body.value("script" ).toObject().value("name" ).toString()).fileName(), | 
| 345 |              QLatin1String(TEST_JSFILE)); | 
| 346 | } | 
| 347 |  | 
| 348 | void tst_QQmlDebugJS::() | 
| 349 | { | 
| 350 |     //void setBreakpoint(QString type, QString target, int line = -1, int column = -1, bool enabled = false, QString condition = QString(), int ignoreCount = -1) | 
| 351 |     QFETCH(bool, qmlscene); | 
| 352 |  | 
| 353 |     int sourceLine = 34; | 
| 354 |     int actualLine = 36; | 
| 355 |     QCOMPARE(init(qmlscene, BREAKPOINTRELOCATION_QMLFILE), ConnectSuccess); | 
| 356 |  | 
| 357 |     m_client->setBreakpoint(target: QLatin1String(BREAKPOINTRELOCATION_QMLFILE), line: sourceLine, column: -1, enabled: true); | 
| 358 |     m_client->connect(); | 
| 359 |     QEXPECT_FAIL("" , "Relocation of breakpoints is disabled right now" , Abort); | 
| 360 |     QVERIFY(waitForClientSignal(SIGNAL(stopped()), 1)); | 
| 361 |  | 
| 362 |     const QJsonObject body = m_client->response().body.toObject(); | 
| 363 |  | 
| 364 |     QCOMPARE(body.value("sourceLine" ).toInt(), actualLine); | 
| 365 |     QCOMPARE(QFileInfo(body.value("script" ).toObject().value("name" ).toString()).fileName(), | 
| 366 |              QLatin1String(BREAKPOINTRELOCATION_QMLFILE)); | 
| 367 | } | 
| 368 |  | 
| 369 | void tst_QQmlDebugJS::setBreakpointInScriptOnEmptyLine() | 
| 370 | { | 
| 371 |     //void setBreakpoint(QString type, QString target, int line = -1, int column = -1, bool enabled = false, QString condition = QString(), int ignoreCount = -1) | 
| 372 |     QFETCH(bool, qmlscene); | 
| 373 |  | 
| 374 |     int sourceLine = 35; | 
| 375 |     int actualLine = 36; | 
| 376 |     QCOMPARE(init(qmlscene, BREAKPOINTRELOCATION_QMLFILE), ConnectSuccess); | 
| 377 |  | 
| 378 |     m_client->setBreakpoint(target: QLatin1String(BREAKPOINTRELOCATION_QMLFILE), line: sourceLine, column: -1, enabled: true); | 
| 379 |     m_client->connect(); | 
| 380 |     QEXPECT_FAIL("" , "Relocation of breakpoints is disabled right now" , Abort); | 
| 381 |     QVERIFY(waitForClientSignal(SIGNAL(stopped()), 1)); | 
| 382 |  | 
| 383 |     const QJsonObject body = m_client->response().body.toObject(); | 
| 384 |  | 
| 385 |     QCOMPARE(body.value("sourceLine" ).toInt(), actualLine); | 
| 386 |     QCOMPARE(QFileInfo(body.value("script" ).toObject().value("name" ).toString()).fileName(), | 
| 387 |              QLatin1String(BREAKPOINTRELOCATION_QMLFILE)); | 
| 388 | } | 
| 389 |  | 
| 390 | void tst_QQmlDebugJS::setBreakpointInScriptOnOptimizedBinding() | 
| 391 | { | 
| 392 |     //void setBreakpoint(QString type, QString target, int line = -1, int column = -1, bool enabled = false, QString condition = QString(), int ignoreCount = -1) | 
| 393 |     QFETCH(bool, qmlscene); | 
| 394 |  | 
| 395 |     int sourceLine = 39; | 
| 396 |     QCOMPARE(init(qmlscene, BREAKPOINTRELOCATION_QMLFILE), ConnectSuccess); | 
| 397 |  | 
| 398 |     m_client->setBreakpoint(target: QLatin1String(BREAKPOINTRELOCATION_QMLFILE), line: sourceLine, column: -1, enabled: true); | 
| 399 |     m_client->connect(); | 
| 400 |     QVERIFY(waitForClientSignal(SIGNAL(stopped()))); | 
| 401 |  | 
| 402 |     const QJsonObject body = m_client->response().body.toObject(); | 
| 403 |  | 
| 404 |     QCOMPARE(body.value("sourceLine" ).toInt(), sourceLine); | 
| 405 |     QCOMPARE(QFileInfo(body.value("script" ).toObject().value("name" ).toString()).fileName(), | 
| 406 |              QLatin1String(BREAKPOINTRELOCATION_QMLFILE)); | 
| 407 | } | 
| 408 |  | 
| 409 | void tst_QQmlDebugJS::setBreakpointInScriptWithCondition() | 
| 410 | { | 
| 411 |     QFETCH(bool, qmlscene); | 
| 412 |  | 
| 413 |     int out = 10; | 
| 414 |     int sourceLine = 37; | 
| 415 |     QCOMPARE(init(qmlscene, CONDITION_QMLFILE), ConnectSuccess); | 
| 416 |  | 
| 417 |     m_client->connect(); | 
| 418 |     //The breakpoint is in a timer loop so we can set it after connect(). | 
| 419 |     m_client->setBreakpoint(target: QLatin1String(CONDITION_QMLFILE), line: sourceLine, column: 1, enabled: true, condition: QLatin1String("a > 10" )); | 
| 420 |     QVERIFY(waitForClientSignal(SIGNAL(stopped()))); | 
| 421 |  | 
| 422 |     //Get the frame index | 
| 423 |     { | 
| 424 |         const QJsonObject body = m_client->response().body.toObject(); | 
| 425 |         int frameIndex = body.value(key: "index" ).toInt(); | 
| 426 |  | 
| 427 |         //Verify the value of 'result' | 
| 428 |         m_client->evaluate(expr: QLatin1String("a" ),frame: frameIndex); | 
| 429 |         QVERIFY(waitForClientSignal(SIGNAL(result()))); | 
| 430 |     } | 
| 431 |  | 
| 432 |     const QJsonObject body = m_client->response().body.toObject(); | 
| 433 |     QVERIFY(!body.isEmpty()); | 
| 434 |     QJsonValue val = body.value(key: "value" ); | 
| 435 |     QVERIFY(val.isDouble()); | 
| 436 |  | 
| 437 |     const int a = val.toInt(); | 
| 438 |     QVERIFY(a > out); | 
| 439 | } | 
| 440 |  | 
| 441 | void tst_QQmlDebugJS::setBreakpointInScriptThatQuits() | 
| 442 | { | 
| 443 |     QFETCH(bool, qmlscene); | 
| 444 |  | 
| 445 |     QCOMPARE(init(qmlscene, QUIT_QMLFILE), ConnectSuccess); | 
| 446 |  | 
| 447 |     int sourceLine = 36; | 
| 448 |  | 
| 449 |     m_client->setBreakpoint(target: QLatin1String(QUIT_QMLFILE), line: sourceLine, column: -1, enabled: true); | 
| 450 |     m_client->connect(); | 
| 451 |     QVERIFY(waitForClientSignal(SIGNAL(stopped()))); | 
| 452 |  | 
| 453 |     const QJsonObject body = m_client->response().body.toObject(); | 
| 454 |  | 
| 455 |     QCOMPARE(body.value("sourceLine" ).toInt(), sourceLine); | 
| 456 |     QCOMPARE(QFileInfo(body.value("script" ).toObject().value("name" ).toString()).fileName(), QLatin1String(QUIT_QMLFILE)); | 
| 457 |  | 
| 458 |     m_client->continueDebugging(stepAction: QV4DebugClient::Continue); | 
| 459 |     QVERIFY(m_process->waitForFinished()); | 
| 460 |     QCOMPARE(m_process->exitStatus(), QProcess::NormalExit); | 
| 461 | } | 
| 462 |  | 
| 463 | void tst_QQmlDebugJS::setBreakpointInJavaScript_data() | 
| 464 | { | 
| 465 |     QTest::addColumn<bool>(name: "qmlscene" ); | 
| 466 |     QTest::addColumn<bool>(name: "seedCache" ); | 
| 467 |     QTest::newRow(dataTag: "custom / immediate" ) << false << false; | 
| 468 |     QTest::newRow(dataTag: "qmlscene / immediate" ) << true << false; | 
| 469 |     QTest::newRow(dataTag: "custom / seeded" ) << false << true; | 
| 470 |     QTest::newRow(dataTag: "qmlscene / seeded" ) << true << true; | 
| 471 | } | 
| 472 |  | 
| 473 | void tst_QQmlDebugJS::setBreakpointInJavaScript() | 
| 474 | { | 
| 475 |     QFETCH(bool, qmlscene); | 
| 476 |     QFETCH(bool, seedCache); | 
| 477 |  | 
| 478 |     if (seedCache) { // Make sure there is a qmlc file that the engine should _not_ laod. | 
| 479 |         QProcess process; | 
| 480 |         process.start(program: QLibraryInfo::location(QLibraryInfo::BinariesPath) + "/qmlscene" , | 
| 481 |                       arguments: { testFile(fileName: QUITINJS_QMLFILE) }); | 
| 482 |         QTRY_COMPARE(process.state(), QProcess::NotRunning); | 
| 483 |     } | 
| 484 |  | 
| 485 |     QCOMPARE(init(qmlscene, QUITINJS_QMLFILE), ConnectSuccess); | 
| 486 |  | 
| 487 |     const int sourceLine = 2; | 
| 488 |  | 
| 489 |     m_client->setBreakpoint(target: QLatin1String(QUIT_JSFILE), line: sourceLine, column: -1, enabled: true); | 
| 490 |     m_client->connect(); | 
| 491 |     QVERIFY(waitForClientSignal(SIGNAL(stopped()))); | 
| 492 |  | 
| 493 |     const QJsonObject body = m_client->response().body.toObject(); | 
| 494 |  | 
| 495 |     QCOMPARE(body.value("sourceLine" ).toInt(), sourceLine); | 
| 496 |     QCOMPARE(QFileInfo(body.value("script" ).toObject().value("name" ).toString()).fileName(), | 
| 497 |              QLatin1String(QUIT_JSFILE)); | 
| 498 |  | 
| 499 |     m_client->continueDebugging(stepAction: QV4DebugClient::Continue); | 
| 500 |  | 
| 501 |     QVERIFY(m_process->waitForFinished()); | 
| 502 |     QCOMPARE(m_process->exitStatus(), QProcess::NormalExit); | 
| 503 | } | 
| 504 |  | 
| 505 | void tst_QQmlDebugJS::setBreakpointWhenAttaching() | 
| 506 | { | 
| 507 |     int sourceLine = 35; | 
| 508 |     QCOMPARE(init(true, QLatin1String(TIMER_QMLFILE), false), ConnectSuccess); | 
| 509 |  | 
| 510 |     m_client->connect(); | 
| 511 |  | 
| 512 |     QSKIP("\nThe breakpoint may not hit because the engine may run in JIT mode or not have debug\n"  | 
| 513 |           "instructions, as we've connected in non-blocking mode above. That means we may have\n"  | 
| 514 |           "connected after the engine was already running, with all the QML already compiled." ); | 
| 515 |  | 
| 516 |     //The breakpoint is in a timer loop so we can set it after connect(). | 
| 517 |     m_client->setBreakpoint(target: QLatin1String(TIMER_QMLFILE), line: sourceLine); | 
| 518 |  | 
| 519 |     QVERIFY(waitForClientSignal(SIGNAL(stopped()))); | 
| 520 | } | 
| 521 |  | 
| 522 | void tst_QQmlDebugJS::clearBreakpoint() | 
| 523 | { | 
| 524 |     //void clearBreakpoint(int breakpoint); | 
| 525 |     QFETCH(bool, qmlscene); | 
| 526 |  | 
| 527 |     int sourceLine1 = 37; | 
| 528 |     int sourceLine2 = 38; | 
| 529 |     QCOMPARE(init(qmlscene, CHANGEBREAKPOINT_QMLFILE), ConnectSuccess); | 
| 530 |  | 
| 531 |     m_client->connect(); | 
| 532 |     //The breakpoints are in a timer loop so we can set them after connect(). | 
| 533 |     //Furthermore the breakpoints should be hit in the right order because setting of breakpoints | 
| 534 |     //can only occur in the QML event loop. (see QCOMPARE for sourceLine2 below) | 
| 535 |     m_client->setBreakpoint(target: QLatin1String(CHANGEBREAKPOINT_QMLFILE), line: sourceLine1, column: -1, enabled: true); | 
| 536 |     m_client->setBreakpoint(target: QLatin1String(CHANGEBREAKPOINT_QMLFILE), line: sourceLine2, column: -1, enabled: true); | 
| 537 |  | 
| 538 |     QVERIFY(waitForClientSignal(SIGNAL(stopped()))); | 
| 539 |  | 
| 540 |     { | 
| 541 |         //Will hit 1st brakpoint, change this breakpoint enable = false | 
| 542 |         const QJsonObject body = m_client->response().body.toObject(); | 
| 543 |         const QJsonArray breakpointsHit = body.value(key: "breakpoints" ).toArray(); | 
| 544 |  | 
| 545 |         int breakpoint = breakpointsHit.at(i: 0).toInt(); | 
| 546 |         m_client->clearBreakpoint(breakpoint); | 
| 547 |  | 
| 548 |         QVERIFY(waitForClientSignal(SIGNAL(result()))); | 
| 549 |  | 
| 550 |         //Continue with debugging | 
| 551 |         m_client->continueDebugging(stepAction: QV4DebugClient::Continue); | 
| 552 |         //Hit 2nd breakpoint | 
| 553 |         QVERIFY(waitForClientSignal(SIGNAL(stopped()))); | 
| 554 |  | 
| 555 |         //Continue with debugging | 
| 556 |         m_client->continueDebugging(stepAction: QV4DebugClient::Continue); | 
| 557 |     } | 
| 558 |  | 
| 559 |     //Should stop at 2nd breakpoint | 
| 560 |     QVERIFY(waitForClientSignal(SIGNAL(stopped()))); | 
| 561 |  | 
| 562 |     { | 
| 563 |         const QJsonObject body = m_client->response().body.toObject(); | 
| 564 |         QCOMPARE(body.value("sourceLine" ).toInt(), sourceLine2); | 
| 565 |     } | 
| 566 | } | 
| 567 |  | 
| 568 | void tst_QQmlDebugJS::changeBreakpoint() | 
| 569 | { | 
| 570 |     //void clearBreakpoint(int breakpoint); | 
| 571 |     QFETCH(bool, qmlscene); | 
| 572 |  | 
| 573 |     int sourceLine2 = 37; | 
| 574 |     int sourceLine1 = 38; | 
| 575 |     const QString file = QLatin1String(CHANGEBREAKPOINT_QMLFILE); | 
| 576 |     QCOMPARE(init(qmlscene, file), ConnectSuccess); | 
| 577 |  | 
| 578 |     bool isStopped = false; | 
| 579 |     QObject::connect(sender: m_client.data(), signal: &QV4DebugClient::stopped, context: this, slot: [&]() { isStopped = true; }); | 
| 580 |  | 
| 581 |     auto continueDebugging = [&]() { | 
| 582 |         m_client->continueDebugging(stepAction: QV4DebugClient::Continue); | 
| 583 |         isStopped = false; | 
| 584 |     }; | 
| 585 |  | 
| 586 |     m_client->connect(); | 
| 587 |  | 
| 588 |     auto extractBody = [&]() { | 
| 589 |         return m_client->response().body.toObject(); | 
| 590 |     }; | 
| 591 |  | 
| 592 |     auto  = [&](const QJsonObject &body) { | 
| 593 |         const QJsonArray breakpointsHit = body.value(key: "breakpoints" ).toArray(); | 
| 594 |         if (breakpointsHit.size() != 1) | 
| 595 |             return -1; | 
| 596 |         return breakpointsHit[0].toInt(); | 
| 597 |     }; | 
| 598 |  | 
| 599 |     //The breakpoints are in a timer loop so we can set them after connect(). | 
| 600 |     //Furthermore the breakpoints should be hit in the right order because setting of breakpoints | 
| 601 |     //can only occur in the QML event loop. (see QCOMPARE for sourceLine2 below) | 
| 602 |     const int breakpoint1 = setBreakPoint(file, sourceLine: sourceLine1, enabled: false); | 
| 603 |     QVERIFY(breakpoint1 >= 0); | 
| 604 |  | 
| 605 |     const int breakpoint2 = setBreakPoint(file, sourceLine: sourceLine2, enabled: true); | 
| 606 |     QVERIFY(breakpoint2 >= 0); | 
| 607 |  | 
| 608 |     auto verifyBreakpoint = [&](int sourceLine, int breakpointId) { | 
| 609 |         QTRY_VERIFY_WITH_TIMEOUT(isStopped, 30000); | 
| 610 |         const QJsonObject body = extractBody(); | 
| 611 |         QCOMPARE(body.value("sourceLine" ).toInt(), sourceLine); | 
| 612 |         QCOMPARE(extractBreakPointId(body), breakpointId); | 
| 613 |     }; | 
| 614 |  | 
| 615 |     verifyBreakpoint(sourceLine2, breakpoint2); | 
| 616 |  | 
| 617 |     continueDebugging(); | 
| 618 |     verifyBreakpoint(sourceLine2, breakpoint2); | 
| 619 |  | 
| 620 |     m_client->changeBreakpoint(breakpoint: breakpoint2, enabled: false); | 
| 621 |     QVERIFY(waitForClientSignal(SIGNAL(result()))); | 
| 622 |  | 
| 623 |     m_client->changeBreakpoint(breakpoint: breakpoint1, enabled: true); | 
| 624 |     QVERIFY(waitForClientSignal(SIGNAL(result()))); | 
| 625 |  | 
| 626 |     continueDebugging(); | 
| 627 |     verifyBreakpoint(sourceLine1, breakpoint1); | 
| 628 |  | 
| 629 |     continueDebugging(); | 
| 630 |     verifyBreakpoint(sourceLine1, breakpoint1); | 
| 631 |  | 
| 632 |     m_client->changeBreakpoint(breakpoint: breakpoint2, enabled: true); | 
| 633 |     QVERIFY(waitForClientSignal(SIGNAL(result()))); | 
| 634 |  | 
| 635 |     m_client->changeBreakpoint(breakpoint: breakpoint1, enabled: false); | 
| 636 |     QVERIFY(waitForClientSignal(SIGNAL(result()))); | 
| 637 |  | 
| 638 |     for (int i = 0; i < 3; ++i) { | 
| 639 |         continueDebugging(); | 
| 640 |         verifyBreakpoint(sourceLine2, breakpoint2); | 
| 641 |     } | 
| 642 | } | 
| 643 |  | 
| 644 | void tst_QQmlDebugJS::setExceptionBreak() | 
| 645 | { | 
| 646 |     //void setExceptionBreak(QString type, bool enabled = false); | 
| 647 |     QFETCH(bool, qmlscene); | 
| 648 |  | 
| 649 |     QCOMPARE(init(qmlscene, EXCEPTION_QMLFILE), ConnectSuccess); | 
| 650 |     m_client->setExceptionBreak(type: QV4DebugClient::All,enabled: true); | 
| 651 |     m_client->connect(); | 
| 652 |     QVERIFY(waitForClientSignal(SIGNAL(stopped()))); | 
| 653 | } | 
| 654 |  | 
| 655 | void tst_QQmlDebugJS::stepNext() | 
| 656 | { | 
| 657 |     //void continueDebugging(StepAction stepAction, int stepCount = 1); | 
| 658 |     QFETCH(bool, qmlscene); | 
| 659 |  | 
| 660 |     int sourceLine = 37; | 
| 661 |     QCOMPARE(init(qmlscene, STEPACTION_QMLFILE), ConnectSuccess); | 
| 662 |  | 
| 663 |     m_client->setBreakpoint(target: QLatin1String(STEPACTION_QMLFILE), line: sourceLine, column: -1, enabled: true); | 
| 664 |     m_client->connect(); | 
| 665 |     QVERIFY(waitForClientSignal(SIGNAL(stopped()))); | 
| 666 |  | 
| 667 |     m_client->continueDebugging(stepAction: QV4DebugClient::Next); | 
| 668 |     QVERIFY(waitForClientSignal(SIGNAL(stopped()))); | 
| 669 |  | 
| 670 |     const QJsonObject body = m_client->response().body.toObject(); | 
| 671 |  | 
| 672 |     QCOMPARE(body.value("sourceLine" ).toInt(), sourceLine + 1); | 
| 673 |     QCOMPARE(QFileInfo(body.value("script" ).toObject().value("name" ).toString()).fileName(), | 
| 674 |              QLatin1String(STEPACTION_QMLFILE)); | 
| 675 | } | 
| 676 |  | 
| 677 | static QJsonObject responseBody(QV4DebugClient *client) | 
| 678 | { | 
| 679 |     return client->response().body.toObject(); | 
| 680 | } | 
| 681 |  | 
| 682 | void tst_QQmlDebugJS::stepIn() | 
| 683 | { | 
| 684 |     //void continueDebugging(StepAction stepAction, int stepCount = 1); | 
| 685 |     QFETCH(bool, qmlscene); | 
| 686 |  | 
| 687 |     int sourceLine = 41; | 
| 688 |     int actualLine = 36; | 
| 689 |     QCOMPARE(init(qmlscene, STEPACTION_QMLFILE), ConnectSuccess); | 
| 690 |  | 
| 691 |     m_client->setBreakpoint(target: QLatin1String(STEPACTION_QMLFILE), line: sourceLine, column: 1, enabled: true); | 
| 692 |     m_client->connect(); | 
| 693 |     QVERIFY(waitForClientSignal(SIGNAL(stopped()))); | 
| 694 |     QCOMPARE(responseBody(m_client).value("sourceLine" ).toInt(), sourceLine); | 
| 695 |  | 
| 696 |     m_client->continueDebugging(stepAction: QV4DebugClient::In); | 
| 697 |     QVERIFY(waitForClientSignal(SIGNAL(stopped()))); | 
| 698 |  | 
| 699 |     const QJsonObject body = responseBody(client: m_client); | 
| 700 |     QCOMPARE(body.value("sourceLine" ).toInt(), actualLine); | 
| 701 |     QCOMPARE(QFileInfo(body.value("script" ).toObject().value("name" ).toString()).fileName(), QLatin1String(STEPACTION_QMLFILE)); | 
| 702 | } | 
| 703 |  | 
| 704 | void tst_QQmlDebugJS::stepOut() | 
| 705 | { | 
| 706 |     //void continueDebugging(StepAction stepAction, int stepCount = 1); | 
| 707 |     QFETCH(bool, qmlscene); | 
| 708 |  | 
| 709 |     int sourceLine = 37; | 
| 710 |     int actualLine = 41; | 
| 711 |     QCOMPARE(init(qmlscene, STEPACTION_QMLFILE), ConnectSuccess); | 
| 712 |  | 
| 713 |     m_client->setBreakpoint(target: QLatin1String(STEPACTION_QMLFILE), line: sourceLine, column: -1, enabled: true); | 
| 714 |     m_client->connect(); | 
| 715 |     QVERIFY(waitForClientSignal(SIGNAL(stopped()))); | 
| 716 |     QCOMPARE(responseBody(m_client).value("sourceLine" ).toInt(), sourceLine); | 
| 717 |  | 
| 718 |     m_client->continueDebugging(stepAction: QV4DebugClient::Out); | 
| 719 |     QVERIFY(waitForClientSignal(SIGNAL(stopped()))); | 
| 720 |  | 
| 721 |     const QJsonObject body = responseBody(client: m_client); | 
| 722 |     QCOMPARE(body.value("sourceLine" ).toInt(), actualLine); | 
| 723 |     QCOMPARE(QFileInfo(body.value("script" ).toObject().value("name" ).toString()).fileName(), QLatin1String(STEPACTION_QMLFILE)); | 
| 724 | } | 
| 725 |  | 
| 726 | void tst_QQmlDebugJS::continueDebugging() | 
| 727 | { | 
| 728 |     //void continueDebugging(StepAction stepAction, int stepCount = 1); | 
| 729 |     QFETCH(bool, qmlscene); | 
| 730 |  | 
| 731 |     int sourceLine1 = 41; | 
| 732 |     int sourceLine2 = 38; | 
| 733 |     QCOMPARE(init(qmlscene, STEPACTION_QMLFILE), ConnectSuccess); | 
| 734 |  | 
| 735 |     m_client->setBreakpoint(target: QLatin1String(STEPACTION_QMLFILE), line: sourceLine1, column: -1, enabled: true); | 
| 736 |     m_client->setBreakpoint(target: QLatin1String(STEPACTION_QMLFILE), line: sourceLine2, column: -1, enabled: true); | 
| 737 |     m_client->connect(); | 
| 738 |     QVERIFY(waitForClientSignal(SIGNAL(stopped()))); | 
| 739 |  | 
| 740 |     m_client->continueDebugging(stepAction: QV4DebugClient::Continue); | 
| 741 |     QVERIFY(waitForClientSignal(SIGNAL(stopped()))); | 
| 742 |  | 
| 743 |     const QJsonObject body = responseBody(client: m_client); | 
| 744 |  | 
| 745 |     QCOMPARE(body.value("sourceLine" ).toInt(), sourceLine2); | 
| 746 |     QCOMPARE(QFileInfo(body.value("script" ).toObject().value("name" ).toString()).fileName(), | 
| 747 |              QLatin1String(STEPACTION_QMLFILE)); | 
| 748 | } | 
| 749 |  | 
| 750 | void tst_QQmlDebugJS::backtrace() | 
| 751 | { | 
| 752 |     //void backtrace(int fromFrame = -1, int toFrame = -1, bool bottom = false); | 
| 753 |     QFETCH(bool, qmlscene); | 
| 754 |  | 
| 755 |     int sourceLine = 34; | 
| 756 |     QCOMPARE(init(qmlscene, ONCOMPLETED_QMLFILE), ConnectSuccess); | 
| 757 |  | 
| 758 |     m_client->setBreakpoint(target: QLatin1String(ONCOMPLETED_QMLFILE), line: sourceLine, column: -1, enabled: true); | 
| 759 |     m_client->connect(); | 
| 760 |     QVERIFY(waitForClientSignal(SIGNAL(stopped()))); | 
| 761 |  | 
| 762 |     m_client->backtrace(); | 
| 763 |     QVERIFY(waitForClientSignal(SIGNAL(result()))); | 
| 764 | } | 
| 765 |  | 
| 766 | void tst_QQmlDebugJS::getFrameDetails() | 
| 767 | { | 
| 768 |     //void frame(int number = -1); | 
| 769 |     QFETCH(bool, qmlscene); | 
| 770 |  | 
| 771 |     int sourceLine = 34; | 
| 772 |     QCOMPARE(init(qmlscene, ONCOMPLETED_QMLFILE), ConnectSuccess); | 
| 773 |  | 
| 774 |     m_client->setBreakpoint(target: QLatin1String(ONCOMPLETED_QMLFILE), line: sourceLine, column: -1, enabled: true); | 
| 775 |     m_client->connect(); | 
| 776 |     QVERIFY(waitForClientSignal(SIGNAL(stopped()))); | 
| 777 |  | 
| 778 |     m_client->frame(); | 
| 779 |     QVERIFY(waitForClientSignal(SIGNAL(result()))); | 
| 780 | } | 
| 781 |  | 
| 782 | void tst_QQmlDebugJS::getScopeDetails() | 
| 783 | { | 
| 784 |     //void scope(int number = -1, int frameNumber = -1); | 
| 785 |     QFETCH(bool, qmlscene); | 
| 786 |  | 
| 787 |     int sourceLine = 34; | 
| 788 |     QCOMPARE(init(qmlscene, ONCOMPLETED_QMLFILE), ConnectSuccess); | 
| 789 |  | 
| 790 |     m_client->setBreakpoint(target: QLatin1String(ONCOMPLETED_QMLFILE), line: sourceLine, column: -1, enabled: true); | 
| 791 |     m_client->connect(); | 
| 792 |     QVERIFY(waitForClientSignal(SIGNAL(stopped()))); | 
| 793 |  | 
| 794 |     m_client->scope(); | 
| 795 |     QVERIFY(waitForClientSignal(SIGNAL(result()))); | 
| 796 | } | 
| 797 |  | 
| 798 | void tst_QQmlDebugJS::evaluateInGlobalScope() | 
| 799 | { | 
| 800 |     //void evaluate(QString expr, int frame = -1); | 
| 801 |     QCOMPARE(init(true), ConnectSuccess); | 
| 802 |  | 
| 803 |     m_client->connect(); | 
| 804 |  | 
| 805 |     for (int i = 0; i < 10; ++i) { | 
| 806 |         // The engine might not be initialized, yet. We just try until it shows up. | 
| 807 |         m_client->evaluate(expr: QLatin1String("console.log('Hello World')" )); | 
| 808 |         if (waitForClientSignal(SIGNAL(result()), timeout: 500)) | 
| 809 |             break; | 
| 810 |     } | 
| 811 |  | 
| 812 |     //Verify the return value of 'console.log()', which is "undefined" | 
| 813 |     QCOMPARE(responseBody(m_client).value("type" ).toString(), QLatin1String("undefined" )); | 
| 814 | } | 
| 815 |  | 
| 816 | void tst_QQmlDebugJS::evaluateInLocalScope() | 
| 817 | { | 
| 818 |     //void evaluate(QString expr, bool global = false, bool disableBreak = false, int frame = -1, const QVariantMap &addContext = QVariantMap()); | 
| 819 |     QFETCH(bool, qmlscene); | 
| 820 |  | 
| 821 |     int sourceLine = 34; | 
| 822 |     QCOMPARE(init(qmlscene, ONCOMPLETED_QMLFILE), ConnectSuccess); | 
| 823 |  | 
| 824 |     m_client->setBreakpoint(target: QLatin1String(ONCOMPLETED_QMLFILE), line: sourceLine, column: -1, enabled: true); | 
| 825 |     m_client->connect(); | 
| 826 |     QVERIFY(waitForClientSignal(SIGNAL(stopped()))); | 
| 827 |  | 
| 828 |     m_client->frame(); | 
| 829 |     QVERIFY(waitForClientSignal(SIGNAL(result()))); | 
| 830 |  | 
| 831 |     { | 
| 832 |         //Get the frame index | 
| 833 |         const QJsonObject body = responseBody(client: m_client); | 
| 834 |         int frameIndex = body.value(key: "index" ).toInt(); | 
| 835 |         m_client->evaluate(expr: QLatin1String("root.a" ), frame: frameIndex); | 
| 836 |         QVERIFY(waitForClientSignal(SIGNAL(result()))); | 
| 837 |     } | 
| 838 |  | 
| 839 |     { | 
| 840 |         //Verify the value of 'timer.interval' | 
| 841 |         const QJsonObject body = responseBody(client: m_client); | 
| 842 |         QCOMPARE(body.value("value" ).toInt(),10); | 
| 843 |     } | 
| 844 | } | 
| 845 |  | 
| 846 | void tst_QQmlDebugJS::evaluateInContext() | 
| 847 | { | 
| 848 |     m_connection = new QQmlDebugConnection(); | 
| 849 |     m_process = new QQmlDebugProcess( | 
| 850 |                 QLibraryInfo::location(QLibraryInfo::BinariesPath) + "/qmlscene" , this); | 
| 851 |     m_client = new QV4DebugClient(m_connection); | 
| 852 |     QScopedPointer<QQmlEngineDebugClient> engineClient(new QQmlEngineDebugClient(m_connection)); | 
| 853 |     m_process->start(arguments: QStringList() << QLatin1String(BLOCKMODE) << testFile(fileName: ONCOMPLETED_QMLFILE)); | 
| 854 |  | 
| 855 |     QVERIFY(m_process->waitForSessionStart()); | 
| 856 |  | 
| 857 |     m_connection->connectToHost(hostName: "127.0.0.1" , port: m_process->debugPort()); | 
| 858 |     QVERIFY(m_connection->waitForConnected()); | 
| 859 |  | 
| 860 |     QTRY_COMPARE(m_client->state(), QQmlEngineDebugClient::Enabled); | 
| 861 |     QTRY_COMPARE(engineClient->state(), QQmlEngineDebugClient::Enabled); | 
| 862 |     m_client->connect(); | 
| 863 |  | 
| 864 |     // "a" not accessible without extra context | 
| 865 |     m_client->evaluate(expr: QLatin1String("a + 10" ), frame: -1, context: -1); | 
| 866 |     QVERIFY(waitForClientSignal(SIGNAL(failure()))); | 
| 867 |  | 
| 868 |     bool success = false; | 
| 869 |     engineClient->queryAvailableEngines(success: &success); | 
| 870 |     QVERIFY(success); | 
| 871 |     QVERIFY(QQmlDebugTest::waitForSignal(engineClient.data(), SIGNAL(result()))); | 
| 872 |  | 
| 873 |     QVERIFY(engineClient->engines().count()); | 
| 874 |     engineClient->queryRootContexts(engineClient->engines()[0], success: &success); | 
| 875 |     QVERIFY(success); | 
| 876 |     QVERIFY(QQmlDebugTest::waitForSignal(engineClient.data(), SIGNAL(result()))); | 
| 877 |  | 
| 878 |     auto contexts = engineClient->rootContext().contexts; | 
| 879 |     QCOMPARE(contexts.count(), 1); | 
| 880 |     auto objects = contexts[0].objects; | 
| 881 |     QCOMPARE(objects.count(), 1); | 
| 882 |     engineClient->queryObjectRecursive(objects[0], success: &success); | 
| 883 |     QVERIFY(success); | 
| 884 |     QVERIFY(QQmlDebugTest::waitForSignal(engineClient.data(), SIGNAL(result()))); | 
| 885 |     auto object = engineClient->object(); | 
| 886 |  | 
| 887 |     // "a" accessible in context of surrounding object | 
| 888 |     m_client->evaluate(expr: QLatin1String("a + 10" ), frame: -1, context: object.debugId); | 
| 889 |     QVERIFY(waitForClientSignal(SIGNAL(result()))); | 
| 890 |  | 
| 891 |     QTRY_COMPARE(responseBody(m_client).value("value" ).toInt(), 20); | 
| 892 |  | 
| 893 |     auto childObjects = object.children; | 
| 894 |     QVERIFY(childObjects.count() > 0); // QQmlComponentAttached is also in there | 
| 895 |     QCOMPARE(childObjects[0].className, QString::fromLatin1("Item" )); | 
| 896 |  | 
| 897 |     // "b" accessible in context of surrounding (child) object | 
| 898 |     m_client->evaluate(expr: QLatin1String("b" ), frame: -1, context: childObjects[0].debugId); | 
| 899 |     QVERIFY(waitForClientSignal(SIGNAL(result()))); | 
| 900 |  | 
| 901 |     QTRY_COMPARE(responseBody(m_client).value("value" ).toInt(), 11); | 
| 902 | } | 
| 903 |  | 
| 904 | void tst_QQmlDebugJS::getScripts() | 
| 905 | { | 
| 906 |     //void scripts(int types = -1, QList<int> ids = QList<int>(), bool includeSource = false, QVariant filter = QVariant()); | 
| 907 |     QFETCH(bool, qmlscene); | 
| 908 |  | 
| 909 |     QCOMPARE(init(qmlscene), ConnectSuccess); | 
| 910 |  | 
| 911 |     m_client->setBreakpoint(target: QString(TEST_QMLFILE), line: 35, column: -1, enabled: true); | 
| 912 |     m_client->connect(); | 
| 913 |     QVERIFY(waitForClientSignal(SIGNAL(stopped()))); | 
| 914 |  | 
| 915 |     m_client->scripts(); | 
| 916 |     QVERIFY(waitForClientSignal(SIGNAL(result()))); | 
| 917 |  | 
| 918 |     const QJsonArray scripts = m_client->response().body.toArray(); | 
| 919 |  | 
| 920 |     QCOMPARE(scripts.count(), 1); | 
| 921 |     QVERIFY(scripts.first().toObject()[QStringLiteral("name" )].toString() | 
| 922 |             .endsWith(QStringLiteral("data/test.qml" ))); | 
| 923 | } | 
| 924 |  | 
| 925 | void tst_QQmlDebugJS::encodeQmlScope() | 
| 926 | { | 
| 927 |     QString file(ENCODEQMLSCOPE_QMLFILE); | 
| 928 |     QCOMPARE(init(true, file), ConnectSuccess); | 
| 929 |  | 
| 930 |     int numFrames = 0; | 
| 931 |     int numExpectedScopes = 0; | 
| 932 |     int numReceivedScopes = 0; | 
| 933 |     bool isStopped = false; | 
| 934 |     bool scopesFailed = false; | 
| 935 |  | 
| 936 |     QObject::connect(sender: m_client.data(), signal: &QV4DebugClient::failure, context: this, slot: [&]() { | 
| 937 |         qWarning() << "received failure"  << m_client->response().body; | 
| 938 |         scopesFailed = true; | 
| 939 |         m_process->stop(); | 
| 940 |         numFrames = 2; | 
| 941 |         isStopped = false; | 
| 942 |     }); | 
| 943 |  | 
| 944 |     QObject::connect(sender: m_client.data(), signal: &QV4DebugClient::stopped, context: this, slot: [&]() { | 
| 945 |         m_client->frame(); | 
| 946 |         isStopped = true; | 
| 947 |     }); | 
| 948 |  | 
| 949 |     QObject::connect(sender: m_client.data(), signal: &QV4DebugClient::result, context: this, slot: [&]() { | 
| 950 |         const QV4DebugClient::Response value = m_client->response(); | 
| 951 |  | 
| 952 |         if (value.command == QString("scope" )) { | 
| 953 |             // If the scope commands fail we get a failure() signal above. | 
| 954 |             if (++numReceivedScopes == numExpectedScopes) { | 
| 955 |                 m_client->continueDebugging(stepAction: QV4DebugClient::Continue); | 
| 956 |                 isStopped = false; | 
| 957 |             } | 
| 958 |         } else if (value.command == QString("frame" )) { | 
| 959 |  | 
| 960 |             // We want at least a global scope and some kind of local scope here. | 
| 961 |             const QJsonArray scopes = value.body.toObject().value(key: "scopes" ).toArray(); | 
| 962 |             if (scopes.count() < 2) | 
| 963 |                 scopesFailed = true; | 
| 964 |  | 
| 965 |             for (const QJsonValue &scope : scopes) { | 
| 966 |                 ++numExpectedScopes; | 
| 967 |                 m_client->scope(number: scope.toObject().value(key: "index" ).toInt()); | 
| 968 |             } | 
| 969 |  | 
| 970 |             ++numFrames; | 
| 971 |         } | 
| 972 |     }); | 
| 973 |  | 
| 974 |     m_client->setBreakpoint(target: file, line: 6); | 
| 975 |     m_client->setBreakpoint(target: file, line: 8); | 
| 976 |     m_client->connect(); | 
| 977 |  | 
| 978 |     QTRY_COMPARE(numFrames, 2); | 
| 979 |     QVERIFY(numExpectedScopes > 3); | 
| 980 |     QVERIFY(!scopesFailed); | 
| 981 |     QTRY_VERIFY(!isStopped); | 
| 982 |     QCOMPARE(numReceivedScopes, numExpectedScopes); | 
| 983 | } | 
| 984 |  | 
| 985 | void tst_QQmlDebugJS::breakOnAnchor() | 
| 986 | { | 
| 987 |     QString file(BREAKONANCHOR_QMLFILE); | 
| 988 |     QCOMPARE(init(true, file), ConnectSuccess); | 
| 989 |  | 
| 990 |     int breaks = 0; | 
| 991 |     bool stopped = false; | 
| 992 |     QObject::connect(sender: m_client.data(), signal: &QV4DebugClient::stopped, context: this, slot: [&]() { | 
| 993 |         stopped = true; | 
| 994 |         ++breaks; | 
| 995 |         m_client->evaluate(expr: "this" , frame: 0, context: -1); | 
| 996 |     }); | 
| 997 |  | 
| 998 |     QObject::connect(sender: m_client.data(), signal: &QV4DebugClient::result, context: this, slot: [&]() { | 
| 999 |         if (stopped) { | 
| 1000 |             m_client->continueDebugging(stepAction: QV4DebugClient::Continue); | 
| 1001 |             stopped = false; | 
| 1002 |         } | 
| 1003 |     }); | 
| 1004 |  | 
| 1005 |     QObject::connect(sender: m_client.data(), signal: &QV4DebugClient::failure, context: this, slot: [&]() { | 
| 1006 |         qWarning() << "received failure"  << m_client->response().body; | 
| 1007 |     }); | 
| 1008 |  | 
| 1009 |     m_client->setBreakpoint(target: file, line: 34); | 
| 1010 |     m_client->setBreakpoint(target: file, line: 37); | 
| 1011 |  | 
| 1012 |     QTRY_COMPARE(m_process->state(), QProcess::Running); | 
| 1013 |  | 
| 1014 |     m_client->connect(); | 
| 1015 |  | 
| 1016 |     QTRY_COMPARE(m_process->state(), QProcess::NotRunning); | 
| 1017 |     QCOMPARE(m_process->exitStatus(), QProcess::NormalExit); | 
| 1018 |  | 
| 1019 |     QCOMPARE(breaks, 2); | 
| 1020 | } | 
| 1021 |  | 
| 1022 | void tst_QQmlDebugJS::breakPointIds() | 
| 1023 | { | 
| 1024 |     QString file(BREAKPOINTIDS_QMLFILE); | 
| 1025 |     QCOMPARE(init(true, file), ConnectSuccess); | 
| 1026 |  | 
| 1027 |     int breaks = 0; | 
| 1028 |     int breakPointIds[] = { -1, -1, -1, -1, -1, -1}; | 
| 1029 |  | 
| 1030 |     QObject::connect(sender: m_client.data(), signal: &QV4DebugClient::stopped, context: this, slot: [&]() { | 
| 1031 |         const QJsonObject body = m_client->response().body.toObject(); | 
| 1032 |         QCOMPARE(body.value("sourceLine" ).toInt(), breaks + 4); | 
| 1033 |         const QJsonArray breakpointsHit = body.value(key: "breakpoints" ).toArray(); | 
| 1034 |         QVERIFY(breakpointsHit.size() > 0); | 
| 1035 |         QCOMPARE(breakpointsHit[0].toInt(), breakPointIds[breaks]); | 
| 1036 |         ++breaks; | 
| 1037 |         m_client->continueDebugging(stepAction: QV4DebugClient::Continue); | 
| 1038 |     }); | 
| 1039 |  | 
| 1040 |     for (int i = 0; i < 6; ++i) | 
| 1041 |         breakPointIds[i] = setBreakPoint(file, sourceLine: i + 4, enabled: true); | 
| 1042 |  | 
| 1043 |     clearBreakPoint(id: breakPointIds[2]); | 
| 1044 |     breakPointIds[2] = setBreakPoint(file, sourceLine: 6, enabled: true); | 
| 1045 |  | 
| 1046 |     QTRY_COMPARE(m_process->state(), QProcess::Running); | 
| 1047 |     m_client->connect(); | 
| 1048 |  | 
| 1049 |     QTRY_COMPARE(m_process->state(), QProcess::NotRunning); | 
| 1050 |     QCOMPARE(m_process->exitStatus(), QProcess::NormalExit); | 
| 1051 |  | 
| 1052 |     QCOMPARE(breaks, 6); | 
| 1053 | } | 
| 1054 |  | 
| 1055 | void tst_QQmlDebugJS::letConstLocals() | 
| 1056 | { | 
| 1057 |     QString file(LETCONSTLOCALS_QMLFILE); | 
| 1058 |     QCOMPARE(init(true, file), ConnectSuccess); | 
| 1059 |  | 
| 1060 |     QObject::connect(sender: m_client.data(), signal: &QV4DebugClient::stopped, context: this, slot: [&]() { | 
| 1061 |         m_client->frame(); | 
| 1062 |     }); | 
| 1063 |  | 
| 1064 |     int numScopes = 0; | 
| 1065 |     QString expectedMembers = QStringLiteral("abcde" ); | 
| 1066 |     QObject::connect(sender: m_client.data(), signal: &QV4DebugClient::result, context: this, slot: [&]() { | 
| 1067 |         const auto value = m_client->response(); | 
| 1068 |         if (value.command == QStringLiteral("frame" )) { | 
| 1069 |             const auto scopes = value.body.toObject().value(QStringLiteral("scopes" )).toArray(); | 
| 1070 |             for (const auto &scope : scopes) { | 
| 1071 |                 const auto scopeObject = scope.toObject(); | 
| 1072 |                 const int type = scopeObject.value(key: "type" ).toInt(); | 
| 1073 |                 if (type == 1 || type == 4) { | 
| 1074 |                     m_client->scope(number: scopeObject.value(key: "index" ).toInt()); | 
| 1075 |                     ++numScopes; | 
| 1076 |                 } | 
| 1077 |             } | 
| 1078 |             QVERIFY(numScopes > 0); | 
| 1079 |         } else if (value.command == QStringLiteral("scope" )) { | 
| 1080 |             const auto props = value.body.toObject().value(QStringLiteral("object" )).toObject() | 
| 1081 |                     .value(QStringLiteral("properties" )).toArray(); | 
| 1082 |             for (const auto &prop : props) { | 
| 1083 |                 const auto propObj = prop.toObject(); | 
| 1084 |                 const QString name = propObj.value(QStringLiteral("name" )).toString(); | 
| 1085 |                 if (name == QStringLiteral("onCompleted" )) | 
| 1086 |                     continue; | 
| 1087 |                 QVERIFY(name.length() == 1); | 
| 1088 |                 auto i = expectedMembers.indexOf(c: name.at(i: 0)); | 
| 1089 |                 QVERIFY(i != -1); | 
| 1090 |                 expectedMembers.remove(i, len: 1); | 
| 1091 |                 QCOMPARE(propObj.value(QStringLiteral("type" )).toString(), | 
| 1092 |                          QStringLiteral("number" )); | 
| 1093 |                 QCOMPARE(propObj.value(QStringLiteral("value" )).toInt(), | 
| 1094 |                          int(name.at(0).toLatin1())); | 
| 1095 |             } | 
| 1096 |             if (--numScopes == 0) { | 
| 1097 |                 QVERIFY(expectedMembers.isEmpty()); | 
| 1098 |                 m_client->continueDebugging(stepAction: QV4DebugClient::Continue); | 
| 1099 |             } | 
| 1100 |         } | 
| 1101 |     }); | 
| 1102 |  | 
| 1103 |     setBreakPoint(file, sourceLine: 10, enabled: true); | 
| 1104 |  | 
| 1105 |     QTRY_COMPARE(m_process->state(), QProcess::Running); | 
| 1106 |     m_client->connect(); | 
| 1107 |  | 
| 1108 |     QTRY_COMPARE(m_process->state(), QProcess::NotRunning); | 
| 1109 |     QCOMPARE(m_process->exitStatus(), QProcess::NormalExit); | 
| 1110 | } | 
| 1111 |  | 
| 1112 | QList<QQmlDebugClient *> tst_QQmlDebugJS::createClients() | 
| 1113 | { | 
| 1114 |     m_client = new QV4DebugClient(m_connection); | 
| 1115 |     return QList<QQmlDebugClient *>({m_client}); | 
| 1116 | } | 
| 1117 |  | 
| 1118 | void tst_QQmlDebugJS::targetData() | 
| 1119 | { | 
| 1120 |     QTest::addColumn<bool>(name: "qmlscene" ); | 
| 1121 |     QTest::newRow(dataTag: "custom" )   << false; | 
| 1122 |     QTest::newRow(dataTag: "qmlscene" ) << true; | 
| 1123 | } | 
| 1124 |  | 
| 1125 | bool tst_QQmlDebugJS::waitForClientSignal(const char *signal, int timeout) | 
| 1126 | { | 
| 1127 |     return QQmlDebugTest::waitForSignal(receiver: m_client.data(), member: signal, timeout); | 
| 1128 | } | 
| 1129 |  | 
| 1130 | void tst_QQmlDebugJS::checkVersionParameters() | 
| 1131 | { | 
| 1132 |     const QV4DebugClient::Response value = m_client->response(); | 
| 1133 |     QCOMPARE(value.command, QString("version" )); | 
| 1134 |     const QJsonObject body = value.body.toObject(); | 
| 1135 |     QCOMPARE(body.value("UnpausedEvaluate" ).toBool(), true); | 
| 1136 |     QCOMPARE(body.value("ContextEvaluate" ).toBool(), true); | 
| 1137 |     QCOMPARE(body.value("ChangeBreakpoint" ).toBool(), true); | 
| 1138 | } | 
| 1139 |  | 
| 1140 | int tst_QQmlDebugJS::setBreakPoint(const QString &file, int sourceLine, bool enabled) | 
| 1141 | { | 
| 1142 |     int id = -1; | 
| 1143 |     auto connection = QObject::connect(sender: m_client.data(), signal: &QV4DebugClient::result, slot: [&]() { | 
| 1144 |         id = m_client->response().body.toObject().value(key: "breakpoint" ).toInt(); | 
| 1145 |     }); | 
| 1146 |  | 
| 1147 |     m_client->setBreakpoint(target: file, line: sourceLine, column: -1, enabled); | 
| 1148 |     bool success = QTest::qWaitFor(predicate: [&]() { return id >= 0; }); | 
| 1149 |     Q_UNUSED(success); | 
| 1150 |  | 
| 1151 |     QObject::disconnect(connection); | 
| 1152 |     return id; | 
| 1153 | } | 
| 1154 |  | 
| 1155 | void tst_QQmlDebugJS::clearBreakPoint(int id) | 
| 1156 | { | 
| 1157 |     bool ok = false; | 
| 1158 |     auto connection = QObject::connect(sender: m_client.data(), signal: &QV4DebugClient::result, slot: [&]() { | 
| 1159 |         ok = true; | 
| 1160 |     }); | 
| 1161 |  | 
| 1162 |     m_client->clearBreakpoint(breakpoint: id); | 
| 1163 |     bool success = QTest::qWaitFor(predicate: [&]() { return ok; }); | 
| 1164 |     Q_UNUSED(success); | 
| 1165 |  | 
| 1166 |     QObject::disconnect(connection); | 
| 1167 | } | 
| 1168 |  | 
| 1169 | QTEST_MAIN(tst_QQmlDebugJS) | 
| 1170 |  | 
| 1171 | #include "tst_qqmldebugjs.moc" | 
| 1172 |  | 
| 1173 |  |