diff options
Diffstat (limited to 'scripts/Util.py')
-rw-r--r-- | scripts/Util.py | 3025 |
1 files changed, 3025 insertions, 0 deletions
diff --git a/scripts/Util.py b/scripts/Util.py new file mode 100644 index 00000000000..c6b90e20789 --- /dev/null +++ b/scripts/Util.py @@ -0,0 +1,3025 @@ +# ********************************************************************** +# +# Copyright (c) 2003-2017 ZeroC, Inc. All rights reserved. +# +# This copy of Ice is licensed to you under the terms described in the +# ICE_LICENSE file included in this distribution. +# +# ********************************************************************** + +import os, sys, runpy, getopt, traceback, types, threading, time, datetime, re, itertools, random, subprocess, shutil, copy, inspect + +isPython2 = sys.version_info[0] == 2 +if isPython2: + import Queue as queue + from StringIO import StringIO +else: + import queue + from io import StringIO + +from collections import OrderedDict +import Expect + +toplevel = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) + +def run(cmd, cwd=None, err=False): + p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=cwd) + out = p.stdout.read().decode('UTF-8').strip() + if(not err and p.wait() != 0) or (err and p.wait() == 0) : + raise RuntimeError(cmd + " failed:\n" + out) + # + # Without this we get warnings when runing with python_d on Windows + # + # ResourceWarning: unclosed file <_io.TextIOWrapper name=3 encoding='cp1252'> + # + p.stdout.close() + return out + +def val(v, escapeQuotes=False, quoteValue=True): + if type(v) == bool: + return "1" if v else "0" + elif type(v) == str: + if not quoteValue or v.find(" ") < 0: + return v + elif escapeQuotes: + return "\\\"{0}\\\"".format(v.replace("\\\"", "\\\\\\\"")) + else: + return "\"{0}\"".format(v) + else: + return str(v) + +def getIceSoVersion(): + config = open(os.path.join(toplevel, "cpp", "include", "IceUtil", "Config.h"), "r") + intVersion = int(re.search("ICE_INT_VERSION ([0-9]*)", config.read()).group(1)) + majorVersion = int(intVersion / 10000) + minorVersion = int(intVersion / 100) - 100 * majorVersion + patchVersion = intVersion % 100 + if patchVersion < 50: + return '%d' % (majorVersion * 10 + minorVersion) + elif patchVersion < 60: + return '%da%d' % (majorVersion * 10 + minorVersion, patchVersion - 50) + else: + return '%db%d' % (majorVersion * 10 + minorVersion, patchVersion - 60) + +class Platform: + + def __init__(self): + self.parseBuildVariables({ + "supported-platforms" : ("supportedPlatforms", lambda s : s.split(" ")), + "supported-configs" : ("supportedConfigs", lambda s : s.split(" ")) + }) + + def parseBuildVariables(self, variables): + # Run make to get the values of the given variables + output = run('make print V="{0}"'.format(" ".join(variables.keys())), cwd = toplevel) + for l in output.split("\n"): + match = re.match(r'^.*:.*: (.*) = (.*)', l) + if match and match.group(1): + if match.group(1) in variables: + (varname, valuefn) = variables[match.group(1).strip()] + value = match.group(2).strip() or "" + setattr(self, varname, valuefn(value) if valuefn else value) + + def getFilters(self, config): + if config.buildConfig in ["static", "cpp11-static"]: + return (["Ice/.*", "IceSSL/configuration", "IceDiscovery/simple", "IceGrid/simple"], + ["Ice/library", "Ice/plugin"]) + return ([], []) + + def getDefaultBuildPlatform(self): + return self.supportedPlatforms[0] + + def getDefaultBuildConfig(self): + return self.supportedConfigs[0] + + def getBinSubDir(self, mapping, process, current): + # Return the bin sub-directory for the given mapping,platform,config, + # to be overriden by specializations + return "bin" + + def getLibSubDir(self, mapping, process, current): + # Return the bin sub-directory for the given mapping,platform,config, + # to be overriden by specializations + return "lib" + + def getBuildSubDir(self, name, current): + # Return the build sub-directory, to be overriden by specializations + return os.path.join("build", current.config.buildPlatform, current.config.buildConfig) + + def getLdPathEnvName(self): + return "LD_LIBRARY_PATH" + + def getIceInstallDir(self, mapping, current): + return os.environ.get("ICE_HOME", "/usr") + + def getSliceDir(self, iceDir): + if iceDir.startswith("/usr"): + return os.path.join(iceDir, "share", "ice", "slice") + else: + return os.path.join(iceDir, "slice") + + def getDefaultExe(self, name, config): + if name == "icebox" and config.cpp11: + name += "++11" + return name + + def hasOpenSSL(self): + # This is used by the IceSSL test suite to figure out how to setup certificates + return False + + def canRun(self, mapping, current): + return True + +class Darwin(Platform): + + def getFilters(self, config): + if config.buildPlatform in ["iphoneos", "iphonesimulator"]: + return (["Ice/.*", "IceSSL/configuration"], + ["Ice/background", + "Ice/echo", + "Ice/faultTolerance", + "Ice/gc", + "Ice/library", + "Ice/logger", + "Ice/properties", + "Ice/plugin", + "Ice/stringConverter", + "Ice/threadPoolPriority", + "Ice/udp"]) + return Platform.getFilters(self, config) + + def getDefaultBuildPlatform(self): + return "macosx" + + def getLdPathEnvName(self): + return "DYLD_LIBRARY_PATH" + + def getIceInstallDir(self, mapping, current): + return os.environ.get("ICE_HOME", "/usr/local") + +class AIX(Platform): + + def hasOpenSSL(self): + return True + +class Linux(Platform): + + def __init__(self): + Platform.__init__(self) + self.parseBuildVariables({ + "linux_id" : ("linuxId", None), + "build-platform" : ("buildPlatform", None), + "foreign-platforms" : ("foreignPlatforms", lambda s : s.split(" ") if s else []), + }) + self.multiArch = {} + if self.linuxId in ["ubuntu", "debian"]: + for p in [self.buildPlatform] + self.foreignPlatforms: + self.multiArch[p] = run("dpkg-architecture -f -a{0} -qDEB_HOST_MULTIARCH 2> /dev/null".format(p)) + + def hasOpenSSL(self): + return True + + def getBinSubDir(self, mapping, process, current): + if self.linuxId in ["ubuntu", "debian"] and current.config.buildPlatform in self.foreignPlatforms: + return os.path.join("bin", self.multiArch[current.config.buildPlatform]) + return "bin" + + def getLibSubDir(self, mapping, process, current): + + # PHP module is always installed in the lib directory for the default build platform + if isinstance(mapping, PhpMapping) and current.config.buildPlatform == self.getDefaultBuildPlatform(): + return "lib" + + if self.linuxId in ["centos", "rhel", "fedora"]: + return "lib64" if current.config.buildPlatform == "x64" else "lib" + elif self.linuxId in ["ubuntu", "debian"]: + return os.path.join("lib", self.multiArch[current.config.buildPlatform]) + return "lib" + + def getBuildSubDir(self, name, current): + if self.linuxId in ["ubuntu", "debian"]: + return os.path.join("build", self.multiArch[current.config.buildPlatform], current.config.buildConfig) + else: + return os.path.join("build", current.config.buildPlatform, current.config.buildConfig) + + def getDefaultExe(self, name, config): + if name == "icebox": + if self.linuxId in ["centos", "rhel", "fedora"] and config.buildPlatform == "x86": + name += "32" # Multilib platform + if config.cpp11: + name += "++11" + return name + + def canRun(self, mapping, current): + if self.linuxId in ["centos", "rhel", "fedora"] and current.config.buildPlatform == "x86": + # + # Don't test Glacier2/IceStorm/IceGrid services with multilib platforms. We only + # build services for the native platform. + # + parent = re.match(r'^([\w]*).*', current.testcase.getTestSuite().getId()).group(1) + if parent in ["Glacier2", "IceStorm", "IceGrid"]: + return False + return True + +class Windows(Platform): + + def getFilters(self, config): + if config.uwp: + return (["Ice/.*", "IceSSL/configuration"], + ["Ice/background", + "Ice/echo", + "Ice/faultTolerance", + "Ice/gc", + "Ice/library", + "Ice/logger", + "Ice/networkProxy", # SOCKS proxy not supported with UWP + "Ice/properties", # Property files are not supported with UWP + "Ice/plugin", + "Ice/threadPoolPriority"]) + return Platform.getFilters(self, config) + + def parseBuildVariables(self, variables): + pass # Nothing to do, we don't support the make build system on Windows + + def getDefaultBuildPlatform(self): + return "Win32" + + def getDefaultBuildConfig(self): + return "Debug" + + def getCompiler(self): + out = run("cl") + if out.find("Version 16.") != -1: + return "v100" + elif out.find("Version 17.") != -1: + return "v110" + elif out.find("Version 18.") != -1: + return "v120" + elif out.find("Version 19.00.") != -1: + return "v140" + elif out.find("Version 19.10.") != -1: + return "v141" + + def getBinSubDir(self, mapping, process, current): + # + # Platform/Config taget bin directories. + # + platform = current.config.buildPlatform + config = "Debug" if current.config.buildConfig.find("Debug") >= 0 else "Release" + + if current.driver.useIceBinDist(mapping): + iceHome = os.environ.get("ICE_HOME") + v140 = self.getCompiler() == "v140" + cpp = isinstance(mapping, CppMapping) + csharp = isinstance(mapping, CSharpMapping) + + if iceHome and ((cpp and v140 and platform == "x64" and config == "Release") or (not csharp and not cpp)): + return "bin" + elif csharp or isinstance(process, SliceTranslator): + return os.path.join("tools") + else: + + # + # With Windows binary distribution Glacier2 and IcePatch2 binaries are only included + # for Release configuration. + # + binaries = [Glacier2Router, IcePatch2Calc, IcePatch2Client, IcePatch2Server] + config = next(("Release" for p in binaries if isinstance(process, p)), config) + + return os.path.join("build", "native", "bin", platform, config) + else: + if isinstance(mapping, CppMapping): + return os.path.join("bin", platform, config) + elif isinstance(mapping, PhpMapping): + return os.path.join("lib", platform, config) + return "bin" + + def getLibSubDir(self, mapping, process, current): + if isinstance(mapping, PhpMapping): + return "php" if current.driver.useIceBinDist(mapping) else "lib" + return self.getBinSubDir(mapping, process, current) + + def getBuildSubDir(self, name, current): + if os.path.exists(os.path.join(current.testcase.getPath(), "msbuild", name)): + return os.path.join("msbuild", name, current.config.buildPlatform, current.config.buildConfig) + else: + return os.path.join("msbuild", current.config.buildPlatform, current.config.buildConfig) + + def getLdPathEnvName(self): + return "PATH" + + def getIceInstallDir(self, mapping, current): + + platform = current.config.buildPlatform + config = "Debug" if current.config.buildConfig.find("Debug") >= 0 else "Release" + + with open(os.path.join(toplevel, "config", "icebuilder.props"), "r") as configFile: + version = re.search("<IceJSONVersion>(.*)</IceJSONVersion>", configFile.read()).group(1) + comp = self.getCompiler() if isinstance(mapping, CppMapping) else "net" + iceHome = os.environ.get("ICE_HOME") + + v140 = self.getCompiler() == "v140" + cpp = isinstance(mapping, CppMapping) + csharp = isinstance(mapping, CSharpMapping) + + # + # Use binary distribution from ICE_HOME if building for C++/VC140/x64/Release or + # for another mapping than C++ or C#. + # + if iceHome and ((cpp and v140 and platform == "x64" and config == "Release") or (not csharp and not cpp)): + return iceHome + + # + # Otherwise, use the appropriate nuget package + # + return os.path.join(mapping.path, "msbuild", "packages", "{0}".format(mapping.getNugetPackage(comp, version))) + + def canRun(self, mapping, current): + # + # On Windows, if testing with a binary distribution, don't test Glacier2/IceStorm services + # with the Debug configurations since we don't provide binaries for them. + # + if current.driver.useIceBinDist(mapping): + parent = re.match(r'^([\w]*).*', current.testcase.getTestSuite().getId()).group(1) + if parent in ["Glacier2", "IceStorm"] and current.config.buildConfig.find("Debug") >= 0: + return False + return True + +platform = None +if sys.platform == "darwin": + platform = Darwin() +elif sys.platform.startswith("aix"): + platform = AIX() +elif sys.platform.startswith("linux") or sys.platform.startswith("gnukfreebsd"): + platform = Linux() +elif sys.platform == "win32" or sys.platform[:6] == "cygwin": + platform = Windows() + +if not platform: + print("can't run on unknown platform `{0}'".format(sys.platform)) + sys.exit(1) + +def parseOptions(obj, options, mapped={}): + # Transform configuration options provided on the command line to + # object data members. The data members must be already set on the + # object and with the correct type. + if not hasattr(obj, "parsedOptions"): + obj.parsedOptions=[] + remaining = [] + for (o, a) in options: + if o.startswith("--"): o = o[2:] + if o.startswith("-"): o = o[1:] + if o in mapped: + o = mapped[o] + + if hasattr(obj, o): + if isinstance(getattr(obj, o), bool): + setattr(obj, o, a.lower() in ("yes", "true", "1") if a else True) + elif isinstance(getattr(obj, o), list): + l = getattr(obj, o) + l.append(a) + else: + if not a and not isinstance(a, str): + a = "0" + setattr(obj, o, type(getattr(obj, o))(a)) + if not o in obj.parsedOptions: + obj.parsedOptions.append(o) + else: + remaining.append((o, a)) + options[:] = remaining + + +class Mapping: + + mappings = OrderedDict() + + class Config: + + # All option values for Ice/IceBox tests. + coreOptions = { + "protocol" : ["tcp", "ssl", "wss", "ws"], + "compress" : [False, True], + "ipv6" : [False, True], + "serialize" : [False, True], + "mx" : [False, True], + } + + # All option values for IceGrid/IceStorm/Glacier2/IceDiscovery tests. + serviceOptions = { + "protocol" : ["tcp", "wss"], + "compress" : [False, True], + "ipv6" : [False, True], + "serialize" : [False, True], + "mx" : [False, True], + } + + @classmethod + def getSupportedArgs(self): + return ("", ["config=", "platform=", "protocol=", "compress", "ipv6", "serialize", "mx", + "cprops=", "sprops=", "uwp", "openssl"]) + + @classmethod + def usage(self): + pass + + @classmethod + def commonUsage(self): + print("") + print("Mapping options:") + print("--protocol=<prot> Run with the given protocol.") + print("--compress Run the tests with protocol compression.") + print("--ipv6 Use IPv6 addresses.") + print("--serialize Run with connection serialization.") + print("--mx Run with metrics enabled.") + print("--cprops=<properties> Specifies a list of additional client properties.") + print("--sprops=<properties> Specifies a list of additional server properties.") + print("--config=<config> Build configuration for native executables.") + print("--platform=<platform> Build platform for native executables.") + print("--uwp Run UWP (Universal Windows Platform).") + print("--openssl Run SSL tests with OpenSSL instead of the default platform SSL engine.") + + def __init__(self, options=[]): + # Build configuration + self.parsedOptions = [] + self.buildConfig = os.environ.get("CONFIGS", "").split(" ")[0] + if self.buildConfig: + self.parsedOptions.append("buildConfig") + else: + self.buildConfig = platform.getDefaultBuildConfig() + + self.buildPlatform = os.environ.get("PLATFORMS", "").split(" ")[0] + if self.buildPlatform: + self.parsedOptions.append("buildPlatform") + else: + self.buildPlatform = platform.getDefaultBuildPlatform() + + self.protocol = "tcp" + self.compress = False + self.serialize = False + self.ipv6 = False + self.mx = False + self.cprops = [] + self.sprops = [] + self.uwp = False + self.openssl = False + parseOptions(self, options, { "config" : "buildConfig", + "platform" : "buildPlatform", + "uwp" : "uwp", + "openssl" : "openssl" }) + + def __str__(self): + s = [] + for o in self.parsedOptions: + v = getattr(self, o) + if v: s.append(o if type(v) == bool else str(v)) + return ",".join(s) + + def getAll(self, current, testcase, rand=False): + + # + # A generator to generate combinations of options (e.g.: tcp/compress/mx, ssl/ipv6/serialize, etc) + # + def gen(supportedOptions): + + if not supportedOptions: + yield self + return + + supportedOptions = supportedOptions.copy() + supportedOptions.update(testcase.getMapping().getOptions(current)) + supportedOptions.update(testcase.getTestSuite().getOptions(current)) + supportedOptions.update(testcase.getOptions(current)) + + for o in self.parsedOptions: + # Remove options which were explicitly set + if o in supportedOptions: + del supportedOptions[o] + + if len(supportedOptions) == 0: + yield self + return + + # Find the option with the longest list of values + length = max([len(v) for v in supportedOptions.values()]) + + # Replace the values with a cycle iterator on the values + for (k, v) in supportedOptions.items(): + supportedOptions[k] = itertools.cycle(random.sample(v, len(v)) if rand else v) + + # Now, for the length of the longest array of values, we return + # an array with the supported option combinations + for i in range(0, length): + options = [] + for k, v in supportedOptions.items(): + v = next(v) + if v: + if type(v) == bool: + options.append(("--{0}".format(k), None)) + else: + options.append(("--{0}".format(k), v)) + + # Add parsed options + for o in self.parsedOptions: + v = getattr(self, o) + if type(v) == bool: + options.append(("--{0}".format(o), None)) + elif type(v) == list: + options += [("--{0}".format(o), e) for e in v] + else: + options.append(("--{0}".format(o), v)) + + yield self.__class__(options) + + options = None + parent = re.match(r'^([\w]*).*', testcase.getTestSuite().getId()).group(1) + if isinstance(testcase, ClientServerTestCase) and parent in ["Ice", "IceBox"]: + options = current.driver.filterOptions(testcase, self.coreOptions) + elif parent in ["IceGrid", "Glacier2", "IceStorm", "IceDiscovery"]: + options = current.driver.filterOptions(testcase, self.serviceOptions) + + return [c for c in gen(options)] + + def canRun(self, current): + if not platform.canRun(self, current): + return False + + options = {} + options.update(current.testcase.getMapping().getOptions(current)) + options.update(current.testcase.getTestSuite().getOptions(current)) + options.update(current.testcase.getOptions(current)) + + for (k, v) in options.items(): + if not hasattr(self, k): + continue + if not getattr(self, k) in v: + return False + else: + return True + + def cloneRunnable(self, current): + # + # Clone this configuration and make sure all the options are supported + # + options = {} + options.update(current.testcase.getMapping().getOptions(current)) + options.update(current.testcase.getTestSuite().getOptions(current)) + options.update(current.testcase.getOptions(current)) + clone = copy.copy(self) + for o in self.parsedOptions: + if o in options and getattr(self, o) not in options[o]: + setattr(clone, o, options[o][0] if len(options[o]) > 0 else None) + return clone + + def cloneAndOverrideWith(self, current): + # + # Clone this configuration and override options with options from the given configuration + # (the parent configuraton). This is usefull when running cross-testing. For example, JS + # tests don't support all the options so we clone the C++ configuration and override the + # options that are set on the JS configuration. + # + clone = copy.copy(self) + for o in current.config.parsedOptions + ["protocol"]: + if o not in ["buildConfig", "buildPlatform"]: + setattr(clone, o, getattr(current.config, o)) + clone.parsedOptions = current.config.parsedOptions + return clone + + def getArgs(self, process, current): + return [] + + def getProps(self, process, current): + props = {} + if isinstance(process, IceProcess): + props["Ice.Warn.Connections"] = True + if self.protocol: + props["Ice.Default.Protocol"] = self.protocol + if self.compress: + props["Ice.Override.Compress"] = "1" + if self.serialize: + props["Ice.ThreadPool.Server.Serialize"] = "1" + props["Ice.IPv6"] = self.ipv6 + if self.ipv6: + props["Ice.PreferIPv6Address"] = True + if self.mx: + props["Ice.Admin.Endpoints"] = "default -h localhost" + props["Ice.Admin.InstanceName"] = "Server" if isinstance(process, Server) else "Client" + props["IceMX.Metrics.Debug.GroupBy"] ="id" + props["IceMX.Metrics.Parent.GroupBy"] = "parent" + props["IceMX.Metrics.All.GroupBy"] = "none" + + # + # Speed up Windows testing. We override the connect timeout for some tests which are + # establishing connections to inactive ports. It takes around 1s for such connection + # establishment to fail on Windows. + # + # if isinstance(platform, Windows): + # if current.testsuite.getId().startswith("IceGrid") or \ + # current.testsuite.getId() in ["Ice/binding", + # "Ice/location", + # "Ice/background", + # "Ice/faultTolerance", + # "Ice/services", + # "IceDiscovery/simple"]: + # props["Ice.Override.ConnectTimeout"] = "400" + + # Additional properties specified on the command line with --cprops or --sprops + additionalProps = [] + if self.cprops and isinstance(process, Client): + additionalProps = self.cprops + elif self.sprops and isinstance(process, Server): + additionalProps = self.sprops + for pps in additionalProps: + for p in pps.split(" "): + if p.find("=") > 0: + (k , v) = p.split("=") + props[k] = v + else: + props[p] = True + + return props + + @classmethod + def getByName(self, name): + if not name in self.mappings: + raise RuntimeError("unknown mapping `{0}'".format(name)) + return self.mappings.get(name) + + @classmethod + def getByPath(self, path): + path = os.path.abspath(path) + for m in self.mappings.values(): + if path.startswith(m.getPath() + os.sep): + return m + + @classmethod + def add(self, name, mapping): + self.mappings[name] = mapping.init(name) + + @classmethod + def getAll(self): + languages = os.environ.get("LANGUAGES", None) + return [self.getByName(l) for l in languages.split(" ")] if languages else list(self.mappings.values()) + + def __init__(self, path=None): + self.platform = None + self.name = None + self.path = os.path.abspath(path) if path else None + self.testsuites = {} + + def init(self, name): + self.name = name + if not self.path: + self.path = os.path.join(toplevel, name) + return self + + def __str__(self): + return self.name + + def createConfig(self, options): + return self.Config(options) + + def filterTestSuite(self, testId, config, filters=[], rfilters=[]): + (pfilters, prfilters) = platform.getFilters(config) + for includes in [filters, [re.compile(pf) for pf in pfilters]]: + if len(includes) > 0: + for f in includes: + if f.search(self.name + "/" + testId): + break + else: + return True + + for excludes in [rfilters, [re.compile(pf) for pf in prfilters]]: + if len(excludes) > 0: + for f in excludes: + if f.search(self.name + "/" + testId): + return True + + return False + + def loadTestSuites(self, tests, config, filters=[], rfilters=[]): + for test in tests or [""]: + for root, dirs, files in os.walk(os.path.join(self.getTestsPath(), test.replace('/', os.sep))): + + testId = root[len(self.getTestsPath()) + 1:] + if os.sep != "/": + testId = testId.replace(os.sep, "/") + + if self.filterTestSuite(testId, config, filters, rfilters): + continue + + # + # First check if there's a test.py file in the directory, if there's one use it. + # + if "test.py" in files: + # + # WORKAROUND for Python issue 15230 (fixed in 3.2) where run_path doesn't work correctly. + # + #runpy.run_path(os.path.join(root, "test.py")) + origsyspath = sys.path + sys.path = [root] + sys.path + runpy.run_module("test", init_globals=globals(), run_name=root) + origsyspath = sys.path + continue + + # + # If there's no test.py file in the test directory, we check if there's a common + # script for the test in scripts/tests. If there's on we use it. + # + script = os.path.join(self.getCommonTestsPath(), testId + ".py") + if os.path.isfile(script): + runpy.run_module("tests." + testId.replace("/", "."), init_globals=globals(), run_name=root) + continue + + # + # Finally, we try to "discover/compute" the test by looking up for well-known + # files. + # + testcases = self.computeTestCases(testId, files) + if testcases: + TestSuite(root, testcases) + + def getTestSuites(self, ids=[]): + if not ids: + return self.testsuites.values() + return [self.testsuites[testSuiteId] for testSuiteId in ids if testSuiteId in self.testsuites] + + def addTestSuite(self, testsuite): + assert len(testsuite.path) > len(self.getTestsPath()) + 1 + testSuiteId = testsuite.path[len(self.getTestsPath()) + 1:].replace('\\', '/') + self.testsuites[testSuiteId] = testsuite + return testSuiteId + + def findTestSuite(self, testsuite): + return self.testsuites.get(testsuite if isinstance(testsuite, str) else testsuite.id) + + def computeTestCases(self, testId, files): + + # Instantiate a new test suite if the directory contains well-known source files. + + def checkFile(f, m): + try: + # If given mapping is same as local mapping, just check the files set, otherwise check + # with the mapping + return (self.getDefaultSource(f) in files) if m == self else m.hasSource(testId, f) + except KeyError: + # Expected if the mapping doesn't support the process type + return False + + checkClient = lambda f: checkFile(f, self.getClientMapping(testId)) + checkServer = lambda f: checkFile(f, self.getServerMapping(testId)) + + testcases = [] + if checkClient("client") and checkServer("server"): + testcases.append(ClientServerTestCase()) + if checkClient("client") and checkServer("serveramd") and self.getServerMapping(testId) == self: + testcases.append(ClientAMDServerTestCase()) + if checkClient("client") and len(testcases) == 0: + testcases.append(ClientTestCase()) + if checkClient("collocated"): + testcases.append(CollocatedTestCase()) + if len(testcases) > 0: + return testcases + + def hasSource(self, testId, processType): + try: + return os.path.exists(os.path.join(self.getTestsPath(), testId, self.getDefaultSource(processType))) + except KeyError: + return False + + def getPath(self): + return self.path + + def getTestsPath(self): + return os.path.join(self.path, "test") + + def getCommonTestsPath(self): + return os.path.join(self.path, "..", "scripts", "tests") + + def getTestCwd(self, process, current): + return current.testcase.getPath() + + def getDefaultSource(self, processType): + return processType + + def getDefaultProcesses(self, processType, testsuite): + # + # If no server or client is explicitly set with a testcase, getDefaultProcess is called + # to figure out which process class to instantiate. Based on the processType and the testsuite + # we instantiate the right default process class. + # + if processType is None: + return [] + elif testsuite.getId().startswith("IceUtil") or testsuite.getId().startswith("Slice"): + return [SimpleClient()] + elif testsuite.getId().startswith("IceGrid"): + if processType in ["client", "collocated"]: + return [IceGridClient()] + if processType in ["server", "serveramd"]: + return [IceGridServer()] + else: + return [Server()] if processType in ["server", "serveramd"] else [Client()] + + def getDefaultExe(self, processType, config): + return processType + + def getClientMapping(self, testId=None): + # The client mapping is always the same as this mapping. + return self + + def getServerMapping(self, testId=None): + # Can be overridden for client-only mapping that relies on another mapping for servers + return self + + def getBinDir(self, process, current): + return os.path.join(current.driver.getIceDir(self, current), platform.getBinSubDir(self, process, current)) + + def getLibDir(self, process, current): + return os.path.join(current.driver.getIceDir(self, current), platform.getLibSubDir(self, process, current)) + + def getBuildDir(self, name, current): + return platform.getBuildSubDir(name, current) + + def getCommandLine(self, current, process, exe): + name = exe + if isinstance(platform, Windows) and not exe.endswith(".exe"): + exe += ".exe" + if process.isFromBinDir(): + # If it's a process from the bin directory, the location is platform specific + # so we check with the platform. + return os.path.join(self.getBinDir(process, current), exe) + elif current.testcase: + # If it's a process from a testcase, the binary is in the test build directory. + return os.path.join(current.testcase.getPath(), current.getBuildDir(name), exe) + else: + return exe + + def getProps(self, process, current): + props = {} + if isinstance(process, IceProcess): + if current.config.protocol in ["bt", "bts"]: + props["Ice.Plugin.IceBT"] = self.getPluginEntryPoint("IceBT", process, current) + if current.config.protocol in ["ssl", "wss", "bts", "iaps"]: + props.update(self.getSSLProps(process, current)) + return props + + def getSSLProps(self, process, current): + sslProps = { + "Ice.Plugin.IceSSL" : self.getPluginEntryPoint("IceSSL", process, current), + "IceSSL.Password": "password", + "IceSSL.DefaultDir": os.path.join(toplevel, "certs"), + } + + # + # If the client doesn't support client certificates, set IceSSL.VerifyPeer to 0 + # + if isinstance(process, Server): + if isinstance(current.testsuite.getMapping(), JavaScriptMapping): + sslProps["IceSSL.VerifyPeer"] = 0 + + return sslProps + + def getArgs(self, process, current): + return [] + + def getEnv(self, process, current): + return {} + + def getOptions(self, current): + return {} + + def getRunOrder(self): + return ["Slice", "IceUtil", "Ice", "IceSSL", "IceBox", "Glacier2", "IceGrid", "IceStorm"] + + def getCrossTestSuites(self): + return [ + "Ice/ami", + "Ice/info", + "Ice/exceptions", + "Ice/enums", + "Ice/facets", + "Ice/inheritance", + "Ice/invoke", + "Ice/objects", + "Ice/operations", + "Ice/proxy", + "Ice/servantLocator", + "Ice/slicing/exceptions", + "Ice/slicing/objects", + "Ice/optional" + ] + +# +# A Runnable can be used as a "client" for in test cases, it provides +# implements run, setup and teardown methods. +# +class Runnable: + + def __init__(self, desc=None): + self.desc = desc + + def setup(self, current): + ### Only called when ran from testcase + pass + + def teardown(self, current, success): + ### Only called when ran from testcase + pass + + def run(self, current): + pass + +# +# A Process describes how to run an executable process. +# +class Process(Runnable): + + processType = None + + def __init__(self, exe=None, outfilters=None, quiet=False, args=None, props=None, envs=None, desc=None, + mapping=None, preexec_fn=None): + Runnable.__init__(self, desc) + self.exe = exe + self.outfilters = outfilters or [] + self.quiet = quiet + self.args = args or [] + self.props = props or {} + self.envs = envs or {} + self.mapping = mapping + self.preexec_fn = preexec_fn + + def __str__(self): + if not self.exe: + return str(self.__class__) + return self.exe + (" ({0})".format(self.desc) if self.desc else "") + + def getOutput(self, current): + assert(self in current.processes) + + def d(s): + return s if isPython2 else s.decode("utf-8") if isinstance(s, bytes) else s + + output = d(current.processes[self].getOutput()) + try: + # Apply outfilters to the output + if len(self.outfilters) > 0: + lines = output.split('\n') + newLines = [] + previous = "" + for line in [line + '\n' for line in lines]: + for f in self.outfilters: + if isinstance(f, types.LambdaType) or isinstance(f, types.FunctionType): + line = f(line) + elif f.search(line): + break + else: + if line.endswith('\n'): + if previous: + newLines.append(previous + line) + previous = "" + else: + newLines.append(line) + else: + previous += line + output = "".join(newLines) + output = output.strip() + return output + '\n' if output else "" + except Exception as ex: + print("unexpected exception while filtering process output:\n" + str(ex)) + raise + + def run(self, current, args=[], props={}, exitstatus=0, timeout=120): + class WatchDog: + + def __init__(self, timeout): + self.lastProgressTime = time.time() + self.timeout = timeout + self.lock = threading.Lock() + + def reset(self): + with self.lock: self.lastProgressTime = time.time() + + def timedOut(self): + with self.lock: + return (time.time() - self.lastProgressTime) >= self.timeout + + watchDog = WatchDog(timeout) + self.start(current, args, props, watchDog=watchDog) + if not self.quiet and not current.driver.isWorkerThread(): + # Print out the process output to stdout if we're running the client form the main thread. + current.processes[self].trace(self.outfilters) + try: + while True: + try: + current.processes[self].waitSuccess(exitstatus=exitstatus, timeout=30) + break + except Expect.TIMEOUT: + if watchDog and watchDog.timedOut(): + raise + finally: + self.stop(current, True, exitstatus) + + def getEffectiveArgs(self, current, args): + allArgs = [] + allArgs += current.driver.getArgs(self, current) + allArgs += current.config.getArgs(self, current) + allArgs += self.getMapping(current).getArgs(self, current) + allArgs += current.testcase.getArgs(self, current) + allArgs += self.getArgs(current) + allArgs += self.args(self, current) if callable(self.args) else self.args + allArgs += args + allArgs = [a.encode("utf-8") if type(a) == "unicode" else str(a) for a in allArgs] + return allArgs + + def getEffectiveProps(self, current, props): + allProps = {} + allProps.update(current.driver.getProps(self, current)) + allProps.update(current.config.getProps(self, current)) + allProps.update(self.getMapping(current).getProps(self, current)) + allProps.update(current.testcase.getProps(self, current)) + allProps.update(self.getProps(current)) + allProps.update(self.props(self, current) if callable(self.props) else self.props) + allProps.update(props) + return allProps + + def getEffectiveEnv(self, current): + allEnvs = {} + allEnvs.update(self.getMapping(current).getEnv(self, current)) + allEnvs.update(current.testcase.getEnv(self, current)) + allEnvs.update(self.getEnv(current)) + allEnvs.update(self.envs(self, current) if callable(self.envs) else self.envs) + return allEnvs + + def start(self, current, args=[], props={}, watchDog=None): + allArgs = self.getEffectiveArgs(current, args) + allProps = self.getEffectiveProps(current, props) + allEnvs = self.getEffectiveEnv(current) + + processController = current.driver.getProcessController(current, self) + current.processes[self] = processController.start(self, current, allArgs, allProps, allEnvs, watchDog) + try: + self.waitForStart(current) + except: + self.stop(current) + raise + + def waitForStart(self, current): + # To be overridden in specialization to wait for a token indicating the process readiness. + pass + + def stop(self, current, waitSuccess=False, exitstatus=0): + if self in current.processes: + try: + # Wait for the process to exit successfully by itself. + if not current.processes[self].isTerminated() and waitSuccess: + current.processes[self].waitSuccess(exitstatus=exitstatus, timeout=60) + finally: + if not current.processes[self].isTerminated(): + current.processes[self].terminate() + if not self.quiet: # Write the output to the test case (but not on stdout) + current.write(self.getOutput(current), stdout=False) + + def expect(self, current, pattern, timeout=60): + assert(self in current.processes and isinstance(current.processes[self], Expect.Expect)) + return current.processes[self].expect(pattern, timeout) + + def sendline(self, current, data): + assert(self in current.processes and isinstance(current.processes[self], Expect.Expect)) + return current.processes[self].sendline(data) + + def getMatch(self, current): + assert(self in current.processes and isinstance(current.processes[self], Expect.Expect)) + return current.processes[self].match + + def isStarted(self, current): + return self in current.processes and not current.processes[self].isTerminated() + + def isFromBinDir(self): + return False + + def getArgs(self, current): + return [] + + def getProps(self, current): + return {} + + def getEnv(self, current): + return {} + + def getMapping(self, current): + return self.mapping or current.testcase.getMapping() + + def getExe(self, current): + processType = self.processType or current.testcase.getProcessType(self) + return self.exe or self.getMapping(current).getDefaultExe(processType, current.config) + + def getCommandLine(self, current): + return self.getMapping(current).getCommandLine(current, self, self.getExe(current)) + +# +# A simple client (used to run Slice/IceUtil clients for example) +# +class SimpleClient(Process): + pass + +# +# An IceProcess specialization class. This is used by drivers to figure out if +# the process accepts Ice configuration properties. +# +class IceProcess(Process): + pass + +# +# An Ice server process. It's possible to configure when the server is considered +# ready by setting readyCount or ready. The start method will only return once +# the server is considered "ready". It can also be configure to wait (the default) +# or not wait for shutdown when the stop method is invoked. +# +class Server(IceProcess): + + def __init__(self, exe=None, waitForShutdown=True, readyCount=1, ready=None, startTimeout=120, *args, **kargs): + IceProcess.__init__(self, exe, *args, **kargs) + self.waitForShutdown = waitForShutdown + self.readyCount = readyCount + self.ready = ready + self.startTimeout = startTimeout + + def getProps(self, current): + props = IceProcess.getProps(self, current) + props.update({ + "Ice.ThreadPool.Server.Size": 1, + "Ice.ThreadPool.Server.SizeMax": 3, + "Ice.ThreadPool.Server.SizeWarn": 0, + }) + props.update(current.driver.getProcessProps(current, self.ready, self.readyCount + (1 if current.config.mx else 0))) + return props + + def waitForStart(self, current): + # Wait for the process to be ready + current.processes[self].waitReady(self.ready, self.readyCount + (1 if current.config.mx else 0), self.startTimeout) + + # Filter out remaining ready messages + self.outfilters.append(re.compile("[^\n]+ ready")) + + # If we are not asked to be quiet and running from the main thread, print the server output + if not self.quiet and not current.driver.isWorkerThread(): + current.processes[self].trace(self.outfilters) + + def stop(self, current, waitSuccess=False, exitstatus=0): + IceProcess.stop(self, current, waitSuccess and self.waitForShutdown, exitstatus) + +# +# An Ice client process. +# +class Client(IceProcess): + pass + +# +# Executables for processes inheriting this marker class are looked up in the +# Ice distribution bin directory. +# +class ProcessFromBinDir: + + def isFromBinDir(self): + return True + +class SliceTranslator(ProcessFromBinDir, SimpleClient): + + def __init__(self, translator): + SimpleClient.__init__(self, exe=translator, quiet=True, mapping=Mapping.getByName("cpp")) + + def getCommandLine(self, current): + translator = self.getMapping(current).getCommandLine(current, self, self.getExe(current)) + + # + # Look for slice2py installed by Pip if not found in the bin directory + # + if self.exe == "slice2py" and not os.path.exists(translator): + if isinstance(platform, Windows): + return os.path.join(os.path.dirname(sys.executable), "Scripts", "slice2py.exe") + elif os.path.exists("/usr/local/bin/slice2py"): + return "/usr/local/bin/slice2py" + else: + import slice2py + return sys.executable + " " + os.path.normpath( + os.path.join(slice2py.__file__, "..", "..", "..", "..", "bin", "slice2py")) + + return translator + +class EchoServer(Server): + + def __init__(self): + Server.__init__(self, mapping=Mapping.getByName("cpp"), quiet=True, waitForShutdown=False) + + def getCommandLine(self, current): + current.push(self.mapping.findTestSuite("Ice/echo").findTestCase("server")) + try: + return Server.getCommandLine(self, current) + finally: + current.pop() + +# +# A test case is composed of servers and clients. When run, all servers are started +# sequentially. When the servers are ready, the clients are also ran sequentially. +# Once all the clients are terminated, the servers are stopped (which waits for the +# successful completion of the server). +# +# A TestCase is also a "Runnable", like the Process class. In other words, it can be +# used a client to allow nested test cases. +# +class TestCase(Runnable): + + def __init__(self, name, client=None, clients=None, server=None, servers=None, args=None, props=None, envs=None, + options=None, desc=None): + Runnable.__init__(self, desc) + + self.name = name + self.parent = None + self.mapping = None + self.testsuite = None + self.options = options or {} + self.args = args or [] + self.props = props or {} + self.envs = envs or {} + + # + # Setup client list, "client" can be a string in which case it's assumed to + # to the client executable name. + # + self.clients = clients + if client: + client = Client(exe=client) if isinstance(client, str) else client + self.clients = [client] if not self.clients else self.clients + [client] + + # + # Setup server list, "server" can be a string in which case it's assumed to + # to the server executable name. + # + self.servers = servers + if server: + server = Server(exe=server) if isinstance(server, str) else server + self.servers = [server] if not self.servers else self.servers + [server] + + def __str__(self): + return self.name + + def init(self, mapping, testsuite): + # init is called when the testcase is added to the given testsuite + self.mapping = mapping + self.testsuite = testsuite + + # + # If no clients are explicitly specified, we instantiate one if getClientType() + # returns the type of client to instantiate (client, collocated, etc) + # + if not self.clients: + self.clients = self.mapping.getDefaultProcesses(self.getClientType(), testsuite) + + # + # If no servers are explicitly specified, we instantiate one if getServerType() + # returns the type of server to instantiate (server, serveramd, etc) + # + if not self.servers: + self.servers = self.mapping.getDefaultProcesses(self.getServerType(), testsuite) + + def getOptions(self, current): + return self.options(current) if callable(self.options) else self.options + + def canRun(self, current): + # Can be overriden + return True + + def setupServerSide(self, current): + # Can be overridden to perform setup activities before the server side is started + pass + + def teardownServerSide(self, current, success): + # Can be overridden to perform terddown after the server side is stopped + pass + + def setupClientSide(self, current): + # Can be overridden to perform setup activities before the client side is started + pass + + def teardownClientSide(self, current, success): + # Can be overridden to perform terddown after the client side is stopped + pass + + def startServerSide(self, current): + for server in self.servers: + self._startServer(current, server) + + def stopServerSide(self, current, success): + for server in reversed(self.servers): + self._stopServer(current, server, success) + + def runClientSide(self, current): + for client in self.clients: + self._runClient(current, client) + + def getTestSuite(self): + return self.testsuite + + def getParent(self): + return self.parent + + def getName(self): + return self.name + + def getPath(self): + return self.testsuite.getPath() + + def getMapping(self): + return self.mapping + + def getArgs(self, process, current): + return self.args + + def getProps(self, process, current): + return self.props + + def getEnv(self, process, current): + return self.envs + + def getProcessType(self, process): + if process in self.clients: + return self.getClientType() + elif process in self.servers: + return self.getServerType() + elif isinstance(process, Server): + return self.getServerType() + else: + return self.getClientType() + + def getClientType(self): + # Overridden by test case specialization to specify the type of client to instantiate + # if no client is explicitly provided + return None + + def getServerType(self): + # Overridden by test case specialization to specify the type of client to instantiate + # if no server is explicitly provided + return None + + def getServerTestCase(self, cross=None): + testsuite = (cross or self.mapping).getServerMapping(self.testsuite.getId()).findTestSuite(self.testsuite) + return testsuite.findTestCase(self) if testsuite else None + + def getClientTestCase(self): + testsuite = self.mapping.getClientMapping(self.testsuite.getId()).findTestSuite(self.testsuite) + return testsuite.findTestCase(self) if testsuite else None + + def _startServerSide(self, current): + # Set the host to use for the server side + current.push(self) + current.host = current.driver.getProcessController(current).getHost(current) + self.setupServerSide(current) + try: + self.startServerSide(current) + return current.host + except: + self._stopServerSide(current, False) + raise + finally: + current.pop() + + def _stopServerSide(self, current, success): + current.push(self) + try: + self.stopServerSide(current, success) + finally: + for server in reversed(self.servers): + if server.isStarted(current): + self._stopServer(current, server, False) + self.teardownServerSide(current, success) + current.pop() + + def _startServer(self, current, server): + if server.desc: + current.write("starting {0}... ".format(server.desc)) + server.setup(current) + server.start(current) + if server.desc: + current.writeln("ok") + + def _stopServer(self, current, server, success): + try: + server.stop(current, success) + except: + success = False + raise + finally: + server.teardown(current, success) + + def _runClientSide(self, current, host=None): + current.push(self, host) + self.setupClientSide(current) + success = False + try: + self.runClientSide(current) + success = True + finally: + self.teardownClientSide(current, success) + current.pop() + + def _runClient(self, current, client): + success = False + if client.desc: + current.writeln("running {0}...".format(client.desc)) + client.setup(current) + try: + client.run(current) + success = True + finally: + client.teardown(current, success) + + def run(self, current): + try: + current.push(self) + current.result.started(self) + self.runWithDriver(current) + current.result.succeeded(self) + except Exception as ex: + current.result.failed(self, traceback.format_exc() if current.driver.debug else str(ex)) + raise + finally: + current.pop() + +class ClientTestCase(TestCase): + + def __init__(self, name="client", *args, **kargs): + TestCase.__init__(self, name, *args, **kargs) + + def runWithDriver(self, current): + current.driver.runTestCase(current) + + def getClientType(self): + return "client" + +class ClientServerTestCase(ClientTestCase): + + def __init__(self, name="client/server", *args, **kargs): + TestCase.__init__(self, name, *args, **kargs) + + def runWithDriver(self, current): + current.driver.runClientServerTestCase(current) + + def getServerType(self): + return "server" + +class CollocatedTestCase(ClientTestCase): + + def __init__(self, name="collocated", *args, **kargs): + TestCase.__init__(self, name, *args, **kargs) + + def getClientType(self): + return "collocated" + +class ClientAMDServerTestCase(ClientServerTestCase): + + def __init__(self, name="client/amd server", *args, **kargs): + ClientServerTestCase.__init__(self, name, *args, **kargs) + + def getServerType(self): + return "serveramd" + +class Result: + + def __init__(self, testsuite, writeToStdout): + self.testsuite = testsuite + self._skipped = [] + self._failed = {} + self._succeeded = [] + self._stdout = StringIO() + self._writeToStdout = writeToStdout + self._testcases = {} + self._duration = 0 + + def start(self): + self._duration = time.time() + + def finished(self): + self._duration = time.time() - self._duration + + def started(self, testcase): + self._start = self._stdout.tell() + + def failed(self, testcase, exception): + self.writeln("\ntest in {0} failed:\n{1}".format(self.testsuite, exception)) + self._testcases[testcase] = (self._start, self._stdout.tell()) + self._failed[testcase] = exception + + def succeeded(self, testcase): + self._testcases[testcase] = (self._start, self._stdout.tell()) + self._succeeded.append(testcase) + + def isSuccess(self): + return len(self._failed) == 0 + + def getFailed(self): + return self._failed + + def getDuration(self): + return self._duration + + def getOutput(self, testcase=None): + if testcase: + if testcase in self._testcases: + (start, end) = self._testcases[testcase] + self._stdout.seek(start) + try: + return self._stdout.read(end - start) + finally: + self._stdout.seek(os.SEEK_END) + + return self._stdout.getvalue() + + def write(self, msg, stdout=True): + if self._writeToStdout and stdout: + try: + sys.stdout.write(msg) + except UnicodeEncodeError: + # + # The console doesn't support the encoding of the message, we convert the message + # to an UTF-8 byte sequence and print out the byte sequence. We replace all the + # double backslash from the byte sequence string representation to single back + # slash. + # + sys.stdout.write(str(msg.encode("utf-8")).replace("\\\\", "\\")) + sys.stdout.flush() + self._stdout.write(msg) + + def writeln(self, msg, stdout=True): + if self._writeToStdout and stdout: + try: + print(msg) + except UnicodeEncodeError: + # + # The console doesn't support the encoding of the message, we convert the message + # to an UTF-8 byte sequence and print out the byte sequence. We replace all the + # double backslash from the byte sequence string representation to single back + # slash. + # + print(str(msg.encode("utf-8")).replace("\\\\", "\\")) + self._stdout.write(msg) + self._stdout.write("\n") + +class TestSuite: + + def __init__(self, path, testcases=None, options=None, libDirs=None, runOnMainThread=False, chdir=False, + multihost=True): + self.path = os.path.dirname(path) if os.path.basename(path) == "test.py" else path + self.mapping = Mapping.getByPath(self.path) + self.id = self.mapping.addTestSuite(self) + self.options = options or {} + self.libDirs = libDirs or [] + self.runOnMainThread = runOnMainThread + self.chdir = chdir + self.multihost = multihost + if self.chdir: + # Only tests running on main thread can change the current working directory + self.runOnMainThread = True + + if testcases is None: + files = [f for f in os.listdir(self.path) if os.path.isfile(os.path.join(self.path, f))] + testcases = self.mapping.computeTestCases(self.id, files) + self.testcases = OrderedDict() + for testcase in testcases if testcases else []: + testcase.init(self.mapping, self) + if testcase.name in self.testcases: + raise RuntimeError("duplicate testcase {0} in testsuite {1}".format(testcase, self)) + self.testcases[testcase.name] = testcase + + def __str__(self): + return self.id + + def getId(self): + return self.id + + def getOptions(self, current): + return self.options(current) if callable(self.options) else self.options + + def getPath(self): + return self.path + + def getMapping(self): + return self.mapping + + def getLibDirs(self): + return self.libDirs + + def isMainThreadOnly(self): + for m in [CppMapping, JavaMapping, CSharpMapping]: + if isinstance(self.mapping, m): + return self.runOnMainThread + else: + return True + + def addTestCase(self, testcase): + if testcase.name in self.testcases: + raise RuntimeError("duplicate testcase {0} in testsuite {1}".format(testcase, self)) + testcase.init(self.mapping, self) + self.testcases[testcase.name] = testcase + + def findTestCase(self, testcase): + return self.testcases.get(testcase if isinstance(testcase, str) else testcase.name) + + def getTestCases(self): + return self.testcases.values() + + def setup(self, current): + pass + + def run(self, current): + try: + current.result.start() + cwd=None + if self.chdir: + cwd = os.getcwd() + os.chdir(self.path) + current.driver.runTestSuite(current) + finally: + if cwd: os.chdir(cwd) + current.result.finished() + + def teardown(self, current, success): + pass + + def isMultiHost(self): + return self.multihost + + def isCross(self): + # Only run the tests that support cross testing --all-cross or --cross + return self.id in self.mapping.getCrossTestSuites() + +class ProcessController: + + def __init__(self, current): + pass + + def start(self, process, current, args, props, envs, watchDog): + raise NotImplemented() + + def destroy(self, driver): + pass + +class LocalProcessController(ProcessController): + + class LocalProcess(Expect.Expect): + + def waitReady(self, ready, readyCount, startTimeout): + if ready: + self.expect("%s ready\n" % ready, timeout = startTimeout) + else: + while readyCount > 0: + self.expect("[^\n]+ ready\n", timeout = startTimeout) + readyCount -= 1 + + def isTerminated(self): + return self.p is None + + def getHost(self, current): + # Depending on the configuration, either use an IPv4, IPv6 or BT address for Ice.Default.Host + if current.config.protocol == "bt": + if not current.driver.hostBT: + raise Test.Common.TestCaseFailedException("no Bluetooth address set with --host-bt") + return current.driver.hostBT + elif current.config.ipv6: + return current.driver.hostIPv6 or "::1" + else: + return current.driver.host or "127.0.0.1" + + def start(self, process, current, args, props, envs, watchDog): + + # + # Props and arguments can use the format parameters set below in the kargs + # dictionary. It's time to convert them to their values. + # + kargs = { + "process": process, + "testcase": current.testcase, + "testdir": current.testsuite.getPath(), + "builddir": current.getBuildDir(process.getExe(current)), + "icedir" : current.driver.getIceDir(current.testcase.getMapping(), current), + } + + args = ["--{0}={1}".format(k, val(v)) for k,v in props.items()] + [val(a) for a in args] + for k, v in envs.items(): + envs[k] = val(v, quoteValue=False) + + cmd = (process.getCommandLine(current) + (" " + " ".join(args) if len(args) > 0 else "")).format(**kargs) + if current.driver.debug: + if len(envs) > 0: + current.writeln("({0} env={1})".format(cmd, envs)) + else: + current.writeln("({0})".format(cmd)) + + env = os.environ.copy() + env.update(envs) + mapping = process.getMapping(current) + cwd = mapping.getTestCwd(process, current) + process = LocalProcessController.LocalProcess(cmd, + startReader=False, + env=env, + cwd=cwd, + desc=process.desc, + preexec_fn=process.preexec_fn, + mapping=str(mapping)) + process.startReader(watchDog) + return process + +class RemoteProcessController(ProcessController): + + class RemoteProcess: + def __init__(self, exe, proxy): + self.exe = exe + self.proxy = proxy + self.terminated = False + self.stdout = False + + def waitReady(self, ready, readyCount, startTimeout): + self.proxy.waitReady(startTimeout) + + def waitSuccess(self, exitstatus=0, timeout=60): + try: + result = self.proxy.waitSuccess(timeout) + except: + raise Expect.TIMEOUT("waitSuccess timeout") + if exitstatus != result: + raise RuntimeError("unexpected exit status: expected: %d, got %d\n" % (exitstatus, result)) + + def getOutput(self): + return self.output + + def trace(self, outfilters): + self.stdout = True + + def isTerminated(self): + return self.terminated + + def terminate(self): + self.output = self.proxy.terminate().strip() + self.terminated = True + if self.stdout and self.output: + print(self.output) + + def __init__(self, current, endpoints=None): + self.processControllerProxies = {} + self.controllerApps = [] + self.cond = threading.Condition() + if endpoints: + comm = current.driver.getCommunicator() + import Test + + class ProcessControllerRegistryI(Test.Common.ProcessControllerRegistry): + + def __init__(self, remoteProcessController): + self.remoteProcessController = remoteProcessController + + def setProcessController(self, proxy, current): + import Test + proxy = Test.Common.ProcessControllerPrx.uncheckedCast(current.con.createProxy(proxy.ice_getIdentity())) + self.remoteProcessController.setProcessController(proxy) + + self.adapter = comm.createObjectAdapterWithEndpoints("Adapter", endpoints) + self.adapter.add(ProcessControllerRegistryI(self), comm.stringToIdentity("Util/ProcessControllerRegistry")) + self.adapter.activate() + else: + self.adapter = None + + def __str__(self): + return "remote controller" + + def getHost(self, current): + return self.getController(current).getHost(current.config.protocol, current.config.ipv6) + + def getController(self, current): + ident = self.getControllerIdentity(current) + if type(ident) == str: + ident = current.driver.getCommunicator().stringToIdentity(ident) + + with self.cond: + if ident in self.processControllerProxies: + return self.processControllerProxies[ident] + + comm = current.driver.getCommunicator() + import Ice + import Test + + if current.driver.controllerApp: + self.controllerApps.append(ident) + self.startControllerApp(current, ident) + + if not self.adapter: + # Use well-known proxy and IceDiscovery to discover the process controller object from the app. + proxy = Test.Common.ProcessControllerPrx.uncheckedCast(comm.stringToProxy(comm.identityToString(ident))) + try: + proxy.ice_ping() + except Exception as ex: + raise RuntimeError("couldn't reach the remote controller `{0}'".format(proxy)) + + with self.cond: + self.processControllerProxies[ident] = proxy + return self.processControllerProxies[ident] + else: + # Wait 10 seconds for a process controller to be registered with the ProcessControllerRegistry + with self.cond: + if not ident in self.processControllerProxies: + self.cond.wait(10) + if ident in self.processControllerProxies: + return self.processControllerProxies[ident] + raise RuntimeError("couldn't reach the remote controller `{0}'".format(ident)) + + + def setProcessController(self, proxy): + with self.cond: + self.processControllerProxies[proxy.ice_getIdentity()] = proxy + conn = proxy.ice_getConnection() + if(hasattr(conn, "setCloseCallback")): + proxy.ice_getConnection().setCloseCallback(lambda conn : self.clearProcessController(proxy, conn)) + else: + import Ice + class CallbackI(Ice.ConnectionCallback): + def __init__(self, registry): + self.registry = registry + + def heartbeath(self, conn): + pass + + def closed(self, conn): + self.registry.clearProcessController(proxy, conn) + + proxy.ice_getConnection().setCallback(CallbackI(self)) + + self.cond.notifyAll() + + def clearProcessController(self, proxy, conn): + with self.cond: + if proxy.ice_getIdentity() in self.processControllerProxies: + if conn == self.processControllerProxies[proxy.ice_getIdentity()].ice_getCachedConnection(): + del self.processControllerProxies[proxy.ice_getIdentity()] + + def startControllerApp(self, current, ident): + pass + + def stopControllerApp(self, ident): + pass + + def start(self, process, current, args, props, envs, watchDog): + # Get the process controller + processController = self.getController(current) + + # TODO: support envs? + + exe = process.getExe(current) + args = ["--{0}={1}".format(k, val(v, quoteValue=False)) for k,v in props.items()] + [val(a) for a in args] + if current.driver.debug: + current.writeln("(executing `{0}/{1}' on `{2}' args = {3})".format(current.testsuite, exe, self, args)) + prx = processController.start(str(current.testsuite), exe, args) + + # Create bi-dir proxy in case we're talking to a bi-bir process controller. + if self.adapter: + prx = processController.ice_getConnection().createProxy(prx.ice_getIdentity()) + import Test + return RemoteProcessController.RemoteProcess(exe, Test.Common.ProcessPrx.uncheckedCast(prx)) + + def destroy(self, driver): + if driver.controllerApp: + for ident in self.controllerApps: + self.stopControllerApp(ident) + self.controllerApps = [] + if self.adapter: + self.adapter.destroy() + +class iOSSimulatorProcessController(RemoteProcessController): + + device = "iOSSimulatorProcessController" + deviceID = "com.apple.CoreSimulator.SimDeviceType.iPhone-6" + runtimeID = "com.apple.CoreSimulator.SimRuntime.iOS-10-2" + appPath = "ios/controller/build" + + def __init__(self, current): + RemoteProcessController.__init__(self, current) + self.simulatorID = None + + def __str__(self): + return "iOS Simulator" + + def getControllerIdentity(self, current): + if isinstance(current.testcase.getMapping(), ObjCMapping): + if current.config.arc: + return "iPhoneSimulator/com.zeroc.ObjC-ARC-Test-Controller" + else: + return "iPhoneSimulator/com.zeroc.ObjC-Test-Controller" + else: + assert(isinstance(current.testcase.getMapping(), CppMapping)) + if current.config.cpp11: + return "iPhoneSimulator/com.zeroc.Cpp11-Test-Controller" + else: + return "iPhoneSimulator/com.zeroc.Cpp98-Test-Controller" + + def startControllerApp(self, current, ident): + mapping = current.testcase.getMapping() + if isinstance(mapping, ObjCMapping): + appName = "Objective-C ARC Test Controller.app" if current.config.arc else "Objective-C Test Controller.app" + else: + assert(isinstance(mapping, CppMapping)) + appName = "C++11 Test Controller.app" if current.config.cpp11 else "C++98 Test Controller.app" + + sys.stdout.write("launching simulator... ") + sys.stdout.flush() + try: + run("xcrun simctl boot \"{0}\"".format(self.device)) + except Exception as ex: + if str(ex).find("Booted") >= 0: + pass + elif str(ex).find("Invalid device") >= 0: + # Create the simulator device if it doesn't exist + self.simulatorID = run("xcrun simctl create \"{0}\" {1} {2}".format(self.device, self.deviceID, self.runtimeID)) + run("xcrun simctl boot \"{0}\"".format(self.device)) + else: + raise + print("ok") + + sys.stdout.write("launching {0}... ".format(appName)) + sys.stdout.flush() + path = os.path.join(mapping.getTestsPath(), self.appPath, "Debug-iphonesimulator", appName) + if not os.path.exists(path): + path = os.path.join(mapping.getTestsPath(), self.appPath, "Release-iphonesimulator", appName) + if not os.path.exists(path): + raise RuntimeError("couldn't find iOS simulator controller application, did you build it?") + run("xcrun simctl install \"{0}\" \"{1}\"".format(self.device, path)) + run("xcrun simctl launch \"{0}\" {1}".format(self.device, ident.name)) + print("ok") + + def stopControllerApp(self, ident): + try: + run("xcrun simctl uninstall \"{0}\" {1}".format(self.device, ident.name)) + except: + pass + + def destroy(self, driver): + RemoteProcessController.destroy(self, driver) + + sys.stdout.write("shutting down simulator... ") + sys.stdout.flush() + try: + run("xcrun simctl shutdown \"{0}\"".format(self.simulatorID)) + except: + pass + print("ok") + + if self.simulatorID: + sys.stdout.write("destroying simulator... ") + sys.stdout.flush() + try: + run("xcrun simctl delete \"{0}\"".format(self.simulatorID)) + except: + pass + print("ok") + +class iOSDeviceProcessController(RemoteProcessController): + + appPath = "cpp/test/ios/controller/build" + + def __init__(self, current): + RemoteProcessController.__init__(self, current) + + def __str__(self): + return "iOS Device" + + def getControllerIdentity(self, current): + if isinstance(current.testcase.getMapping(), ObjCMapping): + return "iPhoneOS/com.zeroc.ObjC-Test-Controller" + else: + assert(isinstance(current.testcase.getMapping(), CppMapping)) + if current.config.cpp11: + return "iPhoneOS/com.zeroc.Cpp11-Test-Controller" + else: + return "iPhoneOS/com.zeroc.Cpp98-Test-Controller" + + def startControllerApp(self, current, ident): + # TODO: use ios-deploy to deploy and run the application on an attached device? + pass + + def stopControllerApp(self, ident): + pass + +class UWPProcessController(RemoteProcessController): + + def __init__(self, current): + RemoteProcessController.__init__(self, current, "tcp -h 127.0.0.1 -p 15001") + self.name = "ice-uwp-controller" + self.appUserModelId = "ice-uwp-controller_3qjctahehqazm" + + def __str__(self): + return "UWP" + + def getControllerIdentity(self, current): + return "UWP/ProcessController" + + def startControllerApp(self, current, ident): + platform = current.config.buildPlatform + config = current.config.buildConfig + layout = os.path.join(toplevel, "cpp", "test", platform, config, "AppX") + + self.packageFullName = "{0}_1.0.0.0_{1}__3qjctahehqazm".format( + self.name, "x86" if platform == "Win32" else platform) + + prefix = "controller_1.0.0.0_{0}{1}".format(platform, "_{0}".format(config) if config == "Debug" else "") + package = os.path.join(toplevel, "cpp", "msbuild", "AppPackages", "controller", + "{0}_Test".format(prefix), "{0}.appx".format(prefix)) + + # + # If the application is already installed remove it, this will also take care + # of closing it. + # + if self.name in run("powershell Get-AppxPackage -Name {0}".format(self.name)): + run("powershell Remove-AppxPackage {0}".format(self.packageFullName)) + + # + # Remove any previous package we have extracted to ensure we use a + # fresh build + # + if os.path.exists(layout): + shutil.rmtree(layout) + os.makedirs(layout) + + print("Unpacking package: {0} to {1}....".format(os.path.basename(package), layout)) + run("MakeAppx.exe unpack /p \"{0}\" /d \"{1}\" /l".format(package, layout)) + + print("Registering application to run from layout...") + run("powershell Add-AppxPackage -Register \"{0}/AppxManifest.xml\"".format(layout)) + + run("CheckNetIsolation LoopbackExempt -a -n={0}".format(self.appUserModelId)) + + # + # microsoft.windows.softwarelogo.appxlauncher.exe returns the PID as return code + # and 0 on case of failures. We pass err=True to run to handle this. + # + print("starting UWP controller app...") + run('"{0}" {1}!App'.format( + "C:/Program Files (x86)/Windows Kits/10/App Certification Kit/microsoft.windows.softwarelogo.appxlauncher.exe", + self.appUserModelId), err=True) + + def stopControllerApp(self, ident): + try: + run("powershell Remove-AppxPackage {0}".format(self.packageFullName)) + run("CheckNetIsolation LoopbackExempt -c -n={0}".format(self.appUserModelId)) + except: + pass + +class BrowserProcessController(RemoteProcessController): + + def __init__(self, current): + RemoteProcessController.__init__(self, current, "ws -h 127.0.0.1 -p 15002:wss -h 127.0.0.1 -p 15003") + self.httpServer = None + self.testcase = None + try: + from selenium import webdriver + if not hasattr(webdriver, current.config.browser): + raise RuntimeError("unknown browser `{0}'".format(current.config.browser)) + + if current.config.browser == "Firefox": + # + # We need to specify a profile for Firefox. This profile only provides the cert8.db which + # contains our Test CA cert. It should be possible to avoid this by setting the webdriver + # acceptInsecureCerts capability but it's only supported by latest Firefox releases. + # + # capabilities = webdriver.DesiredCapabilities.FIREFOX.copy() + # capabilities["marionette"] = True + # capabilities["acceptInsecureCerts"] = True + # capabilities["moz:firefoxOptions"] = {} + # capabilities["moz:firefoxOptions"]["binary"] = "/Applications/FirefoxNightly.app/Contents/MacOS/firefox-bin" + if isinstance(platform, Linux) and os.environ.get("DISPLAY", "") != ":1" and os.environ.get("USER", "") == "ubuntu": + current.writeln("error: DISPLAY is unset, setting it to :1") + os.environ["DISPLAY"] = ":1" + + profile = webdriver.FirefoxProfile(os.path.join(toplevel, "scripts", "selenium", "firefox")) + self.driver = webdriver.Firefox(firefox_profile=profile) + else: + self.driver = getattr(webdriver, current.config.browser)() + + cmd = "node -e \"require('./bin/HttpServer')()\""; + cwd = current.testcase.getMapping().getPath() + self.httpServer = Expect.Expect(cmd, cwd=cwd) + self.httpServer.expect("listening on ports") + except: + self.destroy(current.driver) + raise + + def __str__(self): + return str(self.driver) + + def getControllerIdentity(self, current): + # + # Load the controller page each time we're asked for the controller and if we're running + # another testcase, the controller page will connect to the process controller registry + # to register itself with this script. + # + if self.testcase != current.testcase: + self.testcase = current.testcase + testsuite = ("es5/" if current.config.es5 else "") + str(current.testsuite) + if current.config.protocol == "wss": + protocol = "https" + port = "9090" + cport = "15003" + else: + protocol = "http" + port = "8080" + cport = "15002" + self.driver.get("{0}://127.0.0.1:{1}/test/{2}/controller.html?port={3}&worker={4}".format(protocol, + port, + testsuite, + cport, + current.config.worker)) + return "Browser/ProcessController" + + def destroy(self, driver): + if self.httpServer: + self.httpServer.terminate() + self.httpServer = None + + try: + self.driver.quit() + except: + pass + +class Driver: + + class Current: + + def __init__(self, driver, testsuite, result): + self.driver = driver + self.testsuite = testsuite + self.config = driver.configs[testsuite.getMapping()] + self.result = result + self.host = None + self.testcase = None + self.testcases = [] + self.processes = {} + self.dirs = [] + self.files = [] + + def getTestEndpoint(self, *args, **kargs): + return self.driver.getTestEndpoint(*args, **kargs) + + def getBuildDir(self, name): + return self.testcase.getMapping().getBuildDir(name, self) + + def getPluginEntryPoint(self, plugin, process): + return self.testcase.getMapping().getPluginEntryPoint(plugin, process, self) + + def write(self, *args, **kargs): + self.result.write(*args, **kargs) + + def writeln(self, *args, **kargs): + self.result.writeln(*args, **kargs) + + def push(self, testcase, host=None): + if not testcase.mapping: + assert(not testcase.parent and not testcase.testsuite) + testcase.mapping = self.testcase.getMapping() + testcase.testsuite = self.testcase.getTestSuite() + testcase.parent = self.testcase + self.testcases.append((self.testcase, self.config, self.host)) + self.testcase = testcase + self.config = self.driver.configs[self.testcase.getMapping()].cloneAndOverrideWith(self) + self.host = host + + def pop(self): + assert(self.testcase) + testcase = self.testcase + (self.testcase, self.config, self.host) = self.testcases.pop() + if testcase.parent and self.testcase != testcase: + testcase.mapping = None + testcase.testsuite = None + testcase.parent = None + + def createFile(self, path, lines, encoding=None): + path = os.path.join(self.testsuite.getPath(), path.decode("utf-8") if isPython2 else path) + with open(path, "w", encoding=encoding) if not isPython2 and encoding else open(path, "w") as file: + for l in lines: + file.write("%s\n" % l) + self.files.append(path) + + def mkdirs(self, dirs): + for d in dirs if isinstance(dirs, list) else [dirs]: + d = os.path.join(self.testsuite.getPath(), d) + self.dirs.append(d) + if not os.path.exists(d): + os.makedirs(d) + + def destroy(self): + for d in self.dirs: + if os.path.exists(d): shutil.rmtree(d) + for f in self.files: + if os.path.exists(f): os.unlink(f) + + drivers = {} + driver = "local" + + @classmethod + def add(self, name, driver, default=False): + if default: + Driver.driver = name + self.driver = name + self.drivers[name] = driver + + @classmethod + def getAll(self): + return list(self.drivers.values()) + + @classmethod + def create(self, options): + parseOptions(self, options) + driver = self.drivers.get(self.driver) + if not driver: + raise RuntimeError("unknown driver `{0}'".format(self.driver)) + return driver(options) + + @classmethod + def getSupportedArgs(self): + return ("dlrR", ["debug", "driver=", "filter=", "rfilter=", "host=", "host-ipv6=", "host-bt=", "interface=", + "controller-app"]) + + @classmethod + def usage(self): + pass + + @classmethod + def commonUsage(self): + print("") + print("Driver options:") + print("-d | --debug Verbose information.") + print("--driver=<driver> Use the given driver (local, client, server or remote).") + print("--filter=<regex> Run all the tests that match the given regex.") + print("--rfilter=<regex> Run all the tests that do not match the given regex.") + print("--host=<addr> The IPv4 address to use for Ice.Default.Host.") + print("--host-ipv6=<addr> The IPv6 address to use for Ice.Default.Host.") + print("--host-bt=<addr> The Bluetooth address to use for Ice.Default.Host.") + print("--interface=<IP> The multicast interface to use to discover controllers.") + print("--controller-app Start the process controller application.") + + def __init__(self, options): + self.debug = False + self.filters = [] + self.rfilters = [] + self.host = "" + self.hostIPv6 = "" + self.hostBT = "" + self.controllerApp = False + + self.failures = [] + parseOptions(self, options, { "d": "debug", + "r" : "filters", + "R" : "rfilters", + "filter" : "filters", + "rfilter" : "rfilters", + "host-ipv6" : "hostIPv6", + "host-bt" : "hostBT", + "controller-app" : "controllerApp"}) + + self.filters = [re.compile(a) for a in self.filters] + self.rfilters = [re.compile(a) for a in self.rfilters] + + self.communicator = None + self.interface = "" + self.processControllers = {} + + def setConfigs(self, configs): + self.configs = configs + + def useIceBinDist(self, mapping): + env = os.environ.get("ICE_BIN_DIST", "").split() + return 'all' in env or mapping in env + + def getIceDir(self, mapping, current): + if self.useIceBinDist(mapping): + return platform.getIceInstallDir(mapping, current) + elif mapping: + return mapping.getPath() + else: + return toplevel + + def getSliceDir(self, mapping, current): + return platform.getSliceDir(self.getIceDir(mapping, current) if self.useIceBinDist(mapping) else toplevel) + + def isWorkerThread(self): + return False + + def getTestEndpoint(self, portnum, protocol="default"): + return "{0} -p {1}".format(protocol, self.getTestPort(portnum)) + + def getTestPort(self, portnum): + return 12010 + portnum + + def getArgs(self, process, current): + ### Return driver specific arguments + return [] + + def getProps(self, process, current): + props = {} + if isinstance(process, IceProcess): + if not self.host: + props["Ice.Default.Host"] = "0:0:0:0:0:0:0:1" if current.config.ipv6 else "127.0.0.1" + else: + props["Ice.Default.Host"] = self.host + return props + + def getMappings(self): + ### Return additional mappings to load required by the driver + return [] + + def getCommunicator(self): + self.initCommunicator() + return self.communicator + + def initCommunicator(self): + if self.communicator: + return + + try: + import Ice + except ImportError: + # Try to add the local Python build to the sys.path + pythonMapping = Mapping.getByName("python") + if pythonMapping: + for p in pythonMapping.getPythonDirs(pythonMapping.getPath(), self.configs[pythonMapping]): + sys.path.append(p) + + import Ice + Ice.loadSlice(os.path.join(toplevel, "scripts", "Controller.ice")) + + initData = Ice.InitializationData() + initData.properties = Ice.createProperties() + + # Load IceSSL, this is useful to talk with WSS for JavaScript + initData.properties.setProperty("Ice.Plugin.IceSSL", "IceSSL:createIceSSL") + initData.properties.setProperty("IceSSL.DefaultDir", os.path.join(toplevel, "certs")) + initData.properties.setProperty("IceSSL.CertFile", "server.p12") + initData.properties.setProperty("IceSSL.Password", "password") + initData.properties.setProperty("IceSSL.Keychain", "test.keychain") + initData.properties.setProperty("IceSSL.KeychainPassword", "password") + initData.properties.setProperty("IceSSL.VerifyPeer", "0"); + + initData.properties.setProperty("Ice.Plugin.IceDiscovery", "IceDiscovery:createIceDiscovery") + initData.properties.setProperty("IceDiscovery.DomainId", "TestController") + initData.properties.setProperty("IceDiscovery.Interface", self.interface) + initData.properties.setProperty("Ice.Default.Host", self.interface) + initData.properties.setProperty("Ice.ThreadPool.Server.Size", "10") + #initData.properties.setProperty("Ice.Trace.Protocol", "1") + #initData.properties.setProperty("Ice.Trace.Network", "2") + initData.properties.setProperty("Ice.Override.Timeout", "10000") + initData.properties.setProperty("Ice.Override.ConnectTimeout", "1000") + self.communicator = Ice.initialize(initData) + + self.ctrlCHandler = Ice.CtrlCHandler() + + def signal(sig): + self.communicator.destroy() + self.ctrlCHandler.setCallback(signal) + + def getProcessController(self, current, process=None): + processController = None + if current.config.buildPlatform == "iphonesimulator": + processController = iOSSimulatorProcessController + elif current.config.buildPlatform == "iphoneos": + processController = iOSDeviceProcessController + elif current.config.uwp: + # No SSL server-side support in UWP. + if current.config.protocol in ["ssl", "wss"] and not isinstance(process, Client): + processController = LocalProcessController + else: + processController = UWPProcessController + elif process and isinstance(process.getMapping(current), JavaScriptMapping) and current.config.browser: + processController = BrowserProcessController + else: + processController = LocalProcessController + + if processController in self.processControllers: + return self.processControllers[processController] + + # Instantiate the controller + self.processControllers[processController] = processController(current) + return self.processControllers[processController] + + def getProcessProps(self, current, ready, readyCount): + props = {} + if ready or readyCount > 0: + if current.config.buildPlatform not in ["iphonesimulator", "iphoneos"]: + props["Ice.PrintAdapterReady"] = 1 + return props + + def destroy(self): + for controller in self.processControllers.values(): + controller.destroy(self) + + if self.communicator: + self.communicator.destroy() + self.ctrlCHandler.destroy() + + +class CppMapping(Mapping): + + class Config(Mapping.Config): + + @classmethod + def getSupportedArgs(self): + return ("", ["cpp-config=", "cpp-platform="]) + + @classmethod + def usage(self): + print("") + print("C++ Mapping options:") + print("--cpp-config=<config> C++ build configuration for native executables (overrides --config).") + print("--cpp-platform=<platform> C++ build platform for native executables (overrides --platform).") + + def __init__(self, options=[]): + Mapping.Config.__init__(self, options) + + # Derive from the build config the cpp11 option. This is used by canRun to allow filtering + # tests on the cpp11 value in the testcase options specification + self.cpp11 = self.buildConfig.lower().find("cpp11") >= 0 + + parseOptions(self, options, { "cpp-config" : "buildConfig", "cpp-platform" : "buildPlatform" }) + + def canRun(self, current): + if not Mapping.Config.canRun(self, current): + return False + + # No C++11 tests for IceStorm, IceGrid, etc + parent = re.match(r'^([\w]*).*', current.testcase.getTestSuite().getId()).group(1) + if self.cpp11 and not parent in ["IceUtil", "Slice", "Ice", "IceSSL", "IceDiscovery", "IceBox"]: + return False + + return True + + def getNugetPackage(self, compiler, version): + return "zeroc.ice.{0}.{1}".format(compiler, version) + + def getDefaultExe(self, processType, config): + return platform.getDefaultExe(processType, config) + + def getOptions(self, current): + return { "compress" : [False] } if current.config.uwp else {} + + def getProps(self, process, current): + props = Mapping.getProps(self, process, current) + if isinstance(process, IceProcess): + props["Ice.NullHandleAbort"] = True + return props + + def getSSLProps(self, process, current): + props = Mapping.getSSLProps(self, process, current) + server = isinstance(process, Server) + uwp = current.config.uwp + + props.update({ + "IceSSL.CAs": "cacert.pem", + "IceSSL.CertFile": "server.p12" if server else "ms-appx:///client.p12" if uwp else "client.p12" + }) + if isinstance(platform, Darwin): + keychainFile = "server.keychain" if server else "client.keychain" + props.update({ + "IceSSL.KeychainPassword" : "password", + "IceSSL.Keychain": keychainFile + }) + return props + + def getPluginEntryPoint(self, plugin, process, current): + return { + "IceSSL" : "IceSSLOpenSSL:createIceSSLOpenSSL" if current.config.openssl else "IceSSL:createIceSSL", + "IceBT" : "IceBT:createIceBT", + "IceDiscovery" : "IceDiscovery:createIceDiscovery" + }[plugin] + + def getEnv(self, process, current): + # + # On Windows, add the testcommon directories to the PATH + # + libPaths = [] + if isinstance(platform, Windows): + testcommon = os.path.join(self.path, "test", "Common") + if os.path.exists(testcommon): + libPaths.append(os.path.join(testcommon, self.getBuildDir("testcommon", current))) + + # + # On most platforms, we also need to add the library directory to the library path environment variable. + # + if not isinstance(platform, Darwin): + libPaths.append(self.getLibDir(process, current)) + + # + # Add the test suite library directories to the platform library path environment variable. + # + if current.testcase: + for d in set([current.getBuildDir(d) for d in current.testcase.getTestSuite().getLibDirs()]): + libPaths.append(d) + + env = {} + if len(libPaths) > 0: + env[platform.getLdPathEnvName()] = os.pathsep.join(libPaths) + return env + + def getDefaultSource(self, processType): + return { + "client" : "Client.cpp", + "server" : "Server.cpp", + "serveramd" : "ServerAMD.cpp", + "collocated" : "Collocated.cpp", + }[processType] + +class JavaMapping(Mapping): + + def getCommandLine(self, current, process, exe): + javaHome = os.getenv("JAVA_HOME", "") + java = os.path.join(javaHome, "bin", "java") if javaHome else "java" + if process.isFromBinDir(): + return "{0} {1}".format(java, exe) + + assert(current.testcase.getPath().startswith(self.getTestsPath())) + package = "test." + current.testcase.getPath()[len(self.getTestsPath()) + 1:].replace(os.sep, ".") + javaArgs = self.getJavaArgs(process, current) + if javaArgs: + return "{0} {1} {2}.{3}".format(java, " ".join(javaArgs), package, exe) + else: + return "{0} {1}.{2}".format(java, package, exe) + + def getJavaArgs(self, process, current): + return [] + + def getSSLProps(self, process, current): + props = Mapping.getSSLProps(self, process, current) + props.update({ + "IceSSL.Keystore": "server.jks" if isinstance(process, Server) else "client.jks", + }) + return props + + def getPluginEntryPoint(self, plugin, process, current): + return { + "IceSSL" : "com.zeroc.IceSSL.PluginFactory", + "IceBT" : "com.zeroc.IceBT.PluginFactory", + "IceDiscovery" : "com.zeroc.IceDiscovery.PluginFactory" + }[plugin] + + def getEnv(self, process, current): + return { "CLASSPATH" : os.path.join(self.path, "lib", "test.jar") } + + def getTestsPath(self): + return os.path.join(self.path, "test/src/main/java/test") + + def getDefaultSource(self, processType): + return self.getDefaultExe(processType) + ".java" + + def getDefaultExe(self, processType, config=None): + return { + "client" : "Client", + "server" : "Server", + "serveramd" : "AMDServer", + "collocated" : "Collocated", + "icebox": "com.zeroc.IceBox.Server", + "iceboxadmin" : "com.zeroc.IceBox.Admin", + }[processType] + +class JavaCompatMapping(JavaMapping): + + def getPluginEntryPoint(self, plugin, process, current): + return { + "IceSSL" : "IceSSL.PluginFactory", + "IceBT" : "IceBT.PluginFactory", + "IceDiscovery" : "IceDiscovery.PluginFactory" + }[plugin] + + def getDefaultExe(self, processType, config=None): + return { + "client" : "Client", + "server" : "Server", + "serveramd" : "AMDServer", + "collocated" : "Collocated", + "icebox": "IceBox.Server", + "iceboxadmin" : "IceBox.Admin", + }[processType] + +class CSharpMapping(Mapping): + + def getTestSuites(self, ids=[]): + return Mapping.getTestSuites(self, ids) if isinstance(platform, Windows) else [] + + def findTestSuite(self, testsuite): + return Mapping.findTestSuite(self, testsuite) if isinstance(platform, Windows) else None + + def getBuildDir(self, name, current): + return os.path.join("msbuild", name) + + def getSSLProps(self, process, current): + props = Mapping.getSSLProps(self, process, current) + props.update({ + "IceSSL.Password": "password", + "IceSSL.DefaultDir": os.path.join(toplevel, "certs"), + "IceSSL.CAs": "cacert.pem", + "IceSSL.VerifyPeer": "0" if current.config.protocol == "wss" else "2", + "IceSSL.CertFile": "server.p12" if isinstance(process, Server) else "client.p12", + }) + return props + + def getPluginEntryPoint(self, plugin, process, current): + plugindir = "{0}/{1}".format(current.driver.getIceDir(self, current), + "lib" if current.driver.useIceBinDist(self) else "Assemblies") + return { + "IceSSL" : plugindir + "/IceSSL.dll:IceSSL.PluginFactory", + "IceDiscovery" : plugindir + "/IceDiscovery.dll:IceDiscovery.PluginFactory" + }[plugin] + + def getEnv(self, process, current): + if current.driver.useIceBinDist(self): + bzip2 = os.path.join(platform.getIceInstallDir(self, current), "tools") + assembliesDir = os.path.join(platform.getIceInstallDir(self, current), "lib") + else: + bzip2 = os.path.join(toplevel, "cpp", "msbuild", "packages", + "bzip2.{0}.1.0.6.6".format(platform.getCompiler()), + "build", "native", "bin", "x64", "Release") + assembliesDir = os.path.join(current.driver.getIceDir(self, current), "Assemblies") + return { "DEVPATH" : assembliesDir, "PATH" : bzip2 }; + + def getDefaultSource(self, processType): + return { + "client" : "Client.cs", + "server" : "Server.cs", + "serveramd" : "ServerAMD.cs", + "collocated" : "Collocated.cs", + }[processType] + + def getDefaultExe(self, processType, config): + return "iceboxnet" if processType == "icebox" else processType + + def getNugetPackage(self, compiler, version): + return "zeroc.ice.net.{0}".format(version) + +class CppBasedMapping(Mapping): + + class Config(Mapping.Config): + + @classmethod + def getSupportedArgs(self): + return ("", [self.mappingName + "-config=", self.mappingName + "-platform="]) + + @classmethod + def usage(self): + print("") + print(self.mappingDesc + " mapping options:") + print("--{0}-config=<config> {1} build configuration for native executables (overrides --config)." + .format(self.mappingName, self.mappingDesc)) + print("--{0}-platform=<platform> {1} build platform for native executables (overrides --platform)." + .format(self.mappingName, self.mappingDesc)) + + def __init__(self, options=[]): + Mapping.Config.__init__(self, options) + parseOptions(self, options, + { self.mappingName + "-config" : "buildConfig", + self.mappingName + "-platform" : "buildPlatform" }) + + def getSSLProps(self, process, current): + return Mapping.getByName("cpp").getSSLProps(process, current) + + def getPluginEntryPoint(self, plugin, process, current): + return Mapping.getByName("cpp").getPluginEntryPoint(plugin, process, current) + + def getEnv(self, process, current): + env = Mapping.getEnv(self, process, current) + if current.driver.getIceDir(self, current) != platform.getIceInstallDir(self, current): + # If not installed in the default platform installation directory, add + # the Ice C++ library directory to the library path + env[platform.getLdPathEnvName()] = Mapping.getByName("cpp").getLibDir(process, current) + return env + + def getNugetPackage(self, compiler, version): + return "zeroc.ice.{0}.{1}".format(compiler, version) + +class ObjCMapping(CppBasedMapping): + + def getTestSuites(self, ids=[]): + return Mapping.getTestSuites(self, ids) if isinstance(platform, Darwin) else [] + + def findTestSuite(self, testsuite): + return Mapping.findTestSuite(self, testsuite) if isinstance(platform, Darwin) else None + + class Config(CppBasedMapping.Config): + mappingName = "objc" + mappingDesc = "Objective-C" + + def __init__(self, options=[]): + Mapping.Config.__init__(self, options) + self.arc = self.buildConfig.lower().find("arc") >= 0 + + def getDefaultSource(self, processType): + return { + "client" : "Client.m", + "server" : "Server.m", + "collocated" : "Collocated.m", + }[processType] + +class PythonMapping(CppBasedMapping): + + class Config(CppBasedMapping.Config): + mappingName = "python" + mappingDesc = "Python" + + def getCommandLine(self, current, process, exe): + return "\"{0}\" {1}".format(sys.executable, exe) + + def getEnv(self, process, current): + env = CppBasedMapping.getEnv(self, process, current) + if current.driver.getIceDir(self, current) != platform.getIceInstallDir(self, current): + # If not installed in the default platform installation directory, add + # the Ice python directory to PYTHONPATH + dirs = self.getPythonDirs(current.driver.getIceDir(self, current), current.config) + env["PYTHONPATH"] = os.pathsep.join(dirs) + return env + + def getPythonDirs(self, iceDir, config): + dirs = [] + if isinstance(platform, Windows): + dirs.append(os.path.join(iceDir, "python", config.buildPlatform, config.buildConfig)) + dirs.append(os.path.join(iceDir, "python")) + return dirs + + def getDefaultExe(self, processType, config): + return self.getDefaultSource(processType) + + def getDefaultSource(self, processType): + return { + "client" : "Client.py", + "server" : "Server.py", + "serveramd" : "ServerAMD.py", + "collocated" : "Collocated.py", + }[processType] + +class CppBasedClientMapping(CppBasedMapping): + + def loadTestSuites(self, tests, config, filters, rfilters): + Mapping.loadTestSuites(self, tests, config, filters, rfilters) + self.getServerMapping().loadTestSuites(self.testsuites.keys(), config) + + def getServerMapping(self, testId=None): + return Mapping.getByName("cpp") # By default, run clients against C++ mapping executables + + def getDefaultExe(self, processType, config): + return self.getDefaultSource(processType) + +class RubyMapping(CppBasedClientMapping): + + class Config(CppBasedClientMapping.Config): + mappingName = "ruby" + mappingDesc = "Ruby" + + def getCommandLine(self, current, process, exe): + return "ruby " + exe + + def getEnv(self, process, current): + env = CppBasedMapping.getEnv(self, process, current) + if current.driver.getIceDir(self, current) != platform.getIceInstallDir(self, current): + # If not installed in the default platform installation directory, add + # the Ice ruby directory to RUBYLIB + env["RUBYLIB"] = os.path.join(self.path, "ruby") + return env + + def getDefaultSource(self, processType): + return { "client" : "Client.rb" }[processType] + +class PhpMapping(CppBasedClientMapping): + + class Config(CppBasedClientMapping.Config): + mappingName = "php" + mappingDesc = "PHP" + + def getEnv(self, process, current): + env = CppBasedMapping.getEnv(self, process, current) + if isinstance(platform, Windows) and current.driver.useIceBinDist(self): + env[platform.getLdPathEnvName()] = self.getBinDir(process, current) + return env + + def getCommandLine(self, current, process, exe): + args = [] + # + # If Ice is not installed in the system directory, specify its location with PHP + # configuration arguments. + # + if current.driver.getIceDir(self, current) != platform.getIceInstallDir(self, current): + useBinDist = current.driver.useIceBinDist(self) + if isinstance(platform, Windows): + extension = "php_ice_nts.dll" if "NTS" in run("php -v") else "php_ice.dll" + extensionDir = self.getBinDir(process, current) + includePath = self.getLibDir(process, current) + else: + extension = "IcePHP.so" + extensionDir = self.getLibDir(process, current) + includePath = "{0}/{1}".format(current.driver.getIceDir(self, current), "php" if useBinDist else "lib") + + args += ["-n"] # Do not load any php.ini files + args += ["-d", "extension_dir='{0}'".format(extensionDir)] + args += ["-d", "extension='{0}'".format(extension)] + args += ["-d", "include_path='{0}'".format(includePath)] + if hasattr(process, "getPhpArgs"): + args += process.getPhpArgs(current) + return "php {0} -f {1} -- ".format(" ".join(args), exe) + + def getDefaultSource(self, processType): + return { "client" : "Client.php" }[processType] + +class JavaScriptMapping(Mapping): + + class Config(Mapping.Config): + + @classmethod + def getSupportedArgs(self): + return ("", ["es5", "browser=", "worker"]) + + @classmethod + def usage(self): + print("") + print("JavaScript mapping options:") + print("--es5 Use JavaScript ES5 (Babel compiled code).") + print("--browser=<name> Run with the given browser.") + print("--worker Run with Web workers enabled.") + + def __init__(self, options=[]): + Mapping.Config.__init__(self, options) + self.es5 = False + self.browser = "" + self.worker = False + parseOptions(self, options) + if self.browser and self.protocol == "tcp": + self.protocol = "ws" + if self.browser in ["Edge", "Ie"]: + self.es5 = True + + def loadTestSuites(self, tests, config, filters, rfilters): + Mapping.loadTestSuites(self, tests, config, filters, rfilters) + self.getServerMapping().loadTestSuites(list(self.testsuites.keys()) + ["Ice/echo"], config, filters, rfilters) + + def getServerMapping(self, testId=None): + if testId and self.hasSource(testId, "server"): + return self + else: + return Mapping.getByName("cpp") # Run clients against C++ mapping servers if no JS server provided + + def getDefaultProcesses(self, processType, testsuite): + if processType in ["server", "serveramd"]: + return [EchoServer(), Server()] + return Mapping.getDefaultProcesses(self, processType, testsuite) + + def getCommandLine(self, current, process, exe): + if current.config.es5: + return "node {0}/test/Common/run.js --es5 {1}".format(self.path, exe) + else: + return "node {0}/test/Common/run.js {1}".format(self.path, exe) + + def getDefaultSource(self, processType): + return { "client" : "Client.js", "serveramd" : "ServerAMD.js", "server" : "Server.js" }[processType] + + def getDefaultExe(self, processType, config=None): + return self.getDefaultSource(processType).replace(".js", "") + + def getEnv(self, process, current): + env = Mapping.getEnv(self, process, current) + env["NODE_PATH"] = self.getTestCwd(process, current) + return env + + def getSSLProps(self, process, current): + return {} + + def getTestCwd(self, process, current): + if current.config.es5: + # Change to the ES5 test directory if testing ES5 + return os.path.join(self.path, "test", "es5", current.testcase.getTestSuite().getId()) + else: + return os.path.join(self.path, "test", current.testcase.getTestSuite().getId()) + + def computeTestCases(self, testId, files): + if testId.find("es5") > -1: + return # Ignore es5 directories + return Mapping.computeTestCases(self, testId, files) + + def getOptions(self, current): + options = { + "protocol" : ["ws", "wss"] if current.config.browser else ["tcp"], + "compress" : [False], + "ipv6" : [False], + "serialize" : [False], + "mx" : [False], + "es5" : [False, True], + "worker" : [False, True] if current.config.browser else [False], + } + + # Edge and Ie only support ES5 for now + if current.config.browser in ["Edge", "Ie"]: + options["es5"] = [True] + + return options + +from Glacier2Util import * +from IceBoxUtil import * +from IcePatch2Util import * +from IceGridUtil import * +from IceStormUtil import * +from LocalDriver import * + +# +# Supported mappings +# +for m in filter(lambda x: os.path.isdir(os.path.join(toplevel, x)), os.listdir(toplevel)): + if m == "cpp" or re.match("cpp-.*", m): + Mapping.add(m, CppMapping()) + elif m == "java-compat" or re.match("java-compat-.*", m): + Mapping.add(m, JavaCompatMapping()) + elif m == "java" or re.match("java-.*", m): + Mapping.add(m, JavaMapping()) + elif m == "python" or re.match("python-.*", m): + Mapping.add(m, PythonMapping()) + elif m == "ruby" or re.match("ruby-.*", m): + Mapping.add(m, RubyMapping()) + elif m == "php" or re.match("php-.*", m): + Mapping.add(m, PhpMapping()) + elif m == "js" or re.match("js-.*", m): + Mapping.add(m, JavaScriptMapping()) + elif m == "csharp" or re.match("csharp-.*", m): + Mapping.add(m, CSharpMapping()) + elif m == "objective-c" or re.match("objective-c-*", m): + Mapping.add(m, ObjCMapping()) + +def runTestsWithPath(path): + runTests([Mapping.getByPath(path)]) + +def runTests(mappings=None, drivers=None): + if not mappings: + mappings = Mapping.getAll() + if not drivers: + drivers = Driver.getAll() + + def usage(): + print("Usage: " + sys.argv[0] + " [options] [tests]") + print("") + print("Options:") + print("-h | --help Show this message") + + Driver.commonUsage() + for driver in drivers: + driver.usage() + + Mapping.Config.commonUsage() + for mapping in Mapping.getAll(): + mapping.Config.usage() + + print("") + + driver = None + try: + options = [Driver.getSupportedArgs(), Mapping.Config.getSupportedArgs()] + options += [driver.getSupportedArgs() for driver in drivers] + options += [mapping.Config.getSupportedArgs() for mapping in Mapping.getAll()] + shortOptions = "h" + longOptions = ["help"] + for so, lo in options: + shortOptions += so + longOptions += lo + opts, args = getopt.gnu_getopt(sys.argv[1:], shortOptions, longOptions) + + for (o, a) in opts: + if o in ["-h", "--help"]: + usage() + sys.exit(0) + + # + # Create the driver + # + driver = Driver.create(opts) + + # + # Create the configurations for each mapping (we always parse the configuration for the + # python mapping because we might use the local IcePy build to initialize a communicator). + # + configs = {} + for mapping in Mapping.getAll(): + if mapping not in configs: + configs[mapping] = mapping.createConfig(opts[:]) + + # + # Provide the configurations to the driver and load the test suites for each mapping. + # + driver.setConfigs(configs) + for mapping in mappings + driver.getMappings(): + mapping.loadTestSuites(args, configs[mapping], driver.filters, driver.rfilters) + + # + # Finally, run the test suites with the driver. + # + try: + sys.exit(driver.run(mappings, args)) + except KeyboardInterrupt: + pass + finally: + driver.destroy() + + except Exception as e: + print(sys.argv[0] + ": unexpected exception raised:\n" + traceback.format_exc()) + sys.exit(1) |