testing/mochitest/runtestsremote.py

Wed, 31 Dec 2014 06:55:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:55:50 +0100
changeset 2
7e26c7da4463
permissions
-rw-r--r--

Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2

     1 # This Source Code Form is subject to the terms of the Mozilla Public
     2 # License, v. 2.0. If a copy of the MPL was not distributed with this
     3 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
     5 import sys
     6 import os
     7 import time
     8 import tempfile
     9 import re
    10 import traceback
    11 import shutil
    12 import math
    13 import base64
    15 sys.path.insert(0, os.path.abspath(os.path.realpath(os.path.dirname(__file__))))
    17 from automation import Automation
    18 from remoteautomation import RemoteAutomation, fennecLogcatFilters
    19 from runtests import Mochitest
    20 from runtests import MochitestServer
    21 from mochitest_options import MochitestOptions
    23 import devicemanager
    24 import droid
    25 import manifestparser
    26 import mozinfo
    27 import mozlog
    28 import moznetwork
    30 SCRIPT_DIR = os.path.abspath(os.path.realpath(os.path.dirname(__file__)))
    31 log = mozlog.getLogger('Mochi-Remote')
    33 class RemoteOptions(MochitestOptions):
    35     def __init__(self, automation, **kwargs):
    36         defaults = {}
    37         self._automation = automation or Automation()
    38         MochitestOptions.__init__(self)
    40         self.add_option("--remote-app-path", action="store",
    41                     type = "string", dest = "remoteAppPath",
    42                     help = "Path to remote executable relative to device root using only forward slashes. Either this or app must be specified but not both")
    43         defaults["remoteAppPath"] = None
    45         self.add_option("--deviceIP", action="store",
    46                     type = "string", dest = "deviceIP",
    47                     help = "ip address of remote device to test")
    48         defaults["deviceIP"] = None
    50         self.add_option("--dm_trans", action="store",
    51                     type = "string", dest = "dm_trans",
    52                     help = "the transport to use to communicate with device: [adb|sut]; default=sut")
    53         defaults["dm_trans"] = "sut"
    55         self.add_option("--devicePort", action="store",
    56                     type = "string", dest = "devicePort",
    57                     help = "port of remote device to test")
    58         defaults["devicePort"] = 20701
    60         self.add_option("--remote-product-name", action="store",
    61                     type = "string", dest = "remoteProductName",
    62                     help = "The executable's name of remote product to test - either fennec or firefox, defaults to fennec")
    63         defaults["remoteProductName"] = "fennec"
    65         self.add_option("--remote-logfile", action="store",
    66                     type = "string", dest = "remoteLogFile",
    67                     help = "Name of log file on the device relative to the device root.  PLEASE ONLY USE A FILENAME.")
    68         defaults["remoteLogFile"] = None
    70         self.add_option("--remote-webserver", action = "store",
    71                     type = "string", dest = "remoteWebServer",
    72                     help = "ip address where the remote web server is hosted at")
    73         defaults["remoteWebServer"] = None
    75         self.add_option("--http-port", action = "store",
    76                     type = "string", dest = "httpPort",
    77                     help = "http port of the remote web server")
    78         defaults["httpPort"] = automation.DEFAULT_HTTP_PORT
    80         self.add_option("--ssl-port", action = "store",
    81                     type = "string", dest = "sslPort",
    82                     help = "ssl port of the remote web server")
    83         defaults["sslPort"] = automation.DEFAULT_SSL_PORT
    85         self.add_option("--robocop-ini", action = "store",
    86                     type = "string", dest = "robocopIni",
    87                     help = "name of the .ini file containing the list of tests to run")
    88         defaults["robocopIni"] = ""
    90         self.add_option("--robocop", action = "store",
    91                     type = "string", dest = "robocop",
    92                     help = "name of the .ini file containing the list of tests to run. [DEPRECATED- please use --robocop-ini")
    93         defaults["robocop"] = ""
    95         self.add_option("--robocop-apk", action = "store",
    96                     type = "string", dest = "robocopApk",
    97                     help = "name of the Robocop APK to use for ADB test running")
    98         defaults["robocopApk"] = ""
   100         self.add_option("--robocop-path", action = "store",
   101                     type = "string", dest = "robocopPath",
   102                     help = "Path to the folder where robocop.apk is located at.  Primarily used for ADB test running. [DEPRECATED- please use --robocop-apk]")
   103         defaults["robocopPath"] = ""
   105         self.add_option("--robocop-ids", action = "store",
   106                     type = "string", dest = "robocopIds",
   107                     help = "name of the file containing the view ID map (fennec_ids.txt)")
   108         defaults["robocopIds"] = ""
   110         self.add_option("--remoteTestRoot", action = "store",
   111                     type = "string", dest = "remoteTestRoot",
   112                     help = "remote directory to use as test root (eg. /mnt/sdcard/tests or /data/local/tests)")
   113         defaults["remoteTestRoot"] = None
   115         defaults["logFile"] = "mochitest.log"
   116         defaults["autorun"] = True
   117         defaults["closeWhenDone"] = True
   118         defaults["testPath"] = ""
   119         defaults["app"] = None
   120         defaults["utilityPath"] = None
   122         self.set_defaults(**defaults)
   124     def verifyRemoteOptions(self, options, automation):
   125         if not options.remoteTestRoot:
   126             options.remoteTestRoot = automation._devicemanager.getDeviceRoot()
   128         if options.remoteWebServer == None:
   129             if os.name != "nt":
   130                 options.remoteWebServer = moznetwork.get_ip()
   131             else:
   132                 log.error("you must specify a --remote-webserver=<ip address>")
   133                 return None
   135         options.webServer = options.remoteWebServer
   137         if (options.deviceIP == None):
   138             log.error("you must provide a device IP")
   139             return None
   141         if (options.remoteLogFile == None):
   142             options.remoteLogFile = options.remoteTestRoot + '/logs/mochitest.log'
   144         if (options.remoteLogFile.count('/') < 1):
   145             options.remoteLogFile = options.remoteTestRoot + '/' + options.remoteLogFile
   147         # remoteAppPath or app must be specified to find the product to launch
   148         if (options.remoteAppPath and options.app):
   149             log.error("You cannot specify both the remoteAppPath and the app setting")
   150             return None
   151         elif (options.remoteAppPath):
   152             options.app = options.remoteTestRoot + "/" + options.remoteAppPath
   153         elif (options.app == None):
   154             # Neither remoteAppPath nor app are set -- error
   155             log.error("You must specify either appPath or app")
   156             return None
   158         # Only reset the xrePath if it wasn't provided
   159         if (options.xrePath == None):
   160             options.xrePath = options.utilityPath
   162         if (options.pidFile != ""):
   163             f = open(options.pidFile, 'w')
   164             f.write("%s" % os.getpid())
   165             f.close()
   167         # Robocop specific deprecated options.
   168         if options.robocop:
   169             if options.robocopIni:
   170                 log.error("can not use deprecated --robocop and replacement --robocop-ini together")
   171                 return None
   172             options.robocopIni = options.robocop
   173             del options.robocop
   175         if options.robocopPath:
   176             if options.robocopApk:
   177                 log.error("can not use deprecated --robocop-path and replacement --robocop-apk together")
   178                 return None
   179             options.robocopApk = os.path.join(options.robocopPath, 'robocop.apk')
   180             del options.robocopPath
   182         # Robocop specific options
   183         if options.robocopIni != "":
   184             if not os.path.exists(options.robocopIni):
   185                 log.error("Unable to find specified robocop .ini manifest '%s'", options.robocopIni)
   186                 return None
   187             options.robocopIni = os.path.abspath(options.robocopIni)
   189         if options.robocopApk != "":
   190             if not os.path.exists(options.robocopApk):
   191                 log.error("Unable to find robocop APK '%s'", options.robocopApk)
   192                 return None
   193             options.robocopApk = os.path.abspath(options.robocopApk)
   195         if options.robocopIds != "":
   196             if not os.path.exists(options.robocopIds):
   197                 log.error("Unable to find specified robocop IDs file '%s'", options.robocopIds)
   198                 return None
   199             options.robocopIds = os.path.abspath(options.robocopIds)
   201         # allow us to keep original application around for cleanup while running robocop via 'am'
   202         options.remoteappname = options.app
   203         return options
   205     def verifyOptions(self, options, mochitest):
   206         # since we are reusing verifyOptions, it will exit if App is not found
   207         temp = options.app
   208         options.app = __file__
   209         tempPort = options.httpPort
   210         tempSSL = options.sslPort
   211         tempIP = options.webServer
   212         # We are going to override this option later anyway, just pretend
   213         # like it's not set for verification purposes.
   214         options.dumpOutputDirectory = None
   215         options = MochitestOptions.verifyOptions(self, options, mochitest)
   216         options.webServer = tempIP
   217         options.app = temp
   218         options.sslPort = tempSSL
   219         options.httpPort = tempPort
   221         return options
   223 class MochiRemote(Mochitest):
   225     _automation = None
   226     _dm = None
   227     localProfile = None
   228     logLines = []
   230     def __init__(self, automation, devmgr, options):
   231         self._automation = automation
   232         Mochitest.__init__(self)
   233         self._dm = devmgr
   234         self.environment = self._automation.environment
   235         self.remoteProfile = options.remoteTestRoot + "/profile"
   236         self._automation.setRemoteProfile(self.remoteProfile)
   237         self.remoteLog = options.remoteLogFile
   238         self.localLog = options.logFile
   239         self._automation.deleteANRs()
   240         self.certdbNew = True
   242     def cleanup(self, manifest, options):
   243         if self._dm.fileExists(self.remoteLog):
   244             self._dm.getFile(self.remoteLog, self.localLog)
   245             self._dm.removeFile(self.remoteLog)
   246         else:
   247             log.warn("Unable to retrieve log file (%s) from remote device",
   248                 self.remoteLog)
   249         self._dm.removeDir(self.remoteProfile)
   250         Mochitest.cleanup(self, manifest, options)
   252     def findPath(self, paths, filename = None):
   253         for path in paths:
   254             p = path
   255             if filename:
   256                 p = os.path.join(p, filename)
   257             if os.path.exists(self.getFullPath(p)):
   258                 return path
   259         return None
   261     def makeLocalAutomation(self):
   262         localAutomation = Automation()
   263         localAutomation.IS_WIN32 = False
   264         localAutomation.IS_LINUX = False
   265         localAutomation.IS_MAC = False
   266         localAutomation.UNIXISH = False
   267         hostos = sys.platform
   268         if (hostos == 'mac' or  hostos == 'darwin'):
   269             localAutomation.IS_MAC = True
   270         elif (hostos == 'linux' or hostos == 'linux2'):
   271             localAutomation.IS_LINUX = True
   272             localAutomation.UNIXISH = True
   273         elif (hostos == 'win32' or hostos == 'win64'):
   274             localAutomation.BIN_SUFFIX = ".exe"
   275             localAutomation.IS_WIN32 = True
   276         return localAutomation
   278     # This seems kludgy, but this class uses paths from the remote host in the
   279     # options, except when calling up to the base class, which doesn't
   280     # understand the distinction.  This switches out the remote values for local
   281     # ones that the base class understands.  This is necessary for the web
   282     # server, SSL tunnel and profile building functions.
   283     def switchToLocalPaths(self, options):
   284         """ Set local paths in the options, return a function that will restore remote values """
   285         remoteXrePath = options.xrePath
   286         remoteProfilePath = options.profilePath
   287         remoteUtilityPath = options.utilityPath
   289         localAutomation = self.makeLocalAutomation()
   290         paths = [
   291             options.xrePath,
   292             localAutomation.DIST_BIN,
   293             self._automation._product,
   294             os.path.join('..', self._automation._product)
   295         ]
   296         options.xrePath = self.findPath(paths)
   297         if options.xrePath == None:
   298             log.error("unable to find xulrunner path for %s, please specify with --xre-path", os.name)
   299             sys.exit(1)
   301         xpcshell = "xpcshell"
   302         if (os.name == "nt"):
   303             xpcshell += ".exe"
   305         if options.utilityPath:
   306             paths = [options.utilityPath, options.xrePath]
   307         else:
   308             paths = [options.xrePath]
   309         options.utilityPath = self.findPath(paths, xpcshell)
   311         if options.utilityPath == None:
   312             log.error("unable to find utility path for %s, please specify with --utility-path", os.name)
   313             sys.exit(1)
   315         xpcshell_path = os.path.join(options.utilityPath, xpcshell)
   316         if localAutomation.elf_arm(xpcshell_path):
   317             log.error('xpcshell at %s is an ARM binary; please use '
   318                       'the --utility-path argument to specify the path '
   319                       'to a desktop version.' % xpcshell_path)
   320             sys.exit(1)
   322         if self.localProfile:
   323             options.profilePath = self.localProfile
   324         else:
   325             options.profilePath = tempfile.mkdtemp()
   327         def fixup():
   328             options.xrePath = remoteXrePath
   329             options.utilityPath = remoteUtilityPath
   330             options.profilePath = remoteProfilePath
   332         return fixup
   334     def startServers(self, options, debuggerInfo):
   335         """ Create the servers on the host and start them up """
   336         restoreRemotePaths = self.switchToLocalPaths(options)
   337         Mochitest.startServers(self, options, debuggerInfo)
   338         restoreRemotePaths()
   340     def buildProfile(self, options):
   341         restoreRemotePaths = self.switchToLocalPaths(options)
   342         manifest = Mochitest.buildProfile(self, options)
   343         self.localProfile = options.profilePath
   344         self._dm.removeDir(self.remoteProfile)
   346         # we do not need this for robotium based tests, lets save a LOT of time
   347         if options.robocopIni:
   348             shutil.rmtree(os.path.join(options.profilePath, 'webapps'))
   349             shutil.rmtree(os.path.join(options.profilePath, 'extensions', 'staged', 'mochikit@mozilla.org'))
   350             shutil.rmtree(os.path.join(options.profilePath, 'extensions', 'staged', 'worker-test@mozilla.org'))
   351             shutil.rmtree(os.path.join(options.profilePath, 'extensions', 'staged', 'workerbootstrap-test@mozilla.org'))
   352             os.remove(os.path.join(options.profilePath, 'userChrome.css'))
   354         try:
   355             self._dm.pushDir(options.profilePath, self.remoteProfile)
   356         except devicemanager.DMError:
   357             log.error("Automation Error: Unable to copy profile to device.")
   358             raise
   360         restoreRemotePaths()
   361         options.profilePath = self.remoteProfile
   362         return manifest
   364     def buildURLOptions(self, options, env):
   365         self.localLog = options.logFile
   366         options.logFile = self.remoteLog
   367         options.profilePath = self.localProfile
   368         env["MOZ_HIDE_RESULTS_TABLE"] = "1"
   369         retVal = Mochitest.buildURLOptions(self, options, env)
   371         if not options.robocopIni:
   372             #we really need testConfig.js (for browser chrome)
   373             try:
   374                 self._dm.pushDir(options.profilePath, self.remoteProfile)
   375             except devicemanager.DMError:
   376                 log.error("Automation Error: Unable to copy profile to device.")
   377                 raise
   379         options.profilePath = self.remoteProfile
   380         options.logFile = self.localLog
   381         return retVal
   383     def buildTestPath(self, options):
   384         if options.robocopIni != "":
   385             # Skip over manifest building if we just want to run
   386             # robocop tests.
   387             return self.buildTestURL(options)
   388         else:
   389             return super(MochiRemote, self).buildTestPath(options)
   391     def installChromeFile(self, filename, options):
   392         parts = options.app.split('/')
   393         if (parts[0] == options.app):
   394           return "NO_CHROME_ON_DROID"
   395         path = '/'.join(parts[:-1])
   396         manifest = path + "/chrome/" + os.path.basename(filename)
   397         try:
   398             self._dm.pushFile(filename, manifest)
   399         except devicemanager.DMError:
   400             log.error("Automation Error: Unable to install Chrome files on device.")
   401             raise
   403         return manifest
   405     def getLogFilePath(self, logFile):
   406         return logFile
   408     # In the future we could use LogParser: http://hg.mozilla.org/automation/logparser/
   409     def addLogData(self):
   410         with open(self.localLog) as currentLog:
   411             data = currentLog.readlines()
   413         restart = re.compile('0 INFO SimpleTest START.*')
   414         reend = re.compile('([0-9]+) INFO TEST-START . Shutdown.*')
   415         refail = re.compile('([0-9]+) INFO TEST-UNEXPECTED-FAIL.*')
   416         start_found = False
   417         end_found = False
   418         fail_found = False
   419         for line in data:
   420             if reend.match(line):
   421                 end_found = True
   422                 start_found = False
   423                 break
   425             if start_found and not end_found:
   426                 # Append the line without the number to increment
   427                 self.logLines.append(' '.join(line.split(' ')[1:]))
   429             if restart.match(line):
   430                 start_found = True
   431             if refail.match(line):
   432                 fail_found = True
   433         result = 0
   434         if fail_found:
   435             result = 1
   436         if not end_found:
   437             log.error("Automation Error: Missing end of test marker (process crashed?)")
   438             result = 1
   439         return result
   441     def printLog(self):
   442         passed = 0
   443         failed = 0
   444         todo = 0
   445         incr = 1
   446         logFile = []
   447         logFile.append("0 INFO SimpleTest START")
   448         for line in self.logLines:
   449             if line.startswith("INFO TEST-PASS"):
   450                 passed += 1
   451             elif line.startswith("INFO TEST-UNEXPECTED"):
   452                 failed += 1
   453             elif line.startswith("INFO TEST-KNOWN"):
   454                 todo += 1
   455             incr += 1
   457         logFile.append("%s INFO TEST-START | Shutdown" % incr)
   458         incr += 1
   459         logFile.append("%s INFO Passed: %s" % (incr, passed))
   460         incr += 1
   461         logFile.append("%s INFO Failed: %s" % (incr, failed))
   462         incr += 1
   463         logFile.append("%s INFO Todo: %s" % (incr, todo))
   464         incr += 1
   465         logFile.append("%s INFO SimpleTest FINISHED" % incr)
   467         # TODO: Consider not printing to stdout because we might be duplicating output
   468         print '\n'.join(logFile)
   469         with open(self.localLog, 'w') as localLog:
   470             localLog.write('\n'.join(logFile))
   472         if failed > 0:
   473             return 1
   474         return 0
   476     def printScreenshots(self, screenShotDir):
   477         # TODO: This can be re-written after completion of bug 749421
   478         if not self._dm.dirExists(screenShotDir):
   479             log.info("SCREENSHOT: No ScreenShots directory available: " + screenShotDir)
   480             return
   482         printed = 0
   483         for name in self._dm.listFiles(screenShotDir):
   484             fullName = screenShotDir + "/" + name
   485             log.info("SCREENSHOT: FOUND: [%s]", fullName)
   486             try:
   487                 image = self._dm.pullFile(fullName)
   488                 encoded = base64.b64encode(image)
   489                 log.info("SCREENSHOT: data:image/jpg;base64,%s", encoded)
   490                 printed += 1
   491             except:
   492                 log.info("SCREENSHOT: Could not be parsed")
   493                 pass
   495         log.info("SCREENSHOT: TOTAL PRINTED: [%s]", printed)
   497     def printDeviceInfo(self, printLogcat=False):
   498         try:
   499             if printLogcat:
   500                 logcat = self._dm.getLogcat(filterOutRegexps=fennecLogcatFilters)
   501                 log.info('\n'+(''.join(logcat)))
   502             log.info("Device info: %s", self._dm.getInfo())
   503             log.info("Test root: %s", self._dm.getDeviceRoot())
   504         except devicemanager.DMError:
   505             log.warn("Error getting device information")
   507     def buildRobotiumConfig(self, options, browserEnv):
   508         deviceRoot = self._dm.getDeviceRoot()
   509         fHandle = tempfile.NamedTemporaryFile(suffix='.config',
   510                                               prefix='robotium-',
   511                                               dir=os.getcwd(),
   512                                               delete=False)
   513         fHandle.write("profile=%s\n" % (self.remoteProfile))
   514         fHandle.write("logfile=%s\n" % (options.remoteLogFile))
   515         fHandle.write("host=http://mochi.test:8888/tests\n")
   516         fHandle.write("rawhost=http://%s:%s/tests\n" % (options.remoteWebServer, options.httpPort))
   518         if browserEnv:
   519             envstr = ""
   520             delim = ""
   521             for key, value in browserEnv.items():
   522                 try:
   523                     value.index(',')
   524                     log.error("buildRobotiumConfig: browserEnv - Found a ',' in our value, unable to process value. key=%s,value=%s", key, value)
   525                     log.error("browserEnv=%s", browserEnv)
   526                 except ValueError, e:
   527                     envstr += "%s%s=%s" % (delim, key, value)
   528                     delim = ","
   530             fHandle.write("envvars=%s\n" % envstr)
   531         fHandle.close()
   533         self._dm.removeFile(os.path.join(deviceRoot, "robotium.config"))
   534         self._dm.pushFile(fHandle.name, os.path.join(deviceRoot, "robotium.config"))
   535         os.unlink(fHandle.name)
   537     def buildBrowserEnv(self, options, debugger=False):
   538         browserEnv = Mochitest.buildBrowserEnv(self, options, debugger=debugger)
   539         self.buildRobotiumConfig(options, browserEnv)
   540         return browserEnv
   542     def runApp(self, *args, **kwargs):
   543         """front-end automation.py's `runApp` functionality until FennecRunner is written"""
   545         # automation.py/remoteautomation `runApp` takes the profile path,
   546         # whereas runtest.py's `runApp` takes a mozprofile object.
   547         if 'profileDir' not in kwargs and 'profile' in kwargs:
   548             kwargs['profileDir'] = kwargs.pop('profile').profile
   550         # We're handling ssltunnel, so we should lie to automation.py to avoid
   551         # it trying to set up ssltunnel as well
   552         kwargs['runSSLTunnel'] = False
   554         return self._automation.runApp(*args, **kwargs)
   556 def main():
   557     auto = RemoteAutomation(None, "fennec")
   558     parser = RemoteOptions(auto)
   559     options, args = parser.parse_args()
   561     if (options.dm_trans == "adb"):
   562         if (options.deviceIP):
   563             dm = droid.DroidADB(options.deviceIP, options.devicePort, deviceRoot=options.remoteTestRoot)
   564         else:
   565             dm = droid.DroidADB(deviceRoot=options.remoteTestRoot)
   566     else:
   567          dm = droid.DroidSUT(options.deviceIP, options.devicePort, deviceRoot=options.remoteTestRoot)
   568     auto.setDeviceManager(dm)
   569     options = parser.verifyRemoteOptions(options, auto)
   570     if (options == None):
   571         log.error("Invalid options specified, use --help for a list of valid options")
   572         sys.exit(1)
   574     productPieces = options.remoteProductName.split('.')
   575     if (productPieces != None):
   576         auto.setProduct(productPieces[0])
   577     else:
   578         auto.setProduct(options.remoteProductName)
   579     auto.setAppName(options.remoteappname)
   581     mochitest = MochiRemote(auto, dm, options)
   583     options = parser.verifyOptions(options, mochitest)
   584     if (options == None):
   585         sys.exit(1)
   587     logParent = os.path.dirname(options.remoteLogFile)
   588     dm.mkDir(logParent);
   589     auto.setRemoteLog(options.remoteLogFile)
   590     auto.setServerInfo(options.webServer, options.httpPort, options.sslPort)
   592     mochitest.printDeviceInfo()
   594     # Add Android version (SDK level) to mozinfo so that manifest entries
   595     # can be conditional on android_version.
   596     androidVersion = dm.shellCheckOutput(['getprop', 'ro.build.version.sdk'])
   597     log.info("Android sdk version '%s'; will use this to filter manifests" % str(androidVersion))
   598     mozinfo.info['android_version'] = androidVersion
   600     deviceRoot = dm.getDeviceRoot()
   601     if options.dmdPath:
   602         dmdLibrary = "libdmd.so"
   603         dmdPathOnDevice = os.path.join(deviceRoot, dmdLibrary)
   604         dm.removeFile(dmdPathOnDevice)
   605         dm.pushFile(os.path.join(options.dmdPath, dmdLibrary), dmdPathOnDevice)
   606         options.dmdPath = deviceRoot
   608     options.dumpOutputDirectory = deviceRoot
   610     procName = options.app.split('/')[-1]
   611     dm.killProcess(procName)
   613     if options.robocopIni != "":
   614         # sut may wait up to 300 s for a robocop am process before returning
   615         dm.default_timeout = 320
   616         mp = manifestparser.TestManifest(strict=False)
   617         # TODO: pull this in dynamically
   618         mp.read(options.robocopIni)
   619         robocop_tests = mp.active_tests(exists=False, **mozinfo.info)
   620         tests = []
   621         my_tests = tests
   622         for test in robocop_tests:
   623             tests.append(test['name'])
   625         if options.totalChunks:
   626             tests_per_chunk = math.ceil(len(tests) / (options.totalChunks * 1.0))
   627             start = int(round((options.thisChunk-1) * tests_per_chunk))
   628             end = int(round(options.thisChunk * tests_per_chunk))
   629             if end > len(tests):
   630                 end = len(tests)
   631             my_tests = tests[start:end]
   632             log.info("Running tests %d-%d/%d", start+1, end, len(tests))
   634         dm.removeFile(os.path.join(deviceRoot, "fennec_ids.txt"))
   635         fennec_ids = os.path.abspath(os.path.join(SCRIPT_DIR, "fennec_ids.txt"))
   636         if not os.path.exists(fennec_ids) and options.robocopIds:
   637             fennec_ids = options.robocopIds
   638         dm.pushFile(fennec_ids, os.path.join(deviceRoot, "fennec_ids.txt"))
   639         options.extraPrefs.append('browser.search.suggest.enabled=true')
   640         options.extraPrefs.append('browser.search.suggest.prompted=true')
   641         options.extraPrefs.append('layout.css.devPixelsPerPx=1.0')
   642         options.extraPrefs.append('browser.chrome.dynamictoolbar=false')
   643         options.extraPrefs.append('browser.snippets.enabled=false')
   645         if (options.dm_trans == 'adb' and options.robocopApk):
   646             dm._checkCmd(["install", "-r", options.robocopApk])
   648         retVal = None
   649         for test in robocop_tests:
   650             if options.testPath and options.testPath != test['name']:
   651                 continue
   653             if not test['name'] in my_tests:
   654                 continue
   656             if 'disabled' in test:
   657                 log.info('TEST-INFO | skipping %s | %s' % (test['name'], test['disabled']))
   658                 continue
   660             # When running in a loop, we need to create a fresh profile for each cycle
   661             if mochitest.localProfile:
   662                 options.profilePath = mochitest.localProfile
   663                 os.system("rm -Rf %s" % options.profilePath)
   664                 options.profilePath = tempfile.mkdtemp()
   665                 mochitest.localProfile = options.profilePath
   667             options.app = "am"
   668             options.browserArgs = ["instrument", "-w", "-e", "deviceroot", deviceRoot, "-e", "class"]
   669             options.browserArgs.append("org.mozilla.gecko.tests.%s" % test['name'])
   670             options.browserArgs.append("org.mozilla.roboexample.test/org.mozilla.gecko.FennecInstrumentationTestRunner")
   672             # If the test is for checking the import from bookmarks then make sure there is data to import
   673             if test['name'] == "testImportFromAndroid":
   675                 # Get the OS so we can run the insert in the apropriate database and following the correct table schema
   676                 osInfo = dm.getInfo("os")
   677                 devOS = " ".join(osInfo['os'])
   679                 if ("pandaboard" in devOS):
   680                     delete = ['execsu', 'sqlite3', "/data/data/com.android.browser/databases/browser2.db \'delete from bookmarks where _id > 14;\'"]
   681                 else:
   682                     delete = ['execsu', 'sqlite3', "/data/data/com.android.browser/databases/browser.db \'delete from bookmarks where _id > 14;\'"]
   683                 if (options.dm_trans == "sut"):
   684                     dm._runCmds([{"cmd": " ".join(delete)}])
   686                 # Insert the bookmarks
   687                 log.info("Insert bookmarks in the default android browser database")
   688                 for i in range(20):
   689                    if ("pandaboard" in devOS):
   690                        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) + ");'"]
   691                    else:
   692                        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);'"]
   693                    if (options.dm_trans == "sut"):
   694                        dm._runCmds([{"cmd": " ".join(cmd)}])
   695             try:
   696                 screenShotDir = "/mnt/sdcard/Robotium-Screenshots"
   697                 dm.removeDir(screenShotDir)
   698                 dm.recordLogcat()
   699                 result = mochitest.runTests(options)
   700                 if result != 0:
   701                     log.error("runTests() exited with code %s", result)
   702                 log_result = mochitest.addLogData()
   703                 if result != 0 or log_result != 0:
   704                     mochitest.printDeviceInfo(printLogcat=True)
   705                     mochitest.printScreenshots(screenShotDir)
   706                 # Ensure earlier failures aren't overwritten by success on this run
   707                 if retVal is None or retVal == 0:
   708                     retVal = result
   709             except:
   710                 log.error("Automation Error: Exception caught while running tests")
   711                 traceback.print_exc()
   712                 mochitest.stopServers()
   713                 try:
   714                     mochitest.cleanup(None, options)
   715                 except devicemanager.DMError:
   716                     # device error cleaning up... oh well!
   717                     pass
   718                 retVal = 1
   719                 break
   720             finally:
   721                 # Clean-up added bookmarks
   722                 if test['name'] == "testImportFromAndroid":
   723                     if ("pandaboard" in devOS):
   724                         cmd_del = ['execsu', 'sqlite3', "/data/data/com.android.browser/databases/browser2.db \'delete from bookmarks where _id > 14;\'"]
   725                     else:
   726                         cmd_del = ['execsu', 'sqlite3', "/data/data/com.android.browser/databases/browser.db \'delete from bookmarks where _id > 14;\'"]
   727                     if (options.dm_trans == "sut"):
   728                         dm._runCmds([{"cmd": " ".join(cmd_del)}])
   729         if retVal is None:
   730             log.warn("No tests run. Did you pass an invalid TEST_PATH?")
   731             retVal = 1
   732         else:
   733             # if we didn't have some kind of error running the tests, make
   734             # sure the tests actually passed
   735             print "INFO | runtests.py | Test summary: start."
   736             overallResult = mochitest.printLog()
   737             print "INFO | runtests.py | Test summary: end."
   738             if retVal == 0:
   739                 retVal = overallResult
   740     else:
   741         try:
   742             dm.recordLogcat()
   743             retVal = mochitest.runTests(options)
   744         except:
   745             log.error("Automation Error: Exception caught while running tests")
   746             traceback.print_exc()
   747             mochitest.stopServers()
   748             try:
   749                 mochitest.cleanup(None, options)
   750             except devicemanager.DMError:
   751                 # device error cleaning up... oh well!
   752                 pass
   753             retVal = 1
   755     mochitest.printDeviceInfo(printLogcat=True)
   757     sys.exit(retVal)
   759 if __name__ == "__main__":
   760     main()

mercurial