build_utils.py 12.4 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
25
26
27
28
29
#############################################################################
##
## Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
## Contact: http://www.qt-project.org/legal
##
## 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 Digia.  For licensing terms and
## conditions see http://qt.digia.com/licensing.  For further information
## use the contact form at http://qt.digia.com/contact-us.
##
## GNU Lesser General Public License Usage
## Alternatively, this file may be used under the terms of the GNU Lesser
## General Public License version 2.1 as published by the Free Software
## Foundation and appearing in the file LICENSE.LGPL included in the
## packaging of this file.  Please review the following information to
## ensure the GNU Lesser General Public License version 2.1 requirements
## will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
##
## In addition, as a special exception, Digia gives you certain additional
## rights.  These rights are described in the Digia Qt LGPL Exception
## version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
##
#############################################################################

30
31
import re;

32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# dictionary to hold a list of all installed handler functions for all object-signalSignature pairs
installedSignalHandlers = {}
# flag to indicate whether overrideInstallLazySignalHandler() has been called already
overridenInstallLazySignalHandlers = False
# flag to indicate whether a tasks file should be created when building ends with errors
createTasksFileOnError = True
# currently used directory for tasks files
tasksFileDir = None
# counter for written tasks files
tasksFileCount = 0

# call this function to override installLazySignalHandler()
def overrideInstallLazySignalHandler():
    global overridenInstallLazySignalHandlers
    if overridenInstallLazySignalHandlers:
        return
    overridenInstallLazySignalHandlers = True
    global installLazySignalHandler
50
    installLazySignalHandler = __addSignalHandlerDict__(installLazySignalHandler)
51
52
53

# avoids adding a handler to a signal twice or more often
# do not call this function directly - use overrideInstallLazySignalHandler() instead
54
def __addSignalHandlerDict__(lazySignalHandlerFunction):
55
56
57
58
59
60
61
    global installedSignalHandlers
    def wrappedFunction(name, signalSignature, handlerFunctionName):
        handlers = installedSignalHandlers.get("%s____%s" % (name,signalSignature))
        if handlers == None:
            lazySignalHandlerFunction(name, signalSignature, handlerFunctionName)
            installedSignalHandlers.setdefault("%s____%s" % (name,signalSignature), [handlerFunctionName])
        else:
Robert Loehning's avatar
Robert Loehning committed
62
            if not handlerFunctionName in handlers:
63
64
65
66
67
68
                lazySignalHandlerFunction(name, signalSignature, handlerFunctionName)
                handlers.append(handlerFunctionName)
                installedSignalHandlers.setdefault("%s____%s" % (name,signalSignature), handlers)
    return wrappedFunction

# this method checks the last build (if there's one) and logs the number of errors, warnings and
69
# lines within the Issues output
70
71
72
73
74
75
76
77
# optional parameter can be used to tell this function if the build was expected to fail or not
def checkLastBuild(expectedToFail=False):
    try:
        # can't use waitForObject() 'cause visible is always 0
        buildProg = findObject("{type='ProjectExplorer::Internal::BuildProgress' unnamed='1' }")
    except LookupError:
        test.log("checkLastBuild called without a build")
        return
78
79
80
81
82
83
    ensureChecked(":Qt Creator_Issues_Core::Internal::OutputPaneToggleButton")
    model = waitForObject(":Qt Creator.Issues_QListView").model()
    buildIssues = dumpBuildIssues(model)
    errors = len(filter(lambda i: i[5] == "1", buildIssues))
    warnings = len(filter(lambda i: i[5] == "2", buildIssues))
    gotErrors = errors != 0
84
85
    if not (gotErrors ^ expectedToFail):
        test.passes("Errors: %s | Warnings: %s" % (errors, warnings))
86
    else:
87
        test.fail("Errors: %s | Warnings: %s" % (errors, warnings))
88
    # additional stuff - could be removed... or improved :)
89
    test.log("Rows inside issues: %d" % model.rowCount())
90
    if gotErrors and createTasksFileOnError:
91
        createTasksFile(buildIssues)
92
93
    return not gotErrors

94
95
# helper function to check the compilation when build wasn't successful
def checkCompile():
96
    ensureChecked(":Qt Creator_CompileOutput_Core::Internal::OutputPaneToggleButton")
