test.py 16.5 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 28 29 30 31 32 33 34 35 36 37 38 39
source("../../shared/qtcreator.py")

import re
import tempfile
import __builtin__

currentSelectedTreeItem = None
warningOrError = re.compile('<p><b>((Error|Warning).*?)</p>')

def main():
    emptySettings = tempDir()
    __createMinimumIni__(emptySettings)
    SettingsPath = ' -settingspath "%s"' % emptySettings
    startApplication("qtcreator" + SettingsPath)
40 41
    if not startedWithoutPluginError():
        return
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
    invokeMenuItem("Tools", "Options...")
    __checkBuildAndRun__()
    clickButton(waitForObject(":Options.Cancel_QPushButton"))
    invokeMenuItem("File", "Exit")
    __checkCreatedSettings__(emptySettings)

def __createMinimumIni__(emptyParent):
    qtProjDir = os.path.join(emptyParent, "QtProject")
    os.mkdir(qtProjDir)
    iniFile = open(os.path.join(qtProjDir, "QtCreator.ini"), "w")
    iniFile.write("[%General]\n")
    iniFile.write("OverrideLanguage=C\n")
    iniFile.close()

def __checkBuildAndRun__():
    waitForObjectItem(":Options_QListView", "Build & Run")
    clickItem(":Options_QListView", "Build & Run", 14, 15, 0, Qt.LeftButton)
    # check compilers
    expectedCompilers = __getExpectedCompilers__()
    foundCompilers = []
    foundCompilerNames = []
63
    clickOnTab(":Options.qt_tabwidget_tabbar_QTabBar", "Compilers")
64
    __iterateTree__(":BuildAndRun_QTreeView", __compFunc__, foundCompilers, foundCompilerNames)
65 66
    test.verify(__compareCompilers__(foundCompilers, expectedCompilers),
                "Verifying found and expected compilers are equal.")
67 68 69 70
    # check debugger
    expectedDebuggers = __getExpectedDebuggers__()
    clickOnTab(":Options.qt_tabwidget_tabbar_QTabBar", "Debuggers")
    foundDebugger = []
71
    __iterateTree__(":BuildAndRun_QTreeView", __dbgFunc__, foundDebugger)
72 73
    test.verify(__compareDebuggers__(foundDebugger, expectedDebuggers),
                "Verifying found and expected debuggers are equal.")
74 75 76
    # check Qt versions
    qmakePath = which("qmake")
    foundQt = []
77
    clickOnTab(":Options.qt_tabwidget_tabbar_QTabBar", "Qt Versions")
78
    __iterateTree__(":qtdirList_QTreeView", __qtFunc__, foundQt, qmakePath)
79 80
    test.verify(not qmakePath or len(foundQt) == 1,
                "Was qmake from %s autodetected? Found %s" % (qmakePath, foundQt))
81
    if foundQt:
82
        foundQt = foundQt[0]    # qmake from "which" should be used in kits
83
    # check kits
84
    clickOnTab(":Options.qt_tabwidget_tabbar_QTabBar", "Kits")
85
    __iterateTree__(":BuildAndRun_QTreeView", __kitFunc__, foundQt, foundCompilerNames)
86

87 88 89
def __processSubItems__(treeObjStr, section, parModelIndexStr, doneItems,
                        additionalFunc, *additionalParameters):
    global currentSelectedTreeItem
90 91
    tree = waitForObject(treeObjStr)
    model = tree.model()
92 93 94 95 96 97 98 99 100
    items = dumpIndices(model, section)
    for it in items:
        indexName = str(it.data().toString())
        itObj = "%s container=%s}" % (objectMap.realName(it)[:-1], parModelIndexStr)
        alreadyDone = doneItems.count(itObj)
        doneItems.append(itObj)
        if alreadyDone:
            itObj = "%s occurrence='%d'}" % (itObj[:-1], alreadyDone + 1)
        currentSelectedTreeItem = waitForObject(itObj, 3000)
101
        tree.scrollTo(it)
102 103 104 105 106 107 108
        mouseClick(currentSelectedTreeItem, 5, 5, 0, Qt.LeftButton)
        additionalFunc(indexName, *additionalParameters)
        currentSelectedTreeItem = None
        if model.rowCount(it) > 0:
            __processSubItems__(treeObjStr, it, itObj, doneItems,
                                additionalFunc, *additionalParameters)

109
def __iterateTree__(treeObjStr, additionalFunc, *additionalParameters):
110
    global currentSelectedTreeItem
