Commit 3089bd3f authored by Christian Stenger's avatar Christian Stenger

Squish: Implements hooking into the subprocess

Change-Id: Ic1ae2a8341d01e179f9d52b7c7ad3cbe24995bd1
Reviewed-by: default avatarRobert Löhning <robert.loehning@nokia.com>
parent 6d36b5f7
Preface
-------
The usage of the hook-into subprocess has following prerequisites:
Either:
* have no firewall at all enabled (sure that's a bad idea)
Or:
* have the Windows Firewall enabled (no other firewalls are handled by the scripts)
* run the Squish tests with administrator privileges
* additionally the UAC should be disabled, too
Otherwise you'll have some trouble with popping up dialogs from the firewall.
If you're using a different firewall - try to figure out and add a rule for this.
Using the internal test data fallback
-------------------------------------
* inside the shared_data folder a "standard" mapping file is located (qt_squish_mapping.tsv)
* this file defines currently some Squish versions that are available for usage
* all of the Squish versions mentioned in there should exist inside QTSDK/src/creator-test-data (see the file for naming the Squish versions) - of course this depends on the OS you're testing on
Attention! This file will be updated with more columns (and rows) for making it possible to also test Debug builds or 64bit builds. So, even the path entries currently listed could change when this feature is implemented.
Using QT_SQUISH_MAPFILE variable
--------------------------------
* create a simple text file (UTF-8 encoded) that follows this scheme:
QtVersion mkspec Path
* QtVersion: only major and minor number will be used (e.g. 4.7, 4.8, 5.0)
* mkspec: a string holding the mkspec as it appears inside QTSDK/mkspec (or inside Qt Creator)
* Path: the path to the Squish directory that can be used with this combination of QtVersion and mkspec
* between QtVersion and mkspec as well as between mkspec and Path use whitespaces only
* lines STARTING with # within the first column will be ignored
* path can contain spaces as well as ~ (for the home directory)
* you can put any entry of a line into quotes (no matter whether single or double quotes)
* a line holding a mapping MUST start with a QtVersion entry in the first column
Example 1: (using single space, no quoting)
#qtversion mkspec path
4.7<SP>win32-g++<SP>C:\Tools\Squish_MinGW
4.7<SP>win32-msvc2008<SP>C:\Tools\Squish_MSVC9
4.7<SP>win32-msvc2010<SP>C:\Tools\Squish_MSVC10
4.8<SP>win32-g++<SP>C:\Tools\Squish_MinGW
4.8<SP>win32-msvc2008<SP>C:\Tools\Squish_MSVC9
4.8<SP>win32-msvc2010<SP>C:\Tools\Squish_MSVC10
Example 2: (using mixed whitespaces, some quoting)
#qtversion<TAB>mkspec<TAB><TAB><SP>path
"4.7"<SP><TAB>win32-g++<TAB><TAB>'C:\Tools\Squish_MinGW'
'4.7'<SP><TAB>'win32-msvc2008'<TAB>"C:\Tools\Squish_MSVC9"
4.7<SP><SP><TAB>win32-msvc2010<TAB>C:\Tools\Squish_MSVC10
"4.8"<SP><TAB>"win32-g++"<SP><SP><TAB>"C:\Tools\Squish_MinGW"
'4.8'<SP><TAB>win32-msvc2008<SP><TAB>'C:\Tools\Squish_MSVC9'
'4.8'<SP><TAB>win32-msvc2010<SP><TAB>C:\Tools\Squish_MSVC10
Explanation: <SP> = space, <TAB> = tabulator
* after creating this file put it somewhere and define environment variable QT_SQUISH_MAPFILE to point directly to the file
* definition of this variable should be done BEFORE the test is started and can be done in various ways:
* declare this variable as a system environment variable (how to do this depends on the OS you're using) [recommended way]
* add this variable to the envvar file inside the directory holding the Squish test suites [please don't do this when pushing envvar back to git]
* you can also modify the os.environ dict inside the test script(s) but this has to be done, before the test tries to read the variable [best place would be right before/after the call to startApplication()]
Hint: You can also use the provided tsv file as a template. Simply remove lines you're not needing and adjust the path entries to point to the correct paths on your system and you should be fine.
Attention! The format of the file might slightly change as soon also Debug builds will be easily testable (and maybe again for 64bit builds)
Preparation of the SQUISH directories
-------------------------------------
To make the hook-into sub-process really work you have to provide a modified Squish.
Steps to set up the Squish directories on Windows:
* get the Squish version you need from froglogic.com
* extract the archive to place of your choice
* go to the bin directory of Squish and remove the following dll's:
* Qt3Support.dll
* QtCore4.dll
* QtGui4.dll
* QtNetwork4.dll
* QtSql4.dll
* QtXml4.dll
You can additionally remove the complete ide folder as well as squishclassicide.exe and squishide.exe (to safe disk space).
......@@ -22,6 +22,7 @@
:Qt Creator.QtCreator.MenuBar_QMenuBar {name='QtCreator.MenuBar' type='QMenuBar' visible='1' window=':Qt Creator_Core::Internal::MainWindow'}
:Qt Creator.ReRun_QToolButton {toolTip='Re-run this run-configuration' type='QToolButton' unnamed='1' visible='1' window=':Qt Creator_Core::Internal::MainWindow'}
:Qt Creator.Stop_QToolButton {text='Stop' type='QToolButton' unnamed='1' visible='1' window=':Qt Creator_Core::Internal::MainWindow'}
:Qt Creator.scrollArea_QScrollArea {name='scrollArea' type='QScrollArea' visible='1' window=':Qt Creator_Core::Internal::MainWindow'}
:Qt Creator_Core::Internal::MainWindow {type='Core::Internal::MainWindow' visible='1' windowTitle?='*Qt Creator'}
:Qt Creator_Core::Internal::OutputPaneToggleButton {occurrence='3' type='Core::Internal::OutputPaneToggleButton' unnamed='1' visible='1' window=':Qt Creator_Core::Internal::MainWindow'}
:Qt Creator_CppEditor::Internal::CPPEditorWidget {type='CppEditor::Internal::CPPEditorWidget' unnamed='1' visible='1' window=':Qt Creator_Core::Internal::MainWindow'}
......@@ -51,4 +52,6 @@
:scrollArea.Qt Version:_QComboBox {aboveWidget=':scrollArea.Use Shadow Building_QCheckBox' container=':Qt Gui Application.scrollArea_QScrollArea' leftWidget=':scrollArea.Qt Version:_QLabel' type='QComboBox' unnamed='1' visible='1'}
:scrollArea.Qt Version:_QLabel {container=':Qt Gui Application.scrollArea_QScrollArea' text='Qt Version:' type='QLabel' unnamed='1' visible='1'}
:scrollArea.Use Shadow Building_QCheckBox {container=':Qt Gui Application.scrollArea_QScrollArea' text='Use Shadow Building' type='QCheckBox' unnamed='1' visible='1'}
:scrollArea.qtVersionComboBox_QComboBox {container=':Qt Creator.scrollArea_QScrollArea' name='qtVersionComboBox' type='QComboBox' visible='1'}
:scrollArea_QTableView {container=':Qt Creator.scrollArea_QScrollArea' type='QTableView' unnamed='1' visible='1'}
:sourceFileLineEdit_Utils::FileNameValidatingLineEdit {buddy=':Qt Gui Application.Source file:_QLabel' name='sourceFileLineEdit' type='Utils::FileNameValidatingLineEdit' visible='1'}
import re
# flag that caches the information whether Windows firewall is running or not
fireWallState = None
# this function modifies all necessary run settings to make it possible to hook into
# the application compiled by Creator
def modifyRunSettingsForHookInto(projectName, port):
prepareBuildSettings(1, 0)
# this uses the defaultQtVersion currently
switchViewTo(ViewConstants.PROJECTS)
switchToBuildOrRunSettingsFor(1, 0, ProjectSettings.BUILD)
qtVersionTT = str(waitForObject("{type='QComboBox' name='qtVersionComboBox' visible='1'}").toolTip)
mkspec = __getMkspec__(qtVersionTT)
qmakeVersion = __getQMakeVersion__(qtVersionTT)
qmakeLibPath = __getQMakeLibPath__(qtVersionTT)
qmakeBinPath = __getQMakeBinPath__(qtVersionTT)
switchToBuildOrRunSettingsFor(1, 0, ProjectSettings.RUN)
result = __configureCustomExecutable__(projectName, port, mkspec, qmakeVersion)
if result:
clickButton(waitForObject("{container=':Qt Creator.scrollArea_QScrollArea' text='Details' "
"type='Utils::DetailsButton' unnamed='1' visible='1' "
"leftWidget={type='QLabel' text='Using <b>Build Environment</b>' unnamed='1' visible='1'}}"))
envVarsTableView = waitForObject("{type='QTableView' visible='1' unnamed='1'}")
model = envVarsTableView.model()
for row in range(model.rowCount()):
# get var name
index = model.index(row, 0)
envVarsTableView.scrollTo(index)
varName = str(model.data(index).toString())
# if its a special SQUISH var simply unset it
if varName == "PATH":
currentItem = __doubleClickQTableView__(row, 1)
test.log("replacing PATH with '%s'" % qmakeBinPath)
replaceEditorContent(currentItem, qmakeBinPath)
elif varName.find("SQUISH") == 0:
if varName == "SQUISH_LIBQTDIR":
currentItem = __doubleClickQTableView__(row, 1)
if platform.system() in ('Microsoft', 'Windows'):
replacement = qmakeBinPath
else:
replacement = qmakeLibPath
test.log("Changing SQUISH_LIBQTDIR",
"Replacing '%s' with '%s'" % (currentItem.text, replacement))
replaceEditorContent(currentItem, replacement)
else:
mouseClick(waitForObject("{container=':scrollArea_QTableView' "
"type='QModelIndex' row='%d' column='1'}" % row), 5, 5, 0, Qt.LeftButton)
clickButton(waitForObject("{type='QPushButton' text='Unset' unnamed='1' visible='1'}"))
#test.log("Unsetting %s for run" % varName)
switchViewTo(ViewConstants.EDIT)
return result
# helper that double clicks the table view at specified row and column
# returns the QExpandingLineEdit (the editable table cell)
def __doubleClickQTableView__(row, column):
doubleClick(waitForObject("{container=':scrollArea_QTableView' "
"type='QModelIndex' row='%d' column='%d'}" % (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
clickButton("{container=':Qt Creator.scrollArea_QScrollArea' occurrence='2' text='Add' type='QPushButton' unnamed='1' visible='1'}")
activateItem(waitForObject("{type='QMenu' visible='1' unnamed='1'}"), "Custom Executable")
exePathChooser = waitForObject("{buddy={container=':Qt Creator.scrollArea_QScrollArea' text='Executable:' type='QLabel'} "
"type='Utils::PathChooser' unnamed='1' visible='1'}")
exeLineEd = getChildByClass(exePathChooser, "Utils::BaseValidatingLineEdit")
argLineEd = waitForObject("{buddy={container={type='QScrollArea' name='scrollArea'} "
"type='QLabel' text='Arguments:' visible='1'} type='QLineEdit' "
"unnamed='1' visible='1'}")
wdPathChooser = waitForObject("{buddy={container=':Qt Creator.scrollArea_QScrollArea' 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):
counter = 0
for child in object.children(parent):
if className(child) == classToSearchFor:
counter = counter + 1
if counter == occurence:
return child
return None
# helper that tries to get the mkspec entry of the QtVersion ToolTip
def __getMkspec__(qtToolTip):
return ___searchInsideQtVersionToolTip___(qtToolTip, "mkspec:")
# helper that tries to get the qmake version entry of the QtVersion ToolTip
def __getQMakeVersion__(qtToolTip):
return ___searchInsideQtVersionToolTip___(qtToolTip, "Version:")
# helper that tries to get the path of the qmake libraries of the QtVersion ToolTip
def __getQMakeLibPath__(qtToolTip):
qmake = ___searchInsideQtVersionToolTip___(qtToolTip, "qmake:")
result = getOutputFromCmdline("%s -v" % qmake)
for line in result.splitlines():
if "Using Qt version" in line:
return line.rsplit(" ", 1)[1]
# helper that tries to get the path of qmake of the QtVersion ToolTip
def __getQMakeBinPath__(qtToolTip):
qmake = ___searchInsideQtVersionToolTip___(qtToolTip, "qmake:")
endIndex = qmake.find("/qmake")
return qmake[:endIndex]
# helper that does the work for __getMkspec__() and __getQMakeVersion__()
def ___searchInsideQtVersionToolTip___(qtToolTip, what):
result = None
tmp = qtToolTip.split("<td>")
for i in range(len(tmp)):
if i % 2 == 0:
continue
if what in tmp[i]:
result = tmp[i + 1].split("</td>", 1)[0]
break
return result
# get the Squish path that is needed to successfully hook into the compiled app
def getSquishPath(mkspec, qmakev):
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, 2)
if tmp[0].strip("'\"") == qmakev and tmp[1].strip("'\"") == mkspec:
path = os.path.expanduser(tmp[2].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 row, record in enumerate(mapData):
if testData.field(record, "qtversion") == qmakev and testData.field(record, "mkspec") == mkspec:
path = os.path.expanduser(testData.field(record, "path"))
break
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__(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__(projectName, isReleaseBuild, False)
if result == 0:
test.log("Deleted %s from firewall" % projectName)
else:
test.fatal("Could not delete %s as allowed program from win firewall" % (mode, 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__(projectName, isReleaseBuild, addToFW=True):
if 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 = ""
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__():
global fireWallState
if fireWallState != None:
return fireWallState
if not platform.system() in ('Microsoft' 'Windows'):
fireWallState = False
return False
result = getOutputFromCmdline("netsh firewall show state")
for line in result.splitlines():
if "Operational mode" in line:
fireWallState = not "Disable" in line
return fireWallState
return None
# 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('%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('%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")
......@@ -77,7 +77,7 @@ def __createProjectSetNameAndPath__(path, projectName = None, checks = True):
# make sure this is not set as default location
ensureChecked("{type='QCheckBox' name='projectsDirectoryCheckBox' visible='1'}", False)
clickButton(waitForObject(":Next_QPushButton"))
return projectName
return str(projectName)
def __createProjectHandleLastPage__(expectedFiles = None):
if expectedFiles != None:
......@@ -156,6 +156,7 @@ def createNewQtQuickApplication(workingDir, projectName = None, templateFile = N
snooze(1)
clickButton(nextButton)
__createProjectHandleLastPage__()
return projectName
def createNewQtQuickUI(workingDir):
__createProjectSelectType__("Qt Quick Project", "Qt Quick UI")
......
import re;
# this class holds some constants for easier usage inside the Projects view
class ProjectSettings:
BUILD = 1
RUN = 2
# this class defines some constants for the views of the creator's MainWindow
class ViewConstants:
WELCOME = 0
EDIT = 1
DESIGN = 2
DEBUG = 3
PROJECTS = 4
ANALYZE = 5
HELP = 6
# always adjust the following to the highest value of the available ViewConstants when adding new
LAST_AVAILABLE = HELP
# this function returns a regex of the tooltip of the FancyTabBar elements
# this is needed because the keyboard shortcut is OS specific
# if the provided argument does not match any of the ViewConstants it returns None
@staticmethod
def getToolTipForViewTab(viewTab):
if viewTab == ViewConstants.WELCOME:
return ur'Switch to <b>Welcome</b> mode <span style="color: gray; font-size: small">(Ctrl\+|\u2303)1</span>'
elif viewTab == ViewConstants.EDIT:
return ur'Switch to <b>Edit</b> mode <span style="color: gray; font-size: small">(Ctrl\+|\u2303)2</span>'
elif viewTab == ViewConstants.DESIGN:
return ur'Switch to <b>Design</b> mode <span style="color: gray; font-size: small">(Ctrl\+|\u2303)3</span>'
elif viewTab == ViewConstants.DEBUG:
return ur'Switch to <b>Debug</b> mode <span style="color: gray; font-size: small">(Ctrl\+|\u2303)4</span>'
elif viewTab == ViewConstants.PROJECTS:
return ur'Switch to <b>Projects</b> mode <span style="color: gray; font-size: small">(Ctrl\+|\u2303)5</span>'
elif viewTab == ViewConstants.ANALYZE:
return ur'Switch to <b>Analyze</b> mode <span style="color: gray; font-size: small">(Ctrl\+|\u2303)6</span>'
elif viewTab == ViewConstants.HELP:
return ur'Switch to <b>Help</b> mode <span style="color: gray; font-size: small">(Ctrl\+|\u2303)7</span>'
else:
return None
# this function switches the MainWindow of creator to the specified view
def switchViewTo(view):
if view < ViewConstants.WELCOME or view > ViewConstants.LAST_AVAILABLE:
return
tabBar = waitForObject("{type='Core::Internal::FancyTabBar' unnamed='1' visible='1' "
"window=':Qt Creator_Core::Internal::MainWindow'}")
mouseMove(tabBar, 10, 10 + 52 * view)
snooze(2)
text = str(QToolTip.text())
pattern = ViewConstants.getToolTipForViewTab(view)
if re.match(pattern, unicode(text), re.UNICODE):
test.passes("ToolTip verified")
else:
test.warning("ToolTip does not match", "Expected pattern: %s\nGot: %s" % (pattern, text))
mouseClick(waitForObject("{type='Core::Internal::FancyTabBar' unnamed='1' visible='1' "
"window=':Qt Creator_Core::Internal::MainWindow'}"), 5, 5 + 52 * view, 0, Qt.LeftButton)
# this function is used to make sure that simple building prerequisites are met
# param targetCount specifies how many build targets had been selected (it's important that this one is correct)
# param currentTarget specifies which target should be selected for the next build (zero based index)
# param setReleaseBuild defines whether the current target(s) will be set to a Release or a Debug build
# param disableShadowBuild defines whether to disable shadow build or leave it unchanged (no matter what is defined)
# param setForAll defines whether to set Release or Debug and ShadowBuild option for all targets or only for the currentTarget
def prepareBuildSettings(targetCount, currentTarget, setReleaseBuild=True, disableShadowBuild=True, setForAll=True):
switchViewTo(ViewConstants.PROJECTS)
success = True
for current in range(targetCount):
if setForAll or current == currentTarget:
switchToBuildOrRunSettingsFor(targetCount, current, ProjectSettings.BUILD)
qtCombo = waitForObject(":scrollArea.qtVersionComboBox_QComboBox")
chooseThis = None
wait = False
try:
if qtCombo.currentText != defaultQtVersion:
selectFromCombo(":scrollArea.qtVersionComboBox_QComboBox", defaultQtVersion.replace(".", "\\."))
if setReleaseBuild:
chooseThis = "%s Release" % defaultQtVersion
else:
chooseThis = "%s Debug" % defaultQtVersion
editBuildCfg = waitForObject("{container={type='QScrollArea' name='scrollArea'} "
"leftWidget={container={type='QScrollArea' name='scrollArea'} "
"text='Edit build configuration:' type='QLabel'}"
"unnamed='1' type='QComboBox' visible='1'}", 20000)
if editBuildCfg.currentText != chooseThis:
wait = True
clickItem(editBuildCfg, chooseThis.replace(".", "\\."), 5, 5, 0, Qt.LeftButton)
else:
wait = False
except:
if current == currentTarget:
success = False
if wait and chooseThis != None:
waitFor("editBuildCfg.currentText==chooseThis")
ensureChecked("{name='shadowBuildCheckBox' type='QCheckBox' visible='1'}", not disableShadowBuild)
# get back to the current target
if currentTarget < 0 or currentTarget >= targetCount:
test.warning("Parameter currentTarget is out of range - will be ignored this time!")
else:
switchToBuildOrRunSettingsFor(targetCount, currentTarget, ProjectSettings.BUILD)
switchViewTo(ViewConstants.EDIT)
return success
# this function switches to the build or the run settings (inside the Projects view)
# if you haven't already switched to the Projects view this will fail and return False
# param currentTarget specifies the target for which to switch into the specified settings (zero based index)
# param targetCount specifies the number of targets currently defined (must be correct!)
# param projectSettings specifies where to switch to (must be one of ProjectSettings.BUILD or ProjectSettings.RUN)
def switchToBuildOrRunSettingsFor(targetCount, currentTarget, projectSettings):
try:
targetSel = waitForObject("{type='ProjectExplorer::Internal::TargetSelector' unnamed='1' "
"visible='1' window=':Qt Creator_Core::Internal::MainWindow'}")
except LookupError:
test.fatal("Wrong (time of) call - must be already at Projects view")
return False
ADD_BUTTON_WIDTH = 27 # bad... (taken from source)
selectorWidth = (targetSel.width - 3 - 2 * (ADD_BUTTON_WIDTH + 1)) / targetCount - 1
yToClick = targetSel.height * 3 / 5 + 5
if projectSettings == ProjectSettings.RUN:
xToClick = ADD_BUTTON_WIDTH + (selectorWidth + 1) * currentTarget - 2 + selectorWidth / 2 + 5
elif projectSettings == ProjectSettings.BUILD:
xToClick = ADD_BUTTON_WIDTH + (selectorWidth + 1) * currentTarget - 2 + selectorWidth / 2 - 5
else:
test.fatal("Don't know what you're trying to switch to")
return False
mouseClick(targetSel, xToClick, yToClick, 0, Qt.LeftButton)
return True
......@@ -18,6 +18,8 @@ source("../../shared/build_utils.py")
source("../../shared/qtquick.py")
source("../../shared/project.py")
source("../../shared/editor_utils.py")
source("../../shared/project_explorer.py")
source("../../shared/hook_utils.py")
def waitForCleanShutdown(timeOut=10):
appCtxt = currentApplicationContext()
......
......@@ -83,7 +83,7 @@ def __chooseTargets__(targets=QtQuickConstants.Targets.DESKTOP):
if mustCheck:
test.fail("Failed to check target '%s'" % QtQuickConstants.getStringForTarget(current))
def runAndCloseApp():
def runAndCloseApp(withHookInto=False, executable=None, port=None):
global processStarted, processExited
processStarted = processExited = False
installLazySignalHandler("{type='ProjectExplorer::ApplicationLaucher'}", "processStarted()", "__handleProcessStarted__")
......@@ -101,13 +101,32 @@ def runAndCloseApp():
test.fatal("Couldn't start application - leaving test")
invokeMenuItem("File", "Exit")
return False
# the following is currently a work-around for not using hooking into subprocesses
if withHookInto and not executable in ("", None):
__closeSubprocessByHookingIntoQmlApplicationViewer__(executable, port)
else:
__closeSubprocessByPushingStop__()
return True
def __closeSubprocessByPushingStop__():
ensureChecked(":Qt Creator_Core::Internal::OutputPaneToggleButton")
playButton = verifyEnabled(":Qt Creator.ReRun_QToolButton", False)
stopButton = verifyEnabled(":Qt Creator.Stop_QToolButton")
clickButton(stopButton)
test.verify(playButton.enabled)
test.compare(stopButton.enabled, False)
def __closeSubprocessByHookingIntoQmlApplicationViewer__(executable, port):
global processExited
ensureChecked(":Qt Creator_Core::Internal::OutputPaneToggleButton")
output = waitForObject("{type='Core::OutputWindow' visible='1' windowTitle='Application Output Window'}", 20000)
if port == None:
test.warning("I need a port number or attaching might fail.")
else:
waitFor("'Listening on port %d for incoming connectionsdone' in str(output.plainText)" % port, 5000)
attachToApplication(executable)
sendEvent("QCloseEvent", "{type='QmlApplicationViewer' unnamed='1' visible='1'}")
waitFor("processExited==True", 10000)
setApplicationContext(applicationContext("qtcreator"))
return True
def runAndCloseQtQuickUI():
......
......@@ -24,6 +24,12 @@ def ensureChecked(objectName, shouldBeChecked = True):
object = waitForObject(objectName, 20000)
if object.checked ^ shouldBeChecked:
clickButton(object)
if shouldBeChecked:
state = "checked"
else:
state = "unchecked"
test.log("New state for QCheckBox: %s" % state)
test.verify(object.checked == shouldBeChecked)
return object
def verifyEnabled(objectName, expectedState = True):
......@@ -151,3 +157,10 @@ def logApplicationOutput():
"window=':Qt Creator_Core::Internal::MainWindow' occurrence='3'}")
output = waitForObject("{type='Core::OutputWindow' visible='1' windowTitle='Application Output Window'}", 20000)
test.log("Application Output:\n%s" % output.plainText)
# get the output from a given cmdline call
def getOutputFromCmdline(cmdline):
versCall = subprocess.Popen(cmdline, stdout=subprocess.PIPE, shell=True)
result = versCall.communicate()[0]
versCall.stdout.close()