testing/mochitest/runtestsremote.py

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/testing/mochitest/runtestsremote.py	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,760 @@
     1.4 +# This Source Code Form is subject to the terms of the Mozilla Public
     1.5 +# License, v. 2.0. If a copy of the MPL was not distributed with this
     1.6 +# file, You can obtain one at http://mozilla.org/MPL/2.0/.
     1.7 +
     1.8 +import sys
     1.9 +import os
    1.10 +import time
    1.11 +import tempfile
    1.12 +import re
    1.13 +import traceback
    1.14 +import shutil
    1.15 +import math
    1.16 +import base64
    1.17 +
    1.18 +sys.path.insert(0, os.path.abspath(os.path.realpath(os.path.dirname(__file__))))
    1.19 +
    1.20 +from automation import Automation
    1.21 +from remoteautomation import RemoteAutomation, fennecLogcatFilters
    1.22 +from runtests import Mochitest
    1.23 +from runtests import MochitestServer
    1.24 +from mochitest_options import MochitestOptions
    1.25 +
    1.26 +import devicemanager
    1.27 +import droid
    1.28 +import manifestparser
    1.29 +import mozinfo
    1.30 +import mozlog
    1.31 +import moznetwork
    1.32 +
    1.33 +SCRIPT_DIR = os.path.abspath(os.path.realpath(os.path.dirname(__file__)))
    1.34 +log = mozlog.getLogger('Mochi-Remote')
    1.35 +
    1.36 +class RemoteOptions(MochitestOptions):
    1.37 +
    1.38 +    def __init__(self, automation, **kwargs):
    1.39 +        defaults = {}
    1.40 +        self._automation = automation or Automation()
    1.41 +        MochitestOptions.__init__(self)
    1.42 +
    1.43 +        self.add_option("--remote-app-path", action="store",
    1.44 +                    type = "string", dest = "remoteAppPath",
    1.45 +                    help = "Path to remote executable relative to device root using only forward slashes. Either this or app must be specified but not both")
    1.46 +        defaults["remoteAppPath"] = None
    1.47 +
    1.48 +        self.add_option("--deviceIP", action="store",
    1.49 +                    type = "string", dest = "deviceIP",
    1.50 +                    help = "ip address of remote device to test")
    1.51 +        defaults["deviceIP"] = None
    1.52 +
    1.53 +        self.add_option("--dm_trans", action="store",
    1.54 +                    type = "string", dest = "dm_trans",
    1.55 +                    help = "the transport to use to communicate with device: [adb|sut]; default=sut")
    1.56 +        defaults["dm_trans"] = "sut"
    1.57 +
    1.58 +        self.add_option("--devicePort", action="store",
    1.59 +                    type = "string", dest = "devicePort",
    1.60 +                    help = "port of remote device to test")
    1.61 +        defaults["devicePort"] = 20701
    1.62 +
    1.63 +        self.add_option("--remote-product-name", action="store",
    1.64 +                    type = "string", dest = "remoteProductName",
    1.65 +                    help = "The executable's name of remote product to test - either fennec or firefox, defaults to fennec")
    1.66 +        defaults["remoteProductName"] = "fennec"
    1.67 +
    1.68 +        self.add_option("--remote-logfile", action="store",
    1.69 +                    type = "string", dest = "remoteLogFile",
    1.70 +                    help = "Name of log file on the device relative to the device root.  PLEASE ONLY USE A FILENAME.")
    1.71 +        defaults["remoteLogFile"] = None
    1.72 +
    1.73 +        self.add_option("--remote-webserver", action = "store",
    1.74 +                    type = "string", dest = "remoteWebServer",
    1.75 +                    help = "ip address where the remote web server is hosted at")
    1.76 +        defaults["remoteWebServer"] = None
    1.77 +
    1.78 +        self.add_option("--http-port", action = "store",
    1.79 +                    type = "string", dest = "httpPort",
    1.80 +                    help = "http port of the remote web server")
    1.81 +        defaults["httpPort"] = automation.DEFAULT_HTTP_PORT
    1.82 +
    1.83 +        self.add_option("--ssl-port", action = "store",
    1.84 +                    type = "string", dest = "sslPort",
    1.85 +                    help = "ssl port of the remote web server")
    1.86 +        defaults["sslPort"] = automation.DEFAULT_SSL_PORT
    1.87 +
    1.88 +        self.add_option("--robocop-ini", action = "store",
    1.89 +                    type = "string", dest = "robocopIni",
    1.90 +                    help = "name of the .ini file containing the list of tests to run")
    1.91 +        defaults["robocopIni"] = ""
    1.92 +
    1.93 +        self.add_option("--robocop", action = "store",
    1.94 +                    type = "string", dest = "robocop",
    1.95 +                    help = "name of the .ini file containing the list of tests to run. [DEPRECATED- please use --robocop-ini")
    1.96 +        defaults["robocop"] = ""
    1.97 +
    1.98 +        self.add_option("--robocop-apk", action = "store",
    1.99 +                    type = "string", dest = "robocopApk",
   1.100 +                    help = "name of the Robocop APK to use for ADB test running")
   1.101 +        defaults["robocopApk"] = ""
   1.102 +
   1.103 +        self.add_option("--robocop-path", action = "store",
   1.104 +                    type = "string", dest = "robocopPath",
   1.105 +                    help = "Path to the folder where robocop.apk is located at.  Primarily used for ADB test running. [DEPRECATED- please use --robocop-apk]")
   1.106 +        defaults["robocopPath"] = ""
   1.107 +
   1.108 +        self.add_option("--robocop-ids", action = "store",
   1.109 +                    type = "string", dest = "robocopIds",
   1.110 +                    help = "name of the file containing the view ID map (fennec_ids.txt)")
   1.111 +        defaults["robocopIds"] = ""
   1.112 +
   1.113 +        self.add_option("--remoteTestRoot", action = "store",
   1.114 +                    type = "string", dest = "remoteTestRoot",
   1.115 +                    help = "remote directory to use as test root (eg. /mnt/sdcard/tests or /data/local/tests)")
   1.116 +        defaults["remoteTestRoot"] = None
   1.117 +
   1.118 +        defaults["logFile"] = "mochitest.log"
   1.119 +        defaults["autorun"] = True
   1.120 +        defaults["closeWhenDone"] = True
   1.121 +        defaults["testPath"] = ""
   1.122 +        defaults["app"] = None
   1.123 +        defaults["utilityPath"] = None
   1.124 +
   1.125 +        self.set_defaults(**defaults)
   1.126 +
   1.127 +    def verifyRemoteOptions(self, options, automation):
   1.128 +        if not options.remoteTestRoot:
   1.129 +            options.remoteTestRoot = automation._devicemanager.getDeviceRoot()
   1.130 +
   1.131 +        if options.remoteWebServer == None:
   1.132 +            if os.name != "nt":
   1.133 +                options.remoteWebServer = moznetwork.get_ip()
   1.134 +            else:
   1.135 +                log.error("you must specify a --remote-webserver=<ip address>")
   1.136 +                return None
   1.137 +
   1.138 +        options.webServer = options.remoteWebServer
   1.139 +
   1.140 +        if (options.deviceIP == None):
   1.141 +            log.error("you must provide a device IP")
   1.142 +            return None
   1.143 +
   1.144 +        if (options.remoteLogFile == None):
   1.145 +            options.remoteLogFile = options.remoteTestRoot + '/logs/mochitest.log'
   1.146 +
   1.147 +        if (options.remoteLogFile.count('/') < 1):
   1.148 +            options.remoteLogFile = options.remoteTestRoot + '/' + options.remoteLogFile
   1.149 +
   1.150 +        # remoteAppPath or app must be specified to find the product to launch
   1.151 +        if (options.remoteAppPath and options.app):
   1.152 +            log.error("You cannot specify both the remoteAppPath and the app setting")
   1.153 +            return None
   1.154 +        elif (options.remoteAppPath):
   1.155 +            options.app = options.remoteTestRoot + "/" + options.remoteAppPath
   1.156 +        elif (options.app == None):
   1.157 +            # Neither remoteAppPath nor app are set -- error
   1.158 +            log.error("You must specify either appPath or app")
   1.159 +            return None
   1.160 +
   1.161 +        # Only reset the xrePath if it wasn't provided
   1.162 +        if (options.xrePath == None):
   1.163 +            options.xrePath = options.utilityPath
   1.164 +
   1.165 +        if (options.pidFile != ""):
   1.166 +            f = open(options.pidFile, 'w')
   1.167 +            f.write("%s" % os.getpid())
   1.168 +            f.close()
   1.169 +
   1.170 +        # Robocop specific deprecated options.
   1.171 +        if options.robocop:
   1.172 +            if options.robocopIni:
   1.173 +                log.error("can not use deprecated --robocop and replacement --robocop-ini together")
   1.174 +                return None
   1.175 +            options.robocopIni = options.robocop
   1.176 +            del options.robocop
   1.177 +
   1.178 +        if options.robocopPath:
   1.179 +            if options.robocopApk:
   1.180 +                log.error("can not use deprecated --robocop-path and replacement --robocop-apk together")
   1.181 +                return None
   1.182 +            options.robocopApk = os.path.join(options.robocopPath, 'robocop.apk')
   1.183 +            del options.robocopPath
   1.184 +
   1.185 +        # Robocop specific options
   1.186 +        if options.robocopIni != "":
   1.187 +            if not os.path.exists(options.robocopIni):
   1.188 +                log.error("Unable to find specified robocop .ini manifest '%s'", options.robocopIni)
   1.189 +                return None
   1.190 +            options.robocopIni = os.path.abspath(options.robocopIni)
   1.191 +
   1.192 +        if options.robocopApk != "":
   1.193 +            if not os.path.exists(options.robocopApk):
   1.194 +                log.error("Unable to find robocop APK '%s'", options.robocopApk)
   1.195 +                return None
   1.196 +            options.robocopApk = os.path.abspath(options.robocopApk)
   1.197 +
   1.198 +        if options.robocopIds != "":
   1.199 +            if not os.path.exists(options.robocopIds):
   1.200 +                log.error("Unable to find specified robocop IDs file '%s'", options.robocopIds)
   1.201 +                return None
   1.202 +            options.robocopIds = os.path.abspath(options.robocopIds)
   1.203 +
   1.204 +        # allow us to keep original application around for cleanup while running robocop via 'am'
   1.205 +        options.remoteappname = options.app
   1.206 +        return options
   1.207 +
   1.208 +    def verifyOptions(self, options, mochitest):
   1.209 +        # since we are reusing verifyOptions, it will exit if App is not found
   1.210 +        temp = options.app
   1.211 +        options.app = __file__
   1.212 +        tempPort = options.httpPort
   1.213 +        tempSSL = options.sslPort
   1.214 +        tempIP = options.webServer
   1.215 +        # We are going to override this option later anyway, just pretend
   1.216 +        # like it's not set for verification purposes.
   1.217 +        options.dumpOutputDirectory = None
   1.218 +        options = MochitestOptions.verifyOptions(self, options, mochitest)
   1.219 +        options.webServer = tempIP
   1.220 +        options.app = temp
   1.221 +        options.sslPort = tempSSL
   1.222 +        options.httpPort = tempPort
   1.223 +
   1.224 +        return options
   1.225 +
   1.226 +class MochiRemote(Mochitest):
   1.227 +
   1.228 +    _automation = None
   1.229 +    _dm = None
   1.230 +    localProfile = None
   1.231 +    logLines = []
   1.232 +
   1.233 +    def __init__(self, automation, devmgr, options):
   1.234 +        self._automation = automation
   1.235 +        Mochitest.__init__(self)
   1.236 +        self._dm = devmgr
   1.237 +        self.environment = self._automation.environment
   1.238 +        self.remoteProfile = options.remoteTestRoot + "/profile"
   1.239 +        self._automation.setRemoteProfile(self.remoteProfile)
   1.240 +        self.remoteLog = options.remoteLogFile
   1.241 +        self.localLog = options.logFile
   1.242 +        self._automation.deleteANRs()
   1.243 +        self.certdbNew = True
   1.244 +
   1.245 +    def cleanup(self, manifest, options):
   1.246 +        if self._dm.fileExists(self.remoteLog):
   1.247 +            self._dm.getFile(self.remoteLog, self.localLog)
   1.248 +            self._dm.removeFile(self.remoteLog)
   1.249 +        else:
   1.250 +            log.warn("Unable to retrieve log file (%s) from remote device",
   1.251 +                self.remoteLog)
   1.252 +        self._dm.removeDir(self.remoteProfile)
   1.253 +        Mochitest.cleanup(self, manifest, options)
   1.254 +
   1.255 +    def findPath(self, paths, filename = None):
   1.256 +        for path in paths:
   1.257 +            p = path
   1.258 +            if filename:
   1.259 +                p = os.path.join(p, filename)
   1.260 +            if os.path.exists(self.getFullPath(p)):
   1.261 +                return path
   1.262 +        return None
   1.263 +
   1.264 +    def makeLocalAutomation(self):
   1.265 +        localAutomation = Automation()
   1.266 +        localAutomation.IS_WIN32 = False
   1.267 +        localAutomation.IS_LINUX = False
   1.268 +        localAutomation.IS_MAC = False
   1.269 +        localAutomation.UNIXISH = False
   1.270 +        hostos = sys.platform
   1.271 +        if (hostos == 'mac' or  hostos == 'darwin'):
   1.272 +            localAutomation.IS_MAC = True
   1.273 +        elif (hostos == 'linux' or hostos == 'linux2'):
   1.274 +            localAutomation.IS_LINUX = True
   1.275 +            localAutomation.UNIXISH = True
   1.276 +        elif (hostos == 'win32' or hostos == 'win64'):
   1.277 +            localAutomation.BIN_SUFFIX = ".exe"
   1.278 +            localAutomation.IS_WIN32 = True
   1.279 +        return localAutomation
   1.280 +
   1.281 +    # This seems kludgy, but this class uses paths from the remote host in the
   1.282 +    # options, except when calling up to the base class, which doesn't
   1.283 +    # understand the distinction.  This switches out the remote values for local
   1.284 +    # ones that the base class understands.  This is necessary for the web
   1.285 +    # server, SSL tunnel and profile building functions.
   1.286 +    def switchToLocalPaths(self, options):
   1.287 +        """ Set local paths in the options, return a function that will restore remote values """
   1.288 +        remoteXrePath = options.xrePath
   1.289 +        remoteProfilePath = options.profilePath
   1.290 +        remoteUtilityPath = options.utilityPath
   1.291 +
   1.292 +        localAutomation = self.makeLocalAutomation()
   1.293 +        paths = [
   1.294 +            options.xrePath,
   1.295 +            localAutomation.DIST_BIN,
   1.296 +            self._automation._product,
   1.297 +            os.path.join('..', self._automation._product)
   1.298 +        ]
   1.299 +        options.xrePath = self.findPath(paths)
   1.300 +        if options.xrePath == None:
   1.301 +            log.error("unable to find xulrunner path for %s, please specify with --xre-path", os.name)
   1.302 +            sys.exit(1)
   1.303 +
   1.304 +        xpcshell = "xpcshell"
   1.305 +        if (os.name == "nt"):
   1.306 +            xpcshell += ".exe"
   1.307 +
   1.308 +        if options.utilityPath:
   1.309 +            paths = [options.utilityPath, options.xrePath]
   1.310 +        else:
   1.311 +            paths = [options.xrePath]
   1.312 +        options.utilityPath = self.findPath(paths, xpcshell)
   1.313 +
   1.314 +        if options.utilityPath == None:
   1.315 +            log.error("unable to find utility path for %s, please specify with --utility-path", os.name)
   1.316 +            sys.exit(1)
   1.317 +
   1.318 +        xpcshell_path = os.path.join(options.utilityPath, xpcshell)
   1.319 +        if localAutomation.elf_arm(xpcshell_path):
   1.320 +            log.error('xpcshell at %s is an ARM binary; please use '
   1.321 +                      'the --utility-path argument to specify the path '
   1.322 +                      'to a desktop version.' % xpcshell_path)
   1.323 +            sys.exit(1)
   1.324 +
   1.325 +        if self.localProfile:
   1.326 +            options.profilePath = self.localProfile
   1.327 +        else:
   1.328 +            options.profilePath = tempfile.mkdtemp()
   1.329 +
   1.330 +        def fixup():
   1.331 +            options.xrePath = remoteXrePath
   1.332 +            options.utilityPath = remoteUtilityPath
   1.333 +            options.profilePath = remoteProfilePath
   1.334 +
   1.335 +        return fixup
   1.336 +
   1.337 +    def startServers(self, options, debuggerInfo):
   1.338 +        """ Create the servers on the host and start them up """
   1.339 +        restoreRemotePaths = self.switchToLocalPaths(options)
   1.340 +        Mochitest.startServers(self, options, debuggerInfo)
   1.341 +        restoreRemotePaths()
   1.342 +
   1.343 +    def buildProfile(self, options):
   1.344 +        restoreRemotePaths = self.switchToLocalPaths(options)
   1.345 +        manifest = Mochitest.buildProfile(self, options)
   1.346 +        self.localProfile = options.profilePath
   1.347 +        self._dm.removeDir(self.remoteProfile)
   1.348 +
   1.349 +        # we do not need this for robotium based tests, lets save a LOT of time
   1.350 +        if options.robocopIni:
   1.351 +            shutil.rmtree(os.path.join(options.profilePath, 'webapps'))
   1.352 +            shutil.rmtree(os.path.join(options.profilePath, 'extensions', 'staged', 'mochikit@mozilla.org'))
   1.353 +            shutil.rmtree(os.path.join(options.profilePath, 'extensions', 'staged', 'worker-test@mozilla.org'))
   1.354 +            shutil.rmtree(os.path.join(options.profilePath, 'extensions', 'staged', 'workerbootstrap-test@mozilla.org'))
   1.355 +            os.remove(os.path.join(options.profilePath, 'userChrome.css'))
   1.356 +
   1.357 +        try:
   1.358 +            self._dm.pushDir(options.profilePath, self.remoteProfile)
   1.359 +        except devicemanager.DMError:
   1.360 +            log.error("Automation Error: Unable to copy profile to device.")
   1.361 +            raise
   1.362 +
   1.363 +        restoreRemotePaths()
   1.364 +        options.profilePath = self.remoteProfile
   1.365 +        return manifest
   1.366 +
   1.367 +    def buildURLOptions(self, options, env):
   1.368 +        self.localLog = options.logFile
   1.369 +        options.logFile = self.remoteLog
   1.370 +        options.profilePath = self.localProfile
   1.371 +        env["MOZ_HIDE_RESULTS_TABLE"] = "1"
   1.372 +        retVal = Mochitest.buildURLOptions(self, options, env)
   1.373 +
   1.374 +        if not options.robocopIni:
   1.375 +            #we really need testConfig.js (for browser chrome)
   1.376 +            try:
   1.377 +                self._dm.pushDir(options.profilePath, self.remoteProfile)
   1.378 +            except devicemanager.DMError:
   1.379 +                log.error("Automation Error: Unable to copy profile to device.")
   1.380 +                raise
   1.381 +
   1.382 +        options.profilePath = self.remoteProfile
   1.383 +        options.logFile = self.localLog
   1.384 +        return retVal
   1.385 +
   1.386 +    def buildTestPath(self, options):
   1.387 +        if options.robocopIni != "":
   1.388 +            # Skip over manifest building if we just want to run
   1.389 +            # robocop tests.
   1.390 +            return self.buildTestURL(options)
   1.391 +        else:
   1.392 +            return super(MochiRemote, self).buildTestPath(options)
   1.393 +
   1.394 +    def installChromeFile(self, filename, options):
   1.395 +        parts = options.app.split('/')
   1.396 +        if (parts[0] == options.app):
   1.397 +          return "NO_CHROME_ON_DROID"
   1.398 +        path = '/'.join(parts[:-1])
   1.399 +        manifest = path + "/chrome/" + os.path.basename(filename)
   1.400 +        try:
   1.401 +            self._dm.pushFile(filename, manifest)
   1.402 +        except devicemanager.DMError:
   1.403 +            log.error("Automation Error: Unable to install Chrome files on device.")
   1.404 +            raise
   1.405 +
   1.406 +        return manifest
   1.407 +
   1.408 +    def getLogFilePath(self, logFile):
   1.409 +        return logFile
   1.410 +
   1.411 +    # In the future we could use LogParser: http://hg.mozilla.org/automation/logparser/
   1.412 +    def addLogData(self):
   1.413 +        with open(self.localLog) as currentLog:
   1.414 +            data = currentLog.readlines()
   1.415 +
   1.416 +        restart = re.compile('0 INFO SimpleTest START.*')
   1.417 +        reend = re.compile('([0-9]+) INFO TEST-START . Shutdown.*')
   1.418 +        refail = re.compile('([0-9]+) INFO TEST-UNEXPECTED-FAIL.*')
   1.419 +        start_found = False
   1.420 +        end_found = False
   1.421 +        fail_found = False
   1.422 +        for line in data:
   1.423 +            if reend.match(line):
   1.424 +                end_found = True
   1.425 +                start_found = False
   1.426 +                break
   1.427 +
   1.428 +            if start_found and not end_found:
   1.429 +                # Append the line without the number to increment
   1.430 +                self.logLines.append(' '.join(line.split(' ')[1:]))
   1.431 +
   1.432 +            if restart.match(line):
   1.433 +                start_found = True
   1.434 +            if refail.match(line):
   1.435 +                fail_found = True
   1.436 +        result = 0
   1.437 +        if fail_found:
   1.438 +            result = 1
   1.439 +        if not end_found:
   1.440 +            log.error("Automation Error: Missing end of test marker (process crashed?)")
   1.441 +            result = 1
   1.442 +        return result
   1.443 +
   1.444 +    def printLog(self):
   1.445 +        passed = 0
   1.446 +        failed = 0
   1.447 +        todo = 0
   1.448 +        incr = 1
   1.449 +        logFile = []
   1.450 +        logFile.append("0 INFO SimpleTest START")
   1.451 +        for line in self.logLines:
   1.452 +            if line.startswith("INFO TEST-PASS"):
   1.453 +                passed += 1
   1.454 +            elif line.startswith("INFO TEST-UNEXPECTED"):
   1.455 +                failed += 1
   1.456 +            elif line.startswith("INFO TEST-KNOWN"):
   1.457 +                todo += 1
   1.458 +            incr += 1
   1.459 +
   1.460 +        logFile.append("%s INFO TEST-START | Shutdown" % incr)
   1.461 +        incr += 1
   1.462 +        logFile.append("%s INFO Passed: %s" % (incr, passed))
   1.463 +        incr += 1
   1.464 +        logFile.append("%s INFO Failed: %s" % (incr, failed))
   1.465 +        incr += 1
   1.466 +        logFile.append("%s INFO Todo: %s" % (incr, todo))
   1.467 +        incr += 1
   1.468 +        logFile.append("%s INFO SimpleTest FINISHED" % incr)
   1.469 +
   1.470 +        # TODO: Consider not printing to stdout because we might be duplicating output
   1.471 +        print '\n'.join(logFile)
   1.472 +        with open(self.localLog, 'w') as localLog:
   1.473 +            localLog.write('\n'.join(logFile))
   1.474 +
   1.475 +        if failed > 0:
   1.476 +            return 1
   1.477 +        return 0
   1.478 +
   1.479 +    def printScreenshots(self, screenShotDir):
   1.480 +        # TODO: This can be re-written after completion of bug 749421
   1.481 +        if not self._dm.dirExists(screenShotDir):
   1.482 +            log.info("SCREENSHOT: No ScreenShots directory available: " + screenShotDir)
   1.483 +            return
   1.484 +
   1.485 +        printed = 0
   1.486 +        for name in self._dm.listFiles(screenShotDir):
   1.487 +            fullName = screenShotDir + "/" + name
   1.488 +            log.info("SCREENSHOT: FOUND: [%s]", fullName)
   1.489 +            try:
   1.490 +                image = self._dm.pullFile(fullName)
   1.491 +                encoded = base64.b64encode(image)
   1.492 +                log.info("SCREENSHOT: data:image/jpg;base64,%s", encoded)
   1.493 +                printed += 1
   1.494 +            except:
   1.495 +                log.info("SCREENSHOT: Could not be parsed")
   1.496 +                pass
   1.497 +
   1.498 +        log.info("SCREENSHOT: TOTAL PRINTED: [%s]", printed)
   1.499 +
   1.500 +    def printDeviceInfo(self, printLogcat=False):
   1.501 +        try:
   1.502 +            if printLogcat:
   1.503 +                logcat = self._dm.getLogcat(filterOutRegexps=fennecLogcatFilters)
   1.504 +                log.info('\n'+(''.join(logcat)))
   1.505 +            log.info("Device info: %s", self._dm.getInfo())
   1.506 +            log.info("Test root: %s", self._dm.getDeviceRoot())
   1.507 +        except devicemanager.DMError:
   1.508 +            log.warn("Error getting device information")
   1.509 +
   1.510 +    def buildRobotiumConfig(self, options, browserEnv):
   1.511 +        deviceRoot = self._dm.getDeviceRoot()
   1.512 +        fHandle = tempfile.NamedTemporaryFile(suffix='.config',
   1.513 +                                              prefix='robotium-',
   1.514 +                                              dir=os.getcwd(),
   1.515 +                                              delete=False)
   1.516 +        fHandle.write("profile=%s\n" % (self.remoteProfile))
   1.517 +        fHandle.write("logfile=%s\n" % (options.remoteLogFile))
   1.518 +        fHandle.write("host=http://mochi.test:8888/tests\n")
   1.519 +        fHandle.write("rawhost=http://%s:%s/tests\n" % (options.remoteWebServer, options.httpPort))
   1.520 +
   1.521 +        if browserEnv:
   1.522 +            envstr = ""
   1.523 +            delim = ""
   1.524 +            for key, value in browserEnv.items():
   1.525 +                try:
   1.526 +                    value.index(',')
   1.527 +                    log.error("buildRobotiumConfig: browserEnv - Found a ',' in our value, unable to process value. key=%s,value=%s", key, value)
   1.528 +                    log.error("browserEnv=%s", browserEnv)
   1.529 +                except ValueError, e:
   1.530 +                    envstr += "%s%s=%s" % (delim, key, value)
   1.531 +                    delim = ","
   1.532 +
   1.533 +            fHandle.write("envvars=%s\n" % envstr)
   1.534 +        fHandle.close()
   1.535 +
   1.536 +        self._dm.removeFile(os.path.join(deviceRoot, "robotium.config"))
   1.537 +        self._dm.pushFile(fHandle.name, os.path.join(deviceRoot, "robotium.config"))
   1.538 +        os.unlink(fHandle.name)
   1.539 +
   1.540 +    def buildBrowserEnv(self, options, debugger=False):
   1.541 +        browserEnv = Mochitest.buildBrowserEnv(self, options, debugger=debugger)
   1.542 +        self.buildRobotiumConfig(options, browserEnv)
   1.543 +        return browserEnv
   1.544 +
   1.545 +    def runApp(self, *args, **kwargs):
   1.546 +        """front-end automation.py's `runApp` functionality until FennecRunner is written"""
   1.547 +
   1.548 +        # automation.py/remoteautomation `runApp` takes the profile path,
   1.549 +        # whereas runtest.py's `runApp` takes a mozprofile object.
   1.550 +        if 'profileDir' not in kwargs and 'profile' in kwargs:
   1.551 +            kwargs['profileDir'] = kwargs.pop('profile').profile
   1.552 +
   1.553 +        # We're handling ssltunnel, so we should lie to automation.py to avoid
   1.554 +        # it trying to set up ssltunnel as well
   1.555 +        kwargs['runSSLTunnel'] = False
   1.556 +
   1.557 +        return self._automation.runApp(*args, **kwargs)
   1.558 +
   1.559 +def main():
   1.560 +    auto = RemoteAutomation(None, "fennec")
   1.561 +    parser = RemoteOptions(auto)
   1.562 +    options, args = parser.parse_args()
   1.563 +
   1.564 +    if (options.dm_trans == "adb"):
   1.565 +        if (options.deviceIP):
   1.566 +            dm = droid.DroidADB(options.deviceIP, options.devicePort, deviceRoot=options.remoteTestRoot)
   1.567 +        else:
   1.568 +            dm = droid.DroidADB(deviceRoot=options.remoteTestRoot)
   1.569 +    else:
   1.570 +         dm = droid.DroidSUT(options.deviceIP, options.devicePort, deviceRoot=options.remoteTestRoot)
   1.571 +    auto.setDeviceManager(dm)
   1.572 +    options = parser.verifyRemoteOptions(options, auto)
   1.573 +    if (options == None):
   1.574 +        log.error("Invalid options specified, use --help for a list of valid options")
   1.575 +        sys.exit(1)
   1.576 +
   1.577 +    productPieces = options.remoteProductName.split('.')
   1.578 +    if (productPieces != None):
   1.579 +        auto.setProduct(productPieces[0])
   1.580 +    else:
   1.581 +        auto.setProduct(options.remoteProductName)
   1.582 +    auto.setAppName(options.remoteappname)
   1.583 +
   1.584 +    mochitest = MochiRemote(auto, dm, options)
   1.585 +
   1.586 +    options = parser.verifyOptions(options, mochitest)
   1.587 +    if (options == None):
   1.588 +        sys.exit(1)
   1.589 +
   1.590 +    logParent = os.path.dirname(options.remoteLogFile)
   1.591 +    dm.mkDir(logParent);
   1.592 +    auto.setRemoteLog(options.remoteLogFile)
   1.593 +    auto.setServerInfo(options.webServer, options.httpPort, options.sslPort)
   1.594 +
   1.595 +    mochitest.printDeviceInfo()
   1.596 +
   1.597 +    # Add Android version (SDK level) to mozinfo so that manifest entries
   1.598 +    # can be conditional on android_version.
   1.599 +    androidVersion = dm.shellCheckOutput(['getprop', 'ro.build.version.sdk'])
   1.600 +    log.info("Android sdk version '%s'; will use this to filter manifests" % str(androidVersion))
   1.601 +    mozinfo.info['android_version'] = androidVersion
   1.602 +
   1.603 +    deviceRoot = dm.getDeviceRoot()
   1.604 +    if options.dmdPath:
   1.605 +        dmdLibrary = "libdmd.so"
   1.606 +        dmdPathOnDevice = os.path.join(deviceRoot, dmdLibrary)
   1.607 +        dm.removeFile(dmdPathOnDevice)
   1.608 +        dm.pushFile(os.path.join(options.dmdPath, dmdLibrary), dmdPathOnDevice)
   1.609 +        options.dmdPath = deviceRoot
   1.610 +
   1.611 +    options.dumpOutputDirectory = deviceRoot
   1.612 +
   1.613 +    procName = options.app.split('/')[-1]
   1.614 +    dm.killProcess(procName)
   1.615 +
   1.616 +    if options.robocopIni != "":
   1.617 +        # sut may wait up to 300 s for a robocop am process before returning
   1.618 +        dm.default_timeout = 320
   1.619 +        mp = manifestparser.TestManifest(strict=False)
   1.620 +        # TODO: pull this in dynamically
   1.621 +        mp.read(options.robocopIni)
   1.622 +        robocop_tests = mp.active_tests(exists=False, **mozinfo.info)
   1.623 +        tests = []
   1.624 +        my_tests = tests
   1.625 +        for test in robocop_tests:
   1.626 +            tests.append(test['name'])
   1.627 +
   1.628 +        if options.totalChunks:
   1.629 +            tests_per_chunk = math.ceil(len(tests) / (options.totalChunks * 1.0))
   1.630 +            start = int(round((options.thisChunk-1) * tests_per_chunk))
   1.631 +            end = int(round(options.thisChunk * tests_per_chunk))
   1.632 +            if end > len(tests):
   1.633 +                end = len(tests)
   1.634 +            my_tests = tests[start:end]
   1.635 +            log.info("Running tests %d-%d/%d", start+1, end, len(tests))
   1.636 +
   1.637 +        dm.removeFile(os.path.join(deviceRoot, "fennec_ids.txt"))
   1.638 +        fennec_ids = os.path.abspath(os.path.join(SCRIPT_DIR, "fennec_ids.txt"))
   1.639 +        if not os.path.exists(fennec_ids) and options.robocopIds:
   1.640 +            fennec_ids = options.robocopIds
   1.641 +        dm.pushFile(fennec_ids, os.path.join(deviceRoot, "fennec_ids.txt"))
   1.642 +        options.extraPrefs.append('browser.search.suggest.enabled=true')
   1.643 +        options.extraPrefs.append('browser.search.suggest.prompted=true')
   1.644 +        options.extraPrefs.append('layout.css.devPixelsPerPx=1.0')
   1.645 +        options.extraPrefs.append('browser.chrome.dynamictoolbar=false')
   1.646 +        options.extraPrefs.append('browser.snippets.enabled=false')
   1.647 +
   1.648 +        if (options.dm_trans == 'adb' and options.robocopApk):
   1.649 +            dm._checkCmd(["install", "-r", options.robocopApk])
   1.650 +
   1.651 +        retVal = None
   1.652 +        for test in robocop_tests:
   1.653 +            if options.testPath and options.testPath != test['name']:
   1.654 +                continue
   1.655 +
   1.656 +            if not test['name'] in my_tests:
   1.657 +                continue
   1.658 +
   1.659 +            if 'disabled' in test:
   1.660 +                log.info('TEST-INFO | skipping %s | %s' % (test['name'], test['disabled']))
   1.661 +                continue
   1.662 +
   1.663 +            # When running in a loop, we need to create a fresh profile for each cycle
   1.664 +            if mochitest.localProfile:
   1.665 +                options.profilePath = mochitest.localProfile
   1.666 +                os.system("rm -Rf %s" % options.profilePath)
   1.667 +                options.profilePath = tempfile.mkdtemp()
   1.668 +                mochitest.localProfile = options.profilePath
   1.669 +
   1.670 +            options.app = "am"
   1.671 +            options.browserArgs = ["instrument", "-w", "-e", "deviceroot", deviceRoot, "-e", "class"]
   1.672 +            options.browserArgs.append("org.mozilla.gecko.tests.%s" % test['name'])
   1.673 +            options.browserArgs.append("org.mozilla.roboexample.test/org.mozilla.gecko.FennecInstrumentationTestRunner")
   1.674 +
   1.675 +            # If the test is for checking the import from bookmarks then make sure there is data to import
   1.676 +            if test['name'] == "testImportFromAndroid":
   1.677 +
   1.678 +                # Get the OS so we can run the insert in the apropriate database and following the correct table schema
   1.679 +                osInfo = dm.getInfo("os")
   1.680 +                devOS = " ".join(osInfo['os'])
   1.681 +
   1.682 +                if ("pandaboard" in devOS):
   1.683 +                    delete = ['execsu', 'sqlite3', "/data/data/com.android.browser/databases/browser2.db \'delete from bookmarks where _id > 14;\'"]
   1.684 +                else:
   1.685 +                    delete = ['execsu', 'sqlite3', "/data/data/com.android.browser/databases/browser.db \'delete from bookmarks where _id > 14;\'"]
   1.686 +                if (options.dm_trans == "sut"):
   1.687 +                    dm._runCmds([{"cmd": " ".join(delete)}])
   1.688 +
   1.689 +                # Insert the bookmarks
   1.690 +                log.info("Insert bookmarks in the default android browser database")
   1.691 +                for i in range(20):
   1.692 +                   if ("pandaboard" in devOS):
   1.693 +                       cmd = ['execsu', 'sqlite3', "/data/data/com.android.browser/databases/browser2.db 'insert or replace into bookmarks(_id,title,url,folder,parent,position) values (" + str(30 + i) + ",\"Bookmark"+ str(i) + "\",\"http://www.bookmark" + str(i) + ".com\",0,1," + str(100 + i) + ");'"]
   1.694 +                   else:
   1.695 +                       cmd = ['execsu', 'sqlite3', "/data/data/com.android.browser/databases/browser.db 'insert into bookmarks(title,url,bookmark) values (\"Bookmark"+ str(i) + "\",\"http://www.bookmark" + str(i) + ".com\",1);'"]
   1.696 +                   if (options.dm_trans == "sut"):
   1.697 +                       dm._runCmds([{"cmd": " ".join(cmd)}])
   1.698 +            try:
   1.699 +                screenShotDir = "/mnt/sdcard/Robotium-Screenshots"
   1.700 +                dm.removeDir(screenShotDir)
   1.701 +                dm.recordLogcat()
   1.702 +                result = mochitest.runTests(options)
   1.703 +                if result != 0:
   1.704 +                    log.error("runTests() exited with code %s", result)
   1.705 +                log_result = mochitest.addLogData()
   1.706 +                if result != 0 or log_result != 0:
   1.707 +                    mochitest.printDeviceInfo(printLogcat=True)
   1.708 +                    mochitest.printScreenshots(screenShotDir)
   1.709 +                # Ensure earlier failures aren't overwritten by success on this run
   1.710 +                if retVal is None or retVal == 0:
   1.711 +                    retVal = result
   1.712 +            except:
   1.713 +                log.error("Automation Error: Exception caught while running tests")
   1.714 +                traceback.print_exc()
   1.715 +                mochitest.stopServers()
   1.716 +                try:
   1.717 +                    mochitest.cleanup(None, options)
   1.718 +                except devicemanager.DMError:
   1.719 +                    # device error cleaning up... oh well!
   1.720 +                    pass
   1.721 +                retVal = 1
   1.722 +                break
   1.723 +            finally:
   1.724 +                # Clean-up added bookmarks
   1.725 +                if test['name'] == "testImportFromAndroid":
   1.726 +                    if ("pandaboard" in devOS):
   1.727 +                        cmd_del = ['execsu', 'sqlite3', "/data/data/com.android.browser/databases/browser2.db \'delete from bookmarks where _id > 14;\'"]
   1.728 +                    else:
   1.729 +                        cmd_del = ['execsu', 'sqlite3', "/data/data/com.android.browser/databases/browser.db \'delete from bookmarks where _id > 14;\'"]
   1.730 +                    if (options.dm_trans == "sut"):
   1.731 +                        dm._runCmds([{"cmd": " ".join(cmd_del)}])
   1.732 +        if retVal is None:
   1.733 +            log.warn("No tests run. Did you pass an invalid TEST_PATH?")
   1.734 +            retVal = 1
   1.735 +        else:
   1.736 +            # if we didn't have some kind of error running the tests, make
   1.737 +            # sure the tests actually passed
   1.738 +            print "INFO | runtests.py | Test summary: start."
   1.739 +            overallResult = mochitest.printLog()
   1.740 +            print "INFO | runtests.py | Test summary: end."
   1.741 +            if retVal == 0:
   1.742 +                retVal = overallResult
   1.743 +    else:
   1.744 +        try:
   1.745 +            dm.recordLogcat()
   1.746 +            retVal = mochitest.runTests(options)
   1.747 +        except:
   1.748 +            log.error("Automation Error: Exception caught while running tests")
   1.749 +            traceback.print_exc()
   1.750 +            mochitest.stopServers()
   1.751 +            try:
   1.752 +                mochitest.cleanup(None, options)
   1.753 +            except devicemanager.DMError:
   1.754 +                # device error cleaning up... oh well!
   1.755 +                pass
   1.756 +            retVal = 1
   1.757 +
   1.758 +    mochitest.printDeviceInfo(printLogcat=True)
   1.759 +
   1.760 +    sys.exit(retVal)
   1.761 +
   1.762 +if __name__ == "__main__":
   1.763 +    main()

mercurial