1############################################################################ 2# 3# Copyright (C) 2016 The Qt Company Ltd. 4# Contact: https://www.qt.io/licensing/ 5# 6# This file is part of Qt Creator. 7# 8# Commercial License Usage 9# Licensees holding valid commercial Qt licenses may use this file in 10# accordance with the commercial license agreement provided with the 11# Software or, alternatively, in accordance with the terms contained in 12# a written agreement between you and The Qt Company. For licensing terms 13# and conditions see https://www.qt.io/terms-conditions. For further 14# information use the contact form at https://www.qt.io/contact-us. 15# 16# GNU General Public License Usage 17# Alternatively, this file may be used under the terms of the GNU 18# General Public License version 3 as published by the Free Software 19# Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT 20# included in the packaging of this file. Please review the following 21# information to ensure the GNU General Public License requirements will 22# be met: https://www.gnu.org/licenses/gpl-3.0.html. 23# 24############################################################################ 25 26import tempfile 27 28def neededFilePresent(path): 29 found = os.path.exists(path) 30 if os.getenv("SYSTEST_DEBUG") == "1": 31 checkAccess(path) 32 elif not found: 33 test.fatal("Missing file or directory: " + path) 34 return found 35 36def tempDir(): 37 Result = os.path.abspath(os.getcwd()+"/../../testing") 38 if not os.path.exists(Result): 39 os.mkdir(Result) 40 return tempfile.mkdtemp(prefix="qtcreator_", dir=Result) 41 42def deleteDirIfExists(path): 43 shutil.rmtree(path, True) 44 45def verifyChecked(objectName, checked=True): 46 object = waitForObject(objectName) 47 test.compare(object.checked, checked) 48 return object 49 50def ensureChecked(objectName, shouldBeChecked = True, timeout=20000): 51 if shouldBeChecked: 52 targetState = Qt.Checked 53 state = "checked" 54 else: 55 targetState = Qt.Unchecked 56 state = "unchecked" 57 widget = waitForObject(objectName, timeout) 58 try: 59 # needed for transition Qt::PartiallyChecked -> Qt::Checked -> Qt::Unchecked 60 clicked = 0 61 while not waitFor('widget.checkState() == targetState', 1500) and clicked < 2: 62 clickButton(widget) 63 clicked += 1 64 test.verify(waitFor("widget.checkState() == targetState", 1000)) 65 except: 66 # widgets not derived from QCheckbox don't have checkState() 67 if not waitFor('widget.checked == shouldBeChecked', 1500): 68 mouseClick(widget) 69 test.verify(waitFor("widget.checked == shouldBeChecked", 1000)) 70 test.log("New state for QCheckBox: %s" % state, 71 str(objectName)) 72 return widget 73 74# verify that an object is in an expected enable state. Returns the object. 75# param objectSpec specifies the object to check. It can either be a string determining an object 76# or the object itself. If it is an object, it must exist already. 77# param expectedState is the expected enable state of the object 78def verifyEnabled(objectSpec, expectedState = True): 79 if isinstance(objectSpec, (str, unicode)): 80 waitFor("object.exists('" + str(objectSpec).replace("'", "\\'") + "')", 20000) 81 foundObject = findObject(objectSpec) 82 else: 83 foundObject = objectSpec 84 if objectSpec == None: 85 test.warning("No valid object in function verifyEnabled.") 86 else: 87 test.compare(foundObject.enabled, expectedState) 88 return foundObject 89 90# select an item from a combo box 91# param objectSpec specifies the combo box. It can either be a string determining an object 92# or the object itself. If it is an object, it must exist already. 93# param itemName is the item to be selected in the combo box 94# returns True if selection was changed or False if the wanted value was already selected 95def selectFromCombo(objectSpec, itemName): 96 object = verifyEnabled(objectSpec) 97 if itemName == str(object.currentText): 98 return False 99 else: 100 mouseClick(object) 101 snooze(1) 102 # params required here 103 mouseClick(waitForObjectItem(object, itemName.replace(".", "\\.")), 5, 5, 0, Qt.LeftButton) 104 test.verify(waitFor("str(object.currentText)==itemName", 5000), 105 "Switched combo item to '%s'" % itemName) 106 return True 107 108def selectFromLocator(filter, itemName = None): 109 if itemName == None: 110 itemName = filter 111 itemName = itemName.replace(".", "\\.").replace("_", "\\_") 112 locator = waitForObject(":*Qt Creator_Utils::FilterLineEdit") 113 mouseClick(locator) 114 replaceEditorContent(locator, filter) 115 # clicking the wanted item 116 # if you replace this by pressing ENTER, be sure that something is selected 117 # otherwise you will run into unwanted behavior 118 snooze(1) 119 wantedItem = waitForObjectItem("{type='QTreeView' unnamed='1' visible='1'}", itemName) 120 doubleClick(wantedItem, 5, 5, 0, Qt.LeftButton) 121 122def wordUnderCursor(window): 123 return textUnderCursor(window, QTextCursor.StartOfWord, QTextCursor.EndOfWord) 124 125def lineUnderCursor(window): 126 return textUnderCursor(window, QTextCursor.StartOfLine, QTextCursor.EndOfLine) 127 128def textUnderCursor(window, fromPos, toPos): 129 cursor = window.textCursor() 130 oldposition = cursor.position() 131 cursor.movePosition(fromPos) 132 cursor.movePosition(toPos, QTextCursor.KeepAnchor) 133 returnValue = cursor.selectedText() 134 cursor.setPosition(oldposition) 135 return returnValue 136 137def which(program): 138 # Don't use spawn.find_executable because it can't find .bat or 139 # .cmd files and doesn't check whether a file is executable (!) 140 if platform.system() in ('Windows', 'Microsoft'): 141 command = "where" 142 else: 143 command = "which" 144 foundPath = getOutputFromCmdline([command, program], acceptedError=1) 145 if foundPath: 146 return foundPath.splitlines()[0] 147 else: 148 return None 149 150# this function removes the user files of given pro file(s) 151# can be called with a single string object or a list of strings holding path(s) to 152# the pro file(s) returns False if it could not remove all user files or has been 153# called with an unsupported object 154def cleanUpUserFiles(pathsToProFiles=None): 155 if pathsToProFiles==None: 156 return False 157 if isinstance(pathsToProFiles, (str, unicode)): 158 filelist = glob.glob(pathsToProFiles+".user*") 159 elif isinstance(pathsToProFiles, (list, tuple)): 160 filelist = [] 161 for p in pathsToProFiles: 162 filelist.extend(glob.glob(p+".user*")) 163 else: 164 test.fatal("Got an unsupported object.") 165 return False 166 doneWithoutErrors = True 167 for file in filelist: 168 try: 169 file = os.path.abspath(file) 170 os.remove(file) 171 except: 172 doneWithoutErrors = False 173 return doneWithoutErrors 174 175def invokeMenuItem(menu, item, *subItems): 176 if platform.system() == "Darwin": 177 try: 178 waitForObject(":Qt Creator.QtCreator.MenuBar_QMenuBar", 2000) 179 except: 180 nativeMouseClick(waitForObject(":Qt Creator_Core::Internal::MainWindow", 1000), 20, 20, 0, Qt.LeftButton) 181 # Use Locator for menu items which wouldn't work on macOS 182 if menu == "Tools" and item == "Options..." or menu == "File" and item == "Exit": 183 selectFromLocator("t %s" % item, item) 184 return 185 menuObject = waitForObjectItem(":Qt Creator.QtCreator.MenuBar_QMenuBar", menu) 186 snooze(1) 187 waitFor("menuObject.visible", 1000) 188 activateItem(menuObject) 189 itemObject = waitForObjectItem(objectMap.realName(menuObject), item) 190 waitFor("itemObject.enabled", 2000) 191 activateItem(itemObject) 192 numberedPrefix = "(&\\d \| )?" 193 for subItem in subItems: 194 sub = itemObject.menu() 195 waitFor("sub.visible", 1000) 196 # we might have numbered sub items (e.g. "Recent Files") - these have this special prefix 197 if subItem.startswith(numberedPrefix): 198 actions = sub.actions() 199 triggered = False 200 for i in range(actions.count()): 201 current = actions.at(i) 202 nonPrefix = subItem[len(numberedPrefix):] 203 matcher = re.match("%s(.*)" % numberedPrefix, str(current.text)) 204 if matcher and matcher.group(2) == nonPrefix: 205 itemObject = current 206 activateItem(itemObject) 207 triggered = True 208 break 209 if not triggered: 210 test.fail("Could not trigger '%s' - item missing or code wrong?" % subItem, 211 "Function arguments: '%s', '%s', %s" % (menu, item, str(subItems))) 212 break # we failed to trigger - no need to process subItems further 213 else: 214 itemObject = waitForObjectItem(sub, subItem) 215 activateItem(itemObject) 216 217def logApplicationOutput(): 218 # make sure application output is shown 219 ensureChecked(":Qt Creator_AppOutput_Core::Internal::OutputPaneToggleButton") 220 try: 221 output = waitForObject("{type='Core::OutputWindow' visible='1' windowTitle='Application Output Window'}") 222 test.log("Application Output:\n%s" % output.plainText) 223 return str(output.plainText) 224 except: 225 test.fail("Could not find any Application Output - did the project run?") 226 return None 227 228# get the output from a given cmdline call 229def getOutputFromCmdline(cmdline, environment=None, acceptedError=0): 230 try: 231 return subprocess.check_output(cmdline, env=environment) 232 except subprocess.CalledProcessError as e: 233 if e.returncode != acceptedError: 234 test.warning("Command '%s' returned %d" % (e.cmd, e.returncode)) 235 return e.output 236 237def selectFromFileDialog(fileName, waitForFile=False, ignoreFinalSnooze=False): 238 def __closePopupIfNecessary__(): 239 if not isNull(QApplication.activePopupWidget()): 240 test.log("Closing active popup widget") 241 QApplication.activePopupWidget().close() 242 243 if platform.system() == "Darwin": 244 snooze(1) 245 nativeType("<Command+Shift+g>") 246 snooze(1) 247 nativeType(fileName) 248 snooze(2) 249 nativeType("<Return>") 250 snooze(3) 251 nativeType("<Return>") 252 if not ignoreFinalSnooze: 253 snooze(1) 254 else: 255 fName = os.path.basename(os.path.abspath(fileName)) 256 pName = os.path.dirname(os.path.abspath(fileName)) + os.sep 257 try: 258 waitForObject("{name='QFileDialog' type='QFileDialog' visible='1'}", 5000) 259 pathLine = waitForObject("{name='fileNameEdit' type='QLineEdit' visible='1'}") 260 replaceEditorContent(pathLine, pName) 261 snooze(1) 262 clickButton(waitForObject("{text='Open' type='QPushButton'}")) 263 waitFor("str(pathLine.text)==''") 264 replaceEditorContent(pathLine, fName) 265 snooze(1) 266 __closePopupIfNecessary__() 267 clickButton(waitForObject("{text='Open' type='QPushButton'}")) 268 except: 269 nativeType("<Ctrl+a>") 270 nativeType("<Delete>") 271 nativeType(pName + fName) 272 seconds = len(pName + fName) / 20 273 test.log("Using snooze(%d) [problems with event processing of nativeType()]" % seconds) 274 snooze(seconds) 275 nativeType("<Return>") 276 if not ignoreFinalSnooze: 277 snooze(3) 278 if waitForFile: 279 fileCombo = waitForObject(":Qt Creator_FilenameQComboBox") 280 if not waitFor("str(fileCombo.currentText) in fileName", 5000): 281 test.fail("%s could not be opened in time." % fileName) 282 283# add Qt documentations from given paths 284# param which a list/tuple of the paths to the qch files to be added 285def addHelpDocumentation(which): 286 invokeMenuItem("Tools", "Options...") 287 mouseClick(waitForObjectItem(":Options_QListView", "Help")) 288 waitForObject("{container=':Options.qt_tabwidget_tabbar_QTabBar' type='TabItem' text='Documentation'}") 289 clickOnTab(":Options.qt_tabwidget_tabbar_QTabBar", "Documentation") 290 # get rid of all docs already registered 291 listWidget = waitForObject("{type='QListView' name='docsListView' visible='1'}") 292 if listWidget.model().rowCount() > 0: 293 mouseClick(listWidget) 294 type(listWidget, "<Ctrl+a>") 295 clickButton(waitForObject("{type='QPushButton' name='removeButton' visible='1'}")) 296 for qch in which: 297 clickButton(waitForObject("{type='QPushButton' name='addButton' visible='1' text='Add...'}")) 298 selectFromFileDialog(qch) 299 clickButton(waitForObject(":Options.OK_QPushButton")) 300 301def addCurrentCreatorDocumentation(): 302 currentCreatorPath = currentApplicationContext().cwd 303 if platform.system() == "Darwin": 304 docPath = os.path.abspath(os.path.join(currentCreatorPath, "Qt Creator.app", "Contents", 305 "Resources", "doc", "qtcreator.qch")) 306 else: 307 docPath = os.path.abspath(os.path.join(currentCreatorPath, "..", "share", "doc", 308 "qtcreator", "qtcreator.qch")) 309 if not os.path.exists(docPath): 310 test.fatal("Missing current Qt Creator documentation (expected in %s)" % docPath) 311 return 312 invokeMenuItem("Tools", "Options...") 313 mouseClick(waitForObjectItem(":Options_QListView", "Help")) 314 waitForObject("{container=':Options.qt_tabwidget_tabbar_QTabBar' type='TabItem' text='Documentation'}") 315 clickOnTab(":Options.qt_tabwidget_tabbar_QTabBar", "Documentation") 316 clickButton(waitForObject("{type='QPushButton' name='addButton' visible='1' text='Add...'}")) 317 selectFromFileDialog(docPath) 318 try: 319 waitForObject("{type='QMessageBox' unnamed='1' visible='1' " 320 "text~='Unable to register documentation.*'}", 3000) 321 test.passes("Qt Creator's documentation found already registered.") 322 clickButton(waitForObject("{type='QPushButton' text='OK' unnamed='1' visible='1' " 323 "container={name='groupBox' type='QGroupBox' visible='1'}}")) 324 except: 325 test.fail("Added Qt Creator's documentation explicitly.") 326 clickButton(waitForObject(":Options.OK_QPushButton")) 327 328def verifyOutput(string, substring, outputFrom, outputIn): 329 index = string.find(substring) 330 if (index == -1): 331 test.fail("Output from " + outputFrom + " could not be found in " + outputIn) 332 else: 333 test.passes("Output from " + outputFrom + " found at position " + str(index) + " of " + outputIn) 334 335# function that verifies the existence and the read permissions 336# of the given file path 337# if the executing user hasn't the read permission it checks 338# the parent folders for their execute permission 339def checkAccess(pathToFile): 340 if os.path.exists(pathToFile): 341 test.log("Path '%s' exists" % pathToFile) 342 if os.access(pathToFile, os.R_OK): 343 test.log("Got read access on '%s'" % pathToFile) 344 else: 345 test.fail("No read permission on '%s'" % pathToFile) 346 else: 347 test.fatal("Path '%s' does not exist or cannot be accessed" % pathToFile) 348 __checkParentAccess__(pathToFile) 349 350# helper function for checking the execute rights of all 351# parents of filePath 352def __checkParentAccess__(filePath): 353 for i in range(1, filePath.count(os.sep)): 354 tmp = filePath.rsplit(os.sep, i)[0] 355 if os.access(tmp, os.X_OK): 356 test.log("Got execute permission on '%s'" % tmp) 357 else: 358 test.fail("No execute permission on '%s'" % tmp) 359 360# this function checks for all configured Qt versions inside 361# options dialog and returns a dict holding the kits as keys 362# and a list of information of its configured Qt 363def getConfiguredKits(): 364 def __retrieveQtVersionName__(target, version): 365 treeView = waitForObject(":qtdirList_QTreeView") 366 return str(treeView.currentIndex().data().toString()) 367 # end of internal function for iterateQtVersions 368 def __setQtVersionForKit__(kit, kitName, kitsQtVersionName): 369 mouseClick(waitForObjectItem(":BuildAndRun_QTreeView", kit)) 370 qtVersionStr = str(waitForObjectExists(":Kits_QtVersion_QComboBox").currentText) 371 kitsQtVersionName[kitName] = qtVersionStr 372 # end of internal function for iterate kits 373 374 kitsWithQtVersionName = {} 375 result = {} 376 # collect kits and their Qt versions 377 targetsQtVersions, qtVersionNames = iterateQtVersions(True, False, __retrieveQtVersionName__) 378 # update collected Qt versions with their configured device and version 379 iterateKits(True, True, __setQtVersionForKit__, kitsWithQtVersionName) 380 # merge defined target names with their configured Qt versions and devices 381 for kit, qtVersion in kitsWithQtVersionName.iteritems(): 382 if kit in ('Fremantle', 'Harmattan', 'Qt Simulator'): 383 test.verify(qtVersion == 'None', 384 "The outdated kit '%s' should not have a Qt version" % kit) 385 elif qtVersion in qtVersionNames: 386 result[kit] = targetsQtVersions[qtVersionNames.index(qtVersion)].items()[0] 387 else: 388 test.fail("Qt version '%s' for kit '%s' can't be found in qtVersionNames." 389 % (qtVersion, kit)) 390 clickButton(waitForObject(":Options.Cancel_QPushButton")) 391 test.log("Configured kits: %s" % str(result)) 392 return result 393 394def enabledCheckBoxExists(text): 395 try: 396 waitForObject("{type='QCheckBox' text='%s'}" % text, 100) 397 return True 398 except: 399 return False 400 401# this function verifies if the text matches the given 402# regex inside expectedTexts 403# param text must be a single str/unicode 404# param expectedTexts can be str/unicode/list/tuple 405def regexVerify(text, expectedTexts): 406 if isinstance(expectedTexts, (str,unicode)): 407 expectedTexts = [expectedTexts] 408 for curr in expectedTexts: 409 pattern = re.compile(curr) 410 if pattern.match(text): 411 return True 412 return False 413 414# function that opens Options Dialog and parses the configured Qt versions 415# param keepOptionsOpen set to True if the Options dialog should stay open when 416# leaving this function 417# param alreadyOnOptionsDialog set to True if you already have opened the Options Dialog 418# (if False this function will open it via the MenuBar -> Tools -> Options...) 419# param additionalFunction pass a function or name of a defined function to execute 420# for each correctly configured item on the list of Qt versions 421# (Qt versions having no assigned toolchain, failing qmake,... will be skipped) 422# this function must take at least 2 parameters - the first is the target name 423# and the second the version of the current selected Qt version item 424# param argsForAdditionalFunc you can specify as much parameters as you want to pass 425# to additionalFunction from the outside 426# the function returns a list of dict holding target-version mappings if used without 427# additionalFunction 428# WATCH OUT! if you're using the additionalFunction parameter - this function will 429# return the list mentioned above as well as the returned value(s) from 430# additionalFunction. You MUST call this function like 431# result, additionalResult = _iterateQtVersions(...) 432# where additionalResult is the result of all executions of additionalFunction which 433# means it is a list of results. 434def iterateQtVersions(keepOptionsOpen=False, alreadyOnOptionsDialog=False, 435 additionalFunction=None, *argsForAdditionalFunc): 436 result = [] 437 additionalResult = [] 438 if not alreadyOnOptionsDialog: 439 invokeMenuItem("Tools", "Options...") 440 mouseClick(waitForObjectItem(":Options_QListView", "Kits")) 441 clickOnTab(":Options.qt_tabwidget_tabbar_QTabBar", "Qt Versions") 442 pattern = re.compile("Qt version (?P<version>.*?) for (?P<target>.*)") 443 treeView = waitForObject(":qtdirList_QTreeView") 444 model = treeView.model() 445 for rootIndex in dumpIndices(model): 446 rootChildText = str(rootIndex.data()).replace(".", "\\.").replace("_", "\\_") 447 for subIndex in dumpIndices(model, rootIndex): 448 subChildText = str(subIndex.data()).replace(".", "\\.").replace("_", "\\_") 449 mouseClick(waitForObjectItem(treeView, ".".join([rootChildText,subChildText]))) 450 currentText = str(waitForObject(":QtSupport__Internal__QtVersionManager.QLabel").text) 451 matches = pattern.match(currentText) 452 if matches: 453 target = matches.group("target").strip() 454 version = matches.group("version").strip() 455 result.append({target:version}) 456 if additionalFunction: 457 try: 458 if isinstance(additionalFunction, (str, unicode)): 459 currResult = globals()[additionalFunction](target, version, *argsForAdditionalFunc) 460 else: 461 currResult = additionalFunction(target, version, *argsForAdditionalFunc) 462 except: 463 t,v,_ = sys.exc_info() 464 currResult = None 465 test.fatal("Function to additionally execute on Options Dialog could not be found or " 466 "an exception occurred while executing it.", "%s(%s)" % (str(t), str(v))) 467 additionalResult.append(currResult) 468 if not keepOptionsOpen: 469 clickButton(waitForObject(":Options.Cancel_QPushButton")) 470 if additionalFunction: 471 return result, additionalResult 472 else: 473 return result 474 475# function that opens Options Dialog (if necessary) and parses the configured Kits 476# param keepOptionsOpen set to True if the Options dialog should stay open when 477# leaving this function 478# param alreadyOnOptionsDialog set to True if you already have opened the Options Dialog 479# (if False this functions will open it via the MenuBar -> Tools -> Options...) 480# param additionalFunction pass a function or name of a defined function to execute 481# for each configured item on the list of Kits 482# this function must take at least 2 parameters - the first is the item (QModelIndex) 483# of the current Kit (if you need to click on it) and the second the Kit name itself 484# param argsForAdditionalFunc you can specify as much parameters as you want to pass 485# to additionalFunction from the outside 486# the function returns a list of Kit names if used without an additional function 487# WATCH OUT! if you're using the additionalFunction parameter - this function will 488# return the list mentioned above as well as the returned value(s) from 489# additionalFunction. You MUST call this function like 490# result, additionalResult = _iterateQtVersions(...) 491# where additionalResult is the result of all executions of additionalFunction which 492# means it is a list of results. 493def iterateKits(keepOptionsOpen=False, alreadyOnOptionsDialog=False, 494 additionalFunction=None, *argsForAdditionalFunc): 495 result = [] 496 additionalResult = [] 497 if not alreadyOnOptionsDialog: 498 invokeMenuItem("Tools", "Options...") 499 mouseClick(waitForObjectItem(":Options_QListView", "Kits")) 500 clickOnTab(":Options.qt_tabwidget_tabbar_QTabBar", "Kits") 501 treeView = waitForObject(":BuildAndRun_QTreeView") 502 model = treeView.model() 503 test.compare(model.rowCount(), 2, "Verifying expected target section count") 504 autoDetected = model.index(0, 0) 505 test.compare(autoDetected.data().toString(), "Auto-detected", 506 "Verifying label for target section") 507 manual = model.index(1, 0) 508 test.compare(manual.data().toString(), "Manual", "Verifying label for target section") 509 for section in [autoDetected, manual]: 510 for currentItem in dumpItems(model, section): 511 kitName = currentItem 512 if (kitName.endswith(" (default)")): 513 kitName = kitName.rsplit(" (default)", 1)[0] 514 result.append(kitName) 515 item = ".".join([str(section.data().toString()), 516 currentItem.replace(".", "\\.")]) 517 if additionalFunction: 518 try: 519 if isinstance(additionalFunction, (str, unicode)): 520 currResult = globals()[additionalFunction](item, kitName, *argsForAdditionalFunc) 521 else: 522 currResult = additionalFunction(item, kitName, *argsForAdditionalFunc) 523 except: 524 t,v,_ = sys.exc_info() 525 currResult = None 526 test.fatal("Function to additionally execute on Options Dialog could not be " 527 "found or an exception occurred while executing it.", "%s(%s)" % 528 (str(t), str(v))) 529 additionalResult.append(currResult) 530 if not keepOptionsOpen: 531 clickButton(waitForObject(":Options.Cancel_QPushButton")) 532 if additionalFunction: 533 return result, additionalResult 534 else: 535 return result 536 537# set a help viewer that will always be used, regardless of Creator's width 538 539class HelpViewer: 540 HELPMODE, SIDEBYSIDE, EXTERNALWINDOW = range(3) 541 542def setFixedHelpViewer(helpViewer): 543 invokeMenuItem("Tools", "Options...") 544 mouseClick(waitForObjectItem(":Options_QListView", "Help")) 545 clickOnTab(":Options.qt_tabwidget_tabbar_QTabBar", "General") 546 mode = "Always Show " 547 if helpViewer == HelpViewer.HELPMODE: 548 mode += "in Help Mode" 549 elif helpViewer == HelpViewer.SIDEBYSIDE: 550 mode += "Side-by-Side" 551 elif helpViewer == HelpViewer.EXTERNALWINDOW: 552 mode += "in External Window" 553 selectFromCombo(":Startup.contextHelpComboBox_QComboBox", mode) 554 clickButton(waitForObject(":Options.OK_QPushButton")) 555 556def removePackagingDirectory(projectPath): 557 qtcPackaging = os.path.join(projectPath, "qtc_packaging") 558 if os.path.exists(qtcPackaging): 559 test.log("Removing old packaging directory '%s'" % qtcPackaging) 560 deleteDirIfExists(qtcPackaging) 561 else: 562 test.log("Couldn't remove packaging directory '%s' - did not exist." % qtcPackaging) 563 564# returns the indices from a QAbstractItemModel 565def dumpIndices(model, parent=None, column=0): 566 if parent: 567 return [model.index(row, column, parent) for row in range(model.rowCount(parent))] 568 else: 569 return [model.index(row, column) for row in range(model.rowCount())] 570 571DisplayRole = 0 572# returns the data from a QAbstractItemModel as strings 573def dumpItems(model, parent=None, role=DisplayRole, column=0): 574 return [str(index.data(role)) for index in dumpIndices(model, parent, column)] 575 576# returns the children of a QTreeWidgetItem 577def dumpChildren(item): 578 return [item.child(index) for index in range(item.childCount())] 579 580def writeTestResults(folder): 581 if not os.path.exists(folder): 582 print("Skipping writing test results (folder '%s' does not exist)." % folder) 583 return 584 resultFile = open("%s.srf" % os.path.join(folder, os.path.basename(squishinfo.testCase)), "w") 585 resultFile.write("suite:%s\n" % os.path.basename(os.path.dirname(squishinfo.testCase))) 586 categories = ["passes", "fails", "fatals", "errors", "tests", "warnings", "xfails", "xpasses"] 587 for cat in categories: 588 resultFile.write("%s:%d\n" % (cat, test.resultCount(cat))) 589 resultFile.close() 590 591# wait and verify if object exists/not exists 592def checkIfObjectExists(name, shouldExist = True, timeout = 3000, verboseOnFail = False): 593 result = waitFor("object.exists(name) == shouldExist", timeout) 594 if verboseOnFail and not result: 595 test.log("checkIfObjectExists() failed for '%s'" % name) 596 return result 597 598# wait for progress bar(s) to appear and disappear 599def progressBarWait(timeout=60000, warn=True): 600 if not checkIfObjectExists(":Qt Creator_Core::Internal::ProgressBar", True, 6000): 601 if warn: 602 test.warning("progressBarWait() timed out when waiting for ProgressBar.", 603 "This may lead to unforeseen behavior. Consider increasing the timeout.") 604 checkIfObjectExists(":Qt Creator_Core::Internal::ProgressBar", False, timeout) 605 606def readFile(filename): 607 with open(filename, "r") as f: 608 return f.read() 609 610def simpleFileName(navigatorFileName): 611 # try to find the last part of the given name, assume it's inside a (folder) structure 612 search = re.search(".*[^\\\\]\.(.*)$", navigatorFileName) 613 if search: 614 return search.group(1).replace("\\", "") 615 # it's just the filename 616 return navigatorFileName.replace("\\", "") 617 618def clickOnTab(tabBarStr, tabText, timeout=5000): 619 if not waitFor("object.exists(tabBarStr)", timeout): 620 raise LookupError("Could not find QTabBar: %s" % objectMap.realName(tabBarStr)) 621 tabBar = findObject(tabBarStr) 622 if not (platform.system() == 'Linux' or tabBar.visible): 623 test.log("Using workaround for Mac and Windows.") 624 setWindowState(tabBar, WindowState.Normal) 625 tabBar = waitForObject(tabBarStr, 2000) 626 clickTab(tabBar, tabText) 627 waitFor("str(tabBar.tabText(tabBar.currentIndex)) == '%s'" % tabText, timeout) 628 629# constructs a string holding the properties for a QModelIndex 630# param property a string holding additional properties including their values 631# ATTENTION! use single quotes for values (e.g. "text='Text'", "text='Text' occurrence='2'") 632# param container the container (str) to be used for this QModelIndex 633def getQModelIndexStr(property, container): 634 if (container.startswith(":")): 635 container = "'%s'" % container 636 return ("{column='0' container=%s %s type='QModelIndex'}" % (container, property)) 637 638def verifyItemOrder(items, text): 639 text = str(text) 640 lastIndex = 0 641 for item in items: 642 index = text.find(item) 643 test.verify(index > lastIndex, "'" + item + "' found at index " + str(index)) 644 lastIndex = index 645 646def openVcsLog(): 647 try: 648 foundObj = waitForObject("{type='Core::OutputWindow' unnamed='1' visible='1' " 649 "window=':Qt Creator_Core::Internal::MainWindow'}", 2000) 650 if className(foundObj) != 'Core::OutputWindow': 651 raise Exception("Found derived class, but not a pure QPlainTextEdit.") 652 waitForObject("{text='Version Control' type='QLabel' unnamed='1' visible='1' " 653 "window=':Qt Creator_Core::Internal::MainWindow'}", 2000) 654 except: 655 invokeMenuItem("View", "Output Panes", "Version Control") 656 657def openGeneralMessages(): 658 if not object.exists(":Qt Creator_Core::OutputWindow"): 659 invokeMenuItem("View", "Output Panes", "General Messages") 660 661# function that retrieves a specific child object by its class 662# this is sometimes the best way to avoid using waitForObject() on objects that 663# occur more than once - but could easily be found by using a compound object 664# (e.g. search for Utils::PathChooser instead of Utils::FancyLineEdit and get the child) 665def getChildByClass(parent, classToSearchFor, occurrence=1): 666 children = [child for child in object.children(parent) if className(child) == classToSearchFor] 667 if len(children) < occurrence: 668 return None 669 else: 670 return children[occurrence - 1] 671 672def getHelpViewer(): 673 return waitForObject("{type='QLiteHtmlWidget' unnamed='1' visible='1' " 674 "window=':Qt Creator_Core::Internal::MainWindow'}", 675 1000) 676 677def getHelpTitle(): 678 return str(getHelpViewer().title()) 679