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()