debugger.py 13.9 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
############################################################################
#
# Copyright (C) 2016 The Qt Company Ltd.
# Contact: https://www.qt.io/licensing/
#
# This file is part of Qt Creator.
#
# Commercial License Usage
# Licensees holding valid commercial Qt licenses may use this file in
# accordance with the commercial license agreement provided with the
# Software or, alternatively, in accordance with the terms contained in
# a written agreement between you and The Qt Company. For licensing terms
# and conditions see https://www.qt.io/terms-conditions. For further
# information use the contact form at https://www.qt.io/contact-us.
#
# GNU General Public License Usage
# Alternatively, this file may be used under the terms of the GNU
# General Public License version 3 as published by the Free Software
# Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
# included in the packaging of this file. Please review the following
# information to ensure the GNU General Public License requirements will
# be met: https://www.gnu.org/licenses/gpl-3.0.html.
#
############################################################################
25

26 27
import re

28 29
def handleDebuggerWarnings(config, isMsvcBuild=False):
    if isMsvcBuild:
30
        try:
31
            popup = waitForObject("{name='msgLabel' text?='<html><head/><body>*' type='QLabel' visible='1' window=':Dialog_Debugger::Internal::SymbolPathsDialog'}", 10000)
32 33 34 35 36 37 38 39
            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>")
40 41 42 43
            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))
44
            clickButton(waitForObject("{text='Cancel' type='QPushButton' unnamed='1' visible='1' window=':Dialog_Debugger::Internal::SymbolPathsDialog'}", 10000))
45 46
        except LookupError:
            pass # No warning. Fine.
47
    if "Release" in config and (isMsvcBuild or platform.system() == "Linux"):
48 49
        msgBox = "{type='QMessageBox' unnamed='1' visible='1' windowTitle='Warning'}"
        message = waitForObject("{name='qt_msgbox_label' type='QLabel' visible='1' window=%s}" % msgBox)
50 51 52
        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)
53
        clickButton("{text='OK' type='QPushButton' unnamed='1' visible='1' window=%s}" % msgBox)
54 55 56

def takeDebuggerLog():
    invokeMenuItem("Window", "Views", "Debugger Log")
57
    debuggerLogWindow = waitForObject("{container=':DebugModeWidget.Debugger Log_QDockWidget' type='Debugger::Internal::CombinedPane' unnamed='1' visible='1'}")
58 59
    debuggerLog = str(debuggerLogWindow.plainText)
    mouseClick(debuggerLogWindow, 5, 5, 0, Qt.LeftButton)
60
    invokeContextMenuItem(debuggerLogWindow, "Clear Contents")
61 62 63
    waitFor("str(debuggerLogWindow.plainText)==''", 5000)
    invokeMenuItem("Window", "Views", "Debugger Log")
    return debuggerLog
64 65

# function to set breakpoints for the current project
66
# on the given file,line pairs inside the given list of dicts
67 68 69 70
# the lines are treated as regular expression
def setBreakpointsForCurrentProject(filesAndLines):
    switchViewTo(ViewConstants.DEBUG)
    removeOldBreakpoints()
71 72
    if not filesAndLines or not isinstance(filesAndLines, (list,tuple)):
        test.fatal("This function only takes a non-empty list/tuple holding dicts.")
73 74
        return False
    navTree = waitForObject("{type='Utils::NavigationTreeView' unnamed='1' visible='1' "
75
                            "window=':Qt Creator_Core::Internal::MainWindow'}")
76 77
    for current in filesAndLines:
        for curFile,curLine in current.iteritems():
78 79
            if not openDocument(curFile):
                return False
80
            editor = getEditorForFileSuffix(curFile, True)
81 82 83
            if not placeCursorToLine(editor, curLine, True):
                return False
            invokeMenuItem("Debug", "Toggle Breakpoint")
84
            test.log('Set breakpoint in %s' % curFile, curLine)
85
    try:
86
        breakPointTreeView = waitForObject(":Breakpoints_Debugger::Internal::BreakTreeView")
87 88 89 90
        waitFor("breakPointTreeView.model().rowCount() == len(filesAndLines)", 2000)
    except:
        test.fatal("UI seems to have changed - check manually and fix this script.")
        return False
91 92 93 94 95 96 97 98 99 100
    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:
101
        breakPointTreeView = waitForObject(":Breakpoints_Debugger::Internal::BreakTreeView")
102 103 104 105 106
        model = breakPointTreeView.model()
        if model.rowCount()==0:
            test.log("No breakpoints found...")
        else:
            test.log("Found %d breakpoints - removing them" % model.rowCount())
107
            for currentIndex in dumpIndices(model):
108 109 110
                rect = breakPointTreeView.visualRect(currentIndex)
                mouseClick(breakPointTreeView, rect.x+5, rect.y+5, 0, Qt.LeftButton)
                type(breakPointTreeView, "<Delete>")
