#############################################################################
##
## 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.
##
#############################################################################
import re
# this function modifies all necessary run settings to make it possible to hook into
# the application compiled by Creator
def modifyRunSettingsForHookInto(projectName, kitCount, port):
prepareBuildSettings(kitCount, 0)
# this uses the default Qt version which Creator activates when opening the project
switchViewTo(ViewConstants.PROJECTS)
switchToBuildOrRunSettingsFor(kitCount, 0, ProjectSettings.BUILD)
qtVersion, mkspec, qtBinPath, qtLibPath = getQtInformationForBuildSettings(kitCount, True)
if None in (qtVersion, mkspec, qtBinPath, qtLibPath):
test.fatal("At least one of the Qt information returned None - leaving...",
"Qt version: %s, mkspec: %s, Qt BinPath: %s, Qt LibPath: %s" %
(qtVersion, mkspec, qtBinPath, qtLibPath))
return False
qtVersion = ".".join(qtVersion.split(".")[:2])
switchToBuildOrRunSettingsFor(kitCount, 0, ProjectSettings.RUN)
result = __configureCustomExecutable__(projectName, port, mkspec, qtVersion)
if result:
ensureChecked(":RunSettingsEnvironmentDetails_Utils::DetailsButton")
envVarsTableView = waitForObject("{type='QTableView' visible='1' unnamed='1'}")
model = envVarsTableView.model()
changingVars = []
for index in dumpIndices(model):
# get var name
envVarsTableView.scrollTo(index)
varName = str(model.data(index).toString())
# if its a special SQUISH var simply unset it, SQUISH_LIBQTDIR and PATH will be replaced with Qt paths
if varName == "PATH":
test.log("Replacing PATH with '%s'" % qtBinPath)
changingVars.append("PATH=%s" % qtBinPath)
elif varName.find("SQUISH") == 0:
if varName == "SQUISH_LIBQTDIR":
if platform.system() in ('Microsoft', 'Windows'):
replacement = qtBinPath
else:
replacement = qtLibPath
test.log("Replacing SQUISH_LIBQTDIR with '%s'" % replacement)
changingVars.append("SQUISH_LIBQTDIR=%s" % replacement)
else:
changingVars.append(varName)
batchEditRunEnvironment(kitCount, 0, changingVars, True)
switchViewTo(ViewConstants.EDIT)
return result
def batchEditRunEnvironment(kitCount, currentTarget, modifications, alreadyOnRunSettings=False):
if not alreadyOnRunSettings:
switchViewTo(ViewConstants.PROJECTS)
switchToBuildOrRunSettingsFor(kitCount, currentTarget, ProjectSettings.RUN)
ensureChecked(":RunSettingsEnvironmentDetails_Utils::DetailsButton")
clickButton(waitForObject("{text='Batch Edit...' type='QPushButton' unnamed='1' visible='1' "
"window=':Qt Creator_Core::Internal::MainWindow'}"))
editor = waitForObject("{type='TextEditor::SnippetEditorWidget' unnamed='1' visible='1' "
"window=':Edit Environment_ProjectExplorer::EnvironmentItemsDialog'}")
typeLines(editor, modifications)
clickButton(waitForObject("{text='OK' type='QPushButton' unnamed='1' visible='1' "
"window=':Edit Environment_ProjectExplorer::EnvironmentItemsDialog'}"))
def modifyRunSettingsForHookIntoQtQuickUI(kitCount, workingDir, projectName, port, quickVersion=1):
switchViewTo(ViewConstants.PROJECTS)
switchToBuildOrRunSettingsFor(kitCount, 0, ProjectSettings.RUN, True)
qtVersion, mkspec, qtLibPath, qmake = getQtInformationForQmlProject()
if None in (qtVersion, mkspec, qtLibPath, qmake):
test.fatal("At least one of the Qt information returned None - leaving...",
"Qt version: %s, mkspec: %s, Qt LibPath: %s, qmake: '%s'"
% (qtVersion, mkspec, qtLibPath, qmake))
return None
squishPath = getSquishPath(mkspec, qtVersion)
if squishPath == None:
test.warning("Could not determine the Squish path for %s/%s" % (qtVersion, mkspec),
"Using fallback of pushing STOP inside Creator.")
return None
test.log("Using (QtVersion/mkspec) %s/%s with SquishPath %s" % (qtVersion, mkspec, squishPath))
if quickVersion == 1:
if platform.system() == "Darwin":
executable = os.path.abspath(os.path.dirname(qmake) + "/QMLViewer.app")
else:
executable = os.path.abspath(os.path.dirname(qmake) + "/qmlviewer")
else:
executable = os.path.abspath(os.path.dirname(qmake) + "/qmlscene")
if platform.system() in ('Microsoft', 'Windows'):
executable = executable + ".exe"
addRunConfig = waitForObject("{container={window=':Qt Creator_Core::Internal::MainWindow' "
"type='ProjectExplorer::Internal::RunSettingsWidget' unnamed='1' "
"visible='1'} occurrence='2' text='Add' type='QPushButton' "
"unnamed='1' visible='1'}")
clickButton(addRunConfig)
activateItem(waitForObject("{type='QMenu' visible='1' unnamed='1'}"), "Custom Executable")
exePathChooser = waitForObject(":Executable:_Utils::PathChooser")
exeLineEd = getChildByClass(exePathChooser, "Utils::BaseValidatingLineEdit")
argLineEd = waitForObject("{buddy={window=':Qt Creator_Core::Internal::MainWindow' "
"type='QLabel' text='Arguments:' visible='1'} type='QLineEdit' "
"unnamed='1' visible='1'}")
wdPathChooser = waitForObject("{buddy={window=':Qt Creator_Core::Internal::MainWindow' "
"text='Working directory:' type='QLabel'} "
"type='Utils::PathChooser' unnamed='1' visible='1'}")
wdLineEd = getChildByClass(wdPathChooser, "Utils::BaseValidatingLineEdit")
startAUT = os.path.abspath(squishPath + "/bin/startaut")
if platform.system() in ('Microsoft', 'Windows'):
startAUT = startAUT + ".exe"
projectPath = os.path.abspath("%s/%s" % (workingDir, projectName))
replaceEditorContent(exeLineEd, startAUT)
replaceEditorContent(argLineEd, "--verbose --port=%d %s %s.qml"
% (port, executable, projectName))
replaceEditorContent(wdLineEd, projectPath)
clickButton(waitForObject("{text='Details' type='Utils::DetailsButton' unnamed='1' visible='1' "
"window=':Qt Creator_Core::Internal::MainWindow' "
"leftWidget={type='QLabel' text~='Us(e|ing) Build Environment'"
" unnamed='1' visible='1'}}"))
for varName in ("PATH", "SQUISH_LIBQTDIR"):
__addVariableToRunEnvironment__(varName, qtLibPath)
if not platform.system() in ('Microsoft', 'Windows', 'Darwin'):
__addVariableToRunEnvironment__("LD_LIBRARY_PATH", qtLibPath)
if platform.system() == "Darwin":
__addVariableToRunEnvironment__("DYLD_FRAMEWORK_PATH", qtLibPath)
if not platform.system() in ('Microsoft', 'Windows'):
if not os.getenv("DISPLAY"):
__addVariableToRunEnvironment__("DISPLAY", ":0.0")
result = executable
switchViewTo(ViewConstants.EDIT)
return result
# this helper method must be called on the run settings page of a Qt Quick UI with DetailsWidget
# for the run settings already opened - it won't work on other views because of a different layout
def __addVariableToRunEnvironment__(name, value):
clickButton(waitForObject("{text='Add' type='QPushButton' unnamed='1' visible='1' "
"container={window=':Qt Creator_Core::Internal::MainWindow' "
"type='Utils::DetailsWidget' unnamed='1' visible='1' occurrence='2'}}"))
varNameLineEd = waitForObject("{type='QExpandingLineEdit' visible='1' unnamed='1'}")
doubleClick(varNameLineEd)
replaceEditorContent(varNameLineEd, name)
type(varNameLineEd, "")
row = getTableRowOf(name, ":Qt Creator_QTableView")
if row == -1:
test.fatal("Could not find entered variable name inside table - skipping entering value.")
return
valueLineEd = __doubleClickQTableView__(":Qt Creator_QTableView", row, 1)
replaceEditorContent(valueLineEd, value)
type(valueLineEd, "")
def getTableRowOf(value, table):
tblModel = waitForObject(table).model()
items = dumpItems(tblModel)
if value in items:
return items.index(value)
else:
return -1
def __getMkspecFromQMakeConf__(qmakeConf):
if qmakeConf==None or not os.path.exists(qmakeConf):
return None
if not platform.system() in ('Microsoft', 'Windows'):
return os.path.basename(os.path.realpath(os.path.dirname(qmakeConf)))
mkspec = None
file = codecs.open(qmakeConf, "r", "utf-8")
for line in file:
if "QMAKESPEC_ORIGINAL" in line:
mkspec = line.split("=")[1]
break
file.close()
if mkspec == None:
test.warning("Could not determine mkspec from '%s'" % qmakeConf)
return None
return os.path.basename(mkspec)
def __getMkspecFromQmake__(qmakeCall):
if getOutputFromCmdline("%s -query QT_VERSION" % qmakeCall).strip().startswith("5."):
return getOutputFromCmdline("%s -query QMAKE_XSPEC" % qmakeCall).strip()
else:
QmakeConfPath = getOutputFromCmdline("%s -query QMAKE_MKSPECS" % qmakeCall).strip()
for tmpPath in QmakeConfPath.split(os.pathsep):
tmpPath = tmpPath + os.sep + "default" + os.sep +"qmake.conf"
result = __getMkspecFromQMakeConf__(tmpPath)
if result != None:
return result.strip()
test.warning("Could not find qmake.conf inside provided QMAKE_MKSPECS path",
"QMAKE_MKSPECS returned: '%s'" % QmakeConfPath)
return None
# helper that double clicks the table view at specified row and column
# returns the QExpandingLineEdit (the editable table cell)
def __doubleClickQTableView__(qtableView, row, column):
doubleClick(waitForObject("{container='%s' "
"type='QModelIndex' row='%d' column='%d'}" % (qtableView, row, column)), 5, 5, 0, Qt.LeftButton)
return waitForObject("{type='QExpandingLineEdit' visible='1' unnamed='1'}")
# this function configures the custom executable onto the run settings page (using startaut from Squish)
def __configureCustomExecutable__(projectName, port, mkspec, qmakeVersion):
startAUT = getSquishPath(mkspec, qmakeVersion)
if startAUT == None:
test.warning("Something went wrong determining the right Squish for %s / %s combination - "
"using fallback without hooking into subprocess." % (qmakeVersion, mkspec))
return False
else:
startAUT = os.path.abspath(startAUT + "/bin/startaut")
if platform.system() in ('Microsoft', 'Windows'):
startAUT += ".exe"
if not os.path.exists(startAUT):
test.warning("Configured Squish directory seems to be missing - using fallback without hooking into subprocess.",
"Failed to find '%s'" % startAUT)
return False
addButton = waitForObject("{container={window=':Qt Creator_Core::Internal::MainWindow' "
"type='ProjectExplorer::Internal::RunSettingsWidget' unnamed='1' "
"visible='1'} occurrence='2' text='Add' type='QPushButton' "
"unnamed='1' visible='1'}")
clickButton(addButton)
addMenu = addButton.menu()
activateItem(waitForObjectItem(objectMap.realName(addMenu), 'Custom Executable'))
exePathChooser = waitForObject(":Executable:_Utils::PathChooser", 2000)
exeLineEd = getChildByClass(exePathChooser, "Utils::BaseValidatingLineEdit")
argLineEd = waitForObject("{buddy={window=':Qt Creator_Core::Internal::MainWindow' "
"type='QLabel' text='Arguments:' visible='1'} type='QLineEdit' "
"unnamed='1' visible='1'}")
wdPathChooser = waitForObject("{buddy={window=':Qt Creator_Core::Internal::MainWindow' text='Working directory:' type='QLabel'} "
"type='Utils::PathChooser' unnamed='1' visible='1'}")
replaceEditorContent(exeLineEd, startAUT)
# the following is currently only configured for release builds (will be enhanced later)
if platform.system() in ('Microsoft', 'Windows'):
debOrRel = "release" + os.sep
else:
debOrRel = ""
replaceEditorContent(argLineEd, "--verbose --port=%d %s%s" % (port, debOrRel, projectName))
return True
# function that retrieves a specific child object by its class
# this is sometimes the best way to avoid using waitForObject() on objects that
# occur more than once - but could easily be found by using a compound object
# (e.g. search for Utils::PathChooser instead of Utils::BaseValidatingLineEdit and get the child)
def getChildByClass(parent, classToSearchFor, occurence=1):
children = [child for child in object.children(parent) if className(child) == classToSearchFor]
if len(children) < occurence:
return None
else:
return children[occurence - 1]
# get the Squish path that is needed to successfully hook into the compiled app
def getSquishPath(mkspec, qmakev):
# assuming major and minor version will be enough
squishVersion = "%d.%d" % (squishinfo.major, squishinfo.minor)
qmakev = ".".join(qmakev.split(".")[0:2])
path = None
mapfile = os.environ.get("QT_SQUISH_MAPFILE")
if mapfile and os.path.isfile(mapfile):
file = codecs.open(mapfile, "r", "utf-8")
pattern = re.compile("\s+")
for line in file:
if line[0] == "#":
continue
tmp = pattern.split(line, 3)
if (tmp[0].strip("'\"") == squishVersion and tmp[1].strip("'\"") == qmakev
and tmp[2].strip("'\"") == mkspec):
path = os.path.expanduser(tmp[3].strip().strip("'\""))
break
file.close()
else:
if not mapfile:
test.warning("Environment variable QT_SQUISH_MAPFILE isn't set. Using fallback test data.",
"See the README file how to use it.")
else:
test.warning("Environment variable QT_SQUISH_MAPFILE isn't set correctly or map file does not exist. Using fallback test data.",
"See the README file how to use it.")
# try the test data fallback
mapData = testData.dataset(os.getcwd() + "/../../shared_data/qt_squish_mapping.tsv")
for record in mapData:
if (testData.field(record, "squishversion") == squishVersion and
testData.field(record, "qtversion") == qmakev
and testData.field(record, "mkspec") == mkspec):
path = os.path.expanduser(testData.field(record, "path"))
break
if path == None:
test.warning("Haven't found suitable Squish version with matching Qt version and mkspec.",
"See the README file how to set up your environment.")
elif not os.path.exists(path):
test.warning("Squish path '%s' from fallback test data file does not exist!" % path,
"See the README file how to set up your environment.")
return None
return path
# function to add a program to allow communication through the win firewall
# param workingDir this directory is the parent of the project folder
# param projectName this is the name of the project (the folder inside workingDir as well as the name for the executable)
# param isReleaseBuild should currently always be set to True (will later add debug build testing)
def allowAppThroughWinFW(workingDir, projectName, isReleaseBuild=True):
if not __isWinFirewallRunning__():
return
# WinFirewall seems to run - hopefully no other
result = __configureFW__(workingDir, projectName, isReleaseBuild)
if result == 0:
test.log("Added %s to firewall" % projectName)
else:
test.fatal("Could not add %s as allowed program to win firewall" % projectName)
# function to delete a (former added) program from the win firewall
# param workingDir this directory is the parent of the project folder
# param projectName this is the name of the project (the folder inside workingDir as well as the name for the executable)
# param isReleaseBuild should currently always be set to True (will later add debug build testing)
def deleteAppFromWinFW(workingDir, projectName, isReleaseBuild=True):
if not __isWinFirewallRunning__():
return
# WinFirewall seems to run - hopefully no other
result = __configureFW__(workingDir, projectName, isReleaseBuild, False)
if result == 0:
test.log("Deleted %s from firewall" % projectName)
else:
test.warning("Could not delete %s as allowed program from win firewall" % (projectName))
# helper that can modify the win firewall to allow a program to communicate through it or delete it
# param addToFW defines whether to add (True) or delete (False) this programm to/from the firewall
def __configureFW__(workingDir, projectName, isReleaseBuild, addToFW=True):
if isReleaseBuild == None:
if projectName[-4:] == ".exe":
projectName = projectName[:-4]
path = "%s%s%s" % (workingDir, os.sep, projectName)
elif isReleaseBuild:
path = "%s%s%s%srelease%s%s" % (workingDir, os.sep, projectName, os.sep, os.sep, projectName)
else:
path = "%s%s%s%sdebug%s%s" % (workingDir, os.sep, projectName, os.sep, os.sep, projectName)
if addToFW:
mode = "add"
enable = "ENABLE"
else:
mode = "delete"
enable = ""
projectName = ""
# Needs admin privileges on Windows 7
# Using the deprecated "netsh firewall" because the newer
# "netsh advfirewall" would need admin privileges on Windows Vista, too.
return subprocess.call('netsh firewall %s allowedprogram "%s.exe" %s %s' % (mode, path, projectName, enable))
# helper to check whether win firewall is running or not
# this doesn't check for other firewalls!
def __isWinFirewallRunning__():
if hasattr(__isWinFirewallRunning__, "fireWallState"):
return __isWinFirewallRunning__.fireWallState
if not platform.system() in ('Microsoft' 'Windows'):
__isWinFirewallRunning__.fireWallState = False
return False
result = getOutputFromCmdline("netsh firewall show state")
for line in result.splitlines():
if "Operational mode" in line:
__isWinFirewallRunning__.fireWallState = not "Disable" in line
return __isWinFirewallRunning__.fireWallState
return None
def __fixQuotes__(string):
if platform.system() in ('Windows', 'Microsoft'):
string = '"' + string + '"'
return string
# this function adds the given executable as an attachable AUT
# Bad: executable/port could be empty strings - you should be aware of this
def addExecutableAsAttachableAUT(executable, port, host=None):
if not __checkParamsForAttachableAUT__(executable, port):
return False
if host == None:
host = "localhost"
squishSrv = __getSquishServer__()
if (squishSrv == None):
return False
result = subprocess.call(__fixQuotes__('"%s" --config addAttachableAUT "%s" %s:%s')
% (squishSrv, executable, host, port), shell=True)
if result == 0:
test.passes("Added %s as attachable AUT" % executable)
else:
test.fail("Failed to add %s as attachable AUT" % executable)
return result == 0
# this function removes the given executable as an attachable AUT
# Bad: executable/port could be empty strings - you should be aware of this
def removeExecutableAsAttachableAUT(executable, port, host=None):
if not __checkParamsForAttachableAUT__(executable, port):
return False
if host == None:
host = "localhost"
squishSrv = __getSquishServer__()
if (squishSrv == None):
return False
result = subprocess.call(__fixQuotes__('"%s" --config removeAttachableAUT "%s" %s:%s')
% (squishSrv, executable, host, port), shell=True)
if result == 0:
test.passes("Removed %s as attachable AUT" % executable)
else:
test.fail("Failed to remove %s as attachable AUT" % executable)
return result == 0
def __checkParamsForAttachableAUT__(executable, port):
return port != None and executable != None
def __getSquishServer__():
squishSrv = currentApplicationContext().environmentVariable("SQUISH_PREFIX")
if (squishSrv == ""):
test.fatal("SQUISH_PREFIX isn't set - leaving test")
return None
return os.path.abspath(squishSrv + "/bin/squishserver")