testing/xpcshell/remotexpcshelltests.py

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/testing/xpcshell/remotexpcshelltests.py	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,610 @@
     1.4 +#!/usr/bin/env python
     1.5 +#
     1.6 +# This Source Code Form is subject to the terms of the Mozilla Public
     1.7 +# License, v. 2.0. If a copy of the MPL was not distributed with this
     1.8 +# file, You can obtain one at http://mozilla.org/MPL/2.0/.
     1.9 +
    1.10 +import posixpath
    1.11 +import sys, os
    1.12 +import subprocess
    1.13 +import runxpcshelltests as xpcshell
    1.14 +import tempfile
    1.15 +from automationutils import replaceBackSlashes
    1.16 +from mozdevice import devicemanagerADB, devicemanagerSUT, devicemanager
    1.17 +from zipfile import ZipFile
    1.18 +import shutil
    1.19 +import mozfile
    1.20 +import mozinfo
    1.21 +
    1.22 +here = os.path.dirname(os.path.abspath(__file__))
    1.23 +
    1.24 +def remoteJoin(path1, path2):
    1.25 +    return posixpath.join(path1, path2)
    1.26 +
    1.27 +class RemoteXPCShellTestThread(xpcshell.XPCShellTestThread):
    1.28 +    def __init__(self, *args, **kwargs):
    1.29 +        xpcshell.XPCShellTestThread.__init__(self, *args, **kwargs)
    1.30 +
    1.31 +        # embed the mobile params from the harness into the TestThread
    1.32 +        mobileArgs = kwargs.get('mobileArgs')
    1.33 +        for key in mobileArgs:
    1.34 +            setattr(self, key, mobileArgs[key])
    1.35 +
    1.36 +    def buildCmdTestFile(self, name):
    1.37 +        remoteDir = self.remoteForLocal(os.path.dirname(name))
    1.38 +        if remoteDir == self.remoteHere:
    1.39 +            remoteName = os.path.basename(name)
    1.40 +        else:
    1.41 +            remoteName = remoteJoin(remoteDir, os.path.basename(name))
    1.42 +        return ['-e', 'const _TEST_FILE = ["%s"];' %
    1.43 +                 replaceBackSlashes(remoteName)]
    1.44 +
    1.45 +    def remoteForLocal(self, local):
    1.46 +        for mapping in self.pathMapping:
    1.47 +            if (os.path.abspath(mapping.local) == os.path.abspath(local)):
    1.48 +                return mapping.remote
    1.49 +        return local
    1.50 +
    1.51 +
    1.52 +    def setupTempDir(self):
    1.53 +        # make sure the temp dir exists
    1.54 +        if not self.device.dirExists(self.remoteTmpDir):
    1.55 +            self.device.mkDir(self.remoteTmpDir)
    1.56 +        # env var is set in buildEnvironment
    1.57 +        return self.remoteTmpDir
    1.58 +
    1.59 +    def setupPluginsDir(self):
    1.60 +        if not os.path.isdir(self.pluginsPath):
    1.61 +            return None
    1.62 +
    1.63 +        # making sure tmp dir is set up
    1.64 +        self.setupTempDir()
    1.65 +
    1.66 +        pluginsDir = remoteJoin(self.remoteTmpDir, "plugins")
    1.67 +        self.device.pushDir(self.pluginsPath, pluginsDir)
    1.68 +        if self.interactive:
    1.69 +            self.log.info("TEST-INFO | plugins dir is %s" % pluginsDir)
    1.70 +        return pluginsDir
    1.71 +
    1.72 +    def setupProfileDir(self):
    1.73 +        self.device.removeDir(self.profileDir)
    1.74 +        self.device.mkDir(self.profileDir)
    1.75 +        if self.interactive or self.singleFile:
    1.76 +            self.log.info("TEST-INFO | profile dir is %s" % self.profileDir)
    1.77 +        return self.profileDir
    1.78 +
    1.79 +    def logCommand(self, name, completeCmd, testdir):
    1.80 +        self.log.info("TEST-INFO | %s | full command: %r" % (name, completeCmd))
    1.81 +        self.log.info("TEST-INFO | %s | current directory: %r" % (name, self.remoteHere))
    1.82 +        self.log.info("TEST-INFO | %s | environment: %s" % (name, self.env))
    1.83 +
    1.84 +    def getHeadAndTailFiles(self, test):
    1.85 +        """Override parent method to find files on remote device."""
    1.86 +        def sanitize_list(s, kind):
    1.87 +            for f in s.strip().split(' '):
    1.88 +                f = f.strip()
    1.89 +                if len(f) < 1:
    1.90 +                    continue
    1.91 +
    1.92 +                path = remoteJoin(self.remoteHere, f)
    1.93 +                if not self.device.fileExists(path):
    1.94 +                    raise Exception('%s file does not exist: %s' % ( kind,
    1.95 +                        path))
    1.96 +
    1.97 +                yield path
    1.98 +
    1.99 +        self.remoteHere = self.remoteForLocal(test['here'])
   1.100 +
   1.101 +        return (list(sanitize_list(test['head'], 'head')),
   1.102 +                list(sanitize_list(test['tail'], 'tail')))
   1.103 +
   1.104 +    def buildXpcsCmd(self, testdir):
   1.105 +        # change base class' paths to remote paths and use base class to build command
   1.106 +        self.xpcshell = remoteJoin(self.remoteBinDir, "xpcw")
   1.107 +        self.headJSPath = remoteJoin(self.remoteScriptsDir, 'head.js')
   1.108 +        self.httpdJSPath = remoteJoin(self.remoteComponentsDir, 'httpd.js')
   1.109 +        self.httpdManifest = remoteJoin(self.remoteComponentsDir, 'httpd.manifest')
   1.110 +        self.testingModulesDir = self.remoteModulesDir
   1.111 +        self.testharnessdir = self.remoteScriptsDir
   1.112 +        xpcshell.XPCShellTestThread.buildXpcsCmd(self, testdir)
   1.113 +        # remove "-g <dir> -a <dir>" and add "--greomni <apk>"
   1.114 +        del(self.xpcsCmd[1:5])
   1.115 +        if self.options.localAPK:
   1.116 +            self.xpcsCmd.insert(3, '--greomni')
   1.117 +            self.xpcsCmd.insert(4, self.remoteAPK)
   1.118 +
   1.119 +        if self.remoteDebugger:
   1.120 +            # for example, "/data/local/gdbserver" "localhost:12345"
   1.121 +            self.xpcsCmd = [
   1.122 +              self.remoteDebugger,
   1.123 +              self.remoteDebuggerArgs,
   1.124 +              self.xpcsCmd]
   1.125 +
   1.126 +    def testTimeout(self, test_file, proc):
   1.127 +        self.timedout = True
   1.128 +        if not self.retry:
   1.129 +            self.log.error("TEST-UNEXPECTED-FAIL | %s | Test timed out" % test_file)
   1.130 +        self.kill(proc)
   1.131 +
   1.132 +    def launchProcess(self, cmd, stdout, stderr, env, cwd):
   1.133 +        self.timedout = False
   1.134 +        cmd.insert(1, self.remoteHere)
   1.135 +        outputFile = "xpcshelloutput"
   1.136 +        with open(outputFile, 'w+') as f:
   1.137 +            try:
   1.138 +                self.shellReturnCode = self.device.shell(cmd, f)
   1.139 +            except devicemanager.DMError as e:
   1.140 +                if self.timedout:
   1.141 +                    # If the test timed out, there is a good chance the SUTagent also
   1.142 +                    # timed out and failed to return a return code, generating a
   1.143 +                    # DMError. Ignore the DMError to simplify the error report.
   1.144 +                    self.shellReturnCode = None
   1.145 +                    pass
   1.146 +                else:
   1.147 +                    raise e
   1.148 +        # The device manager may have timed out waiting for xpcshell.
   1.149 +        # Guard against an accumulation of hung processes by killing
   1.150 +        # them here. Note also that IPC tests may spawn new instances
   1.151 +        # of xpcshell.
   1.152 +        self.device.killProcess(cmd[0])
   1.153 +        self.device.killProcess("xpcshell")
   1.154 +        return outputFile
   1.155 +
   1.156 +    def checkForCrashes(self,
   1.157 +                        dump_directory,
   1.158 +                        symbols_path,
   1.159 +                        test_name=None):
   1.160 +        if not self.device.dirExists(self.remoteMinidumpDir):
   1.161 +            # The minidumps directory is automatically created when Fennec
   1.162 +            # (first) starts, so its lack of presence is a hint that
   1.163 +            # something went wrong.
   1.164 +            print "Automation Error: No crash directory (%s) found on remote device" % self.remoteMinidumpDir
   1.165 +            # Whilst no crash was found, the run should still display as a failure
   1.166 +            return True
   1.167 +        with mozfile.TemporaryDirectory() as dumpDir:
   1.168 +            self.device.getDirectory(self.remoteMinidumpDir, dumpDir)
   1.169 +            crashed = xpcshell.XPCShellTestThread.checkForCrashes(self, dumpDir, symbols_path, test_name)
   1.170 +            self.device.removeDir(self.remoteMinidumpDir)
   1.171 +            self.device.mkDir(self.remoteMinidumpDir)
   1.172 +        return crashed
   1.173 +
   1.174 +    def communicate(self, proc):
   1.175 +        f = open(proc, "r")
   1.176 +        contents = f.read()
   1.177 +        f.close()
   1.178 +        os.remove(proc)
   1.179 +        return contents, ""
   1.180 +
   1.181 +    def poll(self, proc):
   1.182 +        if self.device.processExist("xpcshell") is None:
   1.183 +            return self.getReturnCode(proc)
   1.184 +        # Process is still running
   1.185 +        return None
   1.186 +
   1.187 +    def kill(self, proc):
   1.188 +        return self.device.killProcess("xpcshell", True)
   1.189 +
   1.190 +    def getReturnCode(self, proc):
   1.191 +        if self.shellReturnCode is not None:
   1.192 +            return self.shellReturnCode
   1.193 +        else:
   1.194 +            return -1
   1.195 +
   1.196 +    def removeDir(self, dirname):
   1.197 +        self.device.removeDir(dirname)
   1.198 +
   1.199 +    #TODO: consider creating a separate log dir.  We don't have the test file structure,
   1.200 +    #      so we use filename.log.  Would rather see ./logs/filename.log
   1.201 +    def createLogFile(self, test, stdout):
   1.202 +        try:
   1.203 +            f = None
   1.204 +            filename = test.replace('\\', '/').split('/')[-1] + ".log"
   1.205 +            f = open(filename, "w")
   1.206 +            f.write(stdout)
   1.207 +
   1.208 +        finally:
   1.209 +            if f is not None:
   1.210 +                f.close()
   1.211 +
   1.212 +
   1.213 +# A specialization of XPCShellTests that runs tests on an Android device
   1.214 +# via devicemanager.
   1.215 +class XPCShellRemote(xpcshell.XPCShellTests, object):
   1.216 +
   1.217 +    def __init__(self, devmgr, options, args, log=None):
   1.218 +        xpcshell.XPCShellTests.__init__(self, log)
   1.219 +
   1.220 +        # Add Android version (SDK level) to mozinfo so that manifest entries
   1.221 +        # can be conditional on android_version.
   1.222 +        androidVersion = devmgr.shellCheckOutput(['getprop', 'ro.build.version.sdk'])
   1.223 +        mozinfo.info['android_version'] = androidVersion
   1.224 +
   1.225 +        self.localLib = options.localLib
   1.226 +        self.localBin = options.localBin
   1.227 +        self.options = options
   1.228 +        self.device = devmgr
   1.229 +        self.pathMapping = []
   1.230 +        self.remoteTestRoot = self.device.getTestRoot("xpcshell")
   1.231 +        # remoteBinDir contains xpcshell and its wrapper script, both of which must
   1.232 +        # be executable. Since +x permissions cannot usually be set on /mnt/sdcard,
   1.233 +        # and the test root may be on /mnt/sdcard, remoteBinDir is set to be on
   1.234 +        # /data/local, always.
   1.235 +        self.remoteBinDir = "/data/local/xpcb"
   1.236 +        # Terse directory names are used here ("c" for the components directory)
   1.237 +        # to minimize the length of the command line used to execute
   1.238 +        # xpcshell on the remote device. adb has a limit to the number
   1.239 +        # of characters used in a shell command, and the xpcshell command
   1.240 +        # line can be quite complex.
   1.241 +        self.remoteTmpDir = remoteJoin(self.remoteTestRoot, "tmp")
   1.242 +        self.remoteScriptsDir = self.remoteTestRoot
   1.243 +        self.remoteComponentsDir = remoteJoin(self.remoteTestRoot, "c")
   1.244 +        self.remoteModulesDir = remoteJoin(self.remoteTestRoot, "m")
   1.245 +        self.remoteMinidumpDir = remoteJoin(self.remoteTestRoot, "minidumps")
   1.246 +        self.profileDir = remoteJoin(self.remoteTestRoot, "p")
   1.247 +        self.remoteDebugger = options.debugger
   1.248 +        self.remoteDebuggerArgs = options.debuggerArgs
   1.249 +        self.testingModulesDir = options.testingModulesDir
   1.250 +
   1.251 +        self.env = {}
   1.252 +
   1.253 +        if self.options.objdir:
   1.254 +            self.xpcDir = os.path.join(self.options.objdir, "_tests/xpcshell")
   1.255 +        elif os.path.isdir(os.path.join(here, 'tests')):
   1.256 +            self.xpcDir = os.path.join(here, 'tests')
   1.257 +        else:
   1.258 +            print >> sys.stderr, "Couldn't find local xpcshell test directory"
   1.259 +            sys.exit(1)
   1.260 +
   1.261 +        if options.localAPK:
   1.262 +            self.localAPKContents = ZipFile(options.localAPK)
   1.263 +        if options.setup:
   1.264 +            self.setupUtilities()
   1.265 +            self.setupModules()
   1.266 +            self.setupTestDir()
   1.267 +        self.setupMinidumpDir()
   1.268 +        self.remoteAPK = None
   1.269 +        if options.localAPK:
   1.270 +            self.remoteAPK = remoteJoin(self.remoteBinDir, os.path.basename(options.localAPK))
   1.271 +            self.setAppRoot()
   1.272 +
   1.273 +        # data that needs to be passed to the RemoteXPCShellTestThread
   1.274 +        self.mobileArgs = {
   1.275 +            'device': self.device,
   1.276 +            'remoteBinDir': self.remoteBinDir,
   1.277 +            'remoteScriptsDir': self.remoteScriptsDir,
   1.278 +            'remoteComponentsDir': self.remoteComponentsDir,
   1.279 +            'remoteModulesDir': self.remoteModulesDir,
   1.280 +            'options': self.options,
   1.281 +            'remoteDebugger': self.remoteDebugger,
   1.282 +            'pathMapping': self.pathMapping,
   1.283 +            'profileDir': self.profileDir,
   1.284 +            'remoteTmpDir': self.remoteTmpDir,
   1.285 +            'remoteMinidumpDir': self.remoteMinidumpDir,
   1.286 +        }
   1.287 +        if self.remoteAPK:
   1.288 +            self.mobileArgs['remoteAPK'] = self.remoteAPK
   1.289 +
   1.290 +    def setLD_LIBRARY_PATH(self):
   1.291 +        self.env["LD_LIBRARY_PATH"] = self.remoteBinDir
   1.292 +
   1.293 +    def pushWrapper(self):
   1.294 +        # Rather than executing xpcshell directly, this wrapper script is
   1.295 +        # used. By setting environment variables and the cwd in the script,
   1.296 +        # the length of the per-test command line is shortened. This is
   1.297 +        # often important when using ADB, as there is a limit to the length
   1.298 +        # of the ADB command line.
   1.299 +        localWrapper = tempfile.mktemp()
   1.300 +        f = open(localWrapper, "w")
   1.301 +        f.write("#!/system/bin/sh\n")
   1.302 +        for envkey, envval in self.env.iteritems():
   1.303 +            f.write("export %s=%s\n" % (envkey, envval))
   1.304 +        f.write("cd $1\n")
   1.305 +        f.write("echo xpcw: cd $1\n")
   1.306 +        f.write("shift\n")
   1.307 +        f.write("echo xpcw: xpcshell \"$@\"\n")
   1.308 +        f.write("%s/xpcshell \"$@\"\n" % self.remoteBinDir)
   1.309 +        f.close()
   1.310 +        remoteWrapper = remoteJoin(self.remoteBinDir, "xpcw")
   1.311 +        self.device.pushFile(localWrapper, remoteWrapper)
   1.312 +        os.remove(localWrapper)
   1.313 +        self.device.chmodDir(self.remoteBinDir)
   1.314 +
   1.315 +    def buildEnvironment(self):
   1.316 +        self.buildCoreEnvironment()
   1.317 +        self.setLD_LIBRARY_PATH()
   1.318 +        self.env["MOZ_LINKER_CACHE"] = self.remoteBinDir
   1.319 +        if self.options.localAPK and self.appRoot:
   1.320 +            self.env["GRE_HOME"] = self.appRoot
   1.321 +        self.env["XPCSHELL_TEST_PROFILE_DIR"] = self.profileDir
   1.322 +        self.env["TMPDIR"] = self.remoteTmpDir
   1.323 +        self.env["HOME"] = self.profileDir
   1.324 +        self.env["XPCSHELL_TEST_TEMP_DIR"] = self.remoteTmpDir
   1.325 +        self.env["XPCSHELL_MINIDUMP_DIR"] = self.remoteMinidumpDir
   1.326 +        if self.options.setup:
   1.327 +            self.pushWrapper()
   1.328 +
   1.329 +    def setAppRoot(self):
   1.330 +        # Determine the application root directory associated with the package
   1.331 +        # name used by the Fennec APK.
   1.332 +        self.appRoot = None
   1.333 +        packageName = None
   1.334 +        if self.options.localAPK:
   1.335 +            try:
   1.336 +                packageName = self.localAPKContents.read("package-name.txt")
   1.337 +                if packageName:
   1.338 +                    self.appRoot = self.device.getAppRoot(packageName.strip())
   1.339 +            except Exception as detail:
   1.340 +                print "unable to determine app root: " + str(detail)
   1.341 +                pass
   1.342 +        return None
   1.343 +
   1.344 +    def setupUtilities(self):
   1.345 +        if (not self.device.dirExists(self.remoteBinDir)):
   1.346 +            # device.mkDir may fail here where shellCheckOutput may succeed -- see bug 817235
   1.347 +            try:
   1.348 +                self.device.shellCheckOutput(["mkdir", self.remoteBinDir]);
   1.349 +            except devicemanager.DMError:
   1.350 +                # Might get a permission error; try again as root, if available
   1.351 +                self.device.shellCheckOutput(["mkdir", self.remoteBinDir], root=True);
   1.352 +                self.device.shellCheckOutput(["chmod", "777", self.remoteBinDir], root=True);
   1.353 +
   1.354 +        remotePrefDir = remoteJoin(self.remoteBinDir, "defaults/pref")
   1.355 +        if (self.device.dirExists(self.remoteTmpDir)):
   1.356 +            self.device.removeDir(self.remoteTmpDir)
   1.357 +        self.device.mkDir(self.remoteTmpDir)
   1.358 +        if (not self.device.dirExists(remotePrefDir)):
   1.359 +            self.device.mkDirs(remoteJoin(remotePrefDir, "extra"))
   1.360 +        if (not self.device.dirExists(self.remoteScriptsDir)):
   1.361 +            self.device.mkDir(self.remoteScriptsDir)
   1.362 +        if (not self.device.dirExists(self.remoteComponentsDir)):
   1.363 +            self.device.mkDir(self.remoteComponentsDir)
   1.364 +
   1.365 +        local = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'head.js')
   1.366 +        remoteFile = remoteJoin(self.remoteScriptsDir, "head.js")
   1.367 +        self.device.pushFile(local, remoteFile)
   1.368 +
   1.369 +        local = os.path.join(self.localBin, "xpcshell")
   1.370 +        remoteFile = remoteJoin(self.remoteBinDir, "xpcshell")
   1.371 +        self.device.pushFile(local, remoteFile)
   1.372 +
   1.373 +        local = os.path.join(self.localBin, "components/httpd.js")
   1.374 +        remoteFile = remoteJoin(self.remoteComponentsDir, "httpd.js")
   1.375 +        self.device.pushFile(local, remoteFile)
   1.376 +
   1.377 +        local = os.path.join(self.localBin, "components/httpd.manifest")
   1.378 +        remoteFile = remoteJoin(self.remoteComponentsDir, "httpd.manifest")
   1.379 +        self.device.pushFile(local, remoteFile)
   1.380 +
   1.381 +        local = os.path.join(self.localBin, "components/test_necko.xpt")
   1.382 +        remoteFile = remoteJoin(self.remoteComponentsDir, "test_necko.xpt")
   1.383 +        self.device.pushFile(local, remoteFile)
   1.384 +
   1.385 +        if self.options.localAPK:
   1.386 +            remoteFile = remoteJoin(self.remoteBinDir, os.path.basename(self.options.localAPK))
   1.387 +            self.device.pushFile(self.options.localAPK, remoteFile)
   1.388 +
   1.389 +        self.pushLibs()
   1.390 +
   1.391 +    def pushLibs(self):
   1.392 +        pushed_libs_count = 0
   1.393 +        if self.options.localAPK:
   1.394 +            try:
   1.395 +                dir = tempfile.mkdtemp()
   1.396 +                szip = os.path.join(self.localBin, '..', 'host', 'bin', 'szip')
   1.397 +                if not os.path.exists(szip):
   1.398 +                    # Tinderbox builds must run szip from the test package
   1.399 +                    szip = os.path.join(self.localBin, 'host', 'szip')
   1.400 +                if not os.path.exists(szip):
   1.401 +                    # If the test package doesn't contain szip, it means files
   1.402 +                    # are not szipped in the test package.
   1.403 +                    szip = None
   1.404 +                for info in self.localAPKContents.infolist():
   1.405 +                    if info.filename.endswith(".so"):
   1.406 +                        print >> sys.stderr, "Pushing %s.." % info.filename
   1.407 +                        remoteFile = remoteJoin(self.remoteBinDir, os.path.basename(info.filename))
   1.408 +                        self.localAPKContents.extract(info, dir)
   1.409 +                        file = os.path.join(dir, info.filename)
   1.410 +                        if szip:
   1.411 +                            out = subprocess.check_output([szip, '-d', file], stderr=subprocess.STDOUT)
   1.412 +                        self.device.pushFile(os.path.join(dir, info.filename), remoteFile)
   1.413 +                        pushed_libs_count += 1
   1.414 +            finally:
   1.415 +                shutil.rmtree(dir)
   1.416 +            return pushed_libs_count
   1.417 +
   1.418 +        for file in os.listdir(self.localLib):
   1.419 +            if (file.endswith(".so")):
   1.420 +                print >> sys.stderr, "Pushing %s.." % file
   1.421 +                if 'libxul' in file:
   1.422 +                    print >> sys.stderr, "This is a big file, it could take a while."
   1.423 +                remoteFile = remoteJoin(self.remoteBinDir, file)
   1.424 +                self.device.pushFile(os.path.join(self.localLib, file), remoteFile)
   1.425 +                pushed_libs_count += 1
   1.426 +
   1.427 +        # Additional libraries may be found in a sub-directory such as "lib/armeabi-v7a"
   1.428 +        localArmLib = os.path.join(self.localLib, "lib")
   1.429 +        if os.path.exists(localArmLib):
   1.430 +            for root, dirs, files in os.walk(localArmLib):
   1.431 +                for file in files:
   1.432 +                    if (file.endswith(".so")):
   1.433 +                        print >> sys.stderr, "Pushing %s.." % file
   1.434 +                        remoteFile = remoteJoin(self.remoteBinDir, file)
   1.435 +                        self.device.pushFile(os.path.join(root, file), remoteFile)
   1.436 +                        pushed_libs_count += 1
   1.437 +
   1.438 +        return pushed_libs_count
   1.439 +
   1.440 +    def setupModules(self):
   1.441 +        if self.testingModulesDir:
   1.442 +            self.device.pushDir(self.testingModulesDir, self.remoteModulesDir)
   1.443 +
   1.444 +    def setupTestDir(self):
   1.445 +        print 'pushing %s' % self.xpcDir
   1.446 +        try:
   1.447 +            self.device.pushDir(self.xpcDir, self.remoteScriptsDir, retryLimit=10)
   1.448 +        except TypeError:
   1.449 +            # Foopies have an older mozdevice ver without retryLimit
   1.450 +            self.device.pushDir(self.xpcDir, self.remoteScriptsDir)
   1.451 +
   1.452 +    def setupMinidumpDir(self):
   1.453 +        if self.device.dirExists(self.remoteMinidumpDir):
   1.454 +            self.device.removeDir(self.remoteMinidumpDir)
   1.455 +        self.device.mkDir(self.remoteMinidumpDir)
   1.456 +
   1.457 +    def buildTestList(self):
   1.458 +        xpcshell.XPCShellTests.buildTestList(self)
   1.459 +        uniqueTestPaths = set([])
   1.460 +        for test in self.alltests:
   1.461 +            uniqueTestPaths.add(test['here'])
   1.462 +        for testdir in uniqueTestPaths:
   1.463 +            abbrevTestDir = os.path.relpath(testdir, self.xpcDir)
   1.464 +            remoteScriptDir = remoteJoin(self.remoteScriptsDir, abbrevTestDir)
   1.465 +            self.pathMapping.append(PathMapping(testdir, remoteScriptDir))
   1.466 +
   1.467 +class RemoteXPCShellOptions(xpcshell.XPCShellOptions):
   1.468 +
   1.469 +    def __init__(self):
   1.470 +        xpcshell.XPCShellOptions.__init__(self)
   1.471 +        defaults = {}
   1.472 +
   1.473 +        self.add_option("--deviceIP", action="store",
   1.474 +                        type = "string", dest = "deviceIP",
   1.475 +                        help = "ip address of remote device to test")
   1.476 +        defaults["deviceIP"] = None
   1.477 +
   1.478 +        self.add_option("--devicePort", action="store",
   1.479 +                        type = "string", dest = "devicePort",
   1.480 +                        help = "port of remote device to test")
   1.481 +        defaults["devicePort"] = 20701
   1.482 +
   1.483 +        self.add_option("--dm_trans", action="store",
   1.484 +                        type = "string", dest = "dm_trans",
   1.485 +                        help = "the transport to use to communicate with device: [adb|sut]; default=sut")
   1.486 +        defaults["dm_trans"] = "sut"
   1.487 +
   1.488 +        self.add_option("--objdir", action="store",
   1.489 +                        type = "string", dest = "objdir",
   1.490 +                        help = "local objdir, containing xpcshell binaries")
   1.491 +        defaults["objdir"] = None
   1.492 +
   1.493 +        self.add_option("--apk", action="store",
   1.494 +                        type = "string", dest = "localAPK",
   1.495 +                        help = "local path to Fennec APK")
   1.496 +        defaults["localAPK"] = None
   1.497 +
   1.498 +        self.add_option("--noSetup", action="store_false",
   1.499 +                        dest = "setup",
   1.500 +                        help = "do not copy any files to device (to be used only if device is already setup)")
   1.501 +        defaults["setup"] = True
   1.502 +
   1.503 +        self.add_option("--local-lib-dir", action="store",
   1.504 +                        type = "string", dest = "localLib",
   1.505 +                        help = "local path to library directory")
   1.506 +        defaults["localLib"] = None
   1.507 +
   1.508 +        self.add_option("--local-bin-dir", action="store",
   1.509 +                        type = "string", dest = "localBin",
   1.510 +                        help = "local path to bin directory")
   1.511 +        defaults["localBin"] = None
   1.512 +
   1.513 +        self.add_option("--remoteTestRoot", action = "store",
   1.514 +                    type = "string", dest = "remoteTestRoot",
   1.515 +                    help = "remote directory to use as test root (eg. /mnt/sdcard/tests or /data/local/tests)")
   1.516 +        defaults["remoteTestRoot"] = None
   1.517 +
   1.518 +        self.set_defaults(**defaults)
   1.519 +
   1.520 +    def verifyRemoteOptions(self, options):
   1.521 +        if options.localLib is None:
   1.522 +            if options.localAPK and options.objdir:
   1.523 +                for path in ['dist/fennec', 'fennec/lib']:
   1.524 +                    options.localLib = os.path.join(options.objdir, path)
   1.525 +                    if os.path.isdir(options.localLib):
   1.526 +                        break
   1.527 +                else:
   1.528 +                    self.error("Couldn't find local library dir, specify --local-lib-dir")
   1.529 +            elif options.objdir:
   1.530 +                options.localLib = os.path.join(options.objdir, 'dist/bin')
   1.531 +            elif os.path.isfile(os.path.join(here, '..', 'bin', 'xpcshell')):
   1.532 +                # assume tests are being run from a tests.zip
   1.533 +                options.localLib = os.path.abspath(os.path.join(here, '..', 'bin'))
   1.534 +            else:
   1.535 +                self.error("Couldn't find local library dir, specify --local-lib-dir")
   1.536 +
   1.537 +        if options.localBin is None:
   1.538 +            if options.objdir:
   1.539 +                for path in ['dist/bin', 'bin']:
   1.540 +                    options.localBin = os.path.join(options.objdir, path)
   1.541 +                    if os.path.isdir(options.localBin):
   1.542 +                        break
   1.543 +                else:
   1.544 +                    self.error("Couldn't find local binary dir, specify --local-bin-dir")
   1.545 +            elif os.path.isfile(os.path.join(here, '..', 'bin', 'xpcshell')):
   1.546 +                # assume tests are being run from a tests.zip
   1.547 +                options.localBin = os.path.abspath(os.path.join(here, '..', 'bin'))
   1.548 +            else:
   1.549 +                self.error("Couldn't find local binary dir, specify --local-bin-dir")
   1.550 +        return options
   1.551 +
   1.552 +class PathMapping:
   1.553 +
   1.554 +    def __init__(self, localDir, remoteDir):
   1.555 +        self.local = localDir
   1.556 +        self.remote = remoteDir
   1.557 +
   1.558 +def main():
   1.559 +
   1.560 +    if sys.version_info < (2,7):
   1.561 +        print >>sys.stderr, "Error: You must use python version 2.7 or newer but less than 3.0"
   1.562 +        sys.exit(1)
   1.563 +
   1.564 +    parser = RemoteXPCShellOptions()
   1.565 +    options, args = parser.parse_args()
   1.566 +    if not options.localAPK:
   1.567 +        for file in os.listdir(os.path.join(options.objdir, "dist")):
   1.568 +            if (file.endswith(".apk") and file.startswith("fennec")):
   1.569 +                options.localAPK = os.path.join(options.objdir, "dist")
   1.570 +                options.localAPK = os.path.join(options.localAPK, file)
   1.571 +                print >>sys.stderr, "using APK: " + options.localAPK
   1.572 +                break
   1.573 +        else:
   1.574 +            print >>sys.stderr, "Error: please specify an APK"
   1.575 +            sys.exit(1)
   1.576 +
   1.577 +    options = parser.verifyRemoteOptions(options)
   1.578 +
   1.579 +    if len(args) < 1 and options.manifest is None:
   1.580 +        print >>sys.stderr, """Usage: %s <test dirs>
   1.581 +             or: %s --manifest=test.manifest """ % (sys.argv[0], sys.argv[0])
   1.582 +        sys.exit(1)
   1.583 +
   1.584 +    if (options.dm_trans == "adb"):
   1.585 +        if (options.deviceIP):
   1.586 +            dm = devicemanagerADB.DeviceManagerADB(options.deviceIP, options.devicePort, packageName=None, deviceRoot=options.remoteTestRoot)
   1.587 +        else:
   1.588 +            dm = devicemanagerADB.DeviceManagerADB(packageName=None, deviceRoot=options.remoteTestRoot)
   1.589 +    else:
   1.590 +        dm = devicemanagerSUT.DeviceManagerSUT(options.deviceIP, options.devicePort, deviceRoot=options.remoteTestRoot)
   1.591 +        if (options.deviceIP == None):
   1.592 +            print "Error: you must provide a device IP to connect to via the --device option"
   1.593 +            sys.exit(1)
   1.594 +
   1.595 +    if options.interactive and not options.testPath:
   1.596 +        print >>sys.stderr, "Error: You must specify a test filename in interactive mode!"
   1.597 +        sys.exit(1)
   1.598 +
   1.599 +    xpcsh = XPCShellRemote(dm, options, args)
   1.600 +
   1.601 +    # we don't run concurrent tests on mobile
   1.602 +    options.sequential = True
   1.603 +
   1.604 +    if not xpcsh.runTests(xpcshell='xpcshell',
   1.605 +                          testClass=RemoteXPCShellTestThread,
   1.606 +                          testdirs=args[0:],
   1.607 +                          mobileArgs=xpcsh.mobileArgs,
   1.608 +                          **options.__dict__):
   1.609 +        sys.exit(1)
   1.610 +
   1.611 +
   1.612 +if __name__ == '__main__':
   1.613 +    main()

mercurial