111
    model = waitForObject(treeObjStr).model()
112 113 114
    # 1st row: Auto-detected, 2nd row: Manual
    for sect in dumpIndices(model):
        doneItems = []
115 116 117
        parentModelIndex = "%s container='%s'}" % (objectMap.realName(sect)[:-1], treeObjStr)
        __processSubItems__(treeObjStr, sect, parentModelIndex, doneItems,
                            additionalFunc, *additionalParameters)
118 119

def __compFunc__(it, foundComp, foundCompNames):
120 121 122
    # skip sub section items (will continue on its children)
    if str(it) == "C" or str(it) == "C++":
        return
123
    try:
124 125
        waitFor("object.exists(':Path.Utils_BaseValidatingLineEdit')", 1000)
        pathLineEdit = findObject(":Path.Utils_BaseValidatingLineEdit")
126 127 128 129 130 131 132 133
        foundComp.append(str(pathLineEdit.text))
    except:
        label = findObject("{buddy={container=':qt_tabwidget_stackedwidget_QWidget' "
                           "text='Initialization:' type='QLabel' unnamed='1' visible='1'} "
                           "type='QLabel' unnamed='1' visible='1'}")
        foundComp.append({it:str(label.text)})
    foundCompNames.append(it)

134
def __dbgFunc__(it, foundDbg):
135
    waitFor("object.exists(':Path.Utils_BaseValidatingLineEdit')", 2000)
136 137 138
    pathLineEdit = findObject(":Path.Utils_BaseValidatingLineEdit")
    foundDbg.append(str(pathLineEdit.text))

139 140 141 142 143
def __qtFunc__(it, foundQt, qmakePath):
    qtPath = str(waitForObject(":QtSupport__Internal__QtVersionManager.qmake_QLabel").text)
    if platform.system() in ('Microsoft', 'Windows'):
        qtPath = qtPath.lower()
        qmakePath = qmakePath.lower()
144 145 146 147 148 149
    test.verify(os.path.isfile(qtPath) and os.access(qtPath, os.X_OK),
                "Verifying found Qt (%s) is executable." % qtPath)
    # Two Qt versions will be found when using qtchooser: QTCREATORBUG-14697
    # Only add qmake from "which" to list
    if qtPath == qmakePath:
        foundQt.append(it)
150 151 152 153 154 155 156 157 158 159 160 161
    try:
        errorLabel = findObject(":QtSupport__Internal__QtVersionManager.errorLabel.QLabel")
        test.warning("Detected error or warning: '%s'" % errorLabel.text)
    except:
        pass

def __kitFunc__(it, foundQt, foundCompNames):
    global currentSelectedTreeItem, warningOrError
    qtVersionStr = str(waitForObject(":Kits_QtVersion_QComboBox").currentText)
    test.compare(it, "Desktop (default)", "Verifying whether default Desktop kit has been created.")
    if foundQt:
        test.compare(qtVersionStr, foundQt, "Verifying if Qt versions match.")
162 163 164 165 166 167
    cCompilerCombo = findObject(":CCompiler:_QComboBox")
    test.compare(cCompilerCombo.enabled, cCompilerCombo.count > 1,
                 "Verifying whether C compiler combo is enabled/disabled correctly.")
    cppCompilerCombo = findObject(":CppCompiler:_QComboBox")
    test.compare(cppCompilerCombo.enabled, cppCompilerCombo.count > 1,
                 "Verifying whether C++ compiler combo is enabled/disabled correctly.")
168

169 170 171 172
    test.verify(str(cCompilerCombo.currentText) in foundCompNames,
                "Verifying if one of the found C compilers had been set.")
    test.verify(str(cppCompilerCombo.currentText) in foundCompNames,
                "Verifying if one of the found C++ compilers had been set.")
173 174 175 176 177 178 179 180
    if currentSelectedTreeItem:
        foundWarningOrError = warningOrError.search(str(currentSelectedTreeItem.toolTip))
        if foundWarningOrError:
            details = str(foundWarningOrError.group(1)).replace("<br>", "\n")
            details = details.replace("<b>", "").replace("</b>", "")
            test.warning("Detected error and/or warning: %s" % details)

def __getExpectedCompilers__():
181
    # TODO: enhance this to distinguish between C and C++ compilers
182 183 184
    expected = []
    if platform.system() in ('Microsoft', 'Windows'):
        expected.extend(__getWinCompilers__())
