debugger.py 11.6 KB
Newer Older
1
2
import re

3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def handleDebuggerWarnings(config):
    if "MSVC" in config:
        try:
            popup = waitForObject("{text?='<html><head/><body>*' type='QLabel' unnamed='1' visible='1' window=':Symbol Server_Utils::CheckableMessageBox'}", 10000)
            symServerNotConfiged = ("<html><head/><body><p>The debugger is not configured to use the public "
                                    "<a href=\"http://support.microsoft.com/kb/311503\">Microsoft Symbol Server</a>. "
                                    "This is recommended for retrieval of the symbols of the operating system libraries.</p>"
                                    "<p><i>Note:</i> A fast internet connection is required for this to work smoothly. "
                                    "Also, a delay might occur when connecting for the first time.</p>"
                                    "<p>Would you like to set it up?</p></br></body></html>")
            if popup.text == symServerNotConfiged:
                test.log("Creator warned about the debugger not being configured to use the public Microsoft Symbol Server.")
            else:
                test.warning("Creator showed an unexpected warning: " + str(popup.text))
            clickButton(waitForObject("{text='No' type='QPushButton' unnamed='1' visible='1' window=':Symbol Server_Utils::CheckableMessageBox'}", 10000))
        except LookupError:
            pass # No warning. Fine.
    else:
        if "Release" in config and platform.system() != "Darwin":
            message = waitForObject("{container=':Qt Creator.DebugModeWidget_QSplitter' name='qt_msgbox_label' type='QLabel' visible='1'}", 20000)
23
24
25
            messageText = str(message.text)
            test.verify(messageText.startswith('This does not seem to be a "Debug" build.\nSetting breakpoints by file name and line number may fail.'),
                        "Got warning: %s" % messageText)
26
27
28
29
30
31
32
33
34
35
36
37
            clickButton("{container=':Qt Creator.DebugModeWidget_QSplitter' text='OK' type='QPushButton' unnamed='1' visible='1'}")

def takeDebuggerLog():
    invokeMenuItem("Window", "Views", "Debugger Log")
    debuggerLogWindow = waitForObject("{container=':DebugModeWidget.Debugger Log_QDockWidget' type='Debugger::Internal::CombinedPane' unnamed='1' visible='1'}", 20000)
    debuggerLog = str(debuggerLogWindow.plainText)
    mouseClick(debuggerLogWindow, 5, 5, 0, Qt.LeftButton)
    activateItem(waitForObjectItem(openContextMenuOnTextCursorPosition(debuggerLogWindow),
                                   "Clear Contents"))
    waitFor("str(debuggerLogWindow.plainText)==''", 5000)
    invokeMenuItem("Window", "Views", "Debugger Log")
    return debuggerLog
38
39

# function to set breakpoints for the current project
40
# on the given file,line pairs inside the given list of dicts
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# the lines are treated as regular expression
def setBreakpointsForCurrentProject(filesAndLines):
    # internal helper for setBreakpointsForCurrentProject
    # double clicks the treeElement inside the given navTree
    # TODO: merge with doubleClickFile() from tst_qml_editor & move to utils(?)
    def __doubleClickFile__(navTree, treeElement):
        waitForObjectItem(navTree, treeElement)
        fileNamePattern = re.compile(".*\.(?P<file>(.*\\\..*)?)$")
        fileName = fileNamePattern.search(treeElement).group("file").replace("\\.", ".")
        doubleClickItem(navTree, treeElement, 5, 5, 0, Qt.LeftButton)
        mainWindow = waitForObject(":Qt Creator_Core::Internal::MainWindow")
        waitFor('fileName in str(mainWindow.windowTitle)', 5000)
        return fileName

    switchViewTo(ViewConstants.DEBUG)
    removeOldBreakpoints()
57
58
    if not filesAndLines or not isinstance(filesAndLines, (list,tuple)):
        test.fatal("This function only takes a non-empty list/tuple holding dicts.")
