# Copyright (C) 2016 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 import platform; import re; import shutil; import os; import glob; import atexit; import codecs; import subprocess; import sys import errno; from datetime import datetime,timedelta; if sys.version_info.major > 2: import builtins as __builtin__ else: import __builtin__ srcPath = '' SettingsPath = [] tmpSettingsDir = '' testSettings.logScreenshotOnFail = True testSettings.logScreenshotOnError = True source("../../shared/classes.py") source("../../shared/utils.py") source("../../shared/fs_utils.py") source("../../shared/build_utils.py") source("../../shared/project.py") source("../../shared/editor_utils.py") source("../../shared/project_explorer.py") source("../../shared/debugger.py") source("../../shared/clang.py") source("../../shared/welcome.py") source("../../shared/workarounds.py") # include this at last settingsPathsWithExplicitlyEnabledClangd = set() def __closeInfoBarEntry__(leftButtonText): toolButton = ("text='%s' type='QToolButton' unnamed='1' visible='1' " "window=':Qt Creator_Core::Internal::MainWindow'") doNotShowAgain = toolButton % "Do Not Show Again" leftWidget = "leftWidget={%s}" % (toolButton % leftButtonText) test.log("closing %s" % leftButtonText) clickButton(waitForObject("{%s %s}" % (doNotShowAgain, leftWidget))) # additionalParameters must be a list or tuple of strings or None def startQC(additionalParameters=None, withPreparedSettingsPath=True, closeLinkToQt=True, cancelTour=True): global SettingsPath global settingsPathsWithExplicitlyEnabledClangd appWithOptions = ['"Qt Creator"' if platform.system() == 'Darwin' else "qtcreator"] if withPreparedSettingsPath: appWithOptions.extend(SettingsPath) if additionalParameters is not None: appWithOptions.extend(additionalParameters) if platform.system() in ('Microsoft', 'Windows'): # for hooking into native file dialog appWithOptions.extend(('-platform', 'windows:dialogs=none')) test.log("Starting now: %s" % ' '.join(appWithOptions)) appContext = startApplication(' '.join(appWithOptions)) if (closeLinkToQt or cancelTour or str(SettingsPath) not in settingsPathsWithExplicitlyEnabledClangd): progressBarWait(3000) # wait for the "Updating documentation" progress bar if str(SettingsPath) not in settingsPathsWithExplicitlyEnabledClangd: # This block will incorrectly be skipped when a test calls startQC multiple times in a row # passing different settings paths in "additionalParameters". Currently we don't have such # a test. Even if we did, it would only make a difference if the test relied on clangd # being active and ran on a machine with insufficient memory. try: mouseClick(waitForObject("{text='Enable Anyway' type='QToolButton' " "unnamed='1' visible='1' " "window=':Qt Creator_Core::Internal::MainWindow'}", 500)) settingsPathsWithExplicitlyEnabledClangd.add(str(SettingsPath)) except: pass if closeLinkToQt or cancelTour: if closeLinkToQt: __closeInfoBarEntry__("Link with Qt") if cancelTour: __closeInfoBarEntry__("Take UI Tour") return appContext; def startedWithoutPluginError(): try: loaderErrorWidgetName = ("{name='ExtensionSystem__Internal__PluginErrorOverview' " "type='ExtensionSystem::PluginErrorOverview' visible='1' " "windowTitle='Plugin Loader Messages'}") waitForObject(loaderErrorWidgetName, 1000) test.fatal("Could not perform clean start of Qt Creator - Plugin error occurred.", str(waitForObject("{name='pluginError' type='QTextEdit' visible='1' window=%s}" % loaderErrorWidgetName, 1000).plainText)) clickButton("{text~='(Next.*|Continue)' type='QPushButton' visible='1'}") invokeMenuItem("File", "Exit") return False except: return True def waitForCleanShutdown(timeOut=10): appCtxt = currentApplicationContext() shutdownDone = (str(appCtxt)=="") if platform.system() in ('Windows','Microsoft'): # cleaning helper for running on the build machines checkForStillRunningQmlExecutable(['qmlscene.exe', 'qml.exe']) endtime = datetime.utcnow() + timedelta(seconds=timeOut) while not shutdownDone: # following work-around because os.kill() works for win not until python 2.7 if appCtxt.pid==-1: break output = getOutputFromCmdline(["tasklist", "/FI", "PID eq %d" % appCtxt.pid], acceptedError=1) if (output=="INFO: No tasks are running which match the specified criteria." or output=="" or output.find("ERROR")==0): shutdownDone=True if not shutdownDone and datetime.utcnow() > endtime: break else: endtime = datetime.utcnow() + timedelta(seconds=timeOut) while not shutdownDone: try: os.kill(appCtxt.pid,0) except OSError as err: if err.errno == errno.EPERM or err.errno == errno.ESRCH: shutdownDone=True if not shutdownDone and datetime.utcnow() > endtime: break if platform.system() == 'Linux' and JIRA.isBugStillOpen(15749): pgrepOutput = getOutputFromCmdline(["pgrep", "-f", "qtcreator_process_stub"], acceptedError=1) pids = pgrepOutput.splitlines() if len(pids): print("Killing %d qtcreator_process_stub instances" % len(pids)) for pid in pids: try: os.kill(__builtin__.int(pid), 9) except OSError: # we might kill the parent before the current pid pass def checkForStillRunningQmlExecutable(possibleNames): for qmlHelper in possibleNames: output = getOutputFromCmdline(["tasklist", "/FI", "IMAGENAME eq %s" % qmlHelper]) if "INFO: No tasks are running which match the specified criteria." in output: continue else: if subprocess.call(["taskkill", "/F", "/FI", "IMAGENAME eq %s" % qmlHelper]) == 0: print("Killed still running %s" % qmlHelper) else: print("%s is still running - failed to kill it" % qmlHelper) def __removeTestingDir__(): def __removeIt__(directory): deleteDirIfExists(directory) return not os.path.exists(directory) devicesXML = os.path.join(tmpSettingsDir, "QtProject", "qtcreator", "devices.xml") lastMTime = os.path.getmtime(devicesXML) testingDir = os.path.dirname(tmpSettingsDir) waitForCleanShutdown() waitFor('os.path.getmtime(devicesXML) > lastMTime', 5000) waitFor('__removeIt__(testingDir)', 2000) def __substitute__(fileName, search, replace): origFileName = fileName + "_orig" os.rename(fileName, origFileName) origFile = open(origFileName, "r") modifiedFile = open(fileName, "w") for line in origFile: modifiedFile.write(line.replace(search, replace)) origFile.close() modifiedFile.close() os.remove(origFileName) def substituteTildeWithinToolchains(settingsDir): toolchains = os.path.join(settingsDir, "QtProject", 'qtcreator', 'toolchains.xml') home = os.path.expanduser("~") __substitute__(toolchains, "~", home) test.log("Substituted all tildes with '%s' inside toolchains.xml..." % home) def substituteTildeWithinQtVersion(settingsDir): toolchains = os.path.join(settingsDir, "QtProject", 'qtcreator', 'qtversion.xml') home = os.path.expanduser("~") __substitute__(toolchains, "~", home) test.log("Substituted all tildes with '%s' inside qtversion.xml..." % home) def substituteOnlineInstallerPath(settingsDir): qtversions = os.path.join(settingsDir, "QtProject", 'qtcreator', 'qtversion.xml') dflt = "C:/Qt" if platform.system() in ('Microsoft', 'Windows') else os.path.expanduser("~/Qt") replacement = str(os.getenv("SYSTEST_QTOI_BASEPATH", dflt)).replace('\\', '/') while replacement.endswith('/'): replacement = replacement[:-1] __substitute__(qtversions, "SQUISH_QTOI_BASEPATH", replacement) test.log("Substituted online installer base path (%s) inside qtversions.xml." % replacement) def substituteDefaultCompiler(settingsDir): compiler = None if platform.system() == 'Darwin': compiler = "clang_64" elif platform.system() == 'Linux': if __is64BitOS__(): compiler = "gcc_64" else: compiler = "gcc" else: test.warning("Called substituteDefaultCompiler() on wrong platform.", "This is a script error.") if compiler: qtversion = os.path.join(settingsDir, "QtProject", 'qtcreator', 'qtversion.xml') __substitute__(qtversion, "SQUISH_DEFAULT_COMPILER", compiler) test.log("Injected default compiler '%s' to qtversion.xml..." % compiler) def substituteCdb(settingsDir): def canUse64bitCdb(): try: serverIni = readFile(os.path.join(os.getenv("APPDATA"), "froglogic", "Squish", "ver1", "server.ini")) autLine = list(filter(lambda line: "AUT/qtcreator" in line, serverIni.splitlines()))[0] autPath = autLine.split("\"")[1] return os.path.exists(os.path.join(autPath, "..", "lib", "qtcreatorcdbext64")) except: test.fatal("Something went wrong when determining debugger bitness", "Did Squish's file structure change? Guessing 32-bit cdb can be used...") return True if canUse64bitCdb(): architecture = "x64" bitness = "64" else: architecture = "x86" bitness = "32" debuggers = os.path.join(settingsDir, "QtProject", 'qtcreator', 'debuggers.xml') __substitute__(debuggers, "SQUISH_DEBUGGER_ARCHITECTURE", architecture) __substitute__(debuggers, "SQUISH_DEBUGGER_BITNESS", bitness) test.log("Injected architecture '%s' and bitness '%s' in cdb path..." % (architecture, bitness)) def substituteMsvcPaths(settingsDir, version, targetBitness=64): if not version in ['2017', '2019']: test.fatal('Unexpected MSVC version - "%s" not implemented yet.' % version) return hostArch = "Hostx64" if targetBitness == 64 else "Hostx86" targetArch = "x64" if targetBitness == 64 else "x86" for msvcFlavor in ["Community", "BuildTools"]: try: msvcPath = os.path.join("C:\\Program Files (x86)", "Microsoft Visual Studio", version, msvcFlavor, "VC", "Tools", "MSVC") foundVersions = os.listdir(msvcPath) # undetermined order foundVersions.sort(reverse=True) # we explicitly want the latest and greatest msvcPath = os.path.join(msvcPath, foundVersions[0], "bin", hostArch, targetArch) __substitute__(os.path.join(settingsDir, "QtProject", 'qtcreator', 'toolchains.xml'), "SQUISH_MSVC%s_%d_PATH" % (version, targetBitness), msvcPath) return except: continue test.warning("PATH variable for MSVC%s could not be set, some tests will fail." % version, "Please make sure that MSVC%s is installed correctly." % version) def prependWindowsKit(settingsDir, targetBitness=64): targetArch = "x64" if targetBitness == 64 else "x86" profilesPath = os.path.join(settingsDir, 'QtProject', 'qtcreator', 'profiles.xml') winkits = os.path.join("C:\\Program Files (x86)", "Windows Kits", "10") if not os.path.exists(winkits): __substitute__(profilesPath, "SQUISH_ENV_MODIFICATION", "") return possibleVersions = os.listdir(os.path.join(winkits, 'bin')) possibleVersions.reverse() # prefer higher versions for version in possibleVersions: if not version.startswith("10"): continue toolsPath = os.path.join(winkits, 'bin', version, targetArch) if os.path.exists(os.path.join(toolsPath, 'rc.exe')): __substitute__(profilesPath, "SQUISH_ENV_MODIFICATION", "PATH=+%s" % toolsPath) return test.warning("Windows Kit path could not be added, some tests mail fail.") __substitute__(profilesPath, "SQUISH_ENV_MODIFICATION", "") def __guessABI__(supportedABIs, use64Bit): if platform.system() == 'Linux': supportedABIs = filter(lambda x: 'linux' in x, supportedABIs) elif platform.system() == 'Darwin': supportedABIs = filter(lambda x: 'darwin' in x, supportedABIs) if use64Bit: searchFor = "64bit" else: searchFor = "32bit" for abi in supportedABIs: if searchFor in abi: return abi if use64Bit: test.log("Supported ABIs do not include an ABI supporting 64bit - trying 32bit now") return __guessABI__(supportedABIs, False) test.fatal('Could not guess ABI!', 'Given ABIs: %s' % str(supportedABIs)) return '' def __is64BitOS__(): if platform.system() in ('Microsoft', 'Windows'): machine = os.getenv("PROCESSOR_ARCHITEW6432", os.getenv("PROCESSOR_ARCHITECTURE")) else: machine = platform.machine() if machine: return '64' in machine else: return False def substituteUnchosenTargetABIs(settingsDir): class ReadState: NONE = 0 READING = 1 CLOSED = 2 on64Bit = __is64BitOS__() toolchains = os.path.join(settingsDir, "QtProject", 'qtcreator', 'toolchains.xml') origToolchains = toolchains + "_orig" os.rename(toolchains, origToolchains) origFile = open(origToolchains, "r") modifiedFile = open(toolchains, "w") supported = [] readState = ReadState.NONE for line in origFile: if readState == ReadState.NONE: if "SupportedAbis" in line: supported = [] readState = ReadState.READING elif readState == ReadState.READING: if "" in line: readState = ReadState.CLOSED else: supported.append(line.split(">", 1)[1].rsplit("<", 1)[0]) elif readState == ReadState.CLOSED: if "SupportedAbis" in line: supported = [] readState = ReadState.READING elif "SET_BY_SQUISH" in line: line = line.replace("SET_BY_SQUISH", __guessABI__(supported, on64Bit)) modifiedFile.write(line) origFile.close() modifiedFile.close() os.remove(origToolchains) test.log("Substituted unchosen ABIs inside toolchains.xml...") def copySettingsToTmpDir(destination=None, omitFiles=[]): global tmpSettingsDir, SettingsPath, origSettingsDir if destination: destination = os.path.abspath(destination) if not os.path.exists(destination): os.makedirs(destination) elif os.path.isfile(destination): test.warning("Provided destination for settings exists as file.", "Creating another folder for being able to execute tests.") destination = tempDir() else: destination = tempDir() tmpSettingsDir = destination pathLen = len(origSettingsDir) + 1 for r,d,f in os.walk(origSettingsDir): currentPath = os.path.join(tmpSettingsDir, r[pathLen:]) for dd in d: folder = os.path.join(currentPath, dd) if not os.path.exists(folder): os.makedirs(folder) for ff in f: if ff not in omitFiles: shutil.copy(os.path.join(r, ff), currentPath) if platform.system() in ('Linux', 'Darwin'): substituteTildeWithinToolchains(tmpSettingsDir) substituteTildeWithinQtVersion(tmpSettingsDir) substituteDefaultCompiler(tmpSettingsDir) elif platform.system() in ('Windows', 'Microsoft'): substituteCdb(tmpSettingsDir) substituteMsvcPaths(tmpSettingsDir, '2017', 64) substituteMsvcPaths(tmpSettingsDir, '2017', 32) substituteMsvcPaths(tmpSettingsDir, '2019', 64) prependWindowsKit(tmpSettingsDir, 32) substituteOnlineInstallerPath(tmpSettingsDir) substituteUnchosenTargetABIs(tmpSettingsDir) SettingsPath = ['-settingspath', '"%s"' % tmpSettingsDir] test.log("Test is running on Python %s" % sys.version) # current dir is directory holding qtcreator.py origSettingsDir = os.path.abspath(os.path.join(os.getcwd(), "..", "..", "settings")) if platform.system() in ('Windows', 'Microsoft'): origSettingsDir = os.path.join(origSettingsDir, "windows") elif platform.system() == 'Darwin': origSettingsDir = os.path.join(origSettingsDir, "mac") else: origSettingsDir = os.path.join(origSettingsDir, "unix") srcPath = os.getenv("SYSTEST_SRCPATH", os.path.expanduser(os.path.join("~", "squish-data"))) # the following only doesn't work if the test ends in an exception if os.getenv("SYSTEST_NOSETTINGSPATH") != "1": copySettingsToTmpDir() atexit.register(__removeTestingDir__) if os.getenv("SYSTEST_WRITE_RESULTS") == "1" and os.getenv("SYSTEST_RESULTS_FOLDER") != None: atexit.register(writeTestResults, os.getenv("SYSTEST_RESULTS_FOLDER"))