185
    compilers = ["g++", "gcc"]
186
    if platform.system() in ('Linux', 'Darwin'):
187 188 189
        compilers.extend(["clang++", "clang"])
        compilers.extend(findAllFilesInPATH("*g++*"))
        compilers.extend(findAllFilesInPATH("*gcc*"))
190
    if platform.system() == 'Darwin':
191
        xcodeClang = getOutputFromCmdline(["xcrun", "--find", "clang++"]).strip("\n")
192 193
        if xcodeClang and os.path.exists(xcodeClang) and xcodeClang not in expected:
            expected.append(xcodeClang)
194 195 196
    for compiler in compilers:
        compilerPath = which(compiler)
        if compilerPath:
197
            if compiler.endswith('clang++') or compiler.endswith('clang'):
198
                if subprocess.call([compiler, '-dumpmachine']) != 0:
199 200 201 202 203 204 205 206
                    test.warning("clang found in PATH, but version is not supported.")
                    continue
            expected.append(compilerPath)
    return expected

def __getWinCompilers__():
    result = []
    for record in testData.dataset("win_compiler_paths.tsv"):
207 208 209
        envvar = os.getenv(testData.field(record, "envvar"))
        if not envvar:
            continue
210 211 212 213 214
        compiler = os.path.abspath(os.path.join(envvar, testData.field(record, "path"),
                                                testData.field(record, "file")))
        if os.path.exists(compiler):
            parameters = testData.field(record, "displayedParameters").split(",")
            usedParameters = testData.field(record, "usedParameters").split(",")
215 216 217 218
            idePath = testData.field(record, "IDEPath")
            if len(idePath):
                if not os.path.exists(os.path.abspath(os.path.join(envvar, idePath))):
                    continue
219 220 221 222 223 224 225 226 227 228 229 230
            if testData.field(record, "isSDK") == "true":
                for para, used in zip(parameters, usedParameters):
                    result.append(
                                  {"%s \(.*?\) \(%s\)" % (testData.field(record, 'displayName'),
                                                          para)
                                   :"%s %s" % (compiler, used)})
            else:
                for para, used in zip(parameters, usedParameters):
                    result.append({"%s (%s)" % (testData.field(record, 'displayName'), para)
                                   :"%s %s" % (compiler, used)})
    return result

231
def __getExpectedDebuggers__():
232
    exeSuffix = ""
233 234 235
    result = []
    if platform.system() in ('Microsoft', 'Windows'):
        result.extend(__getCDB__())
236
        exeSuffix = ".exe"
237
    for debugger in ["gdb", "lldb"]:
238
        result.extend(findAllFilesInPATH(debugger + exeSuffix))
239
    if platform.system() == 'Linux':
240 241
        result.extend(filter(lambda s: not ("lldb-platform" in s or "lldb-gdbserver" in s),
                             findAllFilesInPATH("lldb-*")))
242
    if platform.system() == 'Darwin':
243
        xcodeLLDB = getOutputFromCmdline(["xcrun", "--find", "lldb"]).strip("\n")
244
        if xcodeLLDB and os.path.exists(xcodeLLDB) and xcodeLLDB not in result:
245
            result.append(xcodeLLDB)
246 247 248 249 250
    return result

def __getCDB__():
    result = []
    possibleLocations = ["C:\\Program Files\\Debugging Tools for Windows (x64)",
251
                         "C:\\Program Files (x86)\\Debugging Tools for Windows (x86)",
252
                         "C:\\Program Files (x86)\\Windows Kits\\8.0\\Debuggers\\x86",
253
                         "C:\\Program Files (x86)\\Windows Kits\\8.0\\Debuggers\\x64",
254
                         "C:\\Program Files\\Windows Kits\\8.0\\Debuggers\\x86",
255
                         "C:\\Program Files\\Windows Kits\\8.0\\Debuggers\\x64",
256
                         "C:\\Program Files (x86)\\Windows Kits\\8.1\\Debuggers\\x86",
257 258
                         "C:\\Program Files (x86)\\Windows Kits\\8.1\\Debuggers\\x64",
                         "C:\\Program Files\\Windows Kits\\8.1\\Debuggers\\x86",
259 260 261
                         "C:\\Program Files\\Windows Kits\\8.1\\Debuggers\\x64",
                         "C:\\Program Files (x86)\\Windows Kits\\10\\Debuggers\\x86",
                         "C:\\Program Files (x86)\\Windows Kits\\10\\Debuggers\\x64"]
