forked from qt-creator/qt-creator
Do no more use pixels as offset if we can handle this without. Clicking on items by using an (x, y) offset will likely fail if running on a machine with a different DPI setting. Change-Id: I0e5a4985104bd1d68aadf8c5534583fa1b048edb Reviewed-by: Robert Loehning <robert.loehning@qt.io>
395 lines
18 KiB
Python
395 lines
18 KiB
Python
############################################################################
|
|
#
|
|
# 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.
|
|
#
|
|
############################################################################
|
|
|
|
source("../../shared/qtcreator.py")
|
|
|
|
currentSelectedTreeItem = None
|
|
warningOrError = re.compile('<p><b>((Error|Warning).*?)</p>')
|
|
|
|
def main():
|
|
global appContext
|
|
emptySettings = tempDir()
|
|
__createMinimumIni__(emptySettings)
|
|
appContext = startQC(['-settingspath', '"%s"' % emptySettings], False)
|
|
if not startedWithoutPluginError():
|
|
return
|
|
invokeMenuItem("Tools", "Options...")
|
|
__checkKits__()
|
|
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 __checkKits__():
|
|
mouseClick(waitForObjectItem(":Options_QListView", "Kits"))
|
|
# check compilers
|
|
expectedCompilers = __getExpectedCompilers__()
|
|
foundCompilers = []
|
|
foundCompilerNames = []
|
|
clickOnTab(":Options.qt_tabwidget_tabbar_QTabBar", "Compilers")
|
|
__iterateTree__(":BuildAndRun_QTreeView", __compFunc__, foundCompilers, foundCompilerNames)
|
|
test.verify(__compareCompilers__(foundCompilers, expectedCompilers),
|
|
"Verifying found and expected compilers are equal.")
|
|
# check debugger
|
|
expectedDebuggers = __getExpectedDebuggers__()
|
|
clickOnTab(":Options.qt_tabwidget_tabbar_QTabBar", "Debuggers")
|
|
foundDebugger = []
|
|
__iterateTree__(":BuildAndRun_QTreeView", __dbgFunc__, foundDebugger)
|
|
test.verify(__compareDebuggers__(foundDebugger, expectedDebuggers),
|
|
"Verifying found and expected debuggers are equal.")
|
|
# check Qt versions
|
|
qmakePath = which("qmake")
|
|
foundQt = []
|
|
clickOnTab(":Options.qt_tabwidget_tabbar_QTabBar", "Qt Versions")
|
|
__iterateTree__(":qtdirList_QTreeView", __qtFunc__, foundQt, qmakePath)
|
|
test.verify(not qmakePath or len(foundQt) == 1,
|
|
"Was qmake from %s autodetected? Found %s" % (qmakePath, foundQt))
|
|
if foundQt:
|
|
foundQt = foundQt[0] # qmake from "which" should be used in kits
|
|
# check kits
|
|
clickOnTab(":Options.qt_tabwidget_tabbar_QTabBar", "Kits")
|
|
__iterateTree__(":BuildAndRun_QTreeView", __kitFunc__, foundQt, foundCompilerNames)
|
|
|
|
def __processSubItems__(treeObjStr, section, parModelIndexStr, doneItems,
|
|
additionalFunc, *additionalParameters):
|
|
global currentSelectedTreeItem
|
|
tree = waitForObject(treeObjStr)
|
|
model = tree.model()
|
|
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)
|
|
tree.scrollTo(it)
|
|
mouseClick(currentSelectedTreeItem)
|
|
additionalFunc(indexName, *additionalParameters)
|
|
currentSelectedTreeItem = None
|
|
if model.rowCount(it) > 0:
|
|
__processSubItems__(treeObjStr, it, itObj, doneItems,
|
|
additionalFunc, *additionalParameters)
|
|
|
|
def __iterateTree__(treeObjStr, additionalFunc, *additionalParameters):
|
|
global currentSelectedTreeItem
|
|
model = waitForObject(treeObjStr).model()
|
|
# 1st row: Auto-detected, 2nd row: Manual
|
|
for sect in dumpIndices(model):
|
|
doneItems = []
|
|
parentModelIndex = "%s container='%s'}" % (objectMap.realName(sect)[:-1], treeObjStr)
|
|
__processSubItems__(treeObjStr, sect, parentModelIndex, doneItems,
|
|
additionalFunc, *additionalParameters)
|
|
|
|
def __compFunc__(it, foundComp, foundCompNames):
|
|
# skip sub section items (will continue on its children)
|
|
if str(it) == "C" or str(it) == "C++":
|
|
return
|
|
try:
|
|
waitFor("object.exists(':Path.Utils_BaseValidatingLineEdit')", 1000)
|
|
pathLineEdit = findObject(":Path.Utils_BaseValidatingLineEdit")
|
|
foundComp.append(str(pathLineEdit.text))
|
|
except:
|
|
varsBatCombo = waitForObjectExists("{name='varsBatCombo' type='QComboBox' visible='1'}")
|
|
foundComp.append({it:str(varsBatCombo.currentText)})
|
|
|
|
foundCompNames.append(it)
|
|
|
|
def __dbgFunc__(it, foundDbg):
|
|
waitFor("object.exists(':Path.Utils_BaseValidatingLineEdit')", 2000)
|
|
pathLineEdit = findObject(":Path.Utils_BaseValidatingLineEdit")
|
|
foundDbg.append(str(pathLineEdit.text))
|
|
|
|
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()
|
|
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)
|
|
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.")
|
|
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.")
|
|
|
|
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.")
|
|
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 __extendExpectedCompilersWithInternalClang__(expected):
|
|
global appContext
|
|
# QC ships a clang itself
|
|
regex = '^(.*(qtcreator(.exe)?|Qt Creator))( .*)?$' # QC with optional arguments
|
|
qcPath = re.match(regex, appContext.commandLine)
|
|
if qcPath is None:
|
|
test.warning("Regular expression failed.")
|
|
else:
|
|
qcPath = qcPath.group(1)
|
|
if platform.system() == 'Darwin':
|
|
internalClang = os.path.join(qcPath, '..', '..', 'Resources')
|
|
elif platform.system() in ('Windows', 'Microsoft'):
|
|
internalClang = os.path.join(qcPath, '..')
|
|
else:
|
|
internalClang = os.path.join(qcPath, '..', '..', 'libexec', 'qtcreator')
|
|
internalClang = os.path.join(internalClang, 'clang', 'bin', 'clang')
|
|
if platform.system() in ('Microsoft', 'Windows'):
|
|
internalClang += '-cl.exe'
|
|
internalClang = os.path.abspath(internalClang)
|
|
if os.path.exists(internalClang):
|
|
if platform.system() in ('Microsoft', 'Windows'):
|
|
# just add a fuzzy comparable name - everything else is not worth the effort here
|
|
expected.append({'^Default LLVM \d{2} bit based on MSVC\d{4}$':''})
|
|
else:
|
|
expected.append(internalClang)
|
|
else:
|
|
test.fail("QC package seems to be faulty - missing internal provided clang.\nIf this "
|
|
"is not a package, but a self-compiled QC, just copy the clang executable "
|
|
"located inside the LLVM_INSTALL_DIR/bin (used while building) to the "
|
|
"expected path.", "Expected '%s'" % internalClang)
|
|
|
|
def __getExpectedCompilers__():
|
|
# TODO: enhance this to distinguish between C and C++ compilers
|
|
expected = []
|
|
if platform.system() in ('Microsoft', 'Windows'):
|
|
expected.extend(__getWinCompilers__())
|
|
compilers = ["g++", "gcc"]
|
|
if platform.system() in ('Linux', 'Darwin'):
|
|
compilers.extend(["clang++", "clang", "afl-clang"])
|
|
compilers.extend(findAllFilesInPATH("clang-[0-9].[0-9]"))
|
|
compilers.extend(findAllFilesInPATH("*g++*"))
|
|
compilers.extend(findAllFilesInPATH("*gcc*"))
|
|
if platform.system() == 'Darwin':
|
|
for compilerExe in ('clang++', 'clang'):
|
|
xcodeClang = getOutputFromCmdline(["xcrun", "--find", compilerExe]).strip("\n")
|
|
if xcodeClang and os.path.exists(xcodeClang) and xcodeClang not in expected:
|
|
expected.append(xcodeClang)
|
|
|
|
__extendExpectedCompilersWithInternalClang__(expected)
|
|
|
|
for compiler in compilers:
|
|
compilerPath = which(compiler)
|
|
if compilerPath:
|
|
if compiler.endswith('clang++') or compiler.endswith('clang'):
|
|
if subprocess.call([compiler, '-dumpmachine']) != 0:
|
|
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"):
|
|
envvar = os.getenv(testData.field(record, "envvar"))
|
|
if not envvar:
|
|
continue
|
|
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(",")
|
|
idePath = testData.field(record, "IDEPath")
|
|
if len(idePath):
|
|
if not os.path.exists(os.path.abspath(os.path.join(envvar, idePath))):
|
|
continue
|
|
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
|
|
|
|
def __getExpectedDebuggers__():
|
|
exeSuffix = ""
|
|
result = []
|
|
if platform.system() in ('Microsoft', 'Windows'):
|
|
result.extend(__getCDB__())
|
|
exeSuffix = ".exe"
|
|
for debugger in ["gdb", "lldb"]:
|
|
result.extend(findAllFilesInPATH(debugger + exeSuffix))
|
|
if platform.system() == 'Linux':
|
|
result.extend(filter(lambda s: not ("lldb-platform" in s or "lldb-gdbserver" in s),
|
|
findAllFilesInPATH("lldb-*")))
|
|
if platform.system() == 'Darwin':
|
|
xcodeLLDB = getOutputFromCmdline(["xcrun", "--find", "lldb"]).strip("\n")
|
|
if xcodeLLDB and os.path.exists(xcodeLLDB) and xcodeLLDB not in result:
|
|
result.append(xcodeLLDB)
|
|
return result
|
|
|
|
def __getCDB__():
|
|
result = []
|
|
possibleLocations = ["C:\\Program Files\\Debugging Tools for Windows (x64)",
|
|
"C:\\Program Files (x86)\\Debugging Tools for Windows (x86)",
|
|
"C:\\Program Files (x86)\\Windows Kits\\8.0\\Debuggers\\x86",
|
|
"C:\\Program Files (x86)\\Windows Kits\\8.0\\Debuggers\\x64",
|
|
"C:\\Program Files\\Windows Kits\\8.0\\Debuggers\\x86",
|
|
"C:\\Program Files\\Windows Kits\\8.0\\Debuggers\\x64",
|
|
"C:\\Program Files (x86)\\Windows Kits\\8.1\\Debuggers\\x86",
|
|
"C:\\Program Files (x86)\\Windows Kits\\8.1\\Debuggers\\x64",
|
|
"C:\\Program Files\\Windows Kits\\8.1\\Debuggers\\x86",
|
|
"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"]
|
|
for cdbPath in possibleLocations:
|
|
cdb = os.path.join(cdbPath, "cdb.exe")
|
|
if os.path.exists(cdb):
|
|
result.append(cdb)
|
|
return result
|
|
|
|
def __compareCompilers__(foundCompilers, expectedCompilers):
|
|
# TODO: Check if all expected compilers were found
|
|
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]
|
|
# special case for (fuzzy) regex comparison on Windows (internal LLVM)
|
|
if isWin and key.startswith('^') and key.endswith('$'):
|
|
if re.match(key, currentFound.keys()[0], flags):
|
|
test.verify(os.path.exists(currentFound.values()[0].rsplit(" ", 1)[0]),
|
|
"Verifying whether shipped clang got set up.")
|
|
foundExp = True
|
|
break
|
|
# 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
|
|
equal = foundExp
|
|
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
|
|
|
|
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)
|
|
return test.compare(foundSet, expectedSet,
|
|
"Verifying expected and found debuggers match.")
|
|
|
|
def __lowerStrs__(iterable):
|
|
for it in iterable:
|
|
if isinstance(it, (str, unicode)):
|
|
yield it.lower()
|
|
else:
|
|
yield it
|
|
|
|
def __checkCreatedSettings__(settingsFolder):
|
|
waitForCleanShutdown()
|
|
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"))
|
|
files.extend([{os.path.join(folders[0], "debuggers.xml"):0},
|
|
{os.path.join(folders[0], "devices.xml"):0},
|
|
{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))
|
|
|
|
def findAllFilesInPATH(programGlob):
|
|
result = []
|
|
for path in os.environ["PATH"].split(os.pathsep):
|
|
for curr in glob.glob(os.path.join(path, programGlob)):
|
|
if os.path.isfile(curr) and os.access(curr, os.X_OK):
|
|
result.append(curr)
|
|
return result
|