97
    output = waitForObject(":Qt Creator.Compile Output_Core::OutputWindow")
98
    waitFor("len(str(output.plainText))>0",5000)
99
    if compileSucceeded(output.plainText):
100
101
        if os.getenv("SYSTEST_DEBUG") == "1":
            test.log("Compile Output:\n%s" % output.plainText)
102
103
        test.passes("Compile successful")
        return True
104
    else:
105
        test.fail("Compile Output:\n%s" % output.plainText)
106
107
108
109
110
        return False

def compileSucceeded(compileOutput):
    return None != re.match(".*exited normally\.\n\d\d:\d\d:\d\d: Elapsed time: "
                            "(\d:)?\d{2}:\d\d\.$", str(compileOutput), re.S)
111

112
113
114
115
116
117
118
119
120
121
def dumpBuildIssues(listModel):
    issueDump = []
    for row in range(listModel.rowCount()):
        index = listModel.index(row, 0)
        issueDump.extend([map(lambda role: index.data(role).toString(),
                              range(Qt.UserRole, Qt.UserRole + 6))])
    return issueDump

# helper method that writes a tasks file
def createTasksFile(buildIssues):
122
123
    global tasksFileDir, tasksFileCount
    if tasksFileDir == None:
124
125
126
127
128
129
130
131
132
            tasksFileDir = os.getcwd() + "/tasks"
            tasksFileDir = os.path.abspath(tasksFileDir)
    if not os.path.exists(tasksFileDir):
        try:
            os.makedirs(tasksFileDir)
        except OSError:
            test.log("Could not create %s - falling back to a temporary directory" % tasksFileDir)
            tasksFileDir = tempDir()

133
134
135
136
    tasksFileCount += 1
    outfile = os.path.join(tasksFileDir, os.path.basename(squishinfo.testCase)+"_%d.tasks" % tasksFileCount)
    file = codecs.open(outfile, "w", "utf-8")
    test.log("Writing tasks file - can take some time (according to number of issues)")
137
    rows = len(buildIssues)
138
139
140
141
    if os.environ.get("SYSTEST_DEBUG") == "1":
        firstrow = 0
    else:
        firstrow = max(0, rows - 100)
142
    for issue in buildIssues[firstrow:rows]:
143
        # the following is currently a bad work-around
144
145
146
147
        fData = issue[0] # file
        lData = issue[1] # line -> linenumber or empty
        tData = issue[5] # type -> 1==error 2==warning
        dData = issue[3] # description
148
149
150
151
152
153
154
155
156
157
158
159
        if lData == "":
            lData = "-1"
        if tData == "1":
            tData = "error"
        elif tData == "2":
            tData = "warning"
        else:
            tData = "unknown"
        file.write("%s\t%s\t%s\t%s\n" % (fData, lData, tData, dData))
    file.close()
    test.log("Written tasks file %s" % outfile)

160
161
162
# returns a list of pairs each containing the zero based number of a kit
# and the name of the matching build configuration
# param kitCount specifies the number of kits currently defined (must be correct!)
163
# param filter is a regular expression to filter the configuration by their name
164
def iterateBuildConfigs(kitCount, filter = ""):
165
    switchViewTo(ViewConstants.PROJECTS)
166
167
168
    configs = []
    for currentKit in range(kitCount):
        switchToBuildOrRunSettingsFor(kitCount, currentKit, ProjectSettings.BUILD)
169
        model = waitForObject(":scrollArea.Edit build configuration:_QComboBox").model()
170
171
172
173
174
175
        prog = re.compile(filter)
        # for each row in the model, write its data to a list
        configNames = dumpItems(model)
        # pick only those configuration names which pass the filter
        configs += zip([currentKit] * len(configNames),
                       [config for config in configNames if prog.match(config)])
176
177
178
179
180
181
182
    switchViewTo(ViewConstants.EDIT)
    return configs

# selects a build configuration for building the current project
# param targetCount specifies the number of targets currently defined (must be correct!)
# param currentTarget specifies the target for which to switch into the specified settings (zero based index)
# param configName is the name of the configuration that should be selected
183
# returns information about the selected kit, see getQtInformationForBuildSettings
184
185
186
187
def selectBuildConfig(targetCount, currentTarget, configName):
    switchViewTo(ViewConstants.PROJECTS)
    switchToBuildOrRunSettingsFor(targetCount, currentTarget, ProjectSettings.BUILD)
    if selectFromCombo(":scrollArea.Edit build configuration:_QComboBox", configName):