262 263 264 265 266
    for cdbPath in possibleLocations:
        cdb = os.path.join(cdbPath, "cdb.exe")
        if os.path.exists(cdb):
            result.append(cdb)
    return result
267 268

def __compareCompilers__(foundCompilers, expectedCompilers):
269
    # TODO: Check if all expected compilers were found
270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290
    equal = True
    flags = 0
    isWin = platform.system() in ('Microsoft', 'Windows')
    if isWin:
        flags = re.IGNORECASE
    for currentFound in foundCompilers:
        if isinstance(currentFound, dict):
            foundExp = False
            for currentExp in expectedCompilers:
                if isinstance(currentExp, (str, unicode)):
                    continue
                key = currentExp.keys()[0]
                # the regex .*? is used for the different possible version strings of the WinSDK
                # if it's present a regex will be validated otherwise simple string comparison
                if (((".*?" in key and re.match(key, currentFound.keys()[0], flags))
                    or currentFound.keys() == currentExp.keys())):
                    if ((isWin and os.path.abspath(currentFound.values()[0].lower())
                         == os.path.abspath(currentExp.values()[0].lower()))
                        or currentFound.values() == currentExp.values()):
                        foundExp = True
                        break
291
            equal = foundExp
292 293 294 295 296 297 298 299 300 301 302
        else:
            if isWin:
                equal = currentFound.lower() in __lowerStrs__(expectedCompilers)
            else:
                equal = currentFound in expectedCompilers
        if not equal:
            test.fail("Found '%s' but was not expected." % str(currentFound),
                      str(expectedCompilers))
            break
    return equal

303 304 305 306 307 308 309 310 311 312 313
def __compareDebuggers__(foundDebuggers, expectedDebuggers):
    if not len(foundDebuggers) == len(expectedDebuggers):
        test.log("Number of found and expected debuggers do not match.",
                 "Found: %s\nExpected: %s" % (str(foundDebuggers), str(expectedDebuggers)))
        return False
    if platform.system() in ('Microsoft', 'Windows'):
        foundSet = set(__lowerStrs__(foundDebuggers))
        expectedSet = set(__lowerStrs__(expectedDebuggers))
    else:
        foundSet = set(foundDebuggers)
        expectedSet = set(expectedDebuggers)
314 315
    return test.compare(foundSet, expectedSet,
                        "Verifying expected and found debuggers match.")
316

317 318 319 320 321 322 323 324
def __lowerStrs__(iterable):
    for it in iterable:
        if isinstance(it, (str, unicode)):
            yield it.lower()
        else:
            yield it

def __checkCreatedSettings__(settingsFolder):
325
    waitForCleanShutdown()
326 327 328 329 330
    qtProj = os.path.join(settingsFolder, "QtProject")
    folders = []
    files = [{os.path.join(qtProj, "QtCreator.db"):0},
             {os.path.join(qtProj, "QtCreator.ini"):30}]
    folders.append(os.path.join(qtProj, "qtcreator"))
331 332
    files.extend([{os.path.join(folders[0], "debuggers.xml"):0},
                  {os.path.join(folders[0], "devices.xml"):0},
333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349
                  {os.path.join(folders[0], "helpcollection.qhc"):0},
                  {os.path.join(folders[0], "profiles.xml"):0},
                  {os.path.join(folders[0], "qtversion.xml"):0},
                  {os.path.join(folders[0], "toolchains.xml"):0}])
    folders.extend([os.path.join(folders[0], "generic-highlighter"),
                    os.path.join(folders[0], "macros")])
    for f in folders:
        test.verify(os.path.isdir(f),
                    "Verifying whether folder '%s' has been created." % os.path.basename(f))
    for f in files:
        fName = f.keys()[0]
        fMinSize = f.values()[0]
        text = "created non-empty"
        if fMinSize > 0:
            text = "modified"
        test.verify(os.path.isfile(fName) and os.path.getsize(fName) > fMinSize,
                    "Verifying whether file '%s' has been %s." % (os.path.basename(fName), text))
350 351 352 353

def findAllFilesInPATH(programGlob):
    result = []
    for path in os.environ["PATH"].split(os.pathsep):
354
        for curr in glob.glob(os.path.join(path, programGlob)):
355
            if os.path.isfile(curr) and os.access(curr, os.X_OK):
356
                result.append(curr)
357
    return result