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