188
        progressBarWait(30000)
189
    return getQtInformationForBuildSettings(targetCount, True, ViewConstants.EDIT)
190

191
192
# This will not trigger a rebuild. If needed, caller has to do this.
def verifyBuildConfig(targetCount, currentTarget, shouldBeDebug=False, enableShadowBuild=False, enableQmlDebug=False):
193
194
    switchViewTo(ViewConstants.PROJECTS)
    switchToBuildOrRunSettingsFor(targetCount, currentTarget, ProjectSettings.BUILD)
195
    ensureChecked(waitForObject(":scrollArea.Details_Utils::DetailsButton"))
196
197
    ensureChecked("{name='shadowBuildCheckBox' type='QCheckBox' visible='1'}", enableShadowBuild)
    buildCfCombo = waitForObject("{type='QComboBox' name='buildConfigurationComboBox' visible='1' "
198
                                 "window=':Qt Creator_Core::Internal::MainWindow'}")
199
200
201
202
    if shouldBeDebug:
        test.compare(buildCfCombo.currentText, 'Debug', "Verifying whether it's a debug build")
    else:
        test.compare(buildCfCombo.currentText, 'Release', "Verifying whether it's a release build")
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
    if enableQmlDebug:
        try:
            libLabel = waitForObject(":scrollArea.Library not available_QLabel", 2000)
            mouseClick(libLabel, libLabel.width - 10, libLabel.height / 2, 0, Qt.LeftButton)
        except:
            pass
        # Since waitForObject waits for the object to be enabled,
        # it will wait here until compilation of the debug libraries has finished.
        qmlDebugCheckbox = waitForObject(":scrollArea.qmlDebuggingLibraryCheckBox_QCheckBox", 150000)
        if qmlDebugCheckbox.checked != enableQmlDebug:
            clickButton(qmlDebugCheckbox)
            # Don't rebuild now
            clickButton(waitForObject(":QML Debugging.No_QPushButton", 5000))
        try:
            problemFound = waitForObject("{window=':Qt Creator_Core::Internal::MainWindow' "
                                         "type='QLabel' name='problemLabel' visible='1'}", 1000)
            if problemFound:
                test.warning('%s' % problemFound.text)
        except:
            pass
    else:
        qmlDebugCheckbox = findObject(":scrollArea.qmlDebuggingLibraryCheckBox_QCheckBox")
        if qmlDebugCheckbox.enabled and qmlDebugCheckbox.checked:
            test.log("Qml debugging libraries are available - unchecking qml debugging.")
            clickButton(qmlDebugCheckbox)
            # Don't rebuild now
            clickButton(waitForObject(":QML Debugging.No_QPushButton", 5000))
230
    clickButton(waitForObject(":scrollArea.Details_Utils::DetailsButton"))
231
    switchViewTo(ViewConstants.EDIT)
232
233
234
235
236
237
238
239

# verify if building and running of project was successful
def verifyBuildAndRun():
    # check compile output if build successful
    checkCompile()
    # check application output log
    appOutput = logApplicationOutput()
    if appOutput:
240
241
        test.verify((re.search(".* exited with code \d+", str(appOutput)) or
                     re.search("The program has unexpectedly finished\.", str(appOutput))) and
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
                    re.search('[Ss]tarting.*', str(appOutput)),
                    "Verifying if built app started and closed successfully.")

# run project for debug and release
def runVerify(checkedTargets):
    availableConfigs = iterateBuildConfigs(len(checkedTargets))
    if not availableConfigs:
        test.fatal("Haven't found build configurations, quitting")
        invokeMenuItem("File", "Save All")
        invokeMenuItem("File", "Exit")
    # select debug configuration
    for kit, config in availableConfigs:
        selectBuildConfig(len(checkedTargets), kit, config)
        test.log("Using build config '%s'" % config)
        if not runAndCloseApp():
            return
        verifyBuildAndRun()
        mouseClick(waitForObject(":*Qt Creator.Clear_QToolButton"))