aboutsummaryrefslogtreecommitdiffstats
path: root/tests/system/shared/debugger.py
blob: 5ee31dec20125c19d9c6d6c69faeb2c7a30fbe1d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
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
146
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
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
# Copyright (C) 2016 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0

def handleDebuggerWarnings(config, isMsvcBuild=False):
    if isMsvcBuild:
        try:
            popup = waitForObject("{name='msgLabel' text?='<html><head/><body>*' type='QLabel' visible='1' window=':Dialog_Debugger::Internal::SymbolPathsDialog'}", 10000)
            symServerNotConfiged = ("<html><head/><body><p>The debugger is not configured to use the public "
                                    "Microsoft Symbol Server.<br/>"
                                    "This is recommended for retrieval of the symbols of the operating system libraries.</p>"
                                    "<p><span style=\" font-style:italic;\">Note:</span> It is recommended, that if you use the Microsoft Symbol Server, "
                                    "to also use a local symbol cache.<br/>"
                                    "A fast internet connection is required for this to work smoothly,<br/>"
                                    "and a delay might occur when connecting for the first time and caching the symbols.</p>"
                                    "<p>What would you like to set up?</p></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='Cancel' type='QPushButton' unnamed='1' visible='1' window=':Dialog_Debugger::Internal::SymbolPathsDialog'}", 10000))
        except LookupError:
            pass # No warning. Fine.
    isReleaseConfig = "Release" in config and not "with Debug Information" in config
    if isReleaseConfig and (isMsvcBuild or platform.system() == "Linux"):
        msgBox = "{type='QMessageBox' unnamed='1' visible='1' windowTitle='Warning'}"
        message = waitForObject("{name='qt_msgbox_label' type='QLabel' visible='1' window=%s}" % msgBox)
        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)
        clickButton("{text='OK' type='QPushButton' unnamed='1' visible='1' window=%s}" % msgBox)

def takeDebuggerLog():
    invokeMenuItem("View", "Views", "Global Debugger Log")
    debuggerLogWindow = waitForObject("{container=':DebugModeWidget.Debugger Log_QDockWidget' "
                                      "type='QPlainTextEdit' unnamed='1' visible='1'}")
    debuggerLog = str(debuggerLogWindow.plainText)
    mouseClick(debuggerLogWindow)
    invokeContextMenuItem(debuggerLogWindow, "Clear Contents")
    waitFor("str(debuggerLogWindow.plainText)==''", 5000)
    invokeMenuItem("View", "Views", "Global Debugger Log")
    return debuggerLog

# function to set breakpoints for the current project
# on the given file,line pairs inside the given list of dicts
# the lines are treated as regular expression
# returns None on error or a list of dictionaries each containing full path and line number of a
# single breakpoint
# If you passed in the breakpoints in the right order, you can use the return value as input to
# doSimpleDebugging()
def setBreakpointsForCurrentProject(filesAndLines):
    switchViewTo(ViewConstants.DEBUG)
    removeOldBreakpoints()
    if not filesAndLines or not isinstance(filesAndLines, (list,tuple)):
        test.fatal("This function only takes a non-empty list/tuple holding dicts.")
        return None
    waitForObject(":Qt Creator_Utils::NavigationTreeView")
    breakPointList = []
    for current in filesAndLines:
        for curFile,curLine in current.items():
            if not openDocument(curFile):
                return None
            editor = getEditorForFileSuffix(curFile, True)
            if not placeCursorToLine(editor, curLine, True):
                return None
            invokeMenuItem("Debug", "Enable or Disable Breakpoint")
            filePath = str(waitForObjectExists(":Qt Creator_FilenameQComboBox").toolTip)
            breakPointList.append({filePath:lineNumberWithCursor(editor)})
            test.log('Set breakpoint in %s' % curFile, curLine)
    try:
        breakPointTreeView = waitForObject(":Breakpoints_Debugger::Internal::BreakTreeView")
        waitFor("breakPointTreeView.model().rowCount() == len(filesAndLines)", 2000)
    except:
        test.fatal("UI seems to have changed - check manually and fix this script.")
        return None
    test.compare(breakPointTreeView.model().rowCount(), len(filesAndLines),
                 'Expected %d set break points, found %d listed' %
                 (len(filesAndLines), breakPointTreeView.model().rowCount()))
    return breakPointList

# 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:
        breakPointTreeView = waitForObject(":Breakpoints_Debugger::Internal::BreakTreeView")
        model = breakPointTreeView.model()
        if model.rowCount()==0:
            test.log("No breakpoints found...")
        else:
            test.log("Found %d breakpoints - removing them" % model.rowCount())
            for currentIndex in dumpIndices(model):
                rect = breakPointTreeView.visualRect(currentIndex)
                mouseClick(breakPointTreeView, rect.x+5, rect.y+5, 0, Qt.LeftButton)
                type(breakPointTreeView, "<Delete>")
    except:
        test.fatal("UI seems to have changed - check manually and fix this script.")
        return False
    waitFor("model.rowCount() == 0", 1000)
    return test.compare(model.rowCount(), 0, "Check if all breakpoints have been removed.")

