1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2018 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 | |
30 | #include <QtTest/QtTest> |
31 | |
32 | #include <qscriptengine.h> |
33 | #include <qscriptenginedebugger.h> |
34 | #include <qaction.h> |
35 | #include <qlineedit.h> |
36 | #include <qmainwindow.h> |
37 | #include <qmenu.h> |
38 | #include <qplaintextedit.h> |
39 | #include <qtoolbar.h> |
40 | |
41 | // Can't use QTest::qWait() because it causes event loop to hang on some platforms |
42 | static void qsWait(int ms) |
43 | { |
44 | QTimer timer; |
45 | timer.setSingleShot(true); |
46 | timer.setInterval(ms); |
47 | timer.start(); |
48 | QEventLoop loop; |
49 | QObject::connect(sender: &timer, SIGNAL(timeout()), receiver: &loop, SLOT(quit())); |
50 | loop.exec(); |
51 | QCoreApplication::processEvents(); |
52 | } |
53 | |
54 | class tst_QScriptEngineDebugger : public QObject |
55 | { |
56 | Q_OBJECT |
57 | |
58 | public: |
59 | tst_QScriptEngineDebugger(); |
60 | virtual ~tst_QScriptEngineDebugger(); |
61 | |
62 | private slots: |
63 | void attachAndDetach(); |
64 | void action(); |
65 | void widget(); |
66 | void standardObjects(); |
67 | void debuggerSignals(); |
68 | void consoleCommands(); |
69 | void multithreadedDebugging(); |
70 | void autoShowStandardWindow(); |
71 | void standardWindowOwnership(); |
72 | void engineDeleted(); |
73 | }; |
74 | |
75 | tst_QScriptEngineDebugger::tst_QScriptEngineDebugger() |
76 | { |
77 | } |
78 | |
79 | tst_QScriptEngineDebugger::~tst_QScriptEngineDebugger() |
80 | { |
81 | } |
82 | |
83 | void tst_QScriptEngineDebugger::attachAndDetach() |
84 | { |
85 | #if defined(Q_OS_WINCE) && _WIN32_WCE < 0x600 |
86 | QSKIP("skipped due to high mem usage until task 261062 is fixed" ); |
87 | #endif |
88 | { |
89 | QScriptEngineDebugger debugger; |
90 | QCOMPARE(debugger.state(), QScriptEngineDebugger::SuspendedState); |
91 | debugger.attachTo(engine: 0); |
92 | QScriptEngine engine; |
93 | debugger.attachTo(engine: &engine); |
94 | QCOMPARE(debugger.state(), QScriptEngineDebugger::SuspendedState); |
95 | } |
96 | { |
97 | QScriptEngineDebugger debugger; |
98 | QScriptEngine engine; |
99 | QScriptValue oldPrint = engine.globalObject().property(name: "print" ); |
100 | QVERIFY(oldPrint.isFunction()); |
101 | QVERIFY(!engine.globalObject().property("__FILE__" ).isValid()); |
102 | QVERIFY(!engine.globalObject().property("__LINE__" ).isValid()); |
103 | |
104 | debugger.attachTo(engine: &engine); |
105 | QVERIFY(engine.globalObject().property("__FILE__" ).isUndefined()); |
106 | QVERIFY(engine.globalObject().property("__LINE__" ).isNumber()); |
107 | QVERIFY(!engine.globalObject().property("print" ).strictlyEquals(oldPrint)); |
108 | |
109 | debugger.detach(); |
110 | QVERIFY(engine.globalObject().property("print" ).strictlyEquals(oldPrint)); |
111 | QVERIFY(!engine.globalObject().property("__FILE__" ).isValid()); |
112 | QVERIFY(!engine.globalObject().property("__LINE__" ).isValid()); |
113 | } |
114 | { |
115 | QScriptEngineDebugger debugger; |
116 | QScriptEngine engine; |
117 | debugger.attachTo(engine: &engine); |
118 | debugger.detach(); |
119 | QScriptEngine engine2; |
120 | debugger.attachTo(engine: &engine2); |
121 | } |
122 | { |
123 | QScriptEngineDebugger debugger; |
124 | QScriptEngine engine; |
125 | debugger.attachTo(engine: &engine); |
126 | debugger.detach(); |
127 | QScriptEngine engine2; |
128 | debugger.attachTo(engine: &engine2); |
129 | debugger.detach(); |
130 | } |
131 | #ifndef Q_OS_WINCE // demands too much memory for WinCE |
132 | { |
133 | QScriptEngineDebugger debugger; |
134 | QScriptEngine engine; |
135 | debugger.attachTo(engine: &engine); |
136 | QScriptEngineDebugger debugger2; |
137 | debugger2.attachTo(engine: &engine); |
138 | } |
139 | #endif |
140 | { |
141 | QScriptEngine *engine = new QScriptEngine; |
142 | QScriptEngineDebugger debugger; |
143 | debugger.attachTo(engine); |
144 | delete engine; |
145 | QScriptEngine engine2; |
146 | debugger.attachTo(engine: &engine2); |
147 | } |
148 | } |
149 | |
150 | void tst_QScriptEngineDebugger::action() |
151 | { |
152 | #if defined(Q_OS_WINCE) && _WIN32_WCE < 0x600 |
153 | QSKIP("skipped due to high mem usage until task 261062 is fixed" ); |
154 | #endif |
155 | |
156 | QScriptEngine engine; |
157 | QScriptEngineDebugger debugger; |
158 | debugger.attachTo(engine: &engine); |
159 | QList<QScriptEngineDebugger::DebuggerAction> actions; |
160 | actions |
161 | << QScriptEngineDebugger::InterruptAction |
162 | << QScriptEngineDebugger::ContinueAction |
163 | << QScriptEngineDebugger::StepIntoAction |
164 | << QScriptEngineDebugger::StepOverAction |
165 | << QScriptEngineDebugger::StepOutAction |
166 | << QScriptEngineDebugger::RunToCursorAction |
167 | << QScriptEngineDebugger::RunToNewScriptAction |
168 | << QScriptEngineDebugger::ToggleBreakpointAction |
169 | << QScriptEngineDebugger::ClearDebugOutputAction |
170 | << QScriptEngineDebugger::ClearErrorLogAction |
171 | << QScriptEngineDebugger::ClearConsoleAction |
172 | << QScriptEngineDebugger::FindInScriptAction |
173 | << QScriptEngineDebugger::FindNextInScriptAction |
174 | << QScriptEngineDebugger::FindPreviousInScriptAction |
175 | << QScriptEngineDebugger::GoToLineAction; |
176 | QList<QAction*> lst; |
177 | for (int i = 0; i < actions.size(); ++i) { |
178 | QScriptEngineDebugger::DebuggerAction da = actions.at(i); |
179 | QAction *act = debugger.action(action: da); |
180 | QVERIFY(act != 0); |
181 | QCOMPARE(act, debugger.action(da)); |
182 | QCOMPARE(act->parent(), (QObject*)&debugger); |
183 | QVERIFY(lst.indexOf(act) == -1); |
184 | lst.append(t: act); |
185 | } |
186 | } |
187 | |
188 | void tst_QScriptEngineDebugger::widget() |
189 | { |
190 | #if defined(Q_OS_WINCE) && _WIN32_WCE < 0x600 |
191 | QSKIP("skipped due to high mem usage until task 261062 is fixed" ); |
192 | #endif |
193 | |
194 | QScriptEngine engine; |
195 | QScriptEngineDebugger debugger; |
196 | debugger.attachTo(engine: &engine); |
197 | QList<QScriptEngineDebugger::DebuggerWidget> widgets; |
198 | widgets |
199 | << QScriptEngineDebugger::ConsoleWidget |
200 | << QScriptEngineDebugger::StackWidget |
201 | << QScriptEngineDebugger::ScriptsWidget |
202 | << QScriptEngineDebugger::LocalsWidget |
203 | << QScriptEngineDebugger::CodeWidget |
204 | << QScriptEngineDebugger::CodeFinderWidget |
205 | << QScriptEngineDebugger::BreakpointsWidget |
206 | << QScriptEngineDebugger::DebugOutputWidget |
207 | << QScriptEngineDebugger::ErrorLogWidget; |
208 | QList<QWidget*> lst; |
209 | for (int i = 0; i < widgets.size(); ++i) { |
210 | QScriptEngineDebugger::DebuggerWidget dw = widgets.at(i); |
211 | QWidget *wid = debugger.widget(widget: dw); |
212 | QVERIFY(wid != 0); |
213 | QCOMPARE(wid, debugger.widget(dw)); |
214 | QVERIFY(lst.indexOf(wid) == -1); |
215 | lst.append(t: wid); |
216 | QCOMPARE(static_cast<QWidget *>(wid->parent()), (QWidget*)0); |
217 | } |
218 | } |
219 | |
220 | void tst_QScriptEngineDebugger::standardObjects() |
221 | { |
222 | #if defined(Q_OS_WINCE) && _WIN32_WCE < 0x600 |
223 | QSKIP("skipped due to high mem usage until task 261062 is fixed" ); |
224 | #endif |
225 | |
226 | QScriptEngine engine; |
227 | QScriptEngineDebugger debugger; |
228 | debugger.attachTo(engine: &engine); |
229 | |
230 | QMainWindow *win = debugger.standardWindow(); |
231 | QCOMPARE(static_cast<QWidget *>(win->parent()), (QWidget*)0); |
232 | |
233 | QMenu * = debugger.createStandardMenu(); |
234 | QCOMPARE(static_cast<QWidget *>(menu->parent()), (QWidget*)0); |
235 | #ifndef QT_NO_TOOLBAR |
236 | QToolBar *toolBar = debugger.createStandardToolBar(); |
237 | QCOMPARE(static_cast<QWidget *>(toolBar->parent()), (QWidget*)0); |
238 | #endif |
239 | |
240 | QMenu * = debugger.createStandardMenu(parent: win); |
241 | QCOMPARE(static_cast<QWidget *>(menu2->parent()), (QWidget*)win); |
242 | QVERIFY(menu2 != menu); |
243 | #ifndef QT_NO_TOOLBAR |
244 | QToolBar *toolBar2 = debugger.createStandardToolBar(parent: win); |
245 | QCOMPARE(static_cast<QWidget *>(toolBar2->parent()), (QWidget*)win); |
246 | QVERIFY(toolBar2 != toolBar); |
247 | #endif |
248 | |
249 | delete menu; |
250 | #ifndef QT_NO_TOOLBAR |
251 | delete toolBar; |
252 | #endif |
253 | } |
254 | |
255 | void tst_QScriptEngineDebugger::debuggerSignals() |
256 | { |
257 | #if defined(Q_OS_WINCE) && _WIN32_WCE < 0x600 |
258 | QSKIP("skipped due to high mem usage until task 261062 is fixed" ); |
259 | #endif |
260 | |
261 | QScriptEngine engine; |
262 | QScriptEngineDebugger debugger; |
263 | debugger.attachTo(engine: &engine); |
264 | debugger.setAutoShowStandardWindow(false); |
265 | QSignalSpy evaluationSuspendedSpy(&debugger, SIGNAL(evaluationSuspended())); |
266 | QSignalSpy evaluationResumedSpy(&debugger, SIGNAL(evaluationResumed())); |
267 | QObject::connect(sender: &debugger, SIGNAL(evaluationSuspended()), |
268 | receiver: debugger.action(action: QScriptEngineDebugger::ContinueAction), |
269 | SLOT(trigger())); |
270 | engine.evaluate(program: "123" ); |
271 | QCOMPARE(evaluationSuspendedSpy.count(), 0); |
272 | QCOMPARE(evaluationResumedSpy.count(), 0); |
273 | engine.evaluate(program: "debugger" ); |
274 | QCoreApplication::processEvents(); |
275 | QCOMPARE(evaluationSuspendedSpy.count(), 1); |
276 | QCOMPARE(evaluationResumedSpy.count(), 1); |
277 | } |
278 | |
279 | static void executeConsoleCommand(QLineEdit *inputEdit, QPlainTextEdit *outputEdit, |
280 | const QString &text) |
281 | { |
282 | QString before = outputEdit->toPlainText(); |
283 | inputEdit->setText(text); |
284 | QTest::keyPress(widget: inputEdit, key: Qt::Key_Enter); |
285 | const int delay = 100; |
286 | qsWait(ms: delay); |
287 | QString after = outputEdit->toPlainText(); |
288 | int retryCount = 10; |
289 | LAgain: |
290 | while ((before == after) && (retryCount != 0)) { |
291 | qsWait(ms: delay); |
292 | after = outputEdit->toPlainText(); |
293 | --retryCount; |
294 | } |
295 | if (before != after) { |
296 | before = after; |
297 | qsWait(ms: delay); |
298 | after = outputEdit->toPlainText(); |
299 | if (before != after) { |
300 | retryCount = 10; |
301 | goto LAgain; |
302 | } |
303 | } |
304 | } |
305 | |
306 | class DebuggerCommandExecutor : public QObject |
307 | { |
308 | Q_OBJECT |
309 | public: |
310 | DebuggerCommandExecutor(QLineEdit *inputEdit, |
311 | QPlainTextEdit *outputEdit, |
312 | const QString &text, |
313 | QObject *parent = 0) |
314 | : QObject(parent), m_inputEdit(inputEdit), |
315 | m_outputEdit(outputEdit), m_commands(text) {} |
316 | DebuggerCommandExecutor(QLineEdit *inputEdit, |
317 | QPlainTextEdit *outputEdit, |
318 | const QStringList &commands, |
319 | QObject *parent = 0) |
320 | : QObject(parent), m_inputEdit(inputEdit), |
321 | m_outputEdit(outputEdit), m_commands(commands) {} |
322 | public Q_SLOTS: |
323 | void execute() { |
324 | for (int i = 0; i < m_commands.size(); ++i) |
325 | executeConsoleCommand(inputEdit: m_inputEdit, outputEdit: m_outputEdit, text: m_commands.at(i)); |
326 | } |
327 | private: |
328 | QLineEdit *m_inputEdit; |
329 | QPlainTextEdit *m_outputEdit; |
330 | QStringList m_commands; |
331 | }; |
332 | |
333 | void tst_QScriptEngineDebugger::consoleCommands() |
334 | { |
335 | QSKIP("This test can hang / misbehave because of timing/event loop issues (task 241300)" ); |
336 | |
337 | QScriptEngine engine; |
338 | QScriptEngineDebugger debugger; |
339 | debugger.setAutoShowStandardWindow(false); |
340 | debugger.attachTo(engine: &engine); |
341 | |
342 | QWidget *consoleWidget = debugger.widget(widget: QScriptEngineDebugger::ConsoleWidget); |
343 | QLineEdit *inputEdit = consoleWidget->findChild<QLineEdit*>(); |
344 | QVERIFY(inputEdit != 0); |
345 | QPlainTextEdit *outputEdit = consoleWidget->findChild<QPlainTextEdit*>(); |
346 | QVERIFY(outputEdit != 0); |
347 | |
348 | QVERIFY(outputEdit->toPlainText().startsWith("Welcome to the Qt Script debugger." )); |
349 | outputEdit->clear(); |
350 | |
351 | // print() |
352 | { |
353 | QWidget *debugOutputWidget = debugger.widget(widget: QScriptEngineDebugger::DebugOutputWidget); |
354 | QPlainTextEdit *debugOutputEdit = debugOutputWidget->findChild<QPlainTextEdit*>(); |
355 | QVERIFY(debugOutputEdit != 0); |
356 | |
357 | QVERIFY(debugOutputEdit->toPlainText().isEmpty()); |
358 | executeConsoleCommand(inputEdit, outputEdit, text: "print('Test of debug output')" ); |
359 | QCOMPARE(debugOutputEdit->toPlainText(), QString::fromLatin1("Test of debug output" )); |
360 | |
361 | QCOMPARE(outputEdit->toPlainText(), QString::fromLatin1("qsdb> print('Test of debug output')" )); |
362 | } |
363 | |
364 | outputEdit->clear(); |
365 | executeConsoleCommand(inputEdit, outputEdit, text: ".info scripts" ); |
366 | QCOMPARE(outputEdit->toPlainText(), QString::fromLatin1("qsdb> .info scripts\nNo scripts loaded." )); |
367 | |
368 | outputEdit->clear(); |
369 | executeConsoleCommand(inputEdit, outputEdit, text: ".break foo.qs:123" ); |
370 | QCOMPARE(outputEdit->toPlainText(), QString::fromLatin1("qsdb> .break foo.qs:123\nBreakpoint 1: foo.qs, line 123." )); |
371 | |
372 | outputEdit->clear(); |
373 | executeConsoleCommand(inputEdit, outputEdit, text: ".break 123" ); |
374 | QCOMPARE(outputEdit->toPlainText(), QString::fromLatin1("qsdb> .break 123\nNo script." )); |
375 | |
376 | outputEdit->clear(); |
377 | executeConsoleCommand(inputEdit, outputEdit, text: ".info breakpoints" ); |
378 | QCOMPARE(outputEdit->toPlainText(), QString::fromLatin1("qsdb> .info breakpoints\nId\tEnabled\tWhere\n1\tyes\tfoo.qs:123" )); |
379 | |
380 | outputEdit->clear(); |
381 | executeConsoleCommand(inputEdit, outputEdit, text: ".disable 1" ); |
382 | QCOMPARE(outputEdit->toPlainText(), QString::fromLatin1("qsdb> .disable 1" )); |
383 | |
384 | outputEdit->clear(); |
385 | executeConsoleCommand(inputEdit, outputEdit, text: ".info breakpoints" ); |
386 | QCOMPARE(outputEdit->toPlainText(), QString::fromLatin1("qsdb> .info breakpoints\nId\tEnabled\tWhere\n1\tno\tfoo.qs:123" )); |
387 | |
388 | outputEdit->clear(); |
389 | executeConsoleCommand(inputEdit, outputEdit, text: ".disable 1" ); |
390 | QCOMPARE(outputEdit->toPlainText(), QString::fromLatin1("qsdb> .disable 1" )); |
391 | |
392 | outputEdit->clear(); |
393 | executeConsoleCommand(inputEdit, outputEdit, text: ".disable 123" ); |
394 | QCOMPARE(outputEdit->toPlainText(), QString::fromLatin1("qsdb> .disable 123\nNo breakpoint number 123." )); |
395 | |
396 | outputEdit->clear(); |
397 | executeConsoleCommand(inputEdit, outputEdit, text: ".enable 1" ); |
398 | QCOMPARE(outputEdit->toPlainText(), QString::fromLatin1("qsdb> .enable 1" )); |
399 | |
400 | outputEdit->clear(); |
401 | executeConsoleCommand(inputEdit, outputEdit, text: ".info breakpoints" ); |
402 | QCOMPARE(outputEdit->toPlainText(), QString::fromLatin1("qsdb> .info breakpoints\nId\tEnabled\tWhere\n1\tyes\tfoo.qs:123" )); |
403 | |
404 | outputEdit->clear(); |
405 | executeConsoleCommand(inputEdit, outputEdit, text: ".enable 123" ); |
406 | QCOMPARE(outputEdit->toPlainText(), QString::fromLatin1("qsdb> .enable 123\nNo breakpoint number 123." )); |
407 | |
408 | outputEdit->clear(); |
409 | executeConsoleCommand(inputEdit, outputEdit, text: ".condition 1 i > 456" ); |
410 | QCOMPARE(outputEdit->toPlainText(), QString::fromLatin1("qsdb> .condition 1 i > 456" )); |
411 | |
412 | outputEdit->clear(); |
413 | executeConsoleCommand(inputEdit, outputEdit, text: ".info breakpoints" ); |
414 | QCOMPARE(outputEdit->toPlainText(), QString::fromLatin1("qsdb> .info breakpoints\nId\tEnabled\tWhere\n1\tyes\tfoo.qs:123\n\tstop only if i > 456" )); |
415 | |
416 | outputEdit->clear(); |
417 | executeConsoleCommand(inputEdit, outputEdit, text: ".condition 1" ); |
418 | QCOMPARE(outputEdit->toPlainText(), QString::fromLatin1("qsdb> .condition 1\nBreakpoint 1 now unconditional." )); |
419 | |
420 | outputEdit->clear(); |
421 | executeConsoleCommand(inputEdit, outputEdit, text: ".info breakpoints" ); |
422 | QCOMPARE(outputEdit->toPlainText(), QString::fromLatin1("qsdb> .info breakpoints\nId\tEnabled\tWhere\n1\tyes\tfoo.qs:123" )); |
423 | |
424 | outputEdit->clear(); |
425 | executeConsoleCommand(inputEdit, outputEdit, text: ".condition 123" ); |
426 | QCOMPARE(outputEdit->toPlainText(), QString::fromLatin1("qsdb> .condition 123\nNo breakpoint number 123." )); |
427 | |
428 | outputEdit->clear(); |
429 | executeConsoleCommand(inputEdit, outputEdit, text: ".ignore 1" ); |
430 | QCOMPARE(outputEdit->toPlainText(), QString::fromLatin1("qsdb> .ignore 1\nMissing argument (ignore-count)." )); |
431 | |
432 | outputEdit->clear(); |
433 | executeConsoleCommand(inputEdit, outputEdit, text: ".ignore 1 10" ); |
434 | QCOMPARE(outputEdit->toPlainText(), QString::fromLatin1("qsdb> .ignore 1 10\nBreakpoint 1 will be ignored the next 10 time(s)." )); |
435 | |
436 | outputEdit->clear(); |
437 | executeConsoleCommand(inputEdit, outputEdit, text: ".delete 1" ); |
438 | QCOMPARE(outputEdit->toPlainText(), QString::fromLatin1("qsdb> .delete 1" )); |
439 | |
440 | outputEdit->clear(); |
441 | executeConsoleCommand(inputEdit, outputEdit, text: ".info breakpoints" ); |
442 | QCOMPARE(outputEdit->toPlainText(), QString::fromLatin1("qsdb> .info breakpoints\nNo breakpoints set." )); |
443 | |
444 | outputEdit->clear(); |
445 | executeConsoleCommand(inputEdit, outputEdit, text: ".tbreak bar.qs:456" ); |
446 | QCOMPARE(outputEdit->toPlainText(), QString::fromLatin1("qsdb> .tbreak bar.qs:456\nBreakpoint 2: bar.qs, line 456." )); |
447 | |
448 | { |
449 | QString script; |
450 | script.append(s: "function foo(i) {\n" ); |
451 | for (int i = 0; i < 100; ++i) |
452 | script.append(s: QString::fromLatin1(str: " i = i + %0;\n" ).arg(a: i)); |
453 | script.append(s: " return i;\n}" ); |
454 | engine.evaluate(program: script, fileName: "foo.qs" ); |
455 | } |
456 | outputEdit->clear(); |
457 | executeConsoleCommand(inputEdit, outputEdit, text: ".info scripts" ); |
458 | QCOMPARE(outputEdit->toPlainText(), QString::fromLatin1("qsdb> .info scripts\n\tfoo.qs" )); |
459 | |
460 | outputEdit->clear(); |
461 | executeConsoleCommand(inputEdit, outputEdit, text: ".list foo.qs" ); |
462 | QCOMPARE(outputEdit->toPlainText(), QString::fromLatin1("qsdb> .list foo.qs\n" |
463 | "1\tfunction foo(i) {\n" |
464 | "2\t i = i + 0;\n" |
465 | "3\t i = i + 1;\n" |
466 | "4\t i = i + 2;\n" |
467 | "5\t i = i + 3;\n" |
468 | "6\t i = i + 4;\n" |
469 | "7\t i = i + 5;\n" |
470 | "8\t i = i + 6;\n" |
471 | "9\t i = i + 7;\n" |
472 | "10\t i = i + 8;" )); |
473 | outputEdit->clear(); |
474 | executeConsoleCommand(inputEdit, outputEdit, text: ".list" ); |
475 | QCOMPARE(outputEdit->toPlainText(), QString::fromLatin1("qsdb> .list\n" |
476 | "No script." )); |
477 | |
478 | outputEdit->clear(); |
479 | executeConsoleCommand(inputEdit, outputEdit, text: ".backtrace" ); |
480 | QCOMPARE(outputEdit->toPlainText(), QString::fromLatin1("qsdb> .backtrace\n#0 <global>() at -1" )); |
481 | |
482 | outputEdit->clear(); |
483 | executeConsoleCommand(inputEdit, outputEdit, text: ".down" ); |
484 | QCOMPARE(outputEdit->toPlainText(), QString::fromLatin1("qsdb> .down\nAlready at bottom (innermost) frame." )); |
485 | |
486 | outputEdit->clear(); |
487 | executeConsoleCommand(inputEdit, outputEdit, text: ".up" ); |
488 | QCOMPARE(outputEdit->toPlainText(), QString::fromLatin1("qsdb> .up\nAlready at top (outermost) frame." )); |
489 | |
490 | outputEdit->clear(); |
491 | executeConsoleCommand(inputEdit, outputEdit, text: ".frame" ); |
492 | QCOMPARE(outputEdit->toPlainText(), QString::fromLatin1("qsdb> .frame\n#0 <global>() at -1" )); |
493 | |
494 | outputEdit->clear(); |
495 | executeConsoleCommand(inputEdit, outputEdit, text: ".break foo.qs:789" ); |
496 | QCOMPARE(outputEdit->toPlainText(), QString::fromLatin1("qsdb> .break foo.qs:789\nBreakpoint 3: foo.qs, line 789." )); |
497 | |
498 | outputEdit->clear(); |
499 | executeConsoleCommand(inputEdit, outputEdit, text: ".clear foo.qs:789" ); |
500 | QCOMPARE(outputEdit->toPlainText(), QString::fromLatin1("qsdb> .clear foo.qs:789\nDeleted breakpoint 3." )); |
501 | |
502 | outputEdit->clear(); |
503 | executeConsoleCommand(inputEdit, outputEdit, text: ".info breakpoints" ); |
504 | QCOMPARE(outputEdit->toPlainText(), QString::fromLatin1("qsdb> .info breakpoints\nId\tEnabled\tWhere\n2\tyes\tbar.qs:456" )); |
505 | |
506 | outputEdit->clear(); |
507 | executeConsoleCommand(inputEdit, outputEdit, text: ".info locals" ); |
508 | QVERIFY(outputEdit->toPlainText().startsWith("qsdb> .info locals\n" |
509 | "NaN : NaN\n" |
510 | "Infinity : Infinity\n" |
511 | "undefined : undefined\n" |
512 | "print : function () { [native] }\n" |
513 | "parseInt : function () { [native] }\n" |
514 | "parseFloat : function () { [native] }\n" |
515 | "isNaN : function () { [native] }\n" |
516 | "isFinite : function () { [native] }\n" |
517 | "decodeURI : function () { [native] }\n" |
518 | "decodeURIComponent : function () { [native] }\n" |
519 | "encodeURI : function () { [native] }\n" |
520 | "encodeURIComponent : function () { [native] }\n" |
521 | "escape : function () { [native] }\n" |
522 | "unescape : function () { [native] }\n" |
523 | "version : function () { [native] }\n" |
524 | "gc : function () { [native] }\n" |
525 | "Object : function () { [native] }\n" |
526 | "Function : function () { [native] }\n" |
527 | "Number : function () { [native] }\n" |
528 | "Boolean : function () { [native] }" )); |
529 | |
530 | outputEdit->clear(); |
531 | QVERIFY(!engine.globalObject().property("a" ).isValid()); |
532 | executeConsoleCommand(inputEdit, outputEdit, text: ".eval a = 123" ); |
533 | QVERIFY(engine.globalObject().property("a" ).isNumber()); |
534 | QCOMPARE(engine.globalObject().property("a" ).toInt32(), 123); |
535 | |
536 | outputEdit->clear(); |
537 | QVERIFY(!engine.globalObject().property("b" ).isValid()); |
538 | executeConsoleCommand(inputEdit, outputEdit, text: "b = 456" ); |
539 | QVERIFY(engine.globalObject().property("b" ).isNumber()); |
540 | QCOMPARE(engine.globalObject().property("b" ).toInt32(), 456); |
541 | |
542 | outputEdit->clear(); |
543 | executeConsoleCommand(inputEdit, outputEdit, text: ".break myscript.qs:1" ); |
544 | QCOMPARE(outputEdit->toPlainText(), QString::fromLatin1("qsdb> .break myscript.qs:1\nBreakpoint 4: myscript.qs, line 1." )); |
545 | |
546 | { |
547 | DebuggerCommandExecutor executor(inputEdit, outputEdit, ".continue" ); |
548 | QObject::connect(sender: &debugger, SIGNAL(evaluationSuspended()), receiver: &executor, SLOT(execute()), Qt::QueuedConnection); |
549 | outputEdit->clear(); |
550 | engine.evaluate(program: "void(123);" , fileName: "myscript.qs" ); |
551 | QCOMPARE(outputEdit->toPlainText(), QString::fromLatin1("Breakpoint 4 at myscript.qs, line 1.\n1\tvoid(123);\nqsdb> .continue" )); |
552 | } |
553 | |
554 | { |
555 | DebuggerCommandExecutor executor(inputEdit, outputEdit, ".step" ); |
556 | QObject::connect(sender: &debugger, SIGNAL(evaluationSuspended()), receiver: &executor, SLOT(execute()), Qt::QueuedConnection); |
557 | outputEdit->clear(); |
558 | engine.evaluate(program: "void(123);\nvoid(456);" , fileName: "myscript.qs" ); |
559 | QCOMPARE(outputEdit->toPlainText(), QString::fromLatin1("Breakpoint 4 at myscript.qs, line 1.\n" |
560 | "1\tvoid(123);\n" |
561 | "qsdb> .step\n" |
562 | "2\tvoid(456);\n" |
563 | "qsdb> .step" )); |
564 | } |
565 | |
566 | { |
567 | DebuggerCommandExecutor executor(inputEdit, outputEdit, ".step 2" ); |
568 | QObject::connect(sender: &debugger, SIGNAL(evaluationSuspended()), receiver: &executor, SLOT(execute()), Qt::QueuedConnection); |
569 | outputEdit->clear(); |
570 | engine.evaluate(program: "void(123);\nvoid(456);" , fileName: "myscript.qs" ); |
571 | QCOMPARE(outputEdit->toPlainText(), QString::fromLatin1("Breakpoint 4 at myscript.qs, line 1.\n1\tvoid(123);\nqsdb> .step 2" )); |
572 | } |
573 | |
574 | { |
575 | DebuggerCommandExecutor executor(inputEdit, outputEdit, ".next" ); |
576 | QObject::connect(sender: &debugger, SIGNAL(evaluationSuspended()), receiver: &executor, SLOT(execute()), Qt::QueuedConnection); |
577 | outputEdit->clear(); |
578 | engine.evaluate(program: "void(123);\nvoid(456);" , fileName: "myscript.qs" ); |
579 | QCOMPARE(outputEdit->toPlainText(), QString::fromLatin1("Breakpoint 4 at myscript.qs, line 1.\n" |
580 | "1\tvoid(123);\n" |
581 | "qsdb> .next\n" |
582 | "2\tvoid(456);\n" |
583 | "qsdb> .next" )); |
584 | } |
585 | |
586 | { |
587 | DebuggerCommandExecutor executor(inputEdit, outputEdit, ".next 2" ); |
588 | QObject::connect(sender: &debugger, SIGNAL(evaluationSuspended()), receiver: &executor, SLOT(execute()), Qt::QueuedConnection); |
589 | outputEdit->clear(); |
590 | engine.evaluate(program: "void(123);\nvoid(456);" , fileName: "myscript.qs" ); |
591 | QCOMPARE(outputEdit->toPlainText(), QString::fromLatin1("Breakpoint 4 at myscript.qs, line 1.\n" |
592 | "1\tvoid(123);\n" |
593 | "qsdb> .next 2" )); |
594 | } |
595 | |
596 | { |
597 | DebuggerCommandExecutor executor(inputEdit, outputEdit, ".finish" ); |
598 | QObject::connect(sender: &debugger, SIGNAL(evaluationSuspended()), receiver: &executor, SLOT(execute()), Qt::QueuedConnection); |
599 | outputEdit->clear(); |
600 | engine.evaluate(program: "void(123);\nvoid(456);" , fileName: "myscript.qs" ); |
601 | QCOMPARE(outputEdit->toPlainText(), QString::fromLatin1("Breakpoint 4 at myscript.qs, line 1.\n1\tvoid(123);\nqsdb> .finish" )); |
602 | } |
603 | |
604 | { |
605 | DebuggerCommandExecutor executor(inputEdit, outputEdit, ".return" ); |
606 | QObject::connect(sender: &debugger, SIGNAL(evaluationSuspended()), receiver: &executor, SLOT(execute()), Qt::QueuedConnection); |
607 | outputEdit->clear(); |
608 | engine.evaluate(program: "void(123);\nvoid(456);\n789;" , fileName: "myscript.qs" ); |
609 | QCOMPARE(outputEdit->toPlainText(), QString::fromLatin1("Breakpoint 4 at myscript.qs, line 1.\n1\tvoid(123);\nqsdb> .return" )); |
610 | } |
611 | |
612 | { |
613 | DebuggerCommandExecutor executor(inputEdit, outputEdit, QStringList() << ".list" << ".continue" ); |
614 | QObject::connect(sender: &debugger, SIGNAL(evaluationSuspended()), receiver: &executor, SLOT(execute()), Qt::QueuedConnection); |
615 | outputEdit->clear(); |
616 | engine.evaluate(program: "void(123);\nvoid(456);\n789;" , fileName: "myscript.qs" ); |
617 | QCOMPARE(outputEdit->toPlainText(), QString::fromLatin1("Breakpoint 4 at myscript.qs, line 1.\n" |
618 | "1\tvoid(123);\n" |
619 | "qsdb> .list\n" |
620 | "1\tvoid(123);\n" |
621 | "2\tvoid(456);\n" |
622 | "3\t789;\n" |
623 | "4\n" |
624 | "5\n" |
625 | "6\n" |
626 | "7\n" |
627 | "8\n" |
628 | "9\n" |
629 | "10\n" |
630 | "qsdb> .continue" )); |
631 | } |
632 | |
633 | { |
634 | QString script; |
635 | script.append(s: "function bar(i) {\n" ); |
636 | for (int i = 0; i < 10; ++i) |
637 | script.append(s: QString::fromLatin1(str: " i = i + %0;\n" ).arg(a: i)); |
638 | script.append(s: " return i;\n}" ); |
639 | engine.evaluate(program: script, fileName: "bar.qs" ); |
640 | } |
641 | |
642 | outputEdit->clear(); |
643 | executeConsoleCommand(inputEdit, outputEdit, text: ".break bar.qs:7" ); |
644 | |
645 | { |
646 | DebuggerCommandExecutor executor(inputEdit, outputEdit, QStringList() |
647 | << ".list" |
648 | << ".up" |
649 | << ".list" |
650 | << ".frame" |
651 | << ".down" |
652 | << ".list" |
653 | << ".continue" ); |
654 | QObject::connect(sender: &debugger, SIGNAL(evaluationSuspended()), receiver: &executor, SLOT(execute()), Qt::QueuedConnection); |
655 | outputEdit->clear(); |
656 | engine.evaluate(program: "bar(123);" , fileName: "testscript.qs" ); |
657 | QCOMPARE(outputEdit->toPlainText(), QString::fromLatin1("Breakpoint 5 at bar.qs, line 7.\n" |
658 | "7\t i = i + 5;\n" |
659 | "qsdb> .list\n" |
660 | "2\t i = i + 0;\n" |
661 | "3\t i = i + 1;\n" |
662 | "4\t i = i + 2;\n" |
663 | "5\t i = i + 3;\n" |
664 | "6\t i = i + 4;\n" |
665 | "7\t i = i + 5;\n" |
666 | "8\t i = i + 6;\n" |
667 | "9\t i = i + 7;\n" |
668 | "10\t i = i + 8;\n" |
669 | "11\t i = i + 9;\n" |
670 | "qsdb> .up\n" |
671 | "#1 <global>()@testscript.qs:1\n" |
672 | "qsdb> .list\n" |
673 | "1\tbar(123);\n" |
674 | "2\n" |
675 | "3\n" |
676 | "4\n" |
677 | "5\n" |
678 | "6\n" |
679 | "7\n" |
680 | "8\n" |
681 | "9\n" |
682 | "10\n" |
683 | "qsdb> .frame\n" |
684 | "#1 <global>()@testscript.qs:1\n" |
685 | "qsdb> .down\n" |
686 | "#0 bar(123)@bar.qs:7\n" |
687 | "qsdb> .list\n" |
688 | "2\t i = i + 0;\n" |
689 | "3\t i = i + 1;\n" |
690 | "4\t i = i + 2;\n" |
691 | "5\t i = i + 3;\n" |
692 | "6\t i = i + 4;\n" |
693 | "7\t i = i + 5;\n" |
694 | "8\t i = i + 6;\n" |
695 | "9\t i = i + 7;\n" |
696 | "10\t i = i + 8;\n" |
697 | "11\t i = i + 9;\n" |
698 | "qsdb> .continue" )); |
699 | } |
700 | |
701 | { |
702 | DebuggerCommandExecutor executor(inputEdit, outputEdit, QStringList() |
703 | << ".list 9" |
704 | << ".continue" ); |
705 | QObject::connect(sender: &debugger, SIGNAL(evaluationSuspended()), receiver: &executor, SLOT(execute()), Qt::QueuedConnection); |
706 | outputEdit->clear(); |
707 | engine.evaluate(program: "bar(123);" , fileName: "testscript.qs" ); |
708 | QCOMPARE(outputEdit->toPlainText(), QString::fromLatin1("Breakpoint 5 at bar.qs, line 7.\n" |
709 | "7\t i = i + 5;\n" |
710 | "qsdb> .list 9\n" |
711 | "4\t i = i + 2;\n" |
712 | "5\t i = i + 3;\n" |
713 | "6\t i = i + 4;\n" |
714 | "7\t i = i + 5;\n" |
715 | "8\t i = i + 6;\n" |
716 | "9\t i = i + 7;\n" |
717 | "10\t i = i + 8;\n" |
718 | "11\t i = i + 9;\n" |
719 | "12\t return i;\n" |
720 | "13\t}\n" |
721 | "qsdb> .continue" )); |
722 | } |
723 | } |
724 | |
725 | class ScriptEvaluator : public QObject |
726 | { |
727 | Q_OBJECT |
728 | public: |
729 | ScriptEvaluator(QObject *parent = 0) |
730 | : QObject(parent) { |
731 | m_engine = new QScriptEngine(this); |
732 | } |
733 | QScriptEngine *engine() const { |
734 | return m_engine; |
735 | } |
736 | public Q_SLOTS: |
737 | QScriptValue evaluate(const QString &program) { |
738 | return m_engine->evaluate(program); |
739 | } |
740 | private: |
741 | QScriptEngine *m_engine; |
742 | }; |
743 | |
744 | void tst_QScriptEngineDebugger::multithreadedDebugging() |
745 | { |
746 | #ifdef Q_OS_WINCE |
747 | QSKIP("This tests uses too much memory for Windows CE" ); |
748 | #endif |
749 | ScriptEvaluator eval; |
750 | QThread thread; |
751 | eval.moveToThread(thread: &thread); |
752 | eval.engine()->moveToThread(thread: &thread); |
753 | QScriptEngineDebugger debugger; |
754 | QSignalSpy evaluationSuspendedSpy(&debugger, SIGNAL(evaluationSuspended())); |
755 | QSignalSpy evaluationResumedSpy(&debugger, SIGNAL(evaluationResumed())); |
756 | debugger.attachTo(engine: eval.engine()); |
757 | QMetaObject::invokeMethod(obj: &eval, member: "evaluate" , type: Qt::QueuedConnection, Q_ARG(QString, "debugger" )); |
758 | QSignalSpy threadFinishedSpy(&thread, SIGNAL(finished())); |
759 | thread.start(); |
760 | QTRY_COMPARE(evaluationSuspendedSpy.count(), 1); |
761 | QTRY_COMPARE(evaluationResumedSpy.count(), 0); |
762 | thread.quit(); |
763 | QTRY_COMPARE(threadFinishedSpy.count(), 1); |
764 | } |
765 | |
766 | void tst_QScriptEngineDebugger::autoShowStandardWindow() |
767 | { |
768 | { |
769 | QScriptEngine engine; |
770 | QScriptEngineDebugger debugger; |
771 | QCOMPARE(debugger.autoShowStandardWindow(), true); |
772 | debugger.attachTo(engine: &engine); |
773 | QObject::connect(sender: &debugger, SIGNAL(evaluationSuspended()), |
774 | receiver: debugger.action(action: QScriptEngineDebugger::ContinueAction), |
775 | SLOT(trigger())); |
776 | engine.evaluate(program: "debugger" ); |
777 | QTRY_VERIFY(debugger.standardWindow()->isVisible()); |
778 | |
779 | debugger.setAutoShowStandardWindow(true); |
780 | QCOMPARE(debugger.autoShowStandardWindow(), true); |
781 | |
782 | debugger.setAutoShowStandardWindow(false); |
783 | QCOMPARE(debugger.autoShowStandardWindow(), false); |
784 | |
785 | debugger.setAutoShowStandardWindow(true); |
786 | QCOMPARE(debugger.autoShowStandardWindow(), true); |
787 | |
788 | debugger.standardWindow()->hide(); |
789 | |
790 | engine.evaluate(program: "debugger" ); |
791 | QTRY_VERIFY(debugger.standardWindow()->isVisible()); |
792 | } |
793 | |
794 | { |
795 | QScriptEngine engine; |
796 | QScriptEngineDebugger debugger; |
797 | debugger.setAutoShowStandardWindow(false); |
798 | debugger.attachTo(engine: &engine); |
799 | QObject::connect(sender: &debugger, SIGNAL(evaluationSuspended()), |
800 | receiver: debugger.action(action: QScriptEngineDebugger::ContinueAction), |
801 | SLOT(trigger())); |
802 | QSignalSpy evaluationResumedSpy(&debugger, SIGNAL(evaluationResumed())); |
803 | engine.evaluate(program: "debugger" ); |
804 | QTRY_COMPARE(evaluationResumedSpy.count(), 1); |
805 | QVERIFY(!debugger.standardWindow()->isVisible()); |
806 | } |
807 | } |
808 | |
809 | void tst_QScriptEngineDebugger::standardWindowOwnership() |
810 | { |
811 | QScriptEngine engine; |
812 | QPointer<QMainWindow> win; |
813 | { |
814 | QScriptEngineDebugger debugger; |
815 | win = debugger.standardWindow(); |
816 | } |
817 | QVERIFY(win == 0); |
818 | |
819 | // Reparent the window. |
820 | QWidget widget; |
821 | { |
822 | QScriptEngineDebugger debugger; |
823 | win = debugger.standardWindow(); |
824 | win->setParent(&widget); |
825 | } |
826 | QVERIFY(win != 0); |
827 | } |
828 | |
829 | void tst_QScriptEngineDebugger::engineDeleted() |
830 | { |
831 | QScriptEngine* engine = new QScriptEngine; |
832 | QScriptEngineDebugger *debugger = new QScriptEngineDebugger; |
833 | debugger->attachTo(engine); |
834 | |
835 | debugger->standardWindow()->show(); |
836 | QTest::qWaitForWindowExposed(widget: debugger->standardWindow()); |
837 | |
838 | QSignalSpy destroyedSpy(engine, SIGNAL(destroyed())); |
839 | engine->deleteLater(); |
840 | QTRY_COMPARE(destroyedSpy.count(), 1); |
841 | |
842 | // Shouldn't crash (QTBUG-21548) |
843 | debugger->action(action: QScriptEngineDebugger::ContinueAction)->trigger(); |
844 | } |
845 | |
846 | QTEST_MAIN(tst_QScriptEngineDebugger) |
847 | #include "tst_qscriptenginedebugger.moc" |
848 | |