59
60
61
        return False
    navTree = waitForObject("{type='Utils::NavigationTreeView' unnamed='1' visible='1' "
                            "window=':Qt Creator_Core::Internal::MainWindow'}", 20000)
62
63
64
65
66
67
68
69
    for current in filesAndLines:
        for curFile,curLine in current.iteritems():
            fName = __doubleClickFile__(navTree, curFile)
            editor = getEditorForFileSuffix(curFile)
            if not placeCursorToLine(editor, curLine, True):
                return False
            invokeMenuItem("Debug", "Toggle Breakpoint")
            test.log('Set breakpoint in %s' % fName, curLine)
70
    try:
71
        breakPointTreeView = waitForObject(":Breakpoints_Debugger::Internal::BreakTreeView")
72
73
74
75
        waitFor("breakPointTreeView.model().rowCount() == len(filesAndLines)", 2000)
    except:
        test.fatal("UI seems to have changed - check manually and fix this script.")
        return False
76
77
78
79
80
81
82
83
84
85
    test.compare(breakPointTreeView.model().rowCount(), len(filesAndLines),
                 'Expected %d set break points, found %d listed' %
                 (len(filesAndLines), breakPointTreeView.model().rowCount()))
    return True

# helper that removes all breakpoints - assumes that it's getting called
# being already on Debug view and Breakpoints widget is not disabled
def removeOldBreakpoints():
    test.log("Removing old breakpoints if there are any")
    try:
86
        breakPointTreeView = waitForObject(":Breakpoints_Debugger::Internal::BreakTreeView")
87
88
89
90
91
92
93
94
95
96
        model = breakPointTreeView.model()
        if model.rowCount()==0:
            test.log("No breakpoints found...")
        else:
            test.log("Found %d breakpoints - removing them" % model.rowCount())
            for row in range(model.rowCount()):
                currentIndex = model.index(row,0)
                rect = breakPointTreeView.visualRect(currentIndex)
                mouseClick(breakPointTreeView, rect.x+5, rect.y+5, 0, Qt.LeftButton)
                type(breakPointTreeView, "<Delete>")
97
98
99
    except:
        test.fatal("UI seems to have changed - check manually and fix this script.")
        return False
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
    return test.compare(model.rowCount(), 0, "Check if all breakpoints have been removed.")

# function to do simple debugging of the current (configured) project
# param pressContinueCount defines how often it is expected to press
#       the 'Continue' button while debugging
# param expectedBPOrder holds a list of dicts where the dicts contain always
#       only 1 key:value pair - the key is the name of the file, the value is
#       line number where the debugger should stop
def doSimpleDebugging(currentConfigName, pressContinueCount=1, expectedBPOrder=[]):
    expectedLabelTexts = ['Stopped\.', 'Stopped at breakpoint \d+ \(\d+\) in thread \d+\.']
    if len(expectedBPOrder) == 0:
        expectedLabelTexts.append("Running\.")
    if not __startDebugger__(currentConfigName):
        return False
    statusLabel = findObject(":Debugger Toolbar.StatusText_Utils::StatusLabel")
    test.log("Continuing debugging %d times..." % pressContinueCount)
    for i in range(pressContinueCount):
        if waitFor("regexVerify(str(statusLabel.text), expectedLabelTexts)", 20000):
            verifyBreakPoint(expectedBPOrder[i])
        else:
            test.fail('%s' % str(statusLabel.text))
        contDbg = waitForObject(":*Qt Creator.Continue_Core::Internal::FancyToolButton", 3000)
        test.log("Continuing...")
        clickButton(contDbg)
        waitFor("str(statusLabel.text) == 'Running.'", 5000)
    timedOut = not waitFor("str(statusLabel.text) in ['Running.', 'Debugger finished.']", 30000)
    if timedOut:
        test.log("Waiting for 'Running.' / 'Debugger finished.' timed out.",
                 "Debugger is in state: '%s'..." % statusLabel.text)
    if str(statusLabel.text) == 'Running.':
        test.log("Debugger is still running... Will be stopped.")
        return __stopDebugger__()
    elif str(statusLabel.text) == 'Debugger finished.':
        test.log("Debugger has finished.")
        return __logDebugResult__()
    else:
        test.log("Trying to stop debugger...")
        try:
            return __stopDebugger__()
        except:
            # if stopping failed - debugger had already stopped
            return True