# function to do simple debugging of the current (configured) project
# param currentKit specifies the ID of the kit to use (see class Targets)
# param currentConfigName is the name of the configuration that should be used
# 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
#       This can be the return value of setBreakpointsForCurrentProject() if you
#       passed the breakpoints into that function in the right order
def doSimpleDebugging(currentKit, currentConfigName, expectedBPOrder=[], enableQml=True):

    # this function verifies if the text matches the given
    # regex inside expectedTexts
    # param text must be a single str
    # param expectedTexts can be str/list/tuple
    def regexVerify(text, expectedTexts):
        if isString(expectedTexts):
            expectedTexts = [expectedTexts]
        for curr in expectedTexts:
            pattern = re.compile(curr)
            if pattern.match(text):
                return True
        return False

    expectedLabelTexts = ['Stopped\.', 'Stopped at breakpoint \d+ in thread \d+\.']
    if len(expectedBPOrder) == 0:
        expectedLabelTexts.append("Running\.")
    switchViewTo(ViewConstants.PROJECTS)
    switchToBuildOrRunSettingsFor(currentKit, ProjectSettings.RUN)
    selectFromCombo(":EnableQMLDebugger_ComboBox", "Enabled" if enableQml else "Disabled")
    switchViewTo(ViewConstants.EDIT)
    if not __startDebugger__(currentKit, currentConfigName):
        return False
    statusLabel = findObject(":Debugger Toolbar.StatusText_Utils::StatusLabel")
    test.log("Continuing debugging %d times..." % len(expectedBPOrder))
    for expectedBP in expectedBPOrder:
        if waitFor("regexVerify(str(statusLabel.text), expectedLabelTexts)", 20000):
            verifyBreakPoint(expectedBP)
        else:
            test.fail('%s' % str(statusLabel.text))
        try:
            contDbg = waitForObject(":*Qt Creator.Continue_Core::Internal::FancyToolButton", 3000)
            test.log("Continuing...")
            clickButton(contDbg)
        except LookupError:
            test.fail("Debugger did not stop at breakpoint")
        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

# param currentKit specifies the ID of the kit to use (see class Targets)
def isMsvcConfig(currentKit):
    if platform.system() not in ('Microsoft' 'Windows'):
        return False
    switchViewTo(ViewConstants.PROJECTS)
    switchToBuildOrRunSettingsFor(currentKit, ProjectSettings.BUILD)

    waitForObject(":Projects.ProjectNavigationTreeView")
    bAndRIndex = getQModelIndexStr("text='Build & Run'", ":Projects.ProjectNavigationTreeView")
    wantedKitName = Targets.getStringForTarget(currentKit)
    wantedKitIndexString = getQModelIndexStr("text='%s'" % wantedKitName, bAndRIndex)
    if not test.verify(__kitIsActivated__(findObject(wantedKitIndexString)),
                       "Verifying target '%s' is enabled." % wantedKitName):
        raise Exception("Kit '%s' is not activated in the project." % wantedKitName)
    index = waitForObject(wantedKitIndexString)
    toolTip = str(index.data(Qt.ToolTipRole).toString())
    compilerPattern = re.compile('<dt style="font-weight:bold">Compiler:</dt><dd>(?P<compiler>.+)'
                                 '</dd><dt style="font-weight:bold">Environment:')
    match = compilerPattern.search(toolTip)
    if match is None:
        test.warning("UI seems to have changed - failed to check for compiler.")
        return False

    compiler = str(match.group("compiler"))
    isMsvc = compiler.startswith("MSVC") or compiler.startswith("Microsoft Visual C")
    switchViewTo(ViewConstants.EDIT)
    return isMsvc

# param currentKit specifies the ID of the kit to use (see class Targets)
# param config is the name of the configuration that should be used
def __startDebugger__(currentKit, config):
    isMsvcBuild = isMsvcConfig(currentKit)
    clickButton(waitForObject(":*Qt Creator.Start Debugging_Core::Internal::FancyToolButton"))
    handleDebuggerWarnings(config, isMsvcBuild)
    try:
        mBox = waitForObject(":Failed to start application_QMessageBox", 5000)
        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 not test.verify(waitFor("object.exists(':Debugger Toolbar.Continue_QToolButton')", 60000),
                       "Verify start of debugger"):
        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(":Qt Creator_AppOutput_Core::Internal::OutputPaneToggleButton")
    output = waitForObject("{type='Core::OutputWindow' visible='1' windowTitle='Application Output Window'}")
    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 = next(iter(bpToVerify.keys()))
        editor = getEditorForFileSuffix(fileName)
        if editor:
            test.compare(waitForObject(":DebugModeWidget_QComboBox").toolTip, fileName,
                         "Verify that the right file is opened")
            line = lineNumberWithCursor(editor)
            windowTitle = str(waitForObject(":Qt Creator_Core::Internal::MainWindow").windowTitle)
            test.verify(windowTitle.startswith(os.path.basename(fileName) + " "),
                        "Verify that Creator's window title changed according to current file")
            return test.compare(line, next(iter(bpToVerify.values())),
                                "Compare hit breakpoint to expected line number in %s" % fileName)
    else:
        test.fatal("Expected a dict for bpToVerify - got '%s'" % className(bpToVerify))
    return False