1#
2# Copyright (c) ZeroC, Inc. All rights reserved.
3#
4
5import os, sys, runpy, getopt, traceback, types, threading, time, datetime, re, itertools, random, subprocess, shutil
6import copy, inspect, xml.sax.saxutils
7
8isPython2 = sys.version_info[0] == 2
9if isPython2:
10    import Queue as queue
11    from StringIO import StringIO
12else:
13    import queue
14    from io import StringIO
15
16from collections import OrderedDict
17import Expect
18
19toplevel = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
20
21def run(cmd, cwd=None, err=False, stdout=False, stdin=None, stdinRepeat=True):
22    p = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, stdout=None if stdout else subprocess.PIPE,
23                         stderr=subprocess.STDOUT, cwd=cwd)
24    try:
25        if stdin:
26            try:
27                while True:
28                    p.stdin.write(stdin)
29                    p.stdin.flush()
30                    if not stdinRepeat:
31                        break
32                    time.sleep(1)
33            except:
34                pass
35
36        out = (p.stderr if stdout else p.stdout).read().decode('UTF-8').strip()
37        if(not err and p.wait() != 0) or (err and p.wait() == 0) :
38            raise RuntimeError(cmd + " failed:\n" + out)
39    finally:
40        #
41        # Without this we get warnings when running with python_d on Windows
42        #
43        # ResourceWarning: unclosed file <_io.TextIOWrapper name=3 encoding='cp1252'>
44        #
45        (p.stderr if stdout else p.stdout).close()
46        try:
47            p.stdin.close()
48        except Exception:
49            pass
50    return out
51
52def val(v, quoteValue=True):
53    if type(v) == bool:
54        return "1" if v else "0"
55    elif type(v) == str:
56        if not quoteValue or v.find(" ") < 0:
57            return v
58        v = v.replace("\\", "\\\\").replace("\"", "\\\"")
59        return "\"{0}\"".format(v)
60    else:
61        return str(v)
62
63illegalXMLChars = re.compile(u'[\x00-\x08\x0b\x0c\x0e-\x1F\uD800-\uDFFF\uFFFE\uFFFF]')
64
65def escapeXml(s, attribute=False):
66    # Remove backspace characters from the output (they aren't accepted by Jenkins XML parser)
67    if isPython2:
68        s = "".join(ch for ch in unicode(s.decode("utf-8")) if ch != u"\u0008").encode("utf-8")
69    else:
70        s = "".join(ch for ch in s if ch != u"\u0008")
71    s = illegalXMLChars.sub("?", s) # Strip invalid XML characters
72    return xml.sax.saxutils.quoteattr(s) if attribute else xml.sax.saxutils.escape(s)
73
74"""
75Component abstract class. The driver and mapping classes rely on the component
76class to provide component specific information.
77"""
78class Component(object):
79
80    def __init__(self):
81        self.nugetVersion = None
82
83    """
84    Returns whether or not to use the binary distribution.
85    """
86    def useBinDist(self, mapping, current):
87        return True
88
89    """
90    Returns the component installation directory if using a binary distribution
91    or the mapping directory if using a source distribution.
92    """
93    def getInstallDir(self, mapping, current):
94        raise Error("must be overriden")
95
96    def getSourceDir(self):
97        return toplevel
98
99    def getTestDir(self, mapping):
100        if isinstance(mapping, JavaMapping):
101            return os.path.join(mapping.getPath(), "test", "src", "main", "java", "test")
102        elif isinstance(mapping, TypeScriptMapping):
103            return os.path.join(mapping.getPath(), "test", "typescript")
104        return os.path.join(mapping.getPath(), "test")
105
106    def getScriptDir(self):
107        return os.path.join(self.getSourceDir(), "scripts", "tests")
108
109    def getPhpExtension(self, mapping, current):
110        raise RuntimeError("must be overriden if component provides php mapping")
111
112    def getNugetPackage(self, mapping):
113        return "zeroc.{0}.{1}".format(self.__class__.__name__.lower(),
114                                      "net" if isinstance(mapping, CSharpMapping) else platform.getPlatformToolset())
115
116    def getNugetPackageVersion(self, mapping):
117        if not self.nugetVersion:
118            file = self.getNugetPackageVersionFile(mapping)
119            if file.endswith(".nuspec"):
120                expr = "<version>(.*)</version>"
121            elif file.endswith("packages.config"):
122                expr = "id=\"{0}\" version=\"(.*)\" target".format(self.getNugetPackage(mapping))
123            if expr:
124                with open(file, "r") as config:
125                    m = re.search(expr, config.read())
126                    if m:
127                        self.nugetVersion = m.group(1)
128        if not self.nugetVersion:
129            raise RuntimeError("couldn't figure out the nuget version from `{0}'".format(file))
130        return self.nugetVersion
131
132    def getNugetPackageVersionFile(self, mapping):
133        raise RuntimeError("must be overriden if component provides C++ or C# nuget packages")
134
135    def getFilters(self, mapping, config):
136        return ([], [])
137
138    def canRun(self, testId, mapping, current):
139        return True
140
141    def isMainThreadOnly(self, testId):
142        return True
143
144    def getDefaultProcesses(self, mapping, processType, testId):
145        return None
146
147    def getDefaultExe(self, mapping, processType):
148        return None
149
150    def getDefaultSource(self, mapping, processType):
151        return None
152
153    def getOptions(self, testcase, current):
154        return None
155
156    def getRunOrder(self):
157        return []
158
159    def getEnv(self, process, current):
160        return {}
161
162    def getProps(self, process, current):
163        return {}
164
165    def isCross(self, testId):
166        return False
167
168    def getSliceDir(self, mapping, current):
169        installDir = self.getInstallDir(mapping, current)
170        if installDir.endswith(mapping.name):
171            installDir = installDir[0:len(installDir) - len(mapping.name) - 1]
172        if platform.getInstallDir() and installDir == platform.getInstallDir():
173            return os.path.join(installDir, "share", "ice", "slice")
174        else:
175            return os.path.join(installDir, "slice")
176
177    def getBinDir(self, process, mapping, current):
178        return platform._getBinDir(self, process, mapping, current)
179
180    def getLibDir(self, process, mapping, current):
181        return platform._getLibDir(self, process, mapping, current)
182
183    def getPhpIncludePath(self, mapping, current):
184        return "{0}/{1}".format(self.getInstallDir(mapping, current), "php" if self.useBinDist(mapping, current) else "lib")
185
186    def _useBinDist(self, mapping, current, envName):
187        env = os.environ.get(envName, "").split()
188        return 'all' in env or mapping.name in env
189
190    def _getInstallDir(self, mapping, current, envHomeName):
191        if self.useBinDist(mapping, current):
192            # On Windows or for the C# mapping we first look for Nuget packages rather than the binary installation
193            if isinstance(platform, Windows) or isinstance(mapping, CSharpMapping):
194                packageDir = platform.getNugetPackageDir(self, mapping, current)
195                if envHomeName and not os.path.exists(packageDir):
196                    home = os.environ.get(envHomeName, "")
197                    if not home or not os.path.exists(home):
198                        raise RuntimeError("Cannot detect a valid distribution in `" + envHomeName + "'")
199                    return home
200                else:
201                    return packageDir
202            else:
203                return os.environ.get(envHomeName, platform.getInstallDir())
204        elif mapping:
205            return mapping.getPath()
206        else:
207            return self.getSourceDir()
208
209class Platform(object):
210
211    def __init__(self):
212        try:
213            run("dotnet --version")
214            self.nugetPackageCache = re.search("info : global-packages: (.*)",
215                                               run("dotnet nuget locals --list global-packages")).groups(1)[0]
216        except:
217            self.nugetPackageCache = None
218
219    def init(self, component):
220        self.parseBuildVariables(component, {
221            "supported-platforms" : ("supportedPlatforms", lambda s : s.split(" ")),
222            "supported-configs" : ("supportedConfigs", lambda s : s.split(" "))
223        })
224
225    def hasDotNet(self):
226        return self.nugetPackageCache != None
227
228    def parseBuildVariables(self, component, variables):
229        # Run make to get the values of the given variables
230        if os.path.exists(os.path.join(component.getSourceDir(), "Makefile")): # Top level makefile
231            cwd = component.getSourceDir()
232        elif Mapping.getByName("cpp"):
233            cwd = Mapping.getByName("cpp").getPath()
234
235        output = run('gmake print V="{0}"'.format(" ".join(variables.keys())), cwd=cwd)
236        for l in output.split("\n"):
237            match = re.match(r'^.*:.*: (.*) = (.*)', l)
238            if match and match.group(1):
239                if match.group(1) in variables:
240                    (varname, valuefn) = variables[match.group(1).strip()]
241                    value = match.group(2).strip() or ""
242                    setattr(self, varname, valuefn(value) if valuefn else value)
243
244    def getDefaultBuildPlatform(self):
245        return self.supportedPlatforms[0]
246
247    def getDefaultBuildConfig(self):
248        return self.supportedConfigs[0]
249
250    def _getBinDir(self, component, process, mapping, current):
251        installDir = component.getInstallDir(mapping, current)
252        if isinstance(mapping, CSharpMapping):
253            if component.useBinDist(mapping, current):
254                return os.path.join(installDir, "tools", "netcoreapp2.1")
255            else:
256                return os.path.join(installDir, "bin", "netcoreapp2.1")
257        return os.path.join(installDir, "bin")
258
259    def _getLibDir(self, component, process, mapping, current):
260        installDir = component.getInstallDir(mapping, current)
261        if isinstance(mapping, CSharpMapping):
262            return os.path.join(installDir, "lib", "netstandard2.0")
263        return os.path.join(installDir, "lib")
264
265    def getBuildSubDir(self, mapping, name, current):
266        # Return the build sub-directory, to be overriden by specializations
267        buildPlatform = current.driver.configs[mapping].buildPlatform
268        buildConfig = current.driver.configs[mapping].buildConfig
269        return os.path.join("build", buildPlatform, buildConfig)
270
271    def getLdPathEnvName(self):
272        return "LD_LIBRARY_PATH"
273
274    def getInstallDir(self):
275        return "/usr"
276
277    def getNugetPackageDir(self, component, mapping, current):
278        if not self.nugetPackageCache:
279            return None
280        return os.path.join(self.nugetPackageCache,
281                            component.getNugetPackage(mapping),
282                            component.getNugetPackageVersion(mapping))
283
284    def hasOpenSSL(self):
285        # This is used by the IceSSL test suite to figure out how to setup certificates
286        return False
287
288    def getDotNetExe(self):
289        return "dotnet"
290
291class Darwin(Platform):
292
293    def getDefaultBuildPlatform(self):
294        return "macosx"
295
296    def getLdPathEnvName(self):
297        return "DYLD_LIBRARY_PATH"
298
299    def getInstallDir(self):
300        return "/usr/local"
301
302class AIX(Platform):
303
304    def hasOpenSSL(self):
305        return True
306
307class FreeBSD(Platform):
308
309    def __init__(self):
310        self.nugetPackageCache = None
311
312    def hasOpenSSL(self):
313        return True
314
315    def getSliceDir(self, iceDir):
316        installDir = self.getInstallDir(mapping, current)
317        return os.path.join(installDir, "slice")
318
319    def getDefaultExe(self, name, config):
320        if name == "icebox":
321            if config.cpp11:
322                name += "++11"
323        return name
324
325    def canRun(self, mapping, current):
326        return Platform.canRun(self, mapping, current)
327
328class Linux(Platform):
329
330    def __init__(self):
331        Platform.__init__(self)
332        self.multiArch = {}
333
334    def init(self, component):
335        Platform.init(self, component)
336        self.parseBuildVariables(component, {
337            "linux_id" : ("linuxId", None),
338            "build-platform" : ("buildPlatform", None),
339            "foreign-platforms" : ("foreignPlatforms", lambda s : s.split(" ") if s else []),
340        })
341        if self.linuxId in ["ubuntu", "debian"]:
342            for p in [self.buildPlatform] + self.foreignPlatforms:
343                self.multiArch[p] = run("dpkg-architecture -f -a{0} -qDEB_HOST_MULTIARCH 2> /dev/null".format(p))
344
345
346    def hasOpenSSL(self):
347        return True
348
349    def _getBinDir(self, component, process, mapping, current):
350        installDir = component.getInstallDir(mapping, current)
351        if isinstance(mapping, CSharpMapping):
352            return Platform._getBinDir(self, component, process, mapping, current)
353
354        if self.linuxId in ["ubuntu", "debian"]:
355            binDir = os.path.join(installDir, "bin", self.multiArch[current.driver.configs[mapping].buildPlatform])
356            if os.path.exists(binDir):
357                return binDir
358        return os.path.join(installDir, "bin")
359
360    def _getLibDir(self, component, process, mapping, current):
361        installDir = component.getInstallDir(mapping, current)
362        if isinstance(mapping, CSharpMapping):
363            return Platform._getLibDir(self, component, process, mapping, current)
364
365        buildPlatform = current.driver.configs[mapping].buildPlatform
366
367        # PHP module is always installed in the lib directory for the default build platform
368        if isinstance(mapping, PhpMapping) and buildPlatform == self.getDefaultBuildPlatform():
369            return os.path.join(installDir, "lib")
370
371        if self.linuxId in ["centos", "rhel", "fedora"]:
372            return os.path.join(installDir, "lib64" if buildPlatform == "x64" else "lib")
373        elif self.linuxId in ["ubuntu", "debian"]:
374            return os.path.join(installDir, "lib", self.multiArch[buildPlatform])
375        return os.path.join(installDir, "lib")
376
377    def getBuildSubDir(self, mapping, name, current):
378        buildPlatform = current.driver.configs[mapping].buildPlatform
379        buildConfig = current.driver.configs[mapping].buildConfig
380        if self.linuxId in ["ubuntu", "debian"]:
381            return os.path.join("build", self.multiArch[buildPlatform], buildConfig)
382        else:
383            return os.path.join("build", buildPlatform, buildConfig)
384
385    def getLinuxId(self):
386        return self.linuxId
387
388class Windows(Platform):
389
390    def __init__(self):
391        Platform.__init__(self)
392        self.compiler = None
393
394    def parseBuildVariables(self, component, variables):
395        pass # Nothing to do, we don't support the make build system on Windows
396
397    def getDefaultBuildPlatform(self):
398        return "x64" if "X64" in os.environ.get("PLATFORM", "") else "Win32"
399
400    def getDefaultBuildConfig(self):
401        return "Release"
402
403    def getCompiler(self):
404        if self.compiler != None:
405            return self.compiler
406        if os.environ.get("CPP_COMPILER", "") != "":
407            self.compiler = os.environ["CPP_COMPILER"]
408        else:
409            try:
410                out = run("cl")
411                if out.find("Version 16.") != -1:
412                    self.compiler = "v100"
413                elif out.find("Version 17.") != -1:
414                    self.compiler = "v110"
415                elif out.find("Version 18.") != -1:
416                    self.compiler = "v120"
417                elif out.find("Version 19.00.") != -1:
418                    self.compiler = "v140"
419                elif out.find("Version 19.") != -1:
420                    self.compiler = "v141"
421                else:
422                    raise RuntimeError("Unknown compiler version:\n{0}".format(out))
423            except:
424                self.compiler = ""
425        return self.compiler
426
427    def getPlatformToolset(self):
428        return self.getCompiler().replace("VC", "v")
429
430    def _getBinDir(self, component, process, mapping, current):
431        installDir = component.getInstallDir(mapping, current)
432        platform = current.driver.configs[mapping].buildPlatform
433        config = "Debug" if current.driver.configs[mapping].buildConfig.find("Debug") >= 0 else "Release"
434        if component.useBinDist(mapping, current):
435            if installDir != self.getNugetPackageDir(component, mapping, current):
436                return os.path.join(installDir, "bin")
437            elif isinstance(process, SliceTranslator):
438                return os.path.join(installDir, "tools")
439            elif isinstance(mapping, CSharpMapping):
440                return os.path.join(installDir, "tools", mapping.getBinTargetFramework(current))
441            elif process.isReleaseOnly():
442                # Some services are only available in release mode in the Nuget package
443                return os.path.join(installDir, "build", "native", "bin", platform, "Release")
444            else:
445                return os.path.join(installDir, "build", "native", "bin", platform, config)
446        else:
447            if isinstance(mapping, CSharpMapping):
448                return os.path.join(installDir, "bin", mapping.getBinTargetFramework(current))
449            elif isinstance(mapping, PhpMapping):
450                return os.path.join(self.getNugetPackageDir(component, mapping, current),
451                                    "build", "native", "bin", platform, config)
452            else:
453                return os.path.join(installDir, "bin", platform, config)
454
455    def _getLibDir(self, component, process, mapping, current):
456        installDir = component.getInstallDir(mapping, current)
457        if isinstance(mapping, CSharpMapping):
458            return os.path.join(installDir, "lib", mapping.getLibTargetFramework(current))
459        else:
460            platform = current.driver.configs[mapping].buildPlatform
461            config = "Debug" if current.driver.configs[mapping].buildConfig.find("Debug") >= 0 else "Release"
462            if isinstance(mapping, PhpMapping):
463                return os.path.join(installDir, "lib", "php-{0}".format(current.config.phpVersion), platform, config)
464            elif component.useBinDist(mapping, current):
465                return os.path.join(installDir, "build", "native", "bin", platform, config)
466            else:
467                return os.path.join(installDir, "bin", platform, config)
468
469    def getBuildSubDir(self, mapping, name, current):
470        buildPlatform = current.driver.configs[mapping].buildPlatform
471        buildConfig = current.driver.configs[mapping].buildConfig
472        if os.path.exists(os.path.join(current.testcase.getPath(current), "msbuild", name)):
473            return os.path.join("msbuild", name, buildPlatform, buildConfig)
474        else:
475            return os.path.join("msbuild", buildPlatform, buildConfig)
476
477    def getLdPathEnvName(self):
478        return "PATH"
479
480    def getInstallDir(self):
481        return None # No default installation directory on Windows
482
483    def getNugetPackageDir(self, component, mapping, current):
484        if isinstance(mapping, CSharpMapping) and current.config.dotnetcore:
485            return Platform.getNugetPackageDir(self, component, mapping, current)
486        else:
487            package = "{0}.{1}".format(component.getNugetPackage(mapping), component.getNugetPackageVersion(mapping))
488            # The package directory is either under the msbuild directory or in the mapping directory depending
489            # on where the solution is located.
490            if os.path.exists(os.path.join(mapping.path, "msbuild", "packages")):
491                return os.path.join(mapping.path, "msbuild", "packages", package)
492            else:
493                return os.path.join(mapping.path, "packages", package)
494
495    def getDotNetExe(self):
496        try:
497            return run("where dotnet").strip()
498        except:
499            return None
500
501def parseOptions(obj, options, mapped={}):
502    # Transform configuration options provided on the command line to
503    # object data members. The data members must be already set on the
504    # object and with the correct type.
505    if not hasattr(obj, "parsedOptions"):
506        obj.parsedOptions=[]
507    remaining = []
508    for (o, a) in options:
509        if o.startswith("--"): o = o[2:]
510        if o.startswith("-"): o = o[1:]
511        if not a and o.startswith("no-"):
512            a = "false"
513            o = o[3:]
514        if o in mapped:
515            o = mapped[o]
516
517        if hasattr(obj, o):
518            if isinstance(getattr(obj, o), bool):
519                setattr(obj, o, True if not a else (a.lower() in ["yes", "true", "1"]))
520            elif isinstance(getattr(obj, o), list):
521                l = getattr(obj, o)
522                l.append(a)
523            else:
524                if not a and not isinstance(a, str):
525                    a = "0"
526                setattr(obj, o, type(getattr(obj, o))(a))
527            if not o in obj.parsedOptions:
528                obj.parsedOptions.append(o)
529        else:
530            remaining.append((o, a))
531    options[:] = remaining
532
533"""
534Mapping abstract class. The mapping class provides mapping specific information.
535Multiple components can share the same mapping rules as long as the layout is
536similar.
537"""
538class Mapping(object):
539
540    mappings = OrderedDict()
541
542    class Config(object):
543
544        @classmethod
545        def getSupportedArgs(self):
546            return ("", ["config=", "platform=", "protocol=", "target=", "compress", "ipv6", "no-ipv6", "serialize",
547                         "mx", "cprops=", "sprops="])
548
549        @classmethod
550        def usage(self):
551            pass
552
553        @classmethod
554        def commonUsage(self):
555            print("")
556            print("Mapping options:")
557            print("--protocol=<prot>     Run with the given protocol.")
558            print("--compress            Run the tests with protocol compression.")
559            print("--ipv6                Use IPv6 addresses.")
560            print("--serialize           Run with connection serialization.")
561            print("--mx                  Run with metrics enabled.")
562            print("--cprops=<properties> Specifies a list of additional client properties.")
563            print("--sprops=<properties> Specifies a list of additional server properties.")
564            print("--config=<config>     Build configuration for native executables.")
565            print("--platform=<platform> Build platform for native executables.")
566
567        def __init__(self, options=[]):
568            # Build configuration
569            self.parsedOptions = []
570            self.buildConfig = os.environ.get("CONFIGS", "").split(" ")[0]
571            if self.buildConfig:
572                self.parsedOptions.append("buildConfig")
573            else:
574                self.buildConfig = platform.getDefaultBuildConfig()
575
576            self.buildPlatform = os.environ.get("PLATFORMS", "").split(" ")[0]
577            if self.buildPlatform:
578                self.parsedOptions.append("buildPlatform")
579            else:
580                self.buildPlatform = platform.getDefaultBuildPlatform()
581
582            self.pathOverride = ""
583            self.protocol = "tcp"
584            self.compress = False
585            self.serialize = False
586            self.ipv6 = False
587            self.mx = False
588            self.cprops = []
589            self.sprops = []
590
591            # Options bellow are not parsed by the base class by still initialized here for convenience (this
592            # avoid having to check the configuration type)
593            self.uwp = False
594            self.openssl = False
595            self.browser = ""
596            self.es5 = False
597            self.worker = False
598            self.dotnetcore = False
599            self.framework = ""
600            self.android = False
601            self.xamarin = False
602            self.device = ""
603            self.avd = ""
604            self.phpVersion = "7.1"
605
606            parseOptions(self, options, { "config" : "buildConfig", "platform" : "buildPlatform" })
607
608        def __str__(self):
609            s = []
610            for o in self.parsedOptions:
611                v = getattr(self, o)
612                if v: s.append(o if type(v) == bool else str(v))
613            return ",".join(s)
614
615        def getAll(self, current, testcase, rand=False):
616
617            #
618            # A generator to generate combinations of options (e.g.: tcp/compress/mx, ssl/ipv6/serialize, etc)
619            #
620            def gen(supportedOptions):
621
622                if not supportedOptions:
623                    yield self
624                    return
625
626                supportedOptions = supportedOptions.copy()
627                supportedOptions.update(testcase.getMapping().getOptions(current))
628                supportedOptions.update(testcase.getTestSuite().getOptions(current))
629                supportedOptions.update(testcase.getOptions(current))
630
631                for o in self.parsedOptions:
632                    # Remove options which were explicitly set
633                    if o in supportedOptions:
634                        del supportedOptions[o]
635
636                if len(supportedOptions) == 0:
637                    yield self
638                    return
639
640                # Find the option with the longest list of values
641                length = max([len(v) for v in supportedOptions.values()])
642
643                # Replace the values with a cycle iterator on the values
644                for (k, v) in supportedOptions.items():
645                    supportedOptions[k] = itertools.cycle(random.sample(v, len(v)) if rand else v)
646
647                # Now, for the length of the longest array of values, we return
648                # an array with the supported option combinations
649                for i in range(0, length):
650                    options = []
651                    for k, v in supportedOptions.items():
652                        v = next(v)
653                        if v:
654                            if type(v) == bool:
655                                if v:
656                                    options.append(("--{0}".format(k), None))
657                            else:
658                                options.append(("--{0}".format(k), v))
659
660                    # Add parsed options
661                    for o in self.parsedOptions:
662                        v = getattr(self, o)
663                        if type(v) == bool:
664                            if v:
665                                options.append(("--{0}".format(o), None))
666                        elif type(v) == list:
667                            options += [("--{0}".format(o), e) for e in v]
668                        else:
669                            options.append(("--{0}".format(o), v))
670
671                    yield self.__class__(options)
672
673            return [c for c in gen(current.driver.filterOptions(current.driver.getComponent().getOptions(testcase, current)))]
674
675        def canRun(self, testId, current):
676            if not current.driver.getComponent().canRun(testId, current.testcase.getMapping(), current):
677                return False
678
679            options = {}
680            options.update(current.testcase.getMapping().getOptions(current))
681            options.update(current.testcase.getTestSuite().getOptions(current))
682            options.update(current.testcase.getOptions(current))
683
684            for (k, v) in options.items():
685                if hasattr(self, k):
686                    if not getattr(self, k) in v:
687                        return False
688                elif hasattr(current.driver, k):
689                    if not getattr(current.driver, k) in v:
690                        return False
691            else:
692                return True
693
694        def cloneRunnable(self, current):
695            #
696            # Clone this configuration and make sure all the options are supported
697            #
698            options = {}
699            options.update(current.testcase.getMapping().getOptions(current))
700            options.update(current.testcase.getTestSuite().getOptions(current))
701            options.update(current.testcase.getOptions(current))
702            clone = copy.copy(self)
703            for o in self.parsedOptions:
704                if o in options and getattr(self, o) not in options[o]:
705                    setattr(clone, o, options[o][0] if len(options[o]) > 0 else None)
706            return clone
707
708        def cloneAndOverrideWith(self, current):
709            #
710            # Clone this configuration and override options with options from the given configuration
711            # (the parent configuraton). This is usefull when running cross-testing. For example, JS
712            # tests don't support all the options so we clone the C++ configuration and override the
713            # options that are set on the JS configuration.
714            #
715            clone = copy.copy(self)
716            for o in current.config.parsedOptions + ["protocol"]:
717                if o not in ["buildConfig", "buildPlatform"]:
718                    setattr(clone, o, getattr(current.config, o))
719            clone.parsedOptions = current.config.parsedOptions
720            return clone
721
722        def getArgs(self, process, current):
723            return []
724
725        def getProps(self, process, current):
726            props = {}
727            if isinstance(process, IceProcess):
728                props["Ice.Warn.Connections"] = True
729                if self.protocol:
730                    props["Ice.Default.Protocol"] = self.protocol
731                if self.compress:
732                    props["Ice.Override.Compress"] = "1"
733                if self.serialize:
734                    props["Ice.ThreadPool.Server.Serialize"] = "1"
735                props["Ice.IPv6"] = self.ipv6
736                if self.ipv6:
737                    props["Ice.PreferIPv6Address"] = True
738                if self.mx:
739                    props["Ice.Admin.Endpoints"] = "tcp -h \"::1\"" if self.ipv6 else "tcp -h 127.0.0.1"
740                    props["Ice.Admin.InstanceName"] = "Server" if isinstance(process, Server) else "Client"
741                    props["IceMX.Metrics.Debug.GroupBy"] ="id"
742                    props["IceMX.Metrics.Parent.GroupBy"] = "parent"
743                    props["IceMX.Metrics.All.GroupBy"] = "none"
744
745                #
746                # Speed up Windows testing. We override the connect timeout for some tests which are
747                # establishing connections to inactive ports. It takes around 1s for such connection
748                # establishment to fail on Windows.
749                #
750                # if isinstance(platform, Windows):
751                #     if current.testsuite.getId().startswith("IceGrid") or \
752                #         current.testsuite.getId() in ["Ice/binding",
753                #                                       "Ice/location",
754                #                                       "Ice/background",
755                #                                       "Ice/faultTolerance",
756                #                                       "Ice/services",
757                #                                       "IceDiscovery/simple"]:
758                #         props["Ice.Override.ConnectTimeout"] = "400"
759
760                # Additional properties specified on the command line with --cprops or --sprops
761                additionalProps = []
762                if self.cprops and isinstance(process, Client):
763                    additionalProps = self.cprops
764                elif self.sprops and isinstance(process, Server):
765                    additionalProps = self.sprops
766                for pps in additionalProps:
767                    for p in pps.split(" "):
768                        if p.find("=") > 0:
769                            (k , v) = p.split("=")
770                            props[k] = v
771                        else:
772                            props[p] = True
773
774            return props
775
776    @classmethod
777    def getByName(self, name):
778        if not name in self.mappings:
779            raise RuntimeError("unknown mapping `{0}'".format(name))
780        return self.mappings.get(name)
781
782    @classmethod
783    def getByPath(self, path):
784        path = os.path.normpath(path)
785        for m in self.mappings.values():
786            if path.startswith(os.path.normpath(m.getTestDir())):
787                return m
788
789    @classmethod
790    def getAllByPath(self, path):
791        path = os.path.abspath(path)
792        mappings = []
793        for m in self.mappings.values():
794            if path.startswith(m.getPath() + os.sep):
795                mappings.append(m)
796        return mappings
797
798    @classmethod
799    def add(self, name, mapping, component, path=None):
800        name = name.replace("\\", "/")
801        self.mappings[name] = mapping.init(name, component, path)
802
803    @classmethod
804    def remove(self, name):
805        del self.mappings[name]
806
807    @classmethod
808    def getAll(self, driver=None):
809        return [m for m in self.mappings.values() if not driver or driver.matchLanguage(str(m))]
810
811    def __init__(self, path=None):
812        self.name = None
813        self.path = os.path.abspath(path) if path else None
814        self.testsuites = {}
815
816    def init(self, name, component, path=None):
817        self.name = name
818        self.component = component
819        if not self.path:
820            self.path = os.path.normpath(os.path.join(self.component.getSourceDir(), path or name))
821        return self
822
823    def __str__(self):
824        return self.name
825
826    def getTestDir(self):
827        return self.component.getTestDir(self)
828
829    def createConfig(self, options):
830        return self.Config(options)
831
832    def filterTestSuite(self, testId, config, filters=[], rfilters=[]):
833        if len(filters) > 0:
834            for f in filters:
835                if f.search(self.name + "/" + testId):
836                    break
837            else:
838                return True
839
840        if len(rfilters) > 0:
841            for f in rfilters:
842                if f.search(self.name + "/" + testId):
843                    return True
844
845        return False
846
847    def loadTestSuites(self, tests, config, filters=[], rfilters=[]):
848        global currentMapping
849        currentMapping = self
850        try:
851            origsyspath = sys.path
852            prefix = os.path.commonprefix([toplevel, self.component.getScriptDir()])
853            moduleprefix = self.component.getScriptDir()[len(prefix) + 1:].replace(os.sep, ".") + "."
854            sys.path = [prefix] + sys.path
855            for test in tests or [""]:
856                testDir = self.component.getTestDir(self)
857                for root, dirs, files in os.walk(os.path.join(testDir, test.replace('/', os.sep))):
858                    testId = root[len(testDir) + 1:]
859                    if os.sep != "/":
860                        testId = testId.replace(os.sep, "/")
861
862                    if self.filterTestSuite(testId, config, filters, rfilters):
863                        continue
864
865                    #
866                    # First check if there's a test.py file in the test directory, if there's one use it.
867                    #
868                    if "test.py" in files :
869                        #
870                        # WORKAROUND for Python issue 15230 (fixed in 3.2) where run_path doesn't work correctly.
871                        #
872                        #runpy.run_path(os.path.join(root, "test.py"))
873                        origsyspath = sys.path
874                        sys.path = [root] + sys.path
875                        runpy.run_module("test", init_globals=globals(), run_name=root)
876                        origsyspath = sys.path
877                        continue
878
879                    #
880                    # If there's no test.py file in the test directory, we check if there's a common
881                    # script for the test in scripts/tests. If there's one we use it.
882                    #
883                    if os.path.isfile(os.path.join(self.component.getScriptDir(), testId + ".py")):
884                        runpy.run_module(moduleprefix + testId.replace("/", "."), init_globals=globals(), run_name=root)
885                        continue
886
887                    #
888                    # Finally, we try to "discover/compute" the test by looking up for well-known
889                    # files.
890                    #
891                    testcases = self.computeTestCases(testId, files)
892                    if testcases:
893                        TestSuite(root, testcases)
894        finally:
895            currentMapping = None
896            sys.path = origsyspath
897
898    def getTestSuites(self, ids=[]):
899        if not ids:
900            return self.testsuites.values()
901        return [self.testsuites[testSuiteId] for testSuiteId in ids if testSuiteId in self.testsuites]
902
903    def addTestSuite(self, testsuite):
904        assert len(testsuite.path) > len(self.component.getTestDir(self)) + 1
905        testSuiteId = testsuite.path[len(self.component.getTestDir(self)) + 1:].replace('\\', '/')
906        self.testsuites[testSuiteId] = testsuite
907        return testSuiteId
908
909    def findTestSuite(self, testsuite):
910        return self.testsuites.get(testsuite if isinstance(testsuite, str) else testsuite.id)
911
912    def computeTestCases(self, testId, files):
913
914        # Instantiate a new test suite if the directory contains well-known source files.
915        def checkFile(f, m):
916            try:
917                # If given mapping is same as local mapping, just check the files set, otherwise check
918                # with the mapping
919                return (self.getDefaultSource(f) in files) if m == self else m.hasSource(testId, f)
920            except KeyError:
921                # Expected if the mapping doesn't support the process type
922                return False
923
924        checkClient = lambda f: checkFile(f, self.getClientMapping(testId))
925        checkServer = lambda f: checkFile(f, self.getServerMapping(testId))
926
927        testcases = []
928        if checkClient("client") and checkServer("server"):
929            testcases.append(ClientServerTestCase())
930        if checkClient("client") and checkServer("serveramd") and self.getServerMapping(testId) == self:
931            testcases.append(ClientAMDServerTestCase())
932        if checkClient("client") and checkServer("servertie") and self.getServerMapping(testId) == self:
933            testcases.append(ClientTieServerTestCase())
934        if checkClient("client") and checkServer("serveramdtie") and self.getServerMapping(testId) == self:
935            testcases.append(ClientAMDTieServerTestCase())
936        if checkClient("client") and len(testcases) == 0:
937            testcases.append(ClientTestCase())
938        if checkClient("collocated"):
939            testcases.append(CollocatedTestCase())
940        if len(testcases) > 0:
941            return testcases
942
943    def hasSource(self, testId, processType):
944        try:
945            return os.path.exists(os.path.join(self.component.getTestDir(self), testId, self.getDefaultSource(processType)))
946        except KeyError:
947            return False
948
949    def getPath(self):
950        return self.path
951
952    def getTestCwd(self, process, current):
953        return current.testcase.getPath(current)
954
955    def getDefaultSource(self, processType):
956        default = self.component.getDefaultSource(self, processType)
957        if default:
958            return default
959        return self._getDefaultSource(processType)
960
961    def getDefaultProcesses(self, processType, testsuite):
962        default = self.component.getDefaultProcesses(self, processType, testsuite.getId())
963        if default:
964            return default
965        return self._getDefaultProcesses(processType)
966
967    def getDefaultExe(self, processType):
968        default = self.component.getDefaultExe(self, processType)
969        if default:
970            return default
971        return self._getDefaultExe(processType)
972
973    def _getDefaultSource(self, processType):
974        return processType
975
976    def _getDefaultProcesses(self, processType):
977        #
978        # If no server or client is explicitly set with a testcase, getDefaultProcess is called
979        # to figure out which process class to instantiate.
980        #
981        name, ext = os.path.splitext(self.getDefaultSource(processType))
982        if name in globals():
983            return [globals()[name]()]
984        return [Server()] if processType.startswith("server") else [Client()] if processType else []
985
986    def _getDefaultExe(self, processType):
987        return os.path.splitext(self.getDefaultSource(processType))[0]
988
989    def getClientMapping(self, testId=None):
990        # The client mapping is always the same as this mapping.
991        return self
992
993    def getServerMapping(self, testId=None):
994        # Can be overridden for client-only mapping that relies on another mapping for servers
995        return self
996
997    def getBuildDir(self, name, current):
998        return platform.getBuildSubDir(self, name, current)
999
1000    def getCommandLine(self, current, process, exe, args):
1001        cmd = ""
1002        if process.isFromBinDir():
1003            # If it's a process from the bin directory, the location is platform specific
1004            # so we check with the platform.
1005            cmd = os.path.join(self.component.getBinDir(process, self, current), exe)
1006        elif current.testcase:
1007            # If it's a process from a testcase, the binary is in the test build directory.
1008            cmd = os.path.join(current.testcase.getPath(current), current.getBuildDir(exe), exe)
1009        else:
1010            cmd = exe
1011
1012        if isinstance(platform, Windows) and not exe.endswith(".exe"):
1013            cmd += ".exe"
1014
1015        return cmd + " " + args if args else cmd
1016
1017    def getProps(self, process, current):
1018        props = {}
1019        if isinstance(process, IceProcess):
1020            if current.config.protocol in ["bt", "bts"]:
1021                props["Ice.Plugin.IceBT"] = self.getPluginEntryPoint("IceBT", process, current)
1022            if current.config.protocol in ["ssl", "wss", "bts", "iaps"]:
1023                props.update(self.getSSLProps(process, current))
1024        return props
1025
1026    def getSSLProps(self, process, current):
1027        sslProps = {
1028            "Ice.Plugin.IceSSL" : self.getPluginEntryPoint("IceSSL", process, current),
1029            "IceSSL.Password": "password",
1030            "IceSSL.DefaultDir": "" if current.config.buildPlatform == "iphoneos" else os.path.join(self.component.getSourceDir(), "certs"),
1031        }
1032
1033        #
1034        # If the client doesn't support client certificates, set IceSSL.VerifyPeer to 0
1035        #
1036        if isinstance(process, Server):
1037            if isinstance(current.testsuite.getMapping(), JavaScriptMixin):
1038                sslProps["IceSSL.VerifyPeer"] = 0
1039
1040        return sslProps
1041
1042    def getArgs(self, process, current):
1043        return []
1044
1045    def getEnv(self, process, current):
1046        return {}
1047
1048    def getOptions(self, current):
1049        return {}
1050
1051#
1052# A Runnable can be used as a "client" for in test cases, it provides
1053# implements run, setup and teardown methods.
1054#
1055class Runnable(object):
1056
1057    def __init__(self, desc=None):
1058        self.desc = desc
1059
1060    def setup(self, current):
1061        ### Only called when ran from testcase
1062        pass
1063
1064    def teardown(self, current, success):
1065        ### Only called when ran from testcase
1066        pass
1067
1068    def run(self, current):
1069        pass
1070
1071#
1072# A Process describes how to run an executable process.
1073#
1074class Process(Runnable):
1075
1076    processType = None
1077
1078    def __init__(self, exe=None, outfilters=None, quiet=False, args=None, props=None, envs=None, desc=None,
1079                 mapping=None, preexec_fn=None, traceProps=None):
1080        Runnable.__init__(self, desc)
1081        self.exe = exe
1082        self.outfilters = outfilters or []
1083        self.quiet = quiet
1084        self.args = args or []
1085        self.props = props or {}
1086        self.traceProps = traceProps or {}
1087        self.envs = envs or {}
1088        self.mapping = mapping
1089        self.preexec_fn = preexec_fn
1090
1091    def __str__(self):
1092        if not self.exe:
1093            return str(self.__class__)
1094        return self.exe + (" ({0})".format(self.desc) if self.desc else "")
1095
1096    def getOutput(self, current, encoding="utf-8"):
1097        assert(self in current.processes)
1098
1099        def d(s):
1100            return s if isPython2 else s.decode(encoding) if isinstance(s, bytes) else s
1101
1102        output = d(current.processes[self].getOutput())
1103        try:
1104            # Apply outfilters to the output
1105            if len(self.outfilters) > 0:
1106                lines = output.split('\n')
1107                newLines = []
1108                previous = ""
1109                for line in [line + '\n' for line in lines]:
1110                    for f in self.outfilters:
1111                        if isinstance(f, types.LambdaType) or isinstance(f, types.FunctionType):
1112                            line = f(line)
1113                        elif f.search(line):
1114                            break
1115                    else:
1116                        if line.endswith('\n'):
1117                            if previous:
1118                                newLines.append(previous + line)
1119                                previous = ""
1120                            else:
1121                                newLines.append(line)
1122                        else:
1123                            previous += line
1124                output = "".join(newLines)
1125            output = output.strip()
1126            return output + '\n' if output else ""
1127        except Exception as ex:
1128            print("unexpected exception while filtering process output:\n" + str(ex))
1129            raise
1130
1131    def run(self, current, args=[], props={}, exitstatus=0, timeout=None):
1132        class WatchDog:
1133
1134            def __init__(self, timeout):
1135                self.lastProgressTime = time.time()
1136                self.lock = threading.Lock()
1137
1138            def reset(self):
1139                with self.lock: self.lastProgressTime = time.time()
1140
1141            def timedOut(self, timeout):
1142                with self.lock:
1143                    return (time.time() - self.lastProgressTime) >= timeout
1144
1145        watchDog = WatchDog(timeout)
1146        self.start(current, args, props, watchDog=watchDog)
1147        process = current.processes[self]
1148
1149        if timeout is None:
1150            # If it's not a local process use a large timeout as the watch dog might not
1151            # get invoked (TODO: improve remote processes to use the watch dog)
1152            timeout = 60 if isinstance(process, Expect.Expect) else 480
1153
1154        if not self.quiet and not current.driver.isWorkerThread():
1155            # Print out the process output to stdout if we're running the client form the main thread.
1156            process.trace(self.outfilters)
1157        try:
1158            while True:
1159                try:
1160                    process.waitSuccess(exitstatus=exitstatus, timeout=30)
1161                    break
1162                except KeyboardInterrupt:
1163                    current.driver.setInterrupt(True)
1164                    raise
1165                except Expect.TIMEOUT:
1166                    if watchDog and watchDog.timedOut(timeout):
1167                        print("process {0} is hanging - {1}".format(process, time.strftime("%x %X")))
1168                        if current.driver.isInterrupted():
1169                            self.stop(current, False, exitstatus)
1170                            raise
1171        finally:
1172            self.stop(current, True, exitstatus)
1173
1174    def getEffectiveArgs(self, current, args):
1175        allArgs = []
1176        allArgs += current.driver.getArgs(self, current)
1177        allArgs += current.config.getArgs(self, current)
1178        allArgs += self.getMapping(current).getArgs(self, current)
1179        allArgs += current.testcase.getArgs(self, current)
1180        allArgs += self.getArgs(current)
1181        allArgs += self.args(self, current) if callable(self.args) else self.args
1182        allArgs += args
1183        allArgs = [a.encode("utf-8") if type(a) == "unicode" else str(a) for a in allArgs]
1184        return allArgs
1185
1186    def getEffectiveProps(self, current, props):
1187        allProps = {}
1188        allProps.update(current.driver.getProps(self, current))
1189        allProps.update(current.driver.getComponent().getProps(self, current))
1190        allProps.update(current.config.getProps(self, current))
1191        allProps.update(self.getMapping(current).getProps(self, current))
1192        allProps.update(current.testcase.getProps(self, current))
1193        allProps.update(self.getProps(current))
1194        allProps.update(self.props(self, current) if callable(self.props) else self.props)
1195        allProps.update(props)
1196        return allProps
1197
1198    def getEffectiveEnv(self, current):
1199
1200        def merge(envs, newEnvs):
1201            if platform.getLdPathEnvName() in newEnvs and platform.getLdPathEnvName() in envs:
1202                newEnvs[platform.getLdPathEnvName()] += os.pathsep + envs[platform.getLdPathEnvName()]
1203            envs.update(newEnvs)
1204
1205        allEnvs = {}
1206        merge(allEnvs, current.driver.getComponent().getEnv(self, current))
1207        merge(allEnvs, self.getMapping(current).getEnv(self, current))
1208        merge(allEnvs, current.testcase.getEnv(self, current))
1209        merge(allEnvs, self.getEnv(current))
1210        merge(allEnvs, self.envs(self, current) if callable(self.envs) else self.envs)
1211        return allEnvs
1212
1213    def getEffectiveTraceProps(self, current):
1214        traceProps = {}
1215        traceProps.update(current.testcase.getTraceProps(self, current))
1216        traceProps.update(self.traceProps(self, current) if callable(self.traceProps) else self.traceProps)
1217        return traceProps
1218
1219    def start(self, current, args=[], props={}, watchDog=None):
1220        allArgs = self.getEffectiveArgs(current, args)
1221        allProps = self.getEffectiveProps(current, props)
1222        allEnvs = self.getEffectiveEnv(current)
1223
1224        processController = current.driver.getProcessController(current, self)
1225        current.processes[self] = processController.start(self, current, allArgs, allProps, allEnvs, watchDog)
1226        try:
1227            self.waitForStart(current)
1228        except:
1229            self.stop(current)
1230            raise
1231
1232    def waitForStart(self, current):
1233        # To be overridden in specialization to wait for a token indicating the process readiness.
1234        pass
1235
1236    def stop(self, current, waitSuccess=False, exitstatus=0):
1237        if self in current.processes:
1238            process = current.processes[self]
1239            try:
1240                # Wait for the process to exit successfully by itself.
1241                if not process.isTerminated() and waitSuccess:
1242                    while True:
1243                        try:
1244                            process.waitSuccess(exitstatus=exitstatus, timeout=30)
1245                            break
1246                        except KeyboardInterrupt:
1247                            current.driver.setInterrupt(True)
1248                            raise
1249                        except Expect.TIMEOUT:
1250                            print("process {0} is hanging on shutdown - {1}".format(process, time.strftime("%x %X")))
1251                            if current.driver.isInterrupted():
1252                                raise
1253            finally:
1254                if not process.isTerminated():
1255                    process.terminate()
1256                if not self.quiet: # Write the output to the test case (but not on stdout)
1257                    current.write(self.getOutput(current), stdout=False)
1258
1259    def teardown(self, current, success):
1260        if self in current.processes:
1261            current.processes[self].teardown(current, success)
1262
1263    def expect(self, current, pattern, timeout=60):
1264        assert(self in current.processes and isinstance(current.processes[self], Expect.Expect))
1265        return current.processes[self].expect(pattern, timeout)
1266
1267    def expectall(self, current, pattern, timeout=60):
1268        assert(self in current.processes and isinstance(current.processes[self], Expect.Expect))
1269        return current.processes[self].expectall(pattern, timeout)
1270
1271    def sendline(self, current, data):
1272        assert(self in current.processes and isinstance(current.processes[self], Expect.Expect))
1273        return current.processes[self].sendline(data)
1274
1275    def getMatch(self, current):
1276        assert(self in current.processes and isinstance(current.processes[self], Expect.Expect))
1277        return current.processes[self].match
1278
1279    def isStarted(self, current):
1280        return self in current.processes and not current.processes[self].isTerminated()
1281
1282    def isFromBinDir(self):
1283        return False
1284
1285    def isReleaseOnly(self):
1286        return False
1287
1288    def getArgs(self, current):
1289        return []
1290
1291    def getProps(self, current):
1292        return {}
1293
1294    def getEnv(self, current):
1295        return {}
1296
1297    def getMapping(self, current):
1298        return self.mapping or current.testcase.getMapping()
1299
1300    def getExe(self, current):
1301        processType = self.processType or current.testcase.getProcessType(self)
1302        return self.exe or self.getMapping(current).getDefaultExe(processType)
1303
1304    def getCommandLine(self, current, args=""):
1305        return self.getMapping(current).getCommandLine(current, self, self.getExe(current), args).strip()
1306
1307#
1308# A simple client (used to run Slice/IceUtil clients for example)
1309#
1310class SimpleClient(Process):
1311    pass
1312
1313#
1314# An IceProcess specialization class. This is used by drivers to figure out if
1315# the process accepts Ice configuration properties.
1316#
1317class IceProcess(Process):
1318    pass
1319
1320#
1321# An Ice server process. It's possible to configure when the server is considered
1322# ready by setting readyCount or ready. The start method will only return once
1323# the server is considered "ready". It can also be configure to wait (the default)
1324# or not wait for shutdown when the stop method is invoked.
1325#
1326class Server(IceProcess):
1327
1328    def __init__(self, exe=None, waitForShutdown=True, readyCount=1, ready=None, startTimeout=300, *args, **kargs):
1329        IceProcess.__init__(self, exe, *args, **kargs)
1330        self.waitForShutdown = waitForShutdown
1331        self.readyCount = readyCount
1332        self.ready = ready
1333        self.startTimeout = startTimeout
1334
1335    def getProps(self, current):
1336        props = IceProcess.getProps(self, current)
1337        props.update({
1338            "Ice.ThreadPool.Server.Size": 1,
1339            "Ice.ThreadPool.Server.SizeMax": 3,
1340            "Ice.ThreadPool.Server.SizeWarn": 0,
1341        })
1342        props.update(current.driver.getProcessProps(current, self.ready, self.readyCount + (1 if current.config.mx else 0)))
1343        return props
1344
1345    def waitForStart(self, current):
1346        # Wait for the process to be ready
1347        current.processes[self].waitReady(self.ready, self.readyCount + (1 if current.config.mx else 0), self.startTimeout)
1348
1349        # Filter out remaining ready messages
1350        self.outfilters.append(re.compile("[^\n]+ ready"))
1351
1352        # If we are not asked to be quiet and running from the main thread, print the server output
1353        if not self.quiet and not current.driver.isWorkerThread():
1354            current.processes[self].trace(self.outfilters)
1355
1356    def stop(self, current, waitSuccess=False, exitstatus=0):
1357        IceProcess.stop(self, current, waitSuccess and self.waitForShutdown, exitstatus)
1358
1359#
1360# An Ice client process.
1361#
1362class Client(IceProcess):
1363    pass
1364
1365#
1366# Executables for processes inheriting this marker class are looked up in the
1367# Ice distribution bin directory.
1368#
1369class ProcessFromBinDir:
1370
1371    def isFromBinDir(self):
1372        return True
1373
1374#
1375# Executables for processes inheriting this marker class are only provided
1376# as a Release executble on Windows
1377#
1378class ProcessIsReleaseOnly:
1379
1380    def isReleaseOnly(self):
1381        return True
1382
1383
1384class SliceTranslator(ProcessFromBinDir, ProcessIsReleaseOnly, SimpleClient):
1385
1386    def __init__(self, translator):
1387        SimpleClient.__init__(self, exe=translator, quiet=True, mapping=Mapping.getByName("cpp"))
1388
1389    def getCommandLine(self, current, args=""):
1390        #
1391        # Look for slice2py installed by Pip if not found in the bin directory
1392        #
1393        if self.exe == "slice2py":
1394            translator = self.getMapping(current).getCommandLine(current, self, self.getExe(current), "")
1395            if os.path.exists(translator):
1396                return translator + " " + args if args else translator
1397            elif isinstance(platform, Windows):
1398                return os.path.join(os.path.dirname(sys.executable), "Scripts", "slice2py.exe") + " " + args if args else translator
1399            elif os.path.exists("/usr/local/bin/slice2py"):
1400                return "/usr/local/bin/slice2py" + " " + args if args else translator
1401            else:
1402                import slice2py
1403                return sys.executable + " " + os.path.normpath(
1404                            os.path.join(slice2py.__file__, "..", "..", "..", "..", "bin", "slice2py")) + " " + args if args else translator
1405        else:
1406            return Process.getCommandLine(self, current, args)
1407
1408class ServerAMD(Server):
1409    pass
1410
1411class Collocated(Client):
1412    pass
1413
1414class EchoServer(Server):
1415
1416    def __init__(self):
1417        Server.__init__(self, mapping=Mapping.getByName("cpp"), quiet=True, waitForShutdown=False)
1418
1419    def getProps(self, current):
1420        props = Server.getProps(self, current)
1421        props["Ice.MessageSizeMax"] = 8192 # Don't limit the amount of data to transmit between client/server
1422        return props
1423
1424    def getCommandLine(self, current, args=""):
1425        current.push(self.mapping.findTestSuite("Ice/echo").findTestCase("server"))
1426        try:
1427            return Server.getCommandLine(self, current, args)
1428        finally:
1429            current.pop()
1430
1431#
1432# A test case is composed of servers and clients. When run, all servers are started
1433# sequentially. When the servers are ready, the clients are also ran sequentially.
1434# Once all the clients are terminated, the servers are stopped (which waits for the
1435# successful completion of the server).
1436#
1437# A TestCase is also a "Runnable", like the Process class. In other words, it can be
1438# used a client to allow nested test cases.
1439#
1440class TestCase(Runnable):
1441
1442    def __init__(self, name, client=None, clients=None, server=None, servers=None, args=None, props=None, envs=None,
1443                 options=None, desc=None, traceProps=None):
1444        Runnable.__init__(self, desc)
1445
1446        self.name = name
1447        self.parent = None
1448        self.mapping = None
1449        self.testsuite = None
1450        self.options = options or {}
1451        self.args = args or []
1452        self.props = props or {}
1453        self.traceProps = traceProps or {}
1454        self.envs = envs or {}
1455
1456        #
1457        # Setup client list, "client" can be a string in which case it's assumed to
1458        # to the client executable name.
1459        #
1460        self.clients = clients
1461        if client:
1462            client = Client(exe=client) if isinstance(client, str) else client
1463            self.clients = [client] if not self.clients else self.clients + [client]
1464
1465        #
1466        # Setup server list, "server" can be a string in which case it's assumed to
1467        # to the server executable name.
1468        #
1469        self.servers = servers
1470        if server:
1471            server = Server(exe=server) if isinstance(server, str) else server
1472            self.servers = [server] if not self.servers else self.servers + [server]
1473
1474    def __str__(self):
1475        return self.name
1476
1477    def init(self, mapping, testsuite):
1478        # init is called when the testcase is added to the given testsuite
1479        self.mapping = mapping
1480        self.testsuite = testsuite
1481
1482        #
1483        # If no clients are explicitly specified, we instantiate one if getClientType()
1484        # returns the type of client to instantiate (client, collocated, etc)
1485        #
1486        testId = self.testsuite.getId()
1487        if not self.clients:
1488            if self.getClientType():
1489                self.clients = self.mapping.getClientMapping(testId).getDefaultProcesses(self.getClientType(), testsuite)
1490            else:
1491                self.clients = []
1492
1493        #
1494        # If no servers are explicitly specified, we instantiate one if getServerType()
1495        # returns the type of server to instantiate (server, serveramd, etc)
1496        #
1497        if not self.servers:
1498            if self.getServerType():
1499                self.servers = self.mapping.getServerMapping(testId).getDefaultProcesses(self.getServerType(), testsuite)
1500            else:
1501                self.servers = []
1502
1503    def getOptions(self, current):
1504        return self.options(current) if callable(self.options) else self.options
1505
1506    def canRun(self, current):
1507        # Can be overriden
1508        return True
1509
1510    def setupServerSide(self, current):
1511        # Can be overridden to perform setup activities before the server side is started
1512        pass
1513
1514    def teardownServerSide(self, current, success):
1515        # Can be overridden to perform terddown after the server side is stopped
1516        pass
1517
1518    def setupClientSide(self, current):
1519        # Can be overridden to perform setup activities before the client side is started
1520        pass
1521
1522    def teardownClientSide(self, current, success):
1523        # Can be overridden to perform terddown after the client side is stopped
1524        pass
1525
1526    def startServerSide(self, current):
1527        for server in self.servers:
1528            self._startServer(current, server)
1529
1530    def stopServerSide(self, current, success):
1531        for server in reversed(self.servers):
1532            self._stopServer(current, server, success)
1533
1534    def runClientSide(self, current):
1535        for client in self.clients:
1536            self._runClient(current, client)
1537
1538    def getTestSuite(self):
1539        return self.testsuite
1540
1541    def getParent(self):
1542        return self.parent
1543
1544    def getName(self):
1545        return self.name
1546
1547    def getPath(self, current):
1548        path = self.testsuite.getPath()
1549        if current.config.pathOverride:
1550            return path.replace(toplevel, current.config.pathOverride)
1551        else:
1552            return path
1553
1554    def getMapping(self):
1555        return self.mapping
1556
1557    def getArgs(self, process, current):
1558        return self.args
1559
1560    def getProps(self, process, current):
1561        return self.props
1562
1563    def getTraceProps(self, process, current):
1564        return self.traceProps
1565
1566    def getEnv(self, process, current):
1567        return self.envs
1568
1569    def getProcessType(self, process):
1570        if process in self.clients:
1571            return self.getClientType()
1572        elif process in self.servers:
1573            return self.getServerType()
1574        elif isinstance(process, Server):
1575            return self.getServerType()
1576        else:
1577            return self.getClientType()
1578
1579    def getClientType(self):
1580        # Overridden by test case specialization to specify the type of client to instantiate
1581        # if no client is explicitly provided
1582        return None
1583
1584    def getServerType(self):
1585        # Overridden by test case specialization to specify the type of client to instantiate
1586        # if no server is explicitly provided
1587        return None
1588
1589    def getServerTestCase(self, cross=None):
1590        testsuite = (cross or self.mapping).getServerMapping(self.testsuite.getId()).findTestSuite(self.testsuite)
1591        return testsuite.findTestCase(self) if testsuite else None
1592
1593    def getClientTestCase(self):
1594        testsuite = self.mapping.getClientMapping(self.testsuite.getId()).findTestSuite(self.testsuite)
1595        return testsuite.findTestCase(self) if testsuite else None
1596
1597    def _startServerSide(self, current):
1598        # Set the host to use for the server side
1599        current.push(self)
1600        current.host = current.driver.getProcessController(current).getHost(current)
1601        self.setupServerSide(current)
1602        try:
1603            self.startServerSide(current)
1604            return current.host
1605        except:
1606            self._stopServerSide(current, False)
1607            raise
1608        finally:
1609            current.pop()
1610
1611    def _stopServerSide(self, current, success):
1612        current.push(self)
1613        try:
1614            self.stopServerSide(current, success)
1615        finally:
1616            for server in reversed(self.servers):
1617                if server.isStarted(current):
1618                    self._stopServer(current, server, False)
1619            self.teardownServerSide(current, success)
1620            current.pop()
1621
1622    def _startServer(self, current, server):
1623        if server.desc:
1624            current.write("starting {0}... ".format(server.desc))
1625        server.setup(current)
1626        server.start(current)
1627        if server.desc:
1628            current.writeln("ok")
1629
1630    def _stopServer(self, current, server, success):
1631        try:
1632            server.stop(current, success)
1633        except:
1634            success = False
1635            raise
1636        finally:
1637            server.teardown(current, success)
1638
1639    def _runClientSide(self, current, host=None):
1640        current.push(self, host)
1641        self.setupClientSide(current)
1642        success = False
1643        try:
1644            self.runClientSide(current)
1645            success = True
1646        finally:
1647            self.teardownClientSide(current, success)
1648            current.pop()
1649
1650    def _runClient(self, current, client):
1651        success = False
1652        if client.desc:
1653            current.writeln("running {0}...".format(client.desc))
1654        client.setup(current)
1655        try:
1656            client.run(current)
1657            success = True
1658        finally:
1659            client.teardown(current, success)
1660
1661    def run(self, current):
1662        try:
1663            current.push(self)
1664            if not self.parent:
1665                current.result.started(current)
1666            self.setup(current)
1667            self.runWithDriver(current)
1668            self.teardown(current, True)
1669            if not self.parent:
1670                current.result.succeeded(current)
1671        except Exception as ex:
1672            self.teardown(current, False)
1673            if not self.parent:
1674                current.result.failed(current, traceback.format_exc() if current.driver.debug else str(ex))
1675            raise
1676        finally:
1677            current.pop()
1678
1679class ClientTestCase(TestCase):
1680
1681    def __init__(self, name="client", *args, **kargs):
1682        TestCase.__init__(self, name, *args, **kargs)
1683
1684    def runWithDriver(self, current):
1685        current.driver.runTestCase(current)
1686
1687    def getClientType(self):
1688        return "client"
1689
1690class ClientServerTestCase(ClientTestCase):
1691
1692    def __init__(self, name="client/server", *args, **kargs):
1693        TestCase.__init__(self, name, *args, **kargs)
1694
1695    def runWithDriver(self, current):
1696        current.driver.runClientServerTestCase(current)
1697
1698    def getServerType(self):
1699        return "server"
1700
1701class CollocatedTestCase(ClientTestCase):
1702
1703    def __init__(self, name="collocated", *args, **kargs):
1704        TestCase.__init__(self, name, *args, **kargs)
1705
1706    def getClientType(self):
1707        return "collocated"
1708
1709class ClientAMDServerTestCase(ClientServerTestCase):
1710
1711    def __init__(self, name="client/amd server", *args, **kargs):
1712        ClientServerTestCase.__init__(self, name, *args, **kargs)
1713
1714    def getServerType(self):
1715        return "serveramd"
1716
1717class ClientTieServerTestCase(ClientServerTestCase):
1718
1719    def __init__(self, name="client/tie server", *args, **kargs):
1720        ClientServerTestCase.__init__(self, name, *args, **kargs)
1721
1722    def getServerType(self):
1723        return "servertie"
1724
1725class ClientAMDTieServerTestCase(ClientServerTestCase):
1726
1727    def __init__(self, name="client/amd tie server", *args, **kargs):
1728        ClientServerTestCase.__init__(self, name, *args, **kargs)
1729
1730    def getServerType(self):
1731        return "serveramdtie"
1732
1733class Result:
1734
1735    getKey = lambda self, current: (current.testcase, current.config) if isinstance(current, Driver.Current) else current
1736    getDesc = lambda self, current: current.desc if isinstance(current, Driver.Current) else ""
1737
1738    def __init__(self, testsuite, writeToStdout):
1739        self.testsuite = testsuite
1740        self._failed = {}
1741        self._skipped = {}
1742        self._stdout = StringIO()
1743        self._writeToStdout = writeToStdout
1744        self._testcases = {}
1745        self._duration = 0
1746        self._testCaseDuration = 0;
1747
1748    def start(self):
1749        self._duration = time.time()
1750
1751    def finished(self):
1752        self._duration = time.time() - self._duration
1753
1754    def started(self, current):
1755        self._testCaseDuration = time.time();
1756        self._start = self._stdout.tell()
1757
1758    def failed(self, current, exception):
1759        print(exception)
1760        key = self.getKey(current)
1761        self._testCaseDuration = time.time() - self._testCaseDuration;
1762        self.writeln("\ntest in {0} failed:\n{1}".format(self.testsuite, exception))
1763        self._testcases[key] = (self._start, self._stdout.tell(), self._testCaseDuration, self.getDesc(current))
1764        self._failed[key] = exception
1765
1766        # If ADDRINUSE, dump the current processes
1767        output = self.getOutput(key)
1768        for s in ["EADDRINUSE", "Address already in use"]:
1769            if output.find(s) >= 0:
1770                if isinstance(platform, Windows):
1771                    self.writeln(run("netstat -on"))
1772                    self.writeln(run("powershell.exe \"Get-Process | Select id,name,path\""))
1773                else:
1774                    self.writeln(run("lsof -n -P -i; ps ax"))
1775
1776    def succeeded(self, current):
1777        key = self.getKey(current)
1778        self._testCaseDuration = time.time() - self._testCaseDuration;
1779        self._testcases[key] = (self._start, self._stdout.tell(), self._testCaseDuration, self.getDesc(current))
1780
1781    def skipped(self, current, reason):
1782        self.writeln("skipped, " + reason)
1783        self._skipped[self.getKey(current)] = reason
1784
1785    def isSuccess(self):
1786        return len(self._failed) == 0
1787
1788    def getFailed(self):
1789        return self._failed
1790
1791    def getDuration(self):
1792        return self._duration
1793
1794    def getOutput(self, key=None):
1795        if key:
1796            if key in self._testcases:
1797                (start, end, duration, desc) = self._testcases[key]
1798                self._stdout.seek(start)
1799                try:
1800                    return self._stdout.read(end - start)
1801                finally:
1802                    self._stdout.seek(0, os.SEEK_END)
1803
1804        return self._stdout.getvalue()
1805
1806    def write(self, msg, stdout=True):
1807        if self._writeToStdout and stdout:
1808            try:
1809                sys.stdout.write(msg)
1810            except UnicodeEncodeError:
1811                #
1812                # The console doesn't support the encoding of the message, we convert the message
1813                # to an UTF-8 byte sequence and print out the byte sequence. We replace all the
1814                # double backslash from the byte sequence string representation to single back
1815                # slash.
1816                #
1817                sys.stdout.write(str(msg.encode("utf-8")).replace("\\\\", "\\"))
1818            sys.stdout.flush()
1819        self._stdout.write(msg)
1820
1821    def writeln(self, msg, stdout=True):
1822        if self._writeToStdout and stdout:
1823            try:
1824                print(msg)
1825            except UnicodeEncodeError:
1826                #
1827                # The console doesn't support the encoding of the message, we convert the message
1828                # to an UTF-8 byte sequence and print out the byte sequence. We replace all the
1829                # double backslash from the byte sequence string representation to single back
1830                # slash.
1831                #
1832                print(str(msg.encode("utf-8")).replace("\\\\", "\\"))
1833        self._stdout.write(msg)
1834        self._stdout.write("\n")
1835
1836    def writeAsXml(self, out, hostname=""):
1837        out.write('  <testsuite tests="{0}" failures="{1}" skipped="{2}" time="{3:.9f}" name="{5}/{4}">\n'
1838                    .format(len(self._testcases) - 2,
1839                            len(self._failed),
1840                            0,
1841                            self._duration,
1842                            self.testsuite,
1843                            self.testsuite.getMapping()))
1844
1845        for (k, v) in self._testcases.items():
1846            if isinstance(k, str):
1847                # Don't keep track of setup/teardown steps
1848                continue
1849
1850            # Don't write skipped tests, this doesn't really provide useful information and clutters
1851            # the output.
1852            if k in self._skipped:
1853                continue
1854
1855            (tc, cf) = k
1856            (s, e, d, c) = v
1857            if c:
1858                name = "{0} [{1}]".format(tc, c)
1859            else:
1860                name = str(tc)
1861            if hostname:
1862                name += " on " + hostname
1863            out.write('    <testcase name="{0}" time="{1:.9f}" classname="{2}.{3}">\n'
1864                      .format(name,
1865                              d,
1866                              self.testsuite.getMapping(),
1867                              self.testsuite.getId().replace("/", ".")))
1868            if k in self._failed:
1869                last = self._failed[k].strip().split('\n')
1870                if len(last) > 0:
1871                    last = last[len(last) - 1]
1872                if hostname:
1873                    last = "Failed on {0}\n{1}".format(hostname, last)
1874                out.write('      <failure message={1}>{0}</failure>\n'.format(escapeXml(self._failed[k]),
1875                                                                              escapeXml(last, True)))
1876            # elif k in self._skipped:
1877            #     out.write('      <skipped message="{0}"/>\n'.format(escapeXml(self._skipped[k], True)))
1878            out.write('      <system-out>\n')
1879            if hostname:
1880                out.write('Running on {0}\n'.format(hostname))
1881            out.write(escapeXml(self.getOutput(k)))
1882            out.write('      </system-out>\n')
1883            out.write('    </testcase>\n')
1884
1885        out.write(  '</testsuite>\n')
1886
1887class TestSuite(object):
1888
1889    def __init__(self, path, testcases=None, options=None, libDirs=None, runOnMainThread=False, chdir=False,
1890                 multihost=True, mapping=None):
1891        global currentMapping
1892        self.path = os.path.dirname(path) if os.path.basename(path) == "test.py" else path
1893        self.mapping = currentMapping or Mapping.getByPath(self.path)
1894        self.id = self.mapping.addTestSuite(self)
1895        self.options = options or {}
1896        self.libDirs = libDirs or []
1897        self.runOnMainThread = runOnMainThread
1898        self.chdir = chdir
1899        self.multihost = multihost
1900        if self.chdir:
1901            # Only tests running on main thread can change the current working directory
1902            self.runOnMainThread = True
1903
1904        if testcases is None:
1905            files = [f for f in os.listdir(self.path) if os.path.isfile(os.path.join(self.path, f))]
1906            testcases = self.mapping.computeTestCases(self.id, files)
1907        self.testcases = OrderedDict()
1908        for testcase in testcases if testcases else []:
1909            testcase.init(self.mapping, self)
1910            if testcase.name in self.testcases:
1911                raise RuntimeError("duplicate testcase {0} in testsuite {1}".format(testcase, self))
1912            self.testcases[testcase.name] = testcase
1913
1914    def __str__(self):
1915        return self.id
1916
1917    def getId(self):
1918        return self.id
1919
1920    def getOptions(self, current):
1921        return self.options(current) if callable(self.options) else self.options
1922
1923    def getPath(self):
1924        return self.path
1925
1926    def getMapping(self):
1927        return self.mapping
1928
1929    def getLibDirs(self):
1930        return self.libDirs
1931
1932    def isMainThreadOnly(self, driver):
1933        if self.runOnMainThread or driver.getComponent().isMainThreadOnly(self.id):
1934            return True
1935        for m in [CppMapping, JavaMapping, CSharpMapping, PythonMapping, PhpMapping, RubyMapping, JavaScriptMixin]:
1936            if isinstance(self.mapping, m):
1937                config = driver.configs[self.mapping]
1938                if "iphone" in config.buildPlatform or config.uwp or config.browser or config.android:
1939                    return True # Not supported yet for tests that require a remote process controller
1940                return False
1941        else:
1942            return True
1943
1944    def addTestCase(self, testcase):
1945        if testcase.name in self.testcases:
1946            raise RuntimeError("duplicate testcase {0} in testsuite {1}".format(testcase, self))
1947        testcase.init(self.mapping, self)
1948        self.testcases[testcase.name] = testcase
1949
1950    def findTestCase(self, testcase):
1951        return self.testcases.get(testcase if isinstance(testcase, str) else testcase.name)
1952
1953    def getTestCases(self):
1954        return self.testcases.values()
1955
1956    def setup(self, current):
1957        pass
1958
1959    def run(self, current):
1960        try:
1961            current.result.start()
1962            cwd=None
1963            if self.chdir:
1964                cwd = os.getcwd()
1965                os.chdir(self.path)
1966            current.driver.runTestSuite(current)
1967        finally:
1968            if cwd: os.chdir(cwd)
1969            current.result.finished()
1970
1971    def teardown(self, current, success):
1972        pass
1973
1974    def isMultiHost(self):
1975        return self.multihost
1976
1977class ProcessController:
1978
1979    def __init__(self, current):
1980        pass
1981
1982    def start(self, process, current, args, props, envs, watchDog):
1983        raise NotImplemented()
1984
1985    def destroy(self, driver):
1986        pass
1987
1988class LocalProcessController(ProcessController):
1989
1990    class LocalProcess(Expect.Expect):
1991
1992        def __init__(self, traceFile, *args, **kargs):
1993            Expect.Expect.__init__(self, *args, **kargs)
1994            self.traceFile = traceFile
1995
1996        def waitReady(self, ready, readyCount, startTimeout):
1997            if ready:
1998                self.expect("%s ready\n" % ready, timeout = startTimeout)
1999            else:
2000                while readyCount > 0:
2001                    self.expect("[^\n]+ ready\n", timeout = startTimeout)
2002                    readyCount -= 1
2003
2004        def isTerminated(self):
2005            return self.p is None
2006
2007        def teardown(self, current, success):
2008            if self.traceFile:
2009                if success or current.driver.isInterrupted():
2010                    os.remove(self.traceFile)
2011                else:
2012                    current.writeln("saved {0}".format(self.traceFile))
2013
2014    def getHost(self, current):
2015        return current.driver.getHost(current.config.protocol, current.config.ipv6)
2016
2017    def start(self, process, current, args, props, envs, watchDog):
2018
2019        #
2020        # Props and arguments can use the format parameters set below in the kargs
2021        # dictionary. It's time to convert them to their values.
2022        #
2023        kargs = {
2024            "process": process,
2025            "testcase": current.testcase,
2026            "testdir": current.testsuite.getPath(),
2027            "builddir": current.getBuildDir(process.getExe(current)),
2028        }
2029
2030        traceFile = ""
2031        if not isinstance(process.getMapping(current), JavaScriptMixin):
2032            traceProps = process.getEffectiveTraceProps(current)
2033            if traceProps:
2034                if "Ice.ProgramName" in props:
2035                    programName = props["Ice.ProgramName"]
2036                else:
2037                    programName = process.exe or current.testcase.getProcessType(process)
2038                traceFile = os.path.join(current.testsuite.getPath(),
2039                                         "{0}-{1}.log".format(programName, time.strftime("%m%d%y-%H%M")))
2040                if isinstance(process.getMapping(current), ObjCMapping):
2041                    traceProps["Ice.StdErr"] = traceFile
2042                else:
2043                    traceProps["Ice.LogFile"] = traceFile
2044            props.update(traceProps)
2045
2046        args = ["--{0}={1}".format(k, val(v)) for k,v in props.items()] + [val(a) for a in args]
2047        for k, v in envs.items():
2048            envs[k] = val(v, quoteValue=False)
2049
2050        cmd = ""
2051        if current.driver.valgrind:
2052            cmd += "valgrind -q --child-silent-after-fork=yes --leak-check=full --suppressions=\"{0}\" ".format(
2053                                os.path.join(current.driver.getComponent().getSourceDir(), "config", "valgrind.sup"))
2054        exe = process.getCommandLine(current, " ".join(args))
2055        cmd += exe.format(**kargs)
2056
2057        if current.driver.debug:
2058            if len(envs) > 0:
2059                current.writeln("({0} env={1})".format(cmd, envs))
2060            else:
2061                current.writeln("({0})".format(cmd))
2062
2063        env = os.environ.copy()
2064        env.update(envs)
2065        mapping = process.getMapping(current)
2066        cwd = mapping.getTestCwd(process, current)
2067        process = LocalProcessController.LocalProcess(command=cmd,
2068                                                      startReader=False,
2069                                                      env=env,
2070                                                      cwd=cwd,
2071                                                      desc=process.desc or exe,
2072                                                      preexec_fn=process.preexec_fn,
2073                                                      mapping=str(mapping),
2074                                                      traceFile=traceFile)
2075        process.startReader(watchDog)
2076        return process
2077
2078class RemoteProcessController(ProcessController):
2079
2080    class RemoteProcess:
2081        def __init__(self, exe, proxy):
2082            self.exe = exe
2083            self.proxy = proxy
2084            self.terminated = False
2085            self.stdout = False
2086
2087        def __str__(self):
2088            return "{0} proxy={1}".format(self.exe, self.proxy)
2089
2090        def waitReady(self, ready, readyCount, startTimeout):
2091            self.proxy.waitReady(startTimeout)
2092
2093        def waitSuccess(self, exitstatus=0, timeout=60):
2094            import Ice
2095            try:
2096                result = self.proxy.waitSuccess(timeout)
2097            except Ice.UserException:
2098                raise Expect.TIMEOUT("waitSuccess timeout")
2099            except Ice.LocalException:
2100                raise
2101            if exitstatus != result:
2102                raise RuntimeError("unexpected exit status: expected: %d, got %d\n" % (exitstatus, result))
2103
2104        def getOutput(self):
2105            return self.output
2106
2107        def trace(self, outfilters):
2108            self.stdout = True
2109
2110        def isTerminated(self):
2111            return self.terminated
2112
2113        def terminate(self):
2114            self.output = self.proxy.terminate().strip()
2115            self.terminated = True
2116            if self.stdout and self.output:
2117                print(self.output)
2118
2119        def teardown(self, current, success):
2120            pass
2121
2122    def __init__(self, current, endpoints="tcp"):
2123        self.processControllerProxies = {}
2124        self.controllerApps = []
2125        self.driver = current.driver
2126        self.cond = threading.Condition()
2127
2128        comm = current.driver.getCommunicator()
2129        import Test
2130
2131        class ProcessControllerRegistryI(Test.Common.ProcessControllerRegistry):
2132
2133            def __init__(self, remoteProcessController):
2134                self.remoteProcessController = remoteProcessController
2135
2136            def setProcessController(self, proxy, current):
2137                import Test
2138                proxy = Test.Common.ProcessControllerPrx.uncheckedCast(current.con.createProxy(proxy.ice_getIdentity()))
2139                self.remoteProcessController.setProcessController(proxy)
2140
2141        import Ice
2142        comm.getProperties().setProperty("Adapter.AdapterId", Ice.generateUUID())
2143        self.adapter = comm.createObjectAdapterWithEndpoints("Adapter", endpoints)
2144        self.adapter.add(ProcessControllerRegistryI(self), comm.stringToIdentity("Util/ProcessControllerRegistry"))
2145        self.adapter.activate()
2146
2147    def __str__(self):
2148        return "remote controller"
2149
2150    def getHost(self, current):
2151        return self.getController(current).getHost(current.config.protocol, current.config.ipv6)
2152
2153    def getController(self, current):
2154        ident = self.getControllerIdentity(current)
2155        if type(ident) == str:
2156            ident = current.driver.getCommunicator().stringToIdentity(ident)
2157
2158        import Ice
2159        import Test
2160
2161        proxy = None
2162        with self.cond:
2163            if ident in self.processControllerProxies:
2164                proxy = self.processControllerProxies[ident]
2165        if proxy:
2166            try:
2167                proxy.ice_ping()
2168                return proxy
2169            except Ice.NoEndpointException:
2170                self.clearProcessController(proxy)
2171
2172        comm = current.driver.getCommunicator()
2173
2174        if current.driver.controllerApp:
2175            if ident in self.controllerApps:
2176                self.restartControllerApp(current, ident) # Controller must have crashed, restart it
2177            else:
2178                self.controllerApps.append(ident)
2179                self.startControllerApp(current, ident)
2180
2181        # Use well-known proxy and IceDiscovery to discover the process controller object from the app.
2182        proxy = Test.Common.ProcessControllerPrx.uncheckedCast(comm.stringToProxy(comm.identityToString(ident)))
2183        try:
2184            proxy.ice_ping()
2185            with self.cond:
2186                self.processControllerProxies[ident] = proxy
2187                return self.processControllerProxies[ident]
2188        except Exception:
2189            pass
2190
2191        # Wait 30 seconds for a process controller to be registered with the ProcessControllerRegistry
2192        with self.cond:
2193            if not ident in self.processControllerProxies:
2194                self.cond.wait(30)
2195            if ident in self.processControllerProxies:
2196                return self.processControllerProxies[ident]
2197
2198        raise RuntimeError("couldn't reach the remote controller `{0}'".format(ident))
2199
2200    def setProcessController(self, proxy):
2201        with self.cond:
2202            self.processControllerProxies[proxy.ice_getIdentity()] = proxy
2203            conn = proxy.ice_getConnection()
2204            if(hasattr(conn, "setCloseCallback")):
2205                proxy.ice_getConnection().setCloseCallback(lambda conn : self.clearProcessController(proxy, conn))
2206            else:
2207                import Ice
2208                class CallbackI(Ice.ConnectionCallback):
2209                    def __init__(self, registry):
2210                        self.registry = registry
2211
2212                    def heartbeath(self, conn):
2213                        pass
2214
2215                    def closed(self, conn):
2216                        self.registry.clearProcessController(proxy, conn)
2217
2218                proxy.ice_getConnection().setCallback(CallbackI(self))
2219
2220            self.cond.notifyAll()
2221
2222    def clearProcessController(self, proxy, conn=None):
2223        with self.cond:
2224            if proxy.ice_getIdentity() in self.processControllerProxies:
2225                if not conn:
2226                    conn = proxy.ice_getCachedConnection()
2227                if conn == self.processControllerProxies[proxy.ice_getIdentity()].ice_getCachedConnection():
2228                    del self.processControllerProxies[proxy.ice_getIdentity()]
2229
2230    def startControllerApp(self, current, ident):
2231        pass
2232
2233    def restartControllerApp(self, current, ident):
2234        self.stopControllerApp(ident)
2235        self.startControllerApp(current, ident)
2236
2237    def stopControllerApp(self, ident):
2238        pass
2239
2240    def start(self, process, current, args, props, envs, watchDog):
2241        # Get the process controller
2242        processController = self.getController(current)
2243
2244        # TODO: support envs?
2245
2246        exe = process.getExe(current)
2247        args = ["--{0}={1}".format(k, val(v, quoteValue=False)) for k,v in props.items()] + [val(a) for a in args]
2248        if current.driver.debug:
2249            current.writeln("(executing `{0}/{1}' on `{2}' args = {3})".format(current.testsuite, exe, self, args))
2250        prx = processController.start(str(current.testsuite), exe, args)
2251
2252        # Create bi-dir proxy in case we're talking to a bi-bir process controller.
2253        if self.adapter:
2254            prx = processController.ice_getConnection().createProxy(prx.ice_getIdentity())
2255        import Test
2256        return RemoteProcessController.RemoteProcess(exe, Test.Common.ProcessPrx.uncheckedCast(prx))
2257
2258    def destroy(self, driver):
2259        if driver.controllerApp:
2260            for ident in self.controllerApps:
2261                self.stopControllerApp(ident)
2262            self.controllerApps = []
2263        if self.adapter:
2264            self.adapter.destroy()
2265
2266class AndroidProcessController(RemoteProcessController):
2267
2268    def __init__(self, current):
2269        endpoint = None
2270        if current.config.xamarin or current.config.device:
2271            endpoint = "tcp -h 0.0.0.0 -p 15001"
2272        elif current.config.avd or not current.config.device:
2273            endpoint = "tcp -h 127.0.0.1 -p 15001"
2274        RemoteProcessController.__init__(self, current, endpoint)
2275        self.device = current.config.device
2276        self.avd = current.config.avd
2277        self.emulator = None # Keep a reference to the android emulator process
2278
2279    def __str__(self):
2280        return "Android"
2281
2282    def getControllerIdentity(self, current):
2283        if isinstance(current.testcase.getMapping(), CSharpMapping):
2284            return "AndroidXamarin/ProcessController"
2285        elif isinstance(current.testcase.getMapping(), JavaCompatMapping):
2286            return "AndroidCompat/ProcessController"
2287        else:
2288            return "Android/ProcessController"
2289
2290    def adb(self):
2291        return "adb -d" if self.device == "usb" else "adb"
2292
2293    def startEmulator(self, avd):
2294        #
2295        # First check if the AVD image is available
2296        #
2297        print("starting the emulator... ")
2298        out = run("emulator -list-avds")
2299        if avd not in out:
2300            raise RuntimeError("couldn't find AVD `{}'".format(avd))
2301
2302        #
2303        # Find and unused port to run android emulator, between 5554 and 5584
2304        #
2305        port = -1
2306        out = run("adb devices -l")
2307        for p in range(5554, 5586, 2):
2308            if not "emulator-{}".format(p) in out:
2309                port = p
2310
2311        if port == -1:
2312            raise RuntimeError("cannot find free port in range 5554-5584, to run android emulator")
2313
2314        self.device = "emulator-{}".format(port)
2315        cmd = "emulator -avd {0} -port {1} -noaudio -partition-size 768 -no-snapshot".format(avd, port)
2316        self.emulator = subprocess.Popen(cmd, shell=True)
2317
2318        if self.emulator.poll():
2319            raise RuntimeError("failed to start the Android emulator `{}' on port {}".format(avd, port))
2320
2321        self.avd = avd
2322
2323        #
2324        # Wait for the device to be ready
2325        #
2326        t = time.time()
2327        while True:
2328            try:
2329                lines = run("{} shell getprop sys.boot_completed".format(self.adb()))
2330                if len(lines) > 0 and lines[0].strip() == "1":
2331                    break
2332            except RuntimeError:
2333                pass # expected if device is offline
2334            #
2335            # If the emulator doesn't complete boot in 300 seconds give up
2336            #
2337            if (time.time() - t) > 300:
2338                raise RuntimeError("couldn't start the Android emulator `{}'".format(avd))
2339            time.sleep(2)
2340
2341    def startControllerApp(self, current, ident):
2342
2343        # Stop previous controller app before starting new one
2344        for ident in self.controllerApps:
2345            self.stopControllerApp(ident)
2346
2347        if current.config.avd:
2348            self.startEmulator(current.config.avd)
2349        elif not current.config.device:
2350            # Create Android Virtual Device
2351            sdk = current.testcase.getMapping().getSDKPackage()
2352            print("creating virtual device ({0})... ".format(sdk))
2353            try:
2354                run("avdmanager delete avd -n IceTests") # Delete the created device
2355            except:
2356                pass
2357            # The SDK is downloaded by test VMs instead of here.
2358            #run("sdkmanager \"{0}\"".format(sdk), stdout=True, stdin="yes", stdinRepeat=True) # yes to accept licenses
2359            run("avdmanager create avd -k \"{0}\" -d \"Nexus 6\" -n IceTests".format(sdk))
2360            self.startEmulator("IceTests")
2361        elif current.config.device != "usb":
2362            run("adb connect {}".format(current.config.device))
2363
2364        run("{} install -t -r {}".format(self.adb(), current.testcase.getMapping().getApk(current)))
2365        run("{} shell am start -n \"{}\" -a android.intent.action.MAIN -c android.intent.category.LAUNCHER".format(
2366            self.adb(), current.testcase.getMapping().getActivityName()))
2367
2368    def stopControllerApp(self, ident):
2369        try:
2370            run("{} shell pm uninstall com.zeroc.testcontroller".format(self.adb()))
2371        except:
2372            pass
2373
2374        if self.avd:
2375            try:
2376                run("{} emu kill".format(self.adb()))
2377            except:
2378                pass
2379
2380            if self.avd == "IceTests":
2381                try:
2382                    run("avdmanager delete avd -n IceTests") # Delete the created device
2383                except:
2384                    pass
2385
2386        #
2387        # Wait for the emulator to shutdown
2388        #
2389        if self.emulator:
2390            sys.stdout.write("Waiting for the emulator to shutdown..")
2391            sys.stdout.flush()
2392            while True:
2393                if self.emulator.poll() != None:
2394                    print(" ok")
2395                    break
2396                sys.stdout.write(".")
2397                sys.stdout.flush()
2398                time.sleep(0.5)
2399
2400        try:
2401            run("adb kill-server")
2402        except:
2403            pass
2404
2405class iOSSimulatorProcessController(RemoteProcessController):
2406
2407    device = "iOSSimulatorProcessController"
2408    deviceID = "com.apple.CoreSimulator.SimDeviceType.iPhone-X"
2409
2410    def __init__(self, current):
2411        RemoteProcessController.__init__(self, current, "tcp -h 0.0.0.0 -p 15001" if current.config.xamarin else None)
2412        self.simulatorID = None
2413        self.runtimeID = None
2414        # Pick the last iOS simulator runtime ID in the list of iOS simulators (assumed to be the latest).
2415        try:
2416            for r in run("xcrun simctl list runtimes").split('\n'):
2417                m = re.search("iOS .* \\(.*\\) - (.*)", r)
2418                if m:
2419                    self.runtimeID = m.group(1)
2420        except:
2421            pass
2422        if not self.runtimeID:
2423            self.runtimeID = "com.apple.CoreSimulator.SimRuntime.iOS-12-0" # Default value
2424
2425    def __str__(self):
2426        return "iOS Simulator"
2427
2428    def getControllerIdentity(self, current):
2429        return current.testcase.getMapping().getIOSControllerIdentity(current)
2430
2431    def startControllerApp(self, current, ident):
2432        mapping = current.testcase.getMapping()
2433        appFullPath = mapping.getIOSAppFullPath(current)
2434
2435        sys.stdout.write("launching simulator... ")
2436        sys.stdout.flush()
2437        try:
2438            run("xcrun simctl boot \"{0}\"".format(self.device))
2439        except Exception as ex:
2440            if str(ex).find("Booted") >= 0:
2441                pass
2442            elif str(ex).find("Invalid device") >= 0:
2443                # Create the simulator device if it doesn't exist
2444                self.simulatorID = run("xcrun simctl create \"{0}\" {1} {2}".format(self.device, self.deviceID, self.runtimeID))
2445                run("xcrun simctl boot \"{0}\"".format(self.device))
2446            else:
2447                raise
2448        print("ok")
2449
2450        sys.stdout.write("launching {0}... ".format(os.path.basename(appFullPath)))
2451        sys.stdout.flush()
2452
2453        if not os.path.exists(appFullPath):
2454            raise RuntimeError("couldn't find iOS simulator controller application, did you build it?")
2455        run("xcrun simctl install \"{0}\" \"{1}\"".format(self.device, appFullPath))
2456        run("xcrun simctl launch \"{0}\" {1}".format(self.device, ident.name))
2457        print("ok")
2458
2459    def restartControllerApp(self, current, ident):
2460        try:
2461            run("xcrun simctl terminate \"{0}\" {1}".format(self.device, ident.name))
2462        except:
2463            pass
2464        run("xcrun simctl launch \"{0}\" {1}".format(self.device, ident.name))
2465
2466    def stopControllerApp(self, ident):
2467        try:
2468            run("xcrun simctl uninstall \"{0}\" {1}".format(self.device, ident.name))
2469        except:
2470            pass
2471
2472    def destroy(self, driver):
2473        RemoteProcessController.destroy(self, driver)
2474
2475        sys.stdout.write("shutting down simulator... ")
2476        sys.stdout.flush()
2477        try:
2478            run("xcrun simctl shutdown \"{0}\"".format(self.simulatorID))
2479        except:
2480            pass
2481        print("ok")
2482
2483        if self.simulatorID:
2484            sys.stdout.write("destroying simulator... ")
2485            sys.stdout.flush()
2486            try:
2487                run("xcrun simctl delete \"{0}\"".format(self.simulatorID))
2488            except:
2489                pass
2490            print("ok")
2491
2492class iOSDeviceProcessController(RemoteProcessController):
2493
2494    appPath = "cpp/test/ios/controller/build"
2495
2496    def __init__(self, current):
2497        RemoteProcessController.__init__(self, current, "tcp -h 0.0.0.0 -p 15001" if current.config.xamarin else None)
2498
2499    def __str__(self):
2500        return "iOS Device"
2501
2502    def getControllerIdentity(self, current):
2503        return current.testcase.getMapping().getIOSControllerIdentity(current)
2504
2505    def startControllerApp(self, current, ident):
2506        # TODO: use ios-deploy to deploy and run the application on an attached device?
2507        pass
2508
2509    def stopControllerApp(self, ident):
2510        pass
2511
2512class UWPProcessController(RemoteProcessController):
2513
2514    def __init__(self, current):
2515        RemoteProcessController.__init__(self, current, "tcp -h 127.0.0.1 -p 15001")
2516        self.name = current.testcase.getMapping().getUWPPackageName()
2517        self.appUserModelId = current.testcase.getMapping().getUWPUserModelId()
2518
2519    def __str__(self):
2520        return "UWP"
2521
2522    def getControllerIdentity(self, current):
2523        if isinstance(current.testcase.getMapping(), CSharpMapping):
2524            return "UWPXamarin/ProcessController"
2525        else:
2526            return "UWP/ProcessController"
2527
2528    def startControllerApp(self, current, ident):
2529        platform = current.config.buildPlatform
2530        config = current.config.buildConfig
2531        arch = "X86" if platform == "Win32" else "X64"
2532
2533        self.packageFullName = current.testcase.getMapping().getUWPPackageFullName(platform)
2534        packageFullPath = current.testcase.getMapping().getUWPPackageFullPath(platform, config)
2535
2536        #
2537        # If the application is already installed remove it, this will also take care
2538        # of closing it.
2539        #
2540        if self.name in run("powershell Get-AppxPackage -Name {0}".format(self.name)):
2541            run("powershell Remove-AppxPackage {0}".format(self.packageFullName))
2542
2543        #
2544        # Remove any previous package we have extracted to ensure we use a
2545        # fresh build
2546        #
2547        layout = os.path.join(current.testcase.getMapping().getPath(), "AppX")
2548        if os.path.exists(layout):
2549            shutil.rmtree(layout)
2550        os.makedirs(layout)
2551
2552        print("Unpacking package: {0} to {1}....".format(os.path.basename(packageFullPath), layout))
2553        run("MakeAppx.exe unpack /p \"{0}\" /d \"{1}\" /l".format(packageFullPath, layout))
2554
2555        print("Registering application to run from layout...")
2556
2557        for root, dirs, files in os.walk(os.path.join(os.path.dirname(packageFullPath), "Dependencies", arch)):
2558            for f in files:
2559                self.installPackage(os.path.join(root, f), arch)
2560
2561        run("powershell Add-AppxPackage -Register \"{0}/AppxManifest.xml\" -ForceApplicationShutdown".format(layout))
2562        run("CheckNetIsolation LoopbackExempt -a -n={0}".format(self.appUserModelId))
2563
2564        #
2565        # microsoft.windows.softwarelogo.appxlauncher.exe returns the PID as return code
2566        # and 0 on case of failures. We pass err=True to run to handle this.
2567        #
2568        print("starting UWP controller app...")
2569        run('"{0}" {1}!App'.format(
2570            "C:/Program Files (x86)/Windows Kits/10/App Certification Kit/microsoft.windows.softwarelogo.appxlauncher.exe",
2571            self.appUserModelId), err=True)
2572
2573    def stopControllerApp(self, ident):
2574        try:
2575            run("powershell Remove-AppxPackage {0}".format(self.packageFullName))
2576            run("CheckNetIsolation LoopbackExempt -c -n={0}".format(self.appUserModelId))
2577        except:
2578            pass
2579
2580    def getPackageVersion(self, package):
2581        import zipfile
2582        import xml.etree.ElementTree as ElementTree
2583        with zipfile.ZipFile(package) as zipfile:
2584            with zipfile.open('AppxManifest.xml') as file:
2585                xml = ElementTree.fromstring(file.read())
2586                identity = xml.find("{http://schemas.microsoft.com/appx/manifest/foundation/windows10}Identity")
2587                return tuple(map(int, identity.attrib['Version'].split(".")))
2588
2589    def installPackage(self, package, arch):
2590        packages = {
2591            "Microsoft.VCLibs.x64.14.00.appx" : "Microsoft.VCLibs.140.00",
2592            "Microsoft.VCLibs.x86.14.00.appx" : "Microsoft.VCLibs.140.00",
2593            "Microsoft.VCLibs.x64.Debug.14.00.appx" : "Microsoft.VCLibs.140.00.Debug",
2594            "Microsoft.VCLibs.x86.Debug.14.00.appx" : "Microsoft.VCLibs.140.00.Debug",
2595            "Microsoft.NET.CoreRuntime.2.1.appx" : "Microsoft.NET.CoreRuntime.2.1"
2596        }
2597        packageName = packages[os.path.basename(package)]
2598        output = run("powershell Get-AppxPackage -Name {0}".format(packageName))
2599        m = re.search("Architecture.*: {0}\\r\\nResourceId.*:.*\\r\\nVersion.*: (.*)\\r\\n".format(arch), output)
2600        installedVersion = tuple(map(int, m.group(1).split("."))) if m else (0, 0, 0, 0)
2601        version = self.getPackageVersion(package)
2602        if installedVersion <= version:
2603            run("powershell Add-AppxPackage -Path \"{0}\" -ForceApplicationShutdown".format(package))
2604
2605class BrowserProcessController(RemoteProcessController):
2606
2607    def __init__(self, current):
2608        self.host = current.driver.host or "127.0.0.1"
2609        RemoteProcessController.__init__(self, current, "ws -h {0} -p 15002:wss -h {0} -p 15003".format(self.host))
2610        self.httpServer = None
2611        self.url = None
2612        self.driver = None
2613        try:
2614            cmd = "node -e \"require('./bin/HttpServer')()\"";
2615            cwd = current.testcase.getMapping().getPath()
2616            self.httpServer = Expect.Expect(cmd, cwd=cwd)
2617            self.httpServer.expect("listening on ports")
2618
2619            if current.config.browser.startswith("Remote:"):
2620                from selenium import webdriver
2621                from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
2622                (driver, capabilities, port) = current.config.browser.split(":")
2623                self.driver = webdriver.Remote("http://localhost:{0}".format(port),
2624                                               desired_capabilities=getattr(DesiredCapabilities, capabilities),
2625                                               keep_alive=True)
2626            elif current.config.browser != "Manual":
2627                from selenium import webdriver
2628                if current.config.browser.find(":") > 0:
2629                    (driver, port) = current.config.browser.split(":")
2630                else:
2631                    (driver, port) = (current.config.browser, 0)
2632
2633                if not hasattr(webdriver, driver):
2634                    raise RuntimeError("unknown browser `{0}'".format(driver))
2635
2636                if driver == "Firefox":
2637                    if isinstance(platform, Linux) and os.environ.get("DISPLAY", "") != ":1" and os.environ.get("USER", "") == "ubuntu":
2638                        current.writeln("error: DISPLAY is unset, setting it to :1")
2639                        os.environ["DISPLAY"] = ":1"
2640
2641                    #
2642                    # We need to specify a profile for Firefox. This profile only provides the cert8.db which
2643                    # contains our Test CA cert. It should be possible to avoid this by setting the webdriver
2644                    # acceptInsecureCerts capability but it's only supported by latest Firefox releases.
2645                    #
2646                    profilepath = os.path.join(current.driver.getComponent().getSourceDir(), "scripts", "selenium", "firefox")
2647                    profile = webdriver.FirefoxProfile(profilepath)
2648                    self.driver = webdriver.Firefox(firefox_profile=profile)
2649                elif driver == "Ie":
2650                    # Make sure we start with a clean cache
2651                    capabilities = webdriver.DesiredCapabilities.INTERNETEXPLORER.copy()
2652                    capabilities["ie.ensureCleanSession"] = True
2653                    self.driver = webdriver.Ie(capabilities=capabilities)
2654                elif driver == "Safari" and port > 0:
2655                    self.driver = webdriver.Safari(port=int(port), reuse_service=True)
2656                else:
2657                    self.driver = getattr(webdriver, driver)()
2658        except:
2659            self.destroy(current.driver)
2660            raise
2661
2662    def __str__(self):
2663        return str(self.driver) if self.driver else "Manual"
2664
2665    def getControllerIdentity(self, current):
2666        #
2667        # Load the controller page each time we're asked for the controller and if we're running
2668        # another testcase, the controller page will connect to the process controller registry
2669        # to register itself with this script.
2670        #
2671        testsuite = ("es5/" if current.config.es5 else "") + str(current.testsuite)
2672        if current.config.protocol == "wss":
2673            protocol = "https"
2674            port = "9090"
2675            cport = "15003"
2676        else:
2677            protocol = "http"
2678            port = "8080"
2679            cport = "15002"
2680        url = "{0}://{5}:{1}/test/{2}/controller.html?port={3}&worker={4}".format(protocol,
2681                                                                                  port,
2682                                                                                  testsuite,
2683                                                                                  cport,
2684                                                                                  current.config.worker,
2685                                                                                  self.host)
2686        if url != self.url:
2687            self.url = url
2688            if self.driver:
2689                self.driver.get(url)
2690            else:
2691                # If no process controller is registered, we request the user to load the controller
2692                # page in the browser. Once loaded, the controller will register and we'll redirect to
2693                # the correct testsuite page.
2694                ident = current.driver.getCommunicator().stringToIdentity("Browser/ProcessController")
2695                prx = None
2696                with self.cond:
2697                    while True:
2698                        if ident in self.processControllerProxies:
2699                            prx = self.processControllerProxies[ident]
2700                            break
2701                        print("Please load http://{0}:8080/{1}".format(self.host,
2702                                                                       "es5/start" if current.config.es5 else "start"))
2703                        self.cond.wait(5)
2704
2705                try:
2706                    import Test
2707                    Test.Common.BrowserProcessControllerPrx.uncheckedCast(prx).redirect(url)
2708                except:
2709                    pass
2710                finally:
2711                    self.clearProcessController(prx, prx.ice_getCachedConnection())
2712
2713        return "Browser/ProcessController"
2714
2715    def getController(self, current):
2716        try:
2717            return RemoteProcessController.getController(self, current)
2718        except RuntimeError as ex:
2719            if self.driver:
2720                # Print out the client & server console element values
2721                for element in ["clientConsole", "serverConsole"]:
2722                    try:
2723                        console = self.driver.find_element_by_id(element).get_attribute('value')
2724                        if len(console) > 0:
2725                            print("controller {0} value:\n{1}".format(element, console))
2726                    except Exception as exc:
2727                        print("couldn't get controller {0} value:\n{1}".format(element, exc))
2728                        pass
2729                # Print out the browser log
2730                try:
2731                    print("browser log:\n{0}".format(self.driver.get_log("browser")))
2732                except:
2733                    pass # Not all browsers support retrieving the browser console log
2734            raise ex
2735
2736    def destroy(self, driver):
2737        if self.httpServer:
2738            self.httpServer.terminate()
2739            self.httpServer = None
2740
2741        try:
2742            self.driver.quit()
2743        except:
2744            pass
2745
2746class Driver:
2747
2748    class Current:
2749
2750        def __init__(self, driver, testsuite, result):
2751            self.driver = driver
2752            self.testsuite = testsuite
2753            self.config = driver.configs[testsuite.getMapping()]
2754            self.desc = ""
2755            self.result = result
2756            self.host = None
2757            self.testcase = None
2758            self.testcases = []
2759            self.processes = {}
2760            self.dirs = []
2761            self.files = []
2762
2763        def getTestEndpoint(self, *args, **kargs):
2764            return self.driver.getTestEndpoint(*args, **kargs)
2765
2766        def getBuildDir(self, name):
2767            return self.testcase.getMapping().getBuildDir(name, self)
2768
2769        def getPluginEntryPoint(self, plugin, process):
2770            return self.testcase.getMapping().getPluginEntryPoint(plugin, process, self)
2771
2772        def write(self, *args, **kargs):
2773            self.result.write(*args, **kargs)
2774
2775        def writeln(self, *args, **kargs):
2776            self.result.writeln(*args, **kargs)
2777
2778        def push(self, testcase, host=None):
2779            if not testcase.mapping:
2780                assert(not testcase.parent and not testcase.testsuite)
2781                testcase.mapping = self.testcase.getMapping()
2782                testcase.testsuite = self.testcase.getTestSuite()
2783                testcase.parent = self.testcase
2784            self.testcases.append((self.testcase, self.config, self.host))
2785            self.testcase = testcase
2786            self.config = self.driver.configs[self.testcase.getMapping()].cloneAndOverrideWith(self)
2787            self.host = host
2788
2789        def pop(self):
2790            assert(self.testcase)
2791            testcase = self.testcase
2792            (self.testcase, self.config, self.host) = self.testcases.pop()
2793            if testcase.parent and self.testcase != testcase:
2794                testcase.mapping = None
2795                testcase.testsuite = None
2796                testcase.parent = None
2797
2798        def createFile(self, path, lines, encoding=None):
2799            path = os.path.join(self.testsuite.getPath(), path.decode("utf-8") if isPython2 else path)
2800            with open(path, "w", encoding=encoding) if not isPython2 and encoding else open(path, "w") as file:
2801                for l in lines:
2802                    file.write("%s\n" % l)
2803            self.files.append(path)
2804
2805        def mkdirs(self, dirs):
2806            for d in dirs if isinstance(dirs, list) else [dirs]:
2807                d = os.path.join(self.testsuite.getPath(), d)
2808                self.dirs.append(d)
2809                if not os.path.exists(d):
2810                    os.makedirs(d)
2811
2812        def destroy(self):
2813            for d in self.dirs:
2814                if os.path.exists(d): shutil.rmtree(d)
2815            for f in self.files:
2816                if os.path.exists(f): os.unlink(f)
2817
2818    drivers = {}
2819    driver = "local"
2820
2821    @classmethod
2822    def add(self, name, driver, default=False):
2823        if default:
2824            Driver.driver = name
2825        self.driver = name
2826        self.drivers[name] = driver
2827
2828    @classmethod
2829    def getAll(self):
2830        return list(self.drivers.values())
2831
2832    @classmethod
2833    def create(self, options, component):
2834        parseOptions(self, options)
2835        driver = self.drivers.get(self.driver)
2836        if not driver:
2837            raise RuntimeError("unknown driver `{0}'".format(self.driver))
2838        return driver(options, component)
2839
2840    @classmethod
2841    def getSupportedArgs(self):
2842        return ("dlrR", ["debug", "driver=", "filter=", "rfilter=", "host=", "host-ipv6=", "host-bt=", "interface=",
2843                         "controller-app", "valgrind", "languages=", "rlanguages="])
2844
2845    @classmethod
2846    def usage(self):
2847        pass
2848
2849    @classmethod
2850    def commonUsage(self):
2851        print("")
2852        print("Driver options:")
2853        print("-d | --debug          Verbose information.")
2854        print("--driver=<driver>     Use the given driver (local, client, server or remote).")
2855        print("--filter=<regex>      Run all the tests that match the given regex.")
2856        print("--rfilter=<regex>     Run all the tests that do not match the given regex.")
2857        print("--languages=l1,l2,... List of comma-separated language mappings to test.")
2858        print("--rlanguages=l1,l2,.. List of comma-separated language mappings to not test.")
2859        print("--host=<addr>         The IPv4 address to use for Ice.Default.Host.")
2860        print("--host-ipv6=<addr>    The IPv6 address to use for Ice.Default.Host.")
2861        print("--host-bt=<addr>      The Bluetooth address to use for Ice.Default.Host.")
2862        print("--interface=<IP>      The multicast interface to use to discover controllers.")
2863        print("--controller-app      Start the process controller application.")
2864        print("--valgrind            Start executables with valgrind.")
2865
2866    def __init__(self, options, component):
2867        self.component = component
2868        self.debug = False
2869        self.filters = []
2870        self.rfilters = []
2871        self.host = ""
2872        self.hostIPv6 = ""
2873        self.hostBT = ""
2874        self.controllerApp = False
2875        self.valgrind = False
2876        self.languages = ",".join(os.environ.get("LANGUAGES", "").split(" "))
2877        self.languages = [self.languages] if self.languages else []
2878        self.rlanguages = []
2879        self.failures = []
2880
2881        parseOptions(self, options, { "d": "debug",
2882                                      "r" : "filters",
2883                                      "R" : "rfilters",
2884                                      "filter" : "filters",
2885                                      "rfilter" : "rfilters",
2886                                      "host-ipv6" : "hostIPv6",
2887                                      "host-bt" : "hostBT",
2888                                      "controller-app" : "controllerApp"})
2889
2890        if self.languages:
2891            self.languages = [i for sublist in [l.split(",") for l in self.languages] for i in sublist]
2892        if self.rlanguages:
2893            self.rlanguages = [i for sublist in [l.split(",") for l in self.rlanguages] for i in sublist]
2894
2895        (self.filters, self.rfilters) = ([re.compile(a) for a in self.filters], [re.compile(a) for a in self.rfilters])
2896
2897        self.communicator = None
2898        self.interface = ""
2899        self.processControllers = {}
2900
2901    def setConfigs(self, configs):
2902        self.configs = configs
2903
2904    def getFilters(self, mapping, config):
2905        # Return the driver and component filters
2906        (filters, rfilters) = self.component.getFilters(mapping, config)
2907        (filters, rfilters) = ([re.compile(a) for a in filters], [re.compile(a) for a in rfilters])
2908        return (self.filters + filters, self.rfilters + rfilters)
2909
2910    def getHost(self, protocol, ipv6):
2911        if protocol == "bt":
2912            if not self.hostBT:
2913                raise RuntimeError("no Bluetooth address set with --host-bt")
2914            return self.hostBT
2915        elif ipv6:
2916            return self.hostIPv6 or "::1"
2917        else:
2918            return self.host or "127.0.0.1"
2919
2920    def getComponent(self):
2921        return self.component
2922
2923    def isWorkerThread(self):
2924        return False
2925
2926    def getTestEndpoint(self, portnum, protocol="default"):
2927        return "{0} -p {1}".format(protocol, self.getTestPort(portnum))
2928
2929    def getTestPort(self, portnum):
2930        return 12010 + portnum
2931
2932    def getArgs(self, process, current):
2933        ### Return driver specific arguments
2934        return []
2935
2936    def getProps(self, process, current):
2937        props = {}
2938        if isinstance(process, IceProcess):
2939            if not self.host:
2940                props["Ice.Default.Host"] = "0:0:0:0:0:0:0:1" if current.config.ipv6 else "127.0.0.1"
2941            else:
2942                props["Ice.Default.Host"] = self.host
2943        return props
2944
2945    def getMappings(self):
2946        ### Return additional mappings to load required by the driver
2947        return []
2948
2949    def matchLanguage(self, language):
2950        if self.languages and language not in self.languages:
2951            return False
2952        if self.rlanguages and language in self.rlanguages:
2953            return False
2954        return True
2955
2956    def getCommunicator(self):
2957        self.initCommunicator()
2958        return self.communicator
2959
2960    def initCommunicator(self):
2961        if self.communicator:
2962            return
2963
2964        try:
2965            import Ice
2966        except ImportError:
2967            # Try to add the local Python build to the sys.path
2968            try:
2969                pythonMapping = Mapping.getByName("python")
2970                if pythonMapping:
2971                    for p in pythonMapping.getPythonDirs(pythonMapping.getPath(), self.configs[pythonMapping]):
2972                        sys.path.append(p)
2973            except RuntimeError:
2974                print("couldn't find IcePy, running these tests require it to be installed or built")
2975
2976        import Ice
2977        Ice.loadSlice(os.path.join(self.component.getSourceDir(), "scripts", "Controller.ice"))
2978
2979        initData = Ice.InitializationData()
2980        initData.properties = Ice.createProperties()
2981
2982        # Load IceSSL, this is useful to talk with WSS for JavaScript
2983        initData.properties.setProperty("Ice.Plugin.IceSSL", "IceSSL:createIceSSL")
2984        initData.properties.setProperty("IceSSL.DefaultDir", os.path.join(self.component.getSourceDir(), "certs"))
2985        initData.properties.setProperty("IceSSL.CertFile", "server.p12")
2986        initData.properties.setProperty("IceSSL.Password", "password")
2987        initData.properties.setProperty("IceSSL.Keychain", "test.keychain")
2988        initData.properties.setProperty("IceSSL.KeychainPassword", "password")
2989        initData.properties.setProperty("IceSSL.VerifyPeer", "0");
2990
2991        initData.properties.setProperty("Ice.Plugin.IceDiscovery", "IceDiscovery:createIceDiscovery")
2992        initData.properties.setProperty("IceDiscovery.DomainId", "TestController")
2993        initData.properties.setProperty("IceDiscovery.Interface", self.interface)
2994        initData.properties.setProperty("Ice.Default.Host", self.interface)
2995        initData.properties.setProperty("Ice.ThreadPool.Server.Size", "10")
2996        # initData.properties.setProperty("Ice.Trace.Protocol", "1")
2997        # initData.properties.setProperty("Ice.Trace.Network", "3")
2998        # initData.properties.setProperty("Ice.StdErr", "allTests.log")
2999        initData.properties.setProperty("Ice.Override.Timeout", "10000")
3000        initData.properties.setProperty("Ice.Override.ConnectTimeout", "1000")
3001        self.communicator = Ice.initialize(initData)
3002
3003    def getProcessController(self, current, process=None):
3004        processController = None
3005        if current.config.buildPlatform == "iphonesimulator":
3006            processController = iOSSimulatorProcessController
3007        elif current.config.buildPlatform == "iphoneos":
3008            processController = iOSDeviceProcessController
3009        elif current.config.uwp:
3010            # No SSL server-side support in UWP.
3011            if current.config.protocol in ["ssl", "wss"] and not isinstance(process, Client):
3012                processController = LocalProcessController
3013            else:
3014                processController = UWPProcessController
3015        elif process and current.config.browser and isinstance(process.getMapping(current), JavaScriptMixin):
3016            processController = BrowserProcessController
3017        elif process and current.config.android:
3018            processController = AndroidProcessController
3019        else:
3020            processController = LocalProcessController
3021
3022        if processController in self.processControllers:
3023            return self.processControllers[processController]
3024
3025        # Instantiate the controller
3026        self.processControllers[processController] = processController(current)
3027        return self.processControllers[processController]
3028
3029    def getProcessProps(self, current, ready, readyCount):
3030        props = {}
3031        if ready or readyCount > 0:
3032            if current.config.buildPlatform not in ["iphonesimulator", "iphoneos"]:
3033                props["Ice.PrintAdapterReady"] = 1
3034        return props
3035
3036    def destroy(self):
3037        for controller in self.processControllers.values():
3038            controller.destroy(self)
3039
3040        if self.communicator:
3041            self.communicator.destroy()
3042
3043class CppMapping(Mapping):
3044
3045    class Config(Mapping.Config):
3046
3047        @classmethod
3048        def getSupportedArgs(self):
3049            return ("", ["cpp-config=", "cpp-platform=", "cpp-path=", "uwp", "openssl"])
3050
3051        @classmethod
3052        def usage(self):
3053            print("")
3054            print("C++ Mapping options:")
3055            print("--cpp-path=<path>         Path of alternate source tree for the C++ mapping.")
3056            print("--cpp-config=<config>     C++ build configuration for native executables (overrides --config).")
3057            print("--cpp-platform=<platform> C++ build platform for native executables (overrides --platform).")
3058            print("--uwp                     Run UWP (Universal Windows Platform).")
3059            print("--openssl                 Run SSL tests with OpenSSL instead of the default platform SSL engine.")
3060
3061        def __init__(self, options=[]):
3062            Mapping.Config.__init__(self, options)
3063
3064            # Derive from the build config the cpp11 option. This is used by canRun to allow filtering
3065            # tests on the cpp11 value in the testcase options specification
3066            self.cpp11 = self.buildConfig.lower().find("cpp11") >= 0
3067
3068            parseOptions(self, options, { "cpp-config" : "buildConfig",
3069                                          "cpp-platform" : "buildPlatform",
3070                                          "cpp-path" : "pathOverride" })
3071
3072            if self.pathOverride:
3073                self.pathOverride = os.path.abspath(self.pathOverride)
3074
3075    def getOptions(self, current):
3076        return { "compress" : [False] } if current.config.uwp else {}
3077
3078    def getProps(self, process, current):
3079        props = Mapping.getProps(self, process, current)
3080        if isinstance(process, IceProcess):
3081            props["Ice.NullHandleAbort"] = True
3082            props["Ice.PrintStackTraces"] = "1"
3083        return props
3084
3085    def getSSLProps(self, process, current):
3086        props = Mapping.getSSLProps(self, process, current)
3087        server = isinstance(process, Server)
3088        uwp = current.config.uwp
3089
3090        props.update({
3091            "IceSSL.CAs": "cacert.pem",
3092            "IceSSL.CertFile": "server.p12" if server else "ms-appx:///client.p12" if uwp else "client.p12"
3093        })
3094        if isinstance(platform, Darwin):
3095            props.update({
3096                "IceSSL.KeychainPassword" : "password",
3097                "IceSSL.Keychain": "server.keychain" if server else "client.keychain"
3098             })
3099        return props
3100
3101    def getPluginEntryPoint(self, plugin, process, current):
3102        return {
3103            "IceSSL" : "IceSSLOpenSSL:createIceSSLOpenSSL" if current.config.openssl else "IceSSL:createIceSSL",
3104            "IceBT" : "IceBT:createIceBT",
3105            "IceDiscovery" : "IceDiscovery:createIceDiscovery",
3106            "IceLocatorDiscovery" : "IceLocatorDiscovery:createIceLocatorDiscovery"
3107        }[plugin]
3108
3109    def getEnv(self, process, current):
3110        #
3111        # On Windows, add the testcommon directories to the PATH
3112        #
3113        libPaths = []
3114        if isinstance(platform, Windows):
3115            testcommon = os.path.join(self.path, "test", "Common")
3116            if os.path.exists(testcommon):
3117                libPaths.append(os.path.join(testcommon, self.getBuildDir("testcommon", current)))
3118
3119        #
3120        # On most platforms, we also need to add the library directory to the library path environment variable.
3121        #
3122        if not isinstance(platform, Darwin):
3123            libPaths.append(self.component.getLibDir(process, self, current))
3124
3125        #
3126        # Add the test suite library directories to the platform library path environment variable.
3127        #
3128        if current.testcase:
3129            for d in set([current.getBuildDir(d) for d in current.testcase.getTestSuite().getLibDirs()]):
3130                libPaths.append(d)
3131
3132        env = {}
3133        if len(libPaths) > 0:
3134            env[platform.getLdPathEnvName()] = os.pathsep.join(libPaths)
3135        return env
3136
3137    def _getDefaultSource(self, processType):
3138        return {
3139            "client" : "Client.cpp",
3140            "server" : "Server.cpp",
3141            "serveramd" : "ServerAMD.cpp",
3142            "collocated" : "Collocated.cpp",
3143            "subscriber" : "Subscriber.cpp",
3144            "publisher" : "Publisher.cpp",
3145        }[processType]
3146
3147    def _getDefaultExe(self, processType):
3148        return Mapping._getDefaultExe(self, processType).lower()
3149
3150    def getUWPPackageName(self):
3151        return "ice-uwp-controller.cpp"
3152
3153    def getUWPUserModelId(self):
3154        return "ice-uwp-controller.cpp_3qjctahehqazm"
3155
3156    def getUWPPackageFullName(self, platform):
3157        return "{0}_1.0.0.0_{1}__3qjctahehqazm".format(self.getUWPPackageName(),
3158                                                       "x86" if platform == "Win32" else platform)
3159
3160    def getUWPPackageFullPath(self, platform, config):
3161        prefix = "controller_1.0.0.0_{0}{1}".format(platform, "_{0}".format(config) if config == "Debug" else "")
3162        return os.path.join(self.component.getSourceDir(), "cpp", "msbuild", "AppPackages", "controller",
3163                            "{0}_Test".format(prefix), "{0}.appx".format(prefix))
3164
3165    def getIOSControllerIdentity(self, current):
3166        category = "iPhoneSimulator" if current.config.buildPlatform == "iphonesimulator" else "iPhoneOS"
3167        mapping = "Cpp11" if current.config.cpp11 else "Cpp98"
3168        return "{0}/com.zeroc.{1}-Test-Controller".format(category, mapping)
3169
3170    def getIOSAppFullPath(self, current):
3171        appName = "C++11 Test Controller.app" if current.config.cpp11 else "C++98 Test Controller.app"
3172        path = os.path.join(self.component.getTestDir(self), "ios", "controller")
3173        path = os.path.join(path, "build-{0}-{1}".format(current.config.buildPlatform, current.config.buildConfig))
3174        build = "Debug" if os.path.exists(os.path.join(path, "Debug-{0}".format(current.config.buildPlatform))) else "Release"
3175        return os.path.join(path, "{0}-{1}".format(build, current.config.buildPlatform), appName)
3176
3177class JavaMapping(Mapping):
3178
3179    class Config(Mapping.Config):
3180
3181        @classmethod
3182        def getSupportedArgs(self):
3183            return ("", ["device=", "avd=", "android"])
3184
3185        @classmethod
3186        def usage(self):
3187            print("")
3188            print("Java Mapping options:")
3189            print("--android                 Run the Android tests.")
3190            print("--device=<device-id>      ID of the Android emulator or device used to run the tests.")
3191            print("--avd=<name>              Start specific Android Virtual Device.")
3192
3193    def getCommandLine(self, current, process, exe, args):
3194        javaHome = os.getenv("JAVA_HOME", "")
3195        java = os.path.join(javaHome, "bin", "java") if javaHome else "java"
3196        javaArgs = self.getJavaArgs(process, current)
3197        if process.isFromBinDir():
3198            if javaArgs:
3199                return "{0} -ea {1} {2} {3}".format(java, " ".join(javaArgs), exe, args)
3200            else:
3201                return "{0} -ea {1} {2}".format(java, exe, args)
3202
3203        testdir = self.component.getTestDir(self)
3204        assert(current.testcase.getPath(current).startswith(testdir))
3205        package = "test." + current.testcase.getPath(current)[len(testdir) + 1:].replace(os.sep, ".")
3206        javaArgs = self.getJavaArgs(process, current)
3207        if javaArgs:
3208            return "{0} -ea {1} -Dtest.class={2}.{3} test.TestDriver {4}".format(java, " ".join(javaArgs), package, exe, args)
3209        else:
3210            return "{0} -ea -Dtest.class={1}.{2} test.TestDriver {3}".format(java, package, exe, args)
3211
3212    def getJavaArgs(self, process, current):
3213        # TODO: WORKAROUND for https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=911925
3214        if isinstance(platform, Linux) and platform.getLinuxId() in ["debian", "ubuntu"]:
3215            return ["-Djdk.net.URLClassPath.disableClassPathURLCheck=true"]
3216        return []
3217
3218    def getSSLProps(self, process, current):
3219        props = Mapping.getSSLProps(self, process, current)
3220        if current.config.android:
3221            props.update({
3222                "IceSSL.KeystoreType" : "BKS",
3223                "IceSSL.TruststoreType" : "BKS",
3224                "Ice.InitPlugins" : "0",
3225                "IceSSL.Keystore": "server.bks" if isinstance(process, Server) else "client.bks"
3226            })
3227        else:
3228            props.update({
3229                "IceSSL.Keystore": "server.jks" if isinstance(process, Server) else "client.jks",
3230            })
3231        return props
3232
3233    def getPluginEntryPoint(self, plugin, process, current):
3234        return {
3235            "IceSSL" : "com.zeroc.IceSSL.PluginFactory",
3236            "IceBT" : "com.zeroc.IceBT.PluginFactory",
3237            "IceDiscovery" : "com.zeroc.IceDiscovery.PluginFactory",
3238            "IceLocatorDiscovery" : "com.zeroc.IceLocatorDiscovery.PluginFactory"
3239        }[plugin]
3240
3241    def getEnv(self, process, current):
3242        return { "CLASSPATH" : os.path.join(self.path, "lib", "test.jar") }
3243
3244    def _getDefaultSource(self, processType):
3245        return {
3246            "client" : "Client.java",
3247            "server" : "Server.java",
3248            "serveramd" : "AMDServer.java",
3249            "servertie" : "TieServer.java",
3250            "serveramdtie" : "AMDTieServer.java",
3251            "collocated" : "Collocated.java",
3252        }[processType]
3253
3254    def getSDKPackage(self):
3255        return "system-images;android-27;google_apis;x86"
3256
3257    def getApk(self, current):
3258        return os.path.join(self.getPath(), "test", "android", "controller", "build", "outputs", "apk", "debug",
3259                            "controller-debug.apk")
3260
3261    def getActivityName(self):
3262        return "com.zeroc.testcontroller/.ControllerActivity"
3263
3264class JavaCompatMapping(JavaMapping):
3265
3266    def getPluginEntryPoint(self, plugin, process, current):
3267        return {
3268            "IceSSL" : "IceSSL.PluginFactory",
3269            "IceBT" : "IceBT.PluginFactory",
3270            "IceDiscovery" : "IceDiscovery.PluginFactory",
3271            "IceLocatorDiscovery" : "IceLocatorDiscovery.PluginFactory"
3272        }[plugin]
3273
3274    def getEnv(self, process, current):
3275        classPath = [os.path.join(self.path, "lib", "test.jar")]
3276        if os.path.exists(os.path.join(self.path, "lib", "IceTestLambda.jar")):
3277            classPath += [os.path.join(self.path, "lib", "IceTestLambda.jar")]
3278        return { "CLASSPATH" : os.pathsep.join(classPath) }
3279
3280    def getSDKPackage(self):
3281        return "system-images;android-21;google_apis;x86_64"
3282
3283class CSharpMapping(Mapping):
3284
3285    class Config(Mapping.Config):
3286
3287        @classmethod
3288        def getSupportedArgs(self):
3289            return ("", ["dotnetcore", "framework="])
3290
3291        @classmethod
3292        def usage(self):
3293            print("")
3294            print("--dotnetcore                    Run C# tests using .NET Core")
3295            print("--framework=<TargetFramework>   Choose the framework used to run .NET tests")
3296
3297        def __init__(self, options=[]):
3298            Mapping.Config.__init__(self, options)
3299
3300            if not self.dotnetcore and not isinstance(platform, Windows):
3301                self.dotnetcore = True
3302
3303            if self.dotnetcore:
3304                self.libTargetFramework = "netstandard2.0"
3305                self.binTargetFramework = "netcoreapp2.1" if self.framework == "" else self.framework
3306                self.testTargetFramework = "netcoreapp2.1" if self.framework == "" else self.framework
3307            else:
3308                self.libTargetFramework = "net45" if self.framework == "" else "netstandard2.0"
3309                self.binTargetFramework = "net45" if self.framework == "" else self.framework
3310                self.testTargetFramework = "net45" if self.framework == "" else self.framework
3311
3312            # Set Xamarin flag if UWP/iOS or Android testing flag is also specified
3313            if self.uwp or self.android or "iphone" in self.buildPlatform:
3314                self.xamarin = True
3315
3316    def getBinTargetFramework(self, current):
3317        return current.config.binTargetFramework
3318
3319    def getLibTargetFramework(self, current):
3320        return current.config.libTargetFramework
3321
3322    def getTargetFramework(self, current):
3323        return current.config.testTargetFramework
3324
3325    def getBuildDir(self, name, current):
3326        if current.config.dotnetcore or current.config.framework != "":
3327            return os.path.join("msbuild", name, "netstandard2.0", self.getTargetFramework(current))
3328        else:
3329            return os.path.join("msbuild", name, self.getTargetFramework(current))
3330
3331    def getProps(self, process, current):
3332        props = Mapping.getProps(self, process, current)
3333        if current.config.xamarin:
3334            #
3335            # With SSL we need to delay the creation of the admin adapter until the plug-in has
3336            # been initialized.
3337            #
3338            if current.config.protocol in ["ssl", "wss"] and current.config.mx:
3339                props["Ice.Admin.DelayCreation"] = "1"
3340        return props
3341
3342    def getOptions(self, current):
3343        if current.config.xamarin and current.config.uwp:
3344            #
3345            # Do not run MX tests with SSL it cause problems with Xamarin UWP implementation
3346            #
3347            return {"mx" : ["False"]} if current.config.protocol in ["ssl", "wss"] else {}
3348        else:
3349            return {}
3350
3351    def getSSLProps(self, process, current):
3352        props = Mapping.getSSLProps(self, process, current)
3353        props.update({
3354            "IceSSL.Password": "password",
3355            "IceSSL.DefaultDir": os.path.join(self.component.getSourceDir(), "certs"),
3356            "IceSSL.CAs": "cacert.pem",
3357            "IceSSL.VerifyPeer": "0" if current.config.protocol == "wss" else "2",
3358            "IceSSL.CertFile": "server.p12" if isinstance(process, Server) else "client.p12",
3359        })
3360        if current.config.xamarin:
3361            props["Ice.InitPlugins"] = 0
3362            props["IceSSL.CAs"] = "cacert.der";
3363        return props
3364
3365    def getPluginEntryPoint(self, plugin, process, current):
3366        if current.config.xamarin:
3367            plugindir = ""
3368        else:
3369            plugindir = self.component.getLibDir(process, self, current)
3370
3371            #
3372            # If the plug-in assembly exists in the test directory, this is a good indication that the
3373            # test include a reference to the plug-in, in this case we must use the test dir as the
3374            # plug-in base directory to avoid loading two instances of the same assembly.
3375            #
3376            proccessType = current.testcase.getProcessType(process)
3377            if proccessType:
3378                testdir = os.path.join(current.testcase.getPath(current), self.getBuildDir(proccessType, current))
3379                if os.path.isfile(os.path.join(testdir, plugin + ".dll")):
3380                    plugindir = testdir
3381            plugindir += os.sep
3382
3383        return {
3384            "IceSSL" : plugindir + "IceSSL.dll:IceSSL.PluginFactory",
3385            "IceDiscovery" : plugindir + "IceDiscovery.dll:IceDiscovery.PluginFactory",
3386            "IceLocatorDiscovery" : plugindir + "IceLocatorDiscovery.dll:IceLocatorDiscovery.PluginFactory"
3387        }[plugin]
3388
3389    def getEnv(self, process, current):
3390        env = {}
3391        if isinstance(platform, Windows):
3392            if self.component.useBinDist(self, current):
3393                env['PATH'] = self.component.getBinDir(process, self, current)
3394            else:
3395                env['PATH'] = os.path.join(self.component.getSourceDir(), "cpp", "msbuild", "packages",
3396                                           "bzip2.{0}.1.0.6.10".format(platform.getPlatformToolset()),
3397                                           "build", "native", "bin", "x64", "Release")
3398            if not current.config.dotnetcore:
3399                env['DEVPATH'] = self.component.getLibDir(process, self, current)
3400        return env
3401
3402    def _getDefaultSource(self, processType):
3403        return {
3404            "client" : "Client.cs",
3405            "server" : "Server.cs",
3406            "serveramd" : "ServerAMD.cs",
3407            "servertie" : "ServerTie.cs",
3408            "serveramdtie" : "ServerAMDTie.cs",
3409            "collocated" : "Collocated.cs",
3410        }[processType]
3411
3412    def _getDefaultExe(self, processType):
3413        return Mapping._getDefaultExe(self, processType).lower()
3414
3415    def getCommandLine(self, current, process, exe, args):
3416        if process.isFromBinDir():
3417            path = self.component.getBinDir(process, self, current)
3418        else:
3419            path = os.path.join(current.testcase.getPath(current), current.getBuildDir(exe))
3420
3421        if current.config.dotnetcore:
3422            return "dotnet " + os.path.join(path, exe) + ".dll " + args
3423        else:
3424            return os.path.join(path, exe) + ".exe " + args
3425
3426    def getSDKPackage(self):
3427        return "system-images;android-27;google_apis;x86"
3428
3429    def getApk(self, current):
3430        return os.path.join(self.getPath(), "test", "xamarin", "controller.Android", "bin", current.config.buildConfig,
3431                            "com.zeroc.testcontroller-Signed.apk")
3432
3433    def getActivityName(self):
3434        return "com.zeroc.testcontroller/controller.MainActivity"
3435
3436    def getUWPPackageName(self):
3437        return "ice-uwp-controller.xamarin"
3438
3439    def getUWPUserModelId(self):
3440        return "ice-uwp-controller.xamarin_3qjctahehqazm"
3441
3442    def getUWPPackageFullName(self, platform):
3443        return "{0}_1.0.0.0_{1}__3qjctahehqazm".format(self.getUWPPackageName(), platform)
3444
3445    def getUWPPackageFullPath(self, platform, config):
3446        prefix = "controller.UWP_1.0.0.0_{0}{1}".format(platform, "_{0}".format(config) if config == "Debug" else "")
3447        return os.path.join(self.getPath(), "test", "xamarin", "controller.UWP", "AppPackages",
3448                            "{0}_Test".format(prefix), "{0}.appx".format(prefix))
3449
3450    def getIOSControllerIdentity(self, current):
3451        if current.config.buildPlatform == "iphonesimulator":
3452            return "iPhoneSimulator/com.zeroc.Xamarin-Test-Controller"
3453        else:
3454            return "iPhoneOS/com.zeroc.Xamarin-Test-Controller"
3455
3456    def getIOSAppFullPath(self, current):
3457        return os.path.join(self.getPath(), "test", "xamarin", "controller.iOS", "bin", "iPhoneSimulator",
3458                            current.config.buildConfig, "controller.iOS.app")
3459
3460class CppBasedMapping(Mapping):
3461
3462    class Config(Mapping.Config):
3463
3464        @classmethod
3465        def getSupportedArgs(self):
3466            return ("", [self.mappingName + "-config=", self.mappingName + "-platform=", "openssl"])
3467
3468        @classmethod
3469        def usage(self):
3470            print("")
3471            print(self.mappingDesc + " mapping options:")
3472            print("--{0}-config=<config>     {1} build configuration for native executables (overrides --config)."
3473                .format(self.mappingName, self.mappingDesc))
3474            print("--{0}-platform=<platform> {1} build platform for native executables (overrides --platform)."
3475                .format(self.mappingName, self.mappingDesc))
3476            print("--openssl                 Run SSL tests with OpenSSL instead of the default platform SSL engine.")
3477
3478        def __init__(self, options=[]):
3479            Mapping.Config.__init__(self, options)
3480            parseOptions(self, options,
3481                { self.mappingName + "-config" : "buildConfig",
3482                  self.mappingName + "-platform" : "buildPlatform" })
3483
3484    def getSSLProps(self, process, current):
3485        return Mapping.getByName("cpp").getSSLProps(process, current)
3486
3487    def getPluginEntryPoint(self, plugin, process, current):
3488        return Mapping.getByName("cpp").getPluginEntryPoint(plugin, process, current)
3489
3490    def getEnv(self, process, current):
3491        env = Mapping.getEnv(self, process, current)
3492        if self.component.getInstallDir(self, current) != platform.getInstallDir():
3493            # If not installed in the default platform installation directory, add
3494            # the C++ library directory to the library path
3495            env[platform.getLdPathEnvName()] = self.component.getLibDir(process, Mapping.getByName("cpp"), current)
3496        return env
3497
3498class ObjCMapping(CppBasedMapping):
3499
3500    def getTestSuites(self, ids=[]):
3501        return Mapping.getTestSuites(self, ids) if isinstance(platform, Darwin) else []
3502
3503    def findTestSuite(self, testsuite):
3504        return Mapping.findTestSuite(self, testsuite) if isinstance(platform, Darwin) else None
3505
3506    class Config(CppBasedMapping.Config):
3507        mappingName = "objc"
3508        mappingDesc = "Objective-C"
3509
3510        def __init__(self, options=[]):
3511            Mapping.Config.__init__(self, options)
3512            self.arc = self.buildConfig.lower().find("arc") >= 0
3513
3514    def _getDefaultSource(self, processType):
3515        return {
3516            "client" : "Client.m",
3517            "server" : "Server.m",
3518            "collocated" : "Collocated.m",
3519        }[processType]
3520
3521    def _getDefaultExe(self, processType):
3522        return Mapping._getDefaultExe(self, processType).lower()
3523
3524    def getIOSControllerIdentity(self, current):
3525        category = "iPhoneSimulator" if current.config.buildPlatform == "iphonesimulator" else "iPhoneOS"
3526        mapping = "ObjC-ARC" if current.config.arc else "ObjC"
3527        return "{0}/com.zeroc.{1}-Test-Controller".format(category, mapping)
3528
3529    def getIOSAppFullPath(self, current):
3530        appName = "Objective-C ARC Test Controller.app" if current.config.arc else "Objective-C Test Controller.app"
3531        path = os.path.join(self.component.getTestDir(self), "ios", "controller")
3532        path = os.path.join(path, "build-{0}-{1}".format(current.config.buildPlatform, current.config.buildConfig))
3533        build = "Debug" if os.path.exists(os.path.join(path, "Debug-{0}".format(current.config.buildPlatform))) else "Release"
3534        return os.path.join(path, "{0}-{1}".format(build, current.config.buildPlatform), appName)
3535
3536class PythonMapping(CppBasedMapping):
3537
3538    class Config(CppBasedMapping.Config):
3539        mappingName = "python"
3540        mappingDesc = "Python"
3541
3542    def getCommandLine(self, current, process, exe, args):
3543        return "\"{0}\"  {1} {2} {3}".format(sys.executable,
3544                                             os.path.join(self.path, "test", "TestHelper.py"),
3545                                             exe,
3546                                             args)
3547
3548    def getEnv(self, process, current):
3549        env = CppBasedMapping.getEnv(self, process, current)
3550        dirs = []
3551        if self.component.getInstallDir(self, current) != platform.getInstallDir():
3552            # If not installed in the default platform installation directory, add
3553            # the Ice python directory to PYTHONPATH
3554            dirs += self.getPythonDirs(self.component.getInstallDir(self, current), current.config)
3555        dirs += [current.testcase.getPath(current)]
3556        env["PYTHONPATH"] = os.pathsep.join(dirs)
3557        return env
3558
3559    def getPythonDirs(self, iceDir, config):
3560        dirs = []
3561        if isinstance(platform, Windows):
3562            dirs.append(os.path.join(iceDir, "python", config.buildPlatform, config.buildConfig))
3563        dirs.append(os.path.join(iceDir, "python"))
3564        return dirs
3565
3566    def _getDefaultSource(self, processType):
3567        return {
3568            "client" : "Client.py",
3569            "server" : "Server.py",
3570            "serveramd" : "ServerAMD.py",
3571            "collocated" : "Collocated.py",
3572        }[processType]
3573
3574class CppBasedClientMapping(CppBasedMapping):
3575
3576    def loadTestSuites(self, tests, config, filters, rfilters):
3577        Mapping.loadTestSuites(self, tests, config, filters, rfilters)
3578        self.getServerMapping().loadTestSuites(self.testsuites.keys(), config)
3579
3580    def getServerMapping(self, testId=None):
3581        return Mapping.getByName("cpp") # By default, run clients against C++ mapping executables
3582
3583class RubyMapping(CppBasedClientMapping):
3584
3585    class Config(CppBasedClientMapping.Config):
3586        mappingName = "ruby"
3587        mappingDesc = "Ruby"
3588
3589    def getCommandLine(self, current, process, exe, args):
3590        return "ruby  {0} {1} {2}".format(os.path.join(self.path, "test", "TestHelper.rb"), exe, args)
3591
3592    def getEnv(self, process, current):
3593        env = CppBasedMapping.getEnv(self, process, current)
3594        dirs = []
3595        if self.component.getInstallDir(self, current) != platform.getInstallDir():
3596            # If not installed in the default platform installation directory, add
3597            # the Ice ruby directory to RUBYLIB
3598            dirs += [os.path.join(self.path, "ruby")]
3599        dirs += [current.testcase.getPath(current)]
3600        env["RUBYLIB"] = os.pathsep.join(dirs)
3601        return env
3602
3603    def _getDefaultSource(self, processType):
3604        return { "client" : "Client.rb" }[processType]
3605
3606class PhpMapping(CppBasedClientMapping):
3607
3608    class Config(CppBasedClientMapping.Config):
3609        mappingName = "php"
3610        mappingDesc = "PHP"
3611
3612        @classmethod
3613        def getSupportedArgs(self):
3614            return ("", ["php-version="])
3615
3616        @classmethod
3617        def usage(self):
3618            print("")
3619            print("PHP Mapping options:")
3620            print("--php-version=[7.1|7.2|7.3]    PHP Version used for Windows builds")
3621
3622
3623        def __init__(self, options=[]):
3624            CppBasedClientMapping.Config.__init__(self, options)
3625            parseOptions(self, options, { "php-version" : "phpVersion" })
3626
3627    def getCommandLine(self, current, process, exe, args):
3628        phpArgs = []
3629        php = "php"
3630
3631        #
3632        # On Windows, when using a source distribution use the php executable from
3633        # the Nuget PHP dependency.
3634        #
3635        if isinstance(platform, Windows) and not self.component.useBinDist(self, current):
3636            nugetVersions = {
3637                "7.1": "7.1.17",
3638                "7.2": "7.2.8",
3639                "7.3": "7.3.0"
3640            }
3641            nugetVersion = nugetVersions[current.config.phpVersion]
3642            threadSafe = current.driver.configs[self].buildConfig in ["Debug", "Release"]
3643            buildPlatform = current.driver.configs[self].buildPlatform
3644            buildConfig = "Debug" if current.driver.configs[self].buildConfig.find("Debug") >= 0 else "Release"
3645            packageName = "php-{0}-{1}.{2}".format(current.config.phpVersion, "ts" if threadSafe else "nts", nugetVersion)
3646            php = os.path.join(self.path, "msbuild", "packages", packageName, "build", "native", "bin",
3647                               buildPlatform, buildConfig, "php.exe")
3648
3649        #
3650        # If Ice is not installed in the system directory, specify its location with PHP
3651        # configuration arguments.
3652        #
3653        if isinstance(platform, Windows) and not self.component.useBinDist(self, current) or \
3654           platform.getInstallDir() and self.component.getInstallDir(self, current) != platform.getInstallDir():
3655            phpArgs += ["-n"] # Do not load any php.ini files
3656            phpArgs += ["-d", "extension_dir='{0}'".format(self.component.getLibDir(process, self, current))]
3657            phpArgs += ["-d", "extension='{0}'".format(self.component.getPhpExtension(self, current))]
3658            phpArgs += ["-d", "include_path='{0}'".format(self.component.getPhpIncludePath(self, current))]
3659
3660        if hasattr(process, "getPhpArgs"):
3661            phpArgs += process.getPhpArgs(current)
3662
3663        return "{0} {1} -f {2} -- {3} {4}".format(php,
3664                                                  " ".join(phpArgs),
3665                                                  os.path.join(self.path, "test", "TestHelper.php"),
3666                                                  exe,
3667                                                  args)
3668
3669    def _getDefaultSource(self, processType):
3670        return { "client" : "Client.php" }[processType]
3671
3672class MatlabMapping(CppBasedClientMapping):
3673
3674    class Config(CppBasedClientMapping.Config):
3675        mappingName = "matlab"
3676        mappingDesc = "MATLAB"
3677
3678    def getCommandLine(self, current, process, exe, args):
3679        return "matlab -nodesktop -nosplash -wait -log -minimize -r \"cd '{0}', runTest {1} {2} {3}\"".format(
3680            os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "matlab", "test", "lib")),
3681            self.getTestCwd(process, current),
3682            os.path.join(current.config.buildPlatform, current.config.buildConfig),
3683            args)
3684
3685    def getServerMapping(self, testId=None):
3686        return Mapping.getByName("python") # Run clients against Python mapping servers
3687
3688    def _getDefaultSource(self, processType):
3689        return { "client" : "client.m" }[processType]
3690
3691    def getOptions(self, current):
3692        #
3693        # Metrics tests configuration not supported with MATLAB they use the Admin adapter.
3694        #
3695        options = CppBasedClientMapping.getOptions(self, current)
3696        options["mx"] = [ False ]
3697        return options
3698
3699
3700class JavaScriptMixin():
3701
3702    def loadTestSuites(self, tests, config, filters, rfilters):
3703        Mapping.loadTestSuites(self, tests, config, filters, rfilters)
3704        self.getServerMapping().loadTestSuites(list(self.testsuites.keys()) + ["Ice/echo"], config)
3705
3706    def getServerMapping(self, testId=None):
3707        if testId and self.hasSource(testId, "server"):
3708            return self
3709        else:
3710            return Mapping.getByName("cpp") # Run clients against C++ mapping servers if no JS server provided
3711
3712    def _getDefaultProcesses(self, processType):
3713        if processType.startswith("server"):
3714            return [EchoServer(), Server()]
3715        return Mapping._getDefaultProcesses(self, processType)
3716
3717    def getCommonDir(self, current):
3718        return os.path.join(self.getPath(), "test", "Common")
3719
3720    def getCommandLine(self, current, process, exe, args):
3721        return "node {0}/run.js {1} {2}".format(self.getCommonDir(current), exe, args)
3722
3723    def getEnv(self, process, current):
3724        env = Mapping.getEnv(self, process, current)
3725        env["NODE_PATH"] = os.pathsep.join([self.getCommonDir(current), self.getTestCwd(process, current)])
3726        return env
3727
3728    def getSSLProps(self, process, current):
3729        return {}
3730
3731    def getOptions(self, current):
3732        options = {
3733            "protocol" : ["ws", "wss"] if current.config.browser else ["tcp"],
3734            "compress" : [False],
3735            "ipv6" : [False],
3736            "serialize" : [False],
3737            "mx" : [False],
3738        }
3739        return options
3740
3741
3742class JavaScriptMapping(JavaScriptMixin,Mapping):
3743
3744    class Config(Mapping.Config):
3745
3746        @classmethod
3747        def getSupportedArgs(self):
3748            return ("", ["es5", "browser=", "worker"])
3749
3750        @classmethod
3751        def usage(self):
3752            print("")
3753            print("JavaScript mapping options:")
3754            print("--es5                 Use JavaScript ES5 (Babel compiled code).")
3755            print("--browser=<name>      Run with the given browser.")
3756            print("--worker              Run with Web workers enabled.")
3757
3758        def __init__(self, options=[]):
3759            Mapping.Config.__init__(self, options)
3760
3761            if self.browser and self.protocol == "tcp":
3762                self.protocol = "ws"
3763
3764            # Ie only support ES5 for now
3765            if self.browser in ["Ie"]:
3766                self.es5 = True
3767
3768    def getCommonDir(self, current):
3769        if current.config.es5:
3770            return os.path.join(self.getPath(), "test", "es5", "Common")
3771        else:
3772            return os.path.join(self.getPath(), "test", "Common")
3773
3774    def _getDefaultSource(self, processType):
3775        return { "client" : "Client.js", "serveramd" : "ServerAMD.js", "server" : "Server.js" }[processType]
3776
3777    def getTestCwd(self, process, current):
3778        if current.config.es5:
3779            # Change to the ES5 test directory if testing ES5
3780            return os.path.join(self.path, "test", "es5", current.testcase.getTestSuite().getId())
3781        else:
3782            return os.path.join(self.path, "test", current.testcase.getTestSuite().getId())
3783
3784    def getOptions(self, current):
3785        options = JavaScriptMixin.getOptions(self, current)
3786        options.update({
3787            "es5" : [True] if current.config.es5 else [False, True],
3788            "worker" : [False, True] if current.config.browser and current.config.browser != "Ie" else [False],
3789        })
3790        return options
3791
3792class TypeScriptMapping(JavaScriptMixin,Mapping):
3793
3794    class Config(Mapping.Config):
3795
3796        def canRun(self, testId, current):
3797            return Mapping.Config.canRun(self, testId, current) and self.browser != "Ie" # IE doesn't support ES6
3798
3799    def _getDefaultSource(self, processType):
3800        return { "client" : "Client.ts", "serveramd" : "ServerAMD.ts", "server" : "Server.ts" }[processType]
3801
3802#
3803# Instantiate platform global variable
3804#
3805platform = None
3806if sys.platform == "darwin":
3807    platform = Darwin()
3808elif sys.platform.startswith("aix"):
3809    platform = AIX()
3810elif sys.platform.startswith("freebsd") or sys.platform.startswith("dragonfly"):
3811    platform = FreeBSD()
3812elif sys.platform.startswith("linux") or sys.platform.startswith("gnukfreebsd"):
3813    platform = Linux()
3814elif sys.platform == "win32" or sys.platform[:6] == "cygwin":
3815    platform = Windows()
3816if not platform:
3817    print("can't run on unknown platform `{0}'".format(sys.platform))
3818    sys.exit(1)
3819
3820#
3821# Import component classes and instantiate the default component
3822#
3823from Component import *
3824
3825#
3826# Initialize the platform with component
3827#
3828platform.init(component)
3829
3830#
3831# Import local driver
3832#
3833from LocalDriver import *
3834
3835def runTestsWithPath(path):
3836    mappings = Mapping.getAllByPath(path)
3837    if not mappings:
3838        print("couldn't find mapping for `{0}' (is this mapping supported on this platform?)".format(path))
3839        sys.exit(0)
3840    runTests(mappings)
3841
3842def runTests(mappings=None, drivers=None):
3843    if not mappings:
3844        mappings = Mapping.getAll()
3845    if not drivers:
3846        drivers = Driver.getAll()
3847
3848    def usage():
3849        print("Usage: " + sys.argv[0] + " [options] [tests]")
3850        print("")
3851        print("Options:")
3852        print("-h | --help        Show this message")
3853
3854        Driver.commonUsage()
3855        for driver in drivers:
3856            driver.usage()
3857
3858        Mapping.Config.commonUsage()
3859        for mapping in mappings:
3860            mapping.Config.usage()
3861
3862        print("")
3863
3864    driver = None
3865    try:
3866        options = [Driver.getSupportedArgs(), Mapping.Config.getSupportedArgs()]
3867        options += [driver.getSupportedArgs() for driver in drivers]
3868        options += [mapping.Config.getSupportedArgs() for mapping in Mapping.getAll()]
3869        shortOptions = "h"
3870        longOptions = ["help"]
3871        for so, lo in options:
3872            shortOptions += so
3873            longOptions += lo
3874        opts, args = getopt.gnu_getopt(sys.argv[1:], shortOptions, longOptions)
3875
3876        for (o, a) in opts:
3877            if o in ["-h", "--help"]:
3878                usage()
3879                sys.exit(0)
3880
3881        #
3882        # Create the driver
3883        #
3884        driver = Driver.create(opts, component)
3885
3886        #
3887        # Create the configurations for each mapping.
3888        #
3889        configs = {}
3890        for mapping in Mapping.getAll():
3891            if mapping not in configs:
3892                configs[mapping] = mapping.createConfig(opts[:])
3893
3894        #
3895        # If the user specified --languages/rlanguages, only run matching mappings.
3896        #
3897        mappings = [m for m in mappings if driver.matchLanguage(str(m))]
3898
3899        #
3900        # Provide the configurations to the driver and load the test suites for each mapping.
3901        #
3902        driver.setConfigs(configs)
3903        for mapping in mappings + driver.getMappings():
3904            (filters, rfilters) = driver.getFilters(mapping, configs[mapping])
3905            mapping.loadTestSuites(args, configs[mapping], filters, rfilters)
3906
3907        #
3908        # Finally, run the test suites with the driver.
3909        #
3910        try:
3911            sys.exit(driver.run(mappings, args))
3912        except KeyboardInterrupt:
3913            pass
3914        finally:
3915            driver.destroy()
3916
3917    except Exception as e:
3918        print(sys.argv[0] + ": unexpected exception raised:\n" + traceback.format_exc())
3919        sys.exit(1)
3920