111 112 113
    except:
        test.fatal("UI seems to have changed - check manually and fix this script.")
        return False
114 115 116
    return test.compare(model.rowCount(), 0, "Check if all breakpoints have been removed.")

# function to do simple debugging of the current (configured) project
117 118 119
# param kitCount specifies the number of kits currently defined (must be correct!)
# param currentKit specifies the target to use (zero based index)
# param currentConfigName is the name of the configuration that should be used
120 121 122 123 124
# 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
125 126
def doSimpleDebugging(kitCount, currentKit, currentConfigName, pressContinueCount=1,
                      expectedBPOrder=[], enableQml=True):
127 128 129
    expectedLabelTexts = ['Stopped\.', 'Stopped at breakpoint \d+ \(\d+\) in thread \d+\.']
    if len(expectedBPOrder) == 0:
        expectedLabelTexts.append("Running\.")
130 131 132 133 134
    switchViewTo(ViewConstants.PROJECTS)
    switchToBuildOrRunSettingsFor(kitCount, currentKit, ProjectSettings.RUN)
    ensureChecked(waitForObject("{container=':Qt Creator.scrollArea_QScrollArea' text='Enable QML' "
                                "type='QCheckBox' unnamed='1' visible='1'}"), enableQml)
    switchViewTo(ViewConstants.EDIT)
135
    if not __startDebugger__(kitCount, currentKit, currentConfigName):
136 137 138 139 140 141 142 143
        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))
144 145 146 147 148 149
        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")
150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
        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

169 170 171 172 173 174 175 176 177 178 179 180 181 182
# param kitCount specifies the number of kits currently defined (must be correct!)
# param currentKit specifies the target to use (zero based index)
def isMsvcConfig(kitCount, currentKit):
    switchViewTo(ViewConstants.PROJECTS)
    switchToBuildOrRunSettingsFor(kitCount, currentKit, ProjectSettings.BUILD)
    isMsvc = " -spec win32-msvc" in str(waitForObject(":qmakeCallEdit").text)
    switchViewTo(ViewConstants.EDIT)
    return isMsvc

# param kitCount specifies the number of kits currently defined (must be correct!)
# param currentKit specifies the target to use (zero based index)
# param config is the name of the configuration that should be used
def __startDebugger__(kitCount, currentKit, config):
    isMsvcBuild = isMsvcConfig(kitCount, currentKit)
183
    clickButton(waitForObject(":*Qt Creator.Start Debugging_Core::Internal::FancyToolButton"))
184
    handleDebuggerWarnings(config, isMsvcBuild)
185
    try:
186
        mBox = waitForObject(":Failed to start application_QMessageBox", 5000)
187 188 189 190 191 192 193 194 195
        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
196 197
    if not test.verify(waitFor("object.exists(':Debugger Toolbar.Continue_QToolButton')", 60000),
                       "Verify start of debugger"):
198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218
        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"))
219
    ensureChecked(":Qt Creator_AppOutput_Core::Internal::OutputPaneToggleButton")
220
    output = waitForObject("{type='Core::OutputWindow' visible='1' windowTitle='Application Output Window'}")
221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241
    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)
242
        if editor:
243 244
            test.compare(waitForObject(":DebugModeWidget_QComboBox").toolTip, fileName,
                         "Verify that the right file is opened")
245 246 247
            textPos = editor.textCursor().position()
            line = str(editor.plainText)[:textPos].count("\n") + 1
            windowTitle = str(waitForObject(":Qt Creator_Core::Internal::MainWindow").windowTitle)
248
            test.verify(os.path.basename(fileName) in windowTitle,
249 250 251
                        "Verify that Creator's window title changed according to current file")
            return test.compare(line, bpToVerify.values()[0],
                                "Compare hit breakpoint to expected line number in %s" % fileName)
252 253 254
    else:
        test.fatal("Expected a dict for bpToVerify - got '%s'" % className(bpToVerify))
    return False
255 256 257 258 259 260 261 262 263 264 265 266 267

# this function removes the compiled qml-debug library from QtSDK (only necessary for Qt < 4.8)
def removeQmlDebugFolderIfExists():
    paths = [os.path.join(sdkPath, "Desktop", "Qt", "474", "gcc", "qtc-qmldbg"),
             os.path.join(sdkPath, "Desktop", "Qt", "4.7.4", "mingw", "qtc-qmldbg"),
             os.path.join(sdkPath, "Desktop", "Qt", "4.7.4", "msvc2008", "qtc-qmldbg")
             ]
    for path in paths:
        if os.path.exists(path):
            try:
                shutil.rmtree(path)
            except:
                test.warning("Error while removing '%s'" % path)