def __startDebugger__(config):
    clickButton(waitForObject(":*Qt Creator.Start Debugging_Core::Internal::FancyToolButton"))
    handleDebuggerWarnings(config)
146
    hasNotTimedOut = waitFor("object.exists(':Debugger Toolbar.Continue_QToolButton')", 60000)
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
    try:
        mBox = findObject(":Failed to start application_QMessageBox")
        mBoxText = mBox.text
        mBoxIText = mBox.informativeText
        clickButton(":DebugModeWidget.OK_QPushButton")
        test.fail("Debugger hasn't started... QMessageBox appeared!")
        test.log("QMessageBox content: '%s'" % mBoxText,
                 "'%s'" % mBoxIText)
        return False
    except:
        pass
    if hasNotTimedOut:
        test.passes("Debugger started...")
    else:
        test.fail("Debugger seems to have not started...")
        if "MSVC" in config:
            debuggerLog = takeDebuggerLog()
            if "lib\qtcreatorcdbext64\qtcreatorcdbext.dll cannot be found." in debuggerLog:
                test.fatal("qtcreatorcdbext.dll is missing in lib\qtcreatorcdbext64")
            else:
                test.fatal("Debugger log did not behave as expected. Please check manually.")
        logApplicationOutput()
        return False
    try:
        waitForObject(":*Qt Creator.Interrupt_Core::Internal::FancyToolButton", 3000)
        test.passes("'Interrupt' (debugger) button visible.")
    except:
        try:
            waitForObject(":*Qt Creator.Continue_Core::Internal::FancyToolButton", 3000)
            test.passes("'Continue' (debugger) button visible.")
        except:
            test.fatal("Neither 'Interrupt' nor 'Continue' button visible (Debugger).")
    return True

def __stopDebugger__():
    clickButton(waitForObject(":Debugger Toolbar.Exit Debugger_QToolButton"))
    ensureChecked("{type='Core::Internal::OutputPaneToggleButton' unnamed='1' visible='1' "
                  "window=':Qt Creator_Core::Internal::MainWindow' occurrence='3'}")
    output = waitForObject("{type='Core::OutputWindow' visible='1' windowTitle='Application Output Window'}", 20000)
    waitFor("'Debugging has finished' in str(output.plainText)", 20000)
    return __logDebugResult__()

def __logDebugResult__():
    try:
        result = waitForObject(":*Qt Creator.Start Debugging_Core::Internal::FancyToolButton")
        test.passes("'Start Debugging' button visible.")
    except:
        test.fail("'Start Debugging' button is not visible.")
        result = None
    if result:
        test.passes("Debugger stopped.. Qt Creator is back at normal state.")
    else:
        test.fail("Debugger seems to have not stopped...")
        logApplicationOutput()
    return result

def verifyBreakPoint(bpToVerify):
    if isinstance(bpToVerify, dict):
        fileName = bpToVerify.keys()[0]
        editor = getEditorForFileSuffix(fileName)
        if editor == None:
            return False
        textPos = editor.textCursor().position()
        line = str(editor.plainText)[:textPos].count("\n") + 1
        windowTitle = str(waitForObject(":Qt Creator_Core::Internal::MainWindow").windowTitle)
        if fileName in windowTitle:
            test.passes("Creator's window title changed according to current file")
        else:
            test.fail("Creator's window title did not change according to current file")
        if line == bpToVerify.values()[0]:
            test.passes("Breakpoint at expected line (%d) inside expected file (%s)"
                        % (line, fileName))
            return True
        else:
            test.fail("Breakpoint did not match expected line/file",
                         "Found: %d in %s" % (line, fileName))
    else:
        test.fatal("Expected a dict for bpToVerify - got '%s'" % className(bpToVerify))
    return False