michael@0: # This Source Code Form is subject to the terms of the Mozilla Public michael@0: # License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: # file, You can obtain one at http://mozilla.org/MPL/2.0/. michael@0: michael@0: import sys michael@0: import os michael@0: import time michael@0: import tempfile michael@0: import re michael@0: import traceback michael@0: import shutil michael@0: import math michael@0: import base64 michael@0: michael@0: sys.path.insert(0, os.path.abspath(os.path.realpath(os.path.dirname(__file__)))) michael@0: michael@0: from automation import Automation michael@0: from remoteautomation import RemoteAutomation, fennecLogcatFilters michael@0: from runtests import Mochitest michael@0: from runtests import MochitestServer michael@0: from mochitest_options import MochitestOptions michael@0: michael@0: import devicemanager michael@0: import droid michael@0: import manifestparser michael@0: import mozinfo michael@0: import mozlog michael@0: import moznetwork michael@0: michael@0: SCRIPT_DIR = os.path.abspath(os.path.realpath(os.path.dirname(__file__))) michael@0: log = mozlog.getLogger('Mochi-Remote') michael@0: michael@0: class RemoteOptions(MochitestOptions): michael@0: michael@0: def __init__(self, automation, **kwargs): michael@0: defaults = {} michael@0: self._automation = automation or Automation() michael@0: MochitestOptions.__init__(self) michael@0: michael@0: self.add_option("--remote-app-path", action="store", michael@0: type = "string", dest = "remoteAppPath", michael@0: help = "Path to remote executable relative to device root using only forward slashes. Either this or app must be specified but not both") michael@0: defaults["remoteAppPath"] = None michael@0: michael@0: self.add_option("--deviceIP", action="store", michael@0: type = "string", dest = "deviceIP", michael@0: help = "ip address of remote device to test") michael@0: defaults["deviceIP"] = None michael@0: michael@0: self.add_option("--dm_trans", action="store", michael@0: type = "string", dest = "dm_trans", michael@0: help = "the transport to use to communicate with device: [adb|sut]; default=sut") michael@0: defaults["dm_trans"] = "sut" michael@0: michael@0: self.add_option("--devicePort", action="store", michael@0: type = "string", dest = "devicePort", michael@0: help = "port of remote device to test") michael@0: defaults["devicePort"] = 20701 michael@0: michael@0: self.add_option("--remote-product-name", action="store", michael@0: type = "string", dest = "remoteProductName", michael@0: help = "The executable's name of remote product to test - either fennec or firefox, defaults to fennec") michael@0: defaults["remoteProductName"] = "fennec" michael@0: michael@0: self.add_option("--remote-logfile", action="store", michael@0: type = "string", dest = "remoteLogFile", michael@0: help = "Name of log file on the device relative to the device root. PLEASE ONLY USE A FILENAME.") michael@0: defaults["remoteLogFile"] = None michael@0: michael@0: self.add_option("--remote-webserver", action = "store", michael@0: type = "string", dest = "remoteWebServer", michael@0: help = "ip address where the remote web server is hosted at") michael@0: defaults["remoteWebServer"] = None michael@0: michael@0: self.add_option("--http-port", action = "store", michael@0: type = "string", dest = "httpPort", michael@0: help = "http port of the remote web server") michael@0: defaults["httpPort"] = automation.DEFAULT_HTTP_PORT michael@0: michael@0: self.add_option("--ssl-port", action = "store", michael@0: type = "string", dest = "sslPort", michael@0: help = "ssl port of the remote web server") michael@0: defaults["sslPort"] = automation.DEFAULT_SSL_PORT michael@0: michael@0: self.add_option("--robocop-ini", action = "store", michael@0: type = "string", dest = "robocopIni", michael@0: help = "name of the .ini file containing the list of tests to run") michael@0: defaults["robocopIni"] = "" michael@0: michael@0: self.add_option("--robocop", action = "store", michael@0: type = "string", dest = "robocop", michael@0: help = "name of the .ini file containing the list of tests to run. [DEPRECATED- please use --robocop-ini") michael@0: defaults["robocop"] = "" michael@0: michael@0: self.add_option("--robocop-apk", action = "store", michael@0: type = "string", dest = "robocopApk", michael@0: help = "name of the Robocop APK to use for ADB test running") michael@0: defaults["robocopApk"] = "" michael@0: michael@0: self.add_option("--robocop-path", action = "store", michael@0: type = "string", dest = "robocopPath", michael@0: help = "Path to the folder where robocop.apk is located at. Primarily used for ADB test running. [DEPRECATED- please use --robocop-apk]") michael@0: defaults["robocopPath"] = "" michael@0: michael@0: self.add_option("--robocop-ids", action = "store", michael@0: type = "string", dest = "robocopIds", michael@0: help = "name of the file containing the view ID map (fennec_ids.txt)") michael@0: defaults["robocopIds"] = "" michael@0: michael@0: self.add_option("--remoteTestRoot", action = "store", michael@0: type = "string", dest = "remoteTestRoot", michael@0: help = "remote directory to use as test root (eg. /mnt/sdcard/tests or /data/local/tests)") michael@0: defaults["remoteTestRoot"] = None michael@0: michael@0: defaults["logFile"] = "mochitest.log" michael@0: defaults["autorun"] = True michael@0: defaults["closeWhenDone"] = True michael@0: defaults["testPath"] = "" michael@0: defaults["app"] = None michael@0: defaults["utilityPath"] = None michael@0: michael@0: self.set_defaults(**defaults) michael@0: michael@0: def verifyRemoteOptions(self, options, automation): michael@0: if not options.remoteTestRoot: michael@0: options.remoteTestRoot = automation._devicemanager.getDeviceRoot() michael@0: michael@0: if options.remoteWebServer == None: michael@0: if os.name != "nt": michael@0: options.remoteWebServer = moznetwork.get_ip() michael@0: else: michael@0: log.error("you must specify a --remote-webserver=") michael@0: return None michael@0: michael@0: options.webServer = options.remoteWebServer michael@0: michael@0: if (options.deviceIP == None): michael@0: log.error("you must provide a device IP") michael@0: return None michael@0: michael@0: if (options.remoteLogFile == None): michael@0: options.remoteLogFile = options.remoteTestRoot + '/logs/mochitest.log' michael@0: michael@0: if (options.remoteLogFile.count('/') < 1): michael@0: options.remoteLogFile = options.remoteTestRoot + '/' + options.remoteLogFile michael@0: michael@0: # remoteAppPath or app must be specified to find the product to launch michael@0: if (options.remoteAppPath and options.app): michael@0: log.error("You cannot specify both the remoteAppPath and the app setting") michael@0: return None michael@0: elif (options.remoteAppPath): michael@0: options.app = options.remoteTestRoot + "/" + options.remoteAppPath michael@0: elif (options.app == None): michael@0: # Neither remoteAppPath nor app are set -- error michael@0: log.error("You must specify either appPath or app") michael@0: return None michael@0: michael@0: # Only reset the xrePath if it wasn't provided michael@0: if (options.xrePath == None): michael@0: options.xrePath = options.utilityPath michael@0: michael@0: if (options.pidFile != ""): michael@0: f = open(options.pidFile, 'w') michael@0: f.write("%s" % os.getpid()) michael@0: f.close() michael@0: michael@0: # Robocop specific deprecated options. michael@0: if options.robocop: michael@0: if options.robocopIni: michael@0: log.error("can not use deprecated --robocop and replacement --robocop-ini together") michael@0: return None michael@0: options.robocopIni = options.robocop michael@0: del options.robocop michael@0: michael@0: if options.robocopPath: michael@0: if options.robocopApk: michael@0: log.error("can not use deprecated --robocop-path and replacement --robocop-apk together") michael@0: return None michael@0: options.robocopApk = os.path.join(options.robocopPath, 'robocop.apk') michael@0: del options.robocopPath michael@0: michael@0: # Robocop specific options michael@0: if options.robocopIni != "": michael@0: if not os.path.exists(options.robocopIni): michael@0: log.error("Unable to find specified robocop .ini manifest '%s'", options.robocopIni) michael@0: return None michael@0: options.robocopIni = os.path.abspath(options.robocopIni) michael@0: michael@0: if options.robocopApk != "": michael@0: if not os.path.exists(options.robocopApk): michael@0: log.error("Unable to find robocop APK '%s'", options.robocopApk) michael@0: return None michael@0: options.robocopApk = os.path.abspath(options.robocopApk) michael@0: michael@0: if options.robocopIds != "": michael@0: if not os.path.exists(options.robocopIds): michael@0: log.error("Unable to find specified robocop IDs file '%s'", options.robocopIds) michael@0: return None michael@0: options.robocopIds = os.path.abspath(options.robocopIds) michael@0: michael@0: # allow us to keep original application around for cleanup while running robocop via 'am' michael@0: options.remoteappname = options.app michael@0: return options michael@0: michael@0: def verifyOptions(self, options, mochitest): michael@0: # since we are reusing verifyOptions, it will exit if App is not found michael@0: temp = options.app michael@0: options.app = __file__ michael@0: tempPort = options.httpPort michael@0: tempSSL = options.sslPort michael@0: tempIP = options.webServer michael@0: # We are going to override this option later anyway, just pretend michael@0: # like it's not set for verification purposes. michael@0: options.dumpOutputDirectory = None michael@0: options = MochitestOptions.verifyOptions(self, options, mochitest) michael@0: options.webServer = tempIP michael@0: options.app = temp michael@0: options.sslPort = tempSSL michael@0: options.httpPort = tempPort michael@0: michael@0: return options michael@0: michael@0: class MochiRemote(Mochitest): michael@0: michael@0: _automation = None michael@0: _dm = None michael@0: localProfile = None michael@0: logLines = [] michael@0: michael@0: def __init__(self, automation, devmgr, options): michael@0: self._automation = automation michael@0: Mochitest.__init__(self) michael@0: self._dm = devmgr michael@0: self.environment = self._automation.environment michael@0: self.remoteProfile = options.remoteTestRoot + "/profile" michael@0: self._automation.setRemoteProfile(self.remoteProfile) michael@0: self.remoteLog = options.remoteLogFile michael@0: self.localLog = options.logFile michael@0: self._automation.deleteANRs() michael@0: self.certdbNew = True michael@0: michael@0: def cleanup(self, manifest, options): michael@0: if self._dm.fileExists(self.remoteLog): michael@0: self._dm.getFile(self.remoteLog, self.localLog) michael@0: self._dm.removeFile(self.remoteLog) michael@0: else: michael@0: log.warn("Unable to retrieve log file (%s) from remote device", michael@0: self.remoteLog) michael@0: self._dm.removeDir(self.remoteProfile) michael@0: Mochitest.cleanup(self, manifest, options) michael@0: michael@0: def findPath(self, paths, filename = None): michael@0: for path in paths: michael@0: p = path michael@0: if filename: michael@0: p = os.path.join(p, filename) michael@0: if os.path.exists(self.getFullPath(p)): michael@0: return path michael@0: return None michael@0: michael@0: def makeLocalAutomation(self): michael@0: localAutomation = Automation() michael@0: localAutomation.IS_WIN32 = False michael@0: localAutomation.IS_LINUX = False michael@0: localAutomation.IS_MAC = False michael@0: localAutomation.UNIXISH = False michael@0: hostos = sys.platform michael@0: if (hostos == 'mac' or hostos == 'darwin'): michael@0: localAutomation.IS_MAC = True michael@0: elif (hostos == 'linux' or hostos == 'linux2'): michael@0: localAutomation.IS_LINUX = True michael@0: localAutomation.UNIXISH = True michael@0: elif (hostos == 'win32' or hostos == 'win64'): michael@0: localAutomation.BIN_SUFFIX = ".exe" michael@0: localAutomation.IS_WIN32 = True michael@0: return localAutomation michael@0: michael@0: # This seems kludgy, but this class uses paths from the remote host in the michael@0: # options, except when calling up to the base class, which doesn't michael@0: # understand the distinction. This switches out the remote values for local michael@0: # ones that the base class understands. This is necessary for the web michael@0: # server, SSL tunnel and profile building functions. michael@0: def switchToLocalPaths(self, options): michael@0: """ Set local paths in the options, return a function that will restore remote values """ michael@0: remoteXrePath = options.xrePath michael@0: remoteProfilePath = options.profilePath michael@0: remoteUtilityPath = options.utilityPath michael@0: michael@0: localAutomation = self.makeLocalAutomation() michael@0: paths = [ michael@0: options.xrePath, michael@0: localAutomation.DIST_BIN, michael@0: self._automation._product, michael@0: os.path.join('..', self._automation._product) michael@0: ] michael@0: options.xrePath = self.findPath(paths) michael@0: if options.xrePath == None: michael@0: log.error("unable to find xulrunner path for %s, please specify with --xre-path", os.name) michael@0: sys.exit(1) michael@0: michael@0: xpcshell = "xpcshell" michael@0: if (os.name == "nt"): michael@0: xpcshell += ".exe" michael@0: michael@0: if options.utilityPath: michael@0: paths = [options.utilityPath, options.xrePath] michael@0: else: michael@0: paths = [options.xrePath] michael@0: options.utilityPath = self.findPath(paths, xpcshell) michael@0: michael@0: if options.utilityPath == None: michael@0: log.error("unable to find utility path for %s, please specify with --utility-path", os.name) michael@0: sys.exit(1) michael@0: michael@0: xpcshell_path = os.path.join(options.utilityPath, xpcshell) michael@0: if localAutomation.elf_arm(xpcshell_path): michael@0: log.error('xpcshell at %s is an ARM binary; please use ' michael@0: 'the --utility-path argument to specify the path ' michael@0: 'to a desktop version.' % xpcshell_path) michael@0: sys.exit(1) michael@0: michael@0: if self.localProfile: michael@0: options.profilePath = self.localProfile michael@0: else: michael@0: options.profilePath = tempfile.mkdtemp() michael@0: michael@0: def fixup(): michael@0: options.xrePath = remoteXrePath michael@0: options.utilityPath = remoteUtilityPath michael@0: options.profilePath = remoteProfilePath michael@0: michael@0: return fixup michael@0: michael@0: def startServers(self, options, debuggerInfo): michael@0: """ Create the servers on the host and start them up """ michael@0: restoreRemotePaths = self.switchToLocalPaths(options) michael@0: Mochitest.startServers(self, options, debuggerInfo) michael@0: restoreRemotePaths() michael@0: michael@0: def buildProfile(self, options): michael@0: restoreRemotePaths = self.switchToLocalPaths(options) michael@0: manifest = Mochitest.buildProfile(self, options) michael@0: self.localProfile = options.profilePath michael@0: self._dm.removeDir(self.remoteProfile) michael@0: michael@0: # we do not need this for robotium based tests, lets save a LOT of time michael@0: if options.robocopIni: michael@0: shutil.rmtree(os.path.join(options.profilePath, 'webapps')) michael@0: shutil.rmtree(os.path.join(options.profilePath, 'extensions', 'staged', 'mochikit@mozilla.org')) michael@0: shutil.rmtree(os.path.join(options.profilePath, 'extensions', 'staged', 'worker-test@mozilla.org')) michael@0: shutil.rmtree(os.path.join(options.profilePath, 'extensions', 'staged', 'workerbootstrap-test@mozilla.org')) michael@0: os.remove(os.path.join(options.profilePath, 'userChrome.css')) michael@0: michael@0: try: michael@0: self._dm.pushDir(options.profilePath, self.remoteProfile) michael@0: except devicemanager.DMError: michael@0: log.error("Automation Error: Unable to copy profile to device.") michael@0: raise michael@0: michael@0: restoreRemotePaths() michael@0: options.profilePath = self.remoteProfile michael@0: return manifest michael@0: michael@0: def buildURLOptions(self, options, env): michael@0: self.localLog = options.logFile michael@0: options.logFile = self.remoteLog michael@0: options.profilePath = self.localProfile michael@0: env["MOZ_HIDE_RESULTS_TABLE"] = "1" michael@0: retVal = Mochitest.buildURLOptions(self, options, env) michael@0: michael@0: if not options.robocopIni: michael@0: #we really need testConfig.js (for browser chrome) michael@0: try: michael@0: self._dm.pushDir(options.profilePath, self.remoteProfile) michael@0: except devicemanager.DMError: michael@0: log.error("Automation Error: Unable to copy profile to device.") michael@0: raise michael@0: michael@0: options.profilePath = self.remoteProfile michael@0: options.logFile = self.localLog michael@0: return retVal michael@0: michael@0: def buildTestPath(self, options): michael@0: if options.robocopIni != "": michael@0: # Skip over manifest building if we just want to run michael@0: # robocop tests. michael@0: return self.buildTestURL(options) michael@0: else: michael@0: return super(MochiRemote, self).buildTestPath(options) michael@0: michael@0: def installChromeFile(self, filename, options): michael@0: parts = options.app.split('/') michael@0: if (parts[0] == options.app): michael@0: return "NO_CHROME_ON_DROID" michael@0: path = '/'.join(parts[:-1]) michael@0: manifest = path + "/chrome/" + os.path.basename(filename) michael@0: try: michael@0: self._dm.pushFile(filename, manifest) michael@0: except devicemanager.DMError: michael@0: log.error("Automation Error: Unable to install Chrome files on device.") michael@0: raise michael@0: michael@0: return manifest michael@0: michael@0: def getLogFilePath(self, logFile): michael@0: return logFile michael@0: michael@0: # In the future we could use LogParser: http://hg.mozilla.org/automation/logparser/ michael@0: def addLogData(self): michael@0: with open(self.localLog) as currentLog: michael@0: data = currentLog.readlines() michael@0: michael@0: restart = re.compile('0 INFO SimpleTest START.*') michael@0: reend = re.compile('([0-9]+) INFO TEST-START . Shutdown.*') michael@0: refail = re.compile('([0-9]+) INFO TEST-UNEXPECTED-FAIL.*') michael@0: start_found = False michael@0: end_found = False michael@0: fail_found = False michael@0: for line in data: michael@0: if reend.match(line): michael@0: end_found = True michael@0: start_found = False michael@0: break michael@0: michael@0: if start_found and not end_found: michael@0: # Append the line without the number to increment michael@0: self.logLines.append(' '.join(line.split(' ')[1:])) michael@0: michael@0: if restart.match(line): michael@0: start_found = True michael@0: if refail.match(line): michael@0: fail_found = True michael@0: result = 0 michael@0: if fail_found: michael@0: result = 1 michael@0: if not end_found: michael@0: log.error("Automation Error: Missing end of test marker (process crashed?)") michael@0: result = 1 michael@0: return result michael@0: michael@0: def printLog(self): michael@0: passed = 0 michael@0: failed = 0 michael@0: todo = 0 michael@0: incr = 1 michael@0: logFile = [] michael@0: logFile.append("0 INFO SimpleTest START") michael@0: for line in self.logLines: michael@0: if line.startswith("INFO TEST-PASS"): michael@0: passed += 1 michael@0: elif line.startswith("INFO TEST-UNEXPECTED"): michael@0: failed += 1 michael@0: elif line.startswith("INFO TEST-KNOWN"): michael@0: todo += 1 michael@0: incr += 1 michael@0: michael@0: logFile.append("%s INFO TEST-START | Shutdown" % incr) michael@0: incr += 1 michael@0: logFile.append("%s INFO Passed: %s" % (incr, passed)) michael@0: incr += 1 michael@0: logFile.append("%s INFO Failed: %s" % (incr, failed)) michael@0: incr += 1 michael@0: logFile.append("%s INFO Todo: %s" % (incr, todo)) michael@0: incr += 1 michael@0: logFile.append("%s INFO SimpleTest FINISHED" % incr) michael@0: michael@0: # TODO: Consider not printing to stdout because we might be duplicating output michael@0: print '\n'.join(logFile) michael@0: with open(self.localLog, 'w') as localLog: michael@0: localLog.write('\n'.join(logFile)) michael@0: michael@0: if failed > 0: michael@0: return 1 michael@0: return 0 michael@0: michael@0: def printScreenshots(self, screenShotDir): michael@0: # TODO: This can be re-written after completion of bug 749421 michael@0: if not self._dm.dirExists(screenShotDir): michael@0: log.info("SCREENSHOT: No ScreenShots directory available: " + screenShotDir) michael@0: return michael@0: michael@0: printed = 0 michael@0: for name in self._dm.listFiles(screenShotDir): michael@0: fullName = screenShotDir + "/" + name michael@0: log.info("SCREENSHOT: FOUND: [%s]", fullName) michael@0: try: michael@0: image = self._dm.pullFile(fullName) michael@0: encoded = base64.b64encode(image) michael@0: log.info("SCREENSHOT: data:image/jpg;base64,%s", encoded) michael@0: printed += 1 michael@0: except: michael@0: log.info("SCREENSHOT: Could not be parsed") michael@0: pass michael@0: michael@0: log.info("SCREENSHOT: TOTAL PRINTED: [%s]", printed) michael@0: michael@0: def printDeviceInfo(self, printLogcat=False): michael@0: try: michael@0: if printLogcat: michael@0: logcat = self._dm.getLogcat(filterOutRegexps=fennecLogcatFilters) michael@0: log.info('\n'+(''.join(logcat))) michael@0: log.info("Device info: %s", self._dm.getInfo()) michael@0: log.info("Test root: %s", self._dm.getDeviceRoot()) michael@0: except devicemanager.DMError: michael@0: log.warn("Error getting device information") michael@0: michael@0: def buildRobotiumConfig(self, options, browserEnv): michael@0: deviceRoot = self._dm.getDeviceRoot() michael@0: fHandle = tempfile.NamedTemporaryFile(suffix='.config', michael@0: prefix='robotium-', michael@0: dir=os.getcwd(), michael@0: delete=False) michael@0: fHandle.write("profile=%s\n" % (self.remoteProfile)) michael@0: fHandle.write("logfile=%s\n" % (options.remoteLogFile)) michael@0: fHandle.write("host=http://mochi.test:8888/tests\n") michael@0: fHandle.write("rawhost=http://%s:%s/tests\n" % (options.remoteWebServer, options.httpPort)) michael@0: michael@0: if browserEnv: michael@0: envstr = "" michael@0: delim = "" michael@0: for key, value in browserEnv.items(): michael@0: try: michael@0: value.index(',') michael@0: log.error("buildRobotiumConfig: browserEnv - Found a ',' in our value, unable to process value. key=%s,value=%s", key, value) michael@0: log.error("browserEnv=%s", browserEnv) michael@0: except ValueError, e: michael@0: envstr += "%s%s=%s" % (delim, key, value) michael@0: delim = "," michael@0: michael@0: fHandle.write("envvars=%s\n" % envstr) michael@0: fHandle.close() michael@0: michael@0: self._dm.removeFile(os.path.join(deviceRoot, "robotium.config")) michael@0: self._dm.pushFile(fHandle.name, os.path.join(deviceRoot, "robotium.config")) michael@0: os.unlink(fHandle.name) michael@0: michael@0: def buildBrowserEnv(self, options, debugger=False): michael@0: browserEnv = Mochitest.buildBrowserEnv(self, options, debugger=debugger) michael@0: self.buildRobotiumConfig(options, browserEnv) michael@0: return browserEnv michael@0: michael@0: def runApp(self, *args, **kwargs): michael@0: """front-end automation.py's `runApp` functionality until FennecRunner is written""" michael@0: michael@0: # automation.py/remoteautomation `runApp` takes the profile path, michael@0: # whereas runtest.py's `runApp` takes a mozprofile object. michael@0: if 'profileDir' not in kwargs and 'profile' in kwargs: michael@0: kwargs['profileDir'] = kwargs.pop('profile').profile michael@0: michael@0: # We're handling ssltunnel, so we should lie to automation.py to avoid michael@0: # it trying to set up ssltunnel as well michael@0: kwargs['runSSLTunnel'] = False michael@0: michael@0: return self._automation.runApp(*args, **kwargs) michael@0: michael@0: def main(): michael@0: auto = RemoteAutomation(None, "fennec") michael@0: parser = RemoteOptions(auto) michael@0: options, args = parser.parse_args() michael@0: michael@0: if (options.dm_trans == "adb"): michael@0: if (options.deviceIP): michael@0: dm = droid.DroidADB(options.deviceIP, options.devicePort, deviceRoot=options.remoteTestRoot) michael@0: else: michael@0: dm = droid.DroidADB(deviceRoot=options.remoteTestRoot) michael@0: else: michael@0: dm = droid.DroidSUT(options.deviceIP, options.devicePort, deviceRoot=options.remoteTestRoot) michael@0: auto.setDeviceManager(dm) michael@0: options = parser.verifyRemoteOptions(options, auto) michael@0: if (options == None): michael@0: log.error("Invalid options specified, use --help for a list of valid options") michael@0: sys.exit(1) michael@0: michael@0: productPieces = options.remoteProductName.split('.') michael@0: if (productPieces != None): michael@0: auto.setProduct(productPieces[0]) michael@0: else: michael@0: auto.setProduct(options.remoteProductName) michael@0: auto.setAppName(options.remoteappname) michael@0: michael@0: mochitest = MochiRemote(auto, dm, options) michael@0: michael@0: options = parser.verifyOptions(options, mochitest) michael@0: if (options == None): michael@0: sys.exit(1) michael@0: michael@0: logParent = os.path.dirname(options.remoteLogFile) michael@0: dm.mkDir(logParent); michael@0: auto.setRemoteLog(options.remoteLogFile) michael@0: auto.setServerInfo(options.webServer, options.httpPort, options.sslPort) michael@0: michael@0: mochitest.printDeviceInfo() michael@0: michael@0: # Add Android version (SDK level) to mozinfo so that manifest entries michael@0: # can be conditional on android_version. michael@0: androidVersion = dm.shellCheckOutput(['getprop', 'ro.build.version.sdk']) michael@0: log.info("Android sdk version '%s'; will use this to filter manifests" % str(androidVersion)) michael@0: mozinfo.info['android_version'] = androidVersion michael@0: michael@0: deviceRoot = dm.getDeviceRoot() michael@0: if options.dmdPath: michael@0: dmdLibrary = "libdmd.so" michael@0: dmdPathOnDevice = os.path.join(deviceRoot, dmdLibrary) michael@0: dm.removeFile(dmdPathOnDevice) michael@0: dm.pushFile(os.path.join(options.dmdPath, dmdLibrary), dmdPathOnDevice) michael@0: options.dmdPath = deviceRoot michael@0: michael@0: options.dumpOutputDirectory = deviceRoot michael@0: michael@0: procName = options.app.split('/')[-1] michael@0: dm.killProcess(procName) michael@0: michael@0: if options.robocopIni != "": michael@0: # sut may wait up to 300 s for a robocop am process before returning michael@0: dm.default_timeout = 320 michael@0: mp = manifestparser.TestManifest(strict=False) michael@0: # TODO: pull this in dynamically michael@0: mp.read(options.robocopIni) michael@0: robocop_tests = mp.active_tests(exists=False, **mozinfo.info) michael@0: tests = [] michael@0: my_tests = tests michael@0: for test in robocop_tests: michael@0: tests.append(test['name']) michael@0: michael@0: if options.totalChunks: michael@0: tests_per_chunk = math.ceil(len(tests) / (options.totalChunks * 1.0)) michael@0: start = int(round((options.thisChunk-1) * tests_per_chunk)) michael@0: end = int(round(options.thisChunk * tests_per_chunk)) michael@0: if end > len(tests): michael@0: end = len(tests) michael@0: my_tests = tests[start:end] michael@0: log.info("Running tests %d-%d/%d", start+1, end, len(tests)) michael@0: michael@0: dm.removeFile(os.path.join(deviceRoot, "fennec_ids.txt")) michael@0: fennec_ids = os.path.abspath(os.path.join(SCRIPT_DIR, "fennec_ids.txt")) michael@0: if not os.path.exists(fennec_ids) and options.robocopIds: michael@0: fennec_ids = options.robocopIds michael@0: dm.pushFile(fennec_ids, os.path.join(deviceRoot, "fennec_ids.txt")) michael@0: options.extraPrefs.append('browser.search.suggest.enabled=true') michael@0: options.extraPrefs.append('browser.search.suggest.prompted=true') michael@0: options.extraPrefs.append('layout.css.devPixelsPerPx=1.0') michael@0: options.extraPrefs.append('browser.chrome.dynamictoolbar=false') michael@0: options.extraPrefs.append('browser.snippets.enabled=false') michael@0: michael@0: if (options.dm_trans == 'adb' and options.robocopApk): michael@0: dm._checkCmd(["install", "-r", options.robocopApk]) michael@0: michael@0: retVal = None michael@0: for test in robocop_tests: michael@0: if options.testPath and options.testPath != test['name']: michael@0: continue michael@0: michael@0: if not test['name'] in my_tests: michael@0: continue michael@0: michael@0: if 'disabled' in test: michael@0: log.info('TEST-INFO | skipping %s | %s' % (test['name'], test['disabled'])) michael@0: continue michael@0: michael@0: # When running in a loop, we need to create a fresh profile for each cycle michael@0: if mochitest.localProfile: michael@0: options.profilePath = mochitest.localProfile michael@0: os.system("rm -Rf %s" % options.profilePath) michael@0: options.profilePath = tempfile.mkdtemp() michael@0: mochitest.localProfile = options.profilePath michael@0: michael@0: options.app = "am" michael@0: options.browserArgs = ["instrument", "-w", "-e", "deviceroot", deviceRoot, "-e", "class"] michael@0: options.browserArgs.append("org.mozilla.gecko.tests.%s" % test['name']) michael@0: options.browserArgs.append("org.mozilla.roboexample.test/org.mozilla.gecko.FennecInstrumentationTestRunner") michael@0: michael@0: # If the test is for checking the import from bookmarks then make sure there is data to import michael@0: if test['name'] == "testImportFromAndroid": michael@0: michael@0: # Get the OS so we can run the insert in the apropriate database and following the correct table schema michael@0: osInfo = dm.getInfo("os") michael@0: devOS = " ".join(osInfo['os']) michael@0: michael@0: if ("pandaboard" in devOS): michael@0: delete = ['execsu', 'sqlite3', "/data/data/com.android.browser/databases/browser2.db \'delete from bookmarks where _id > 14;\'"] michael@0: else: michael@0: delete = ['execsu', 'sqlite3', "/data/data/com.android.browser/databases/browser.db \'delete from bookmarks where _id > 14;\'"] michael@0: if (options.dm_trans == "sut"): michael@0: dm._runCmds([{"cmd": " ".join(delete)}]) michael@0: michael@0: # Insert the bookmarks michael@0: log.info("Insert bookmarks in the default android browser database") michael@0: for i in range(20): michael@0: if ("pandaboard" in devOS): michael@0: 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) + ");'"] michael@0: else: michael@0: 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);'"] michael@0: if (options.dm_trans == "sut"): michael@0: dm._runCmds([{"cmd": " ".join(cmd)}]) michael@0: try: michael@0: screenShotDir = "/mnt/sdcard/Robotium-Screenshots" michael@0: dm.removeDir(screenShotDir) michael@0: dm.recordLogcat() michael@0: result = mochitest.runTests(options) michael@0: if result != 0: michael@0: log.error("runTests() exited with code %s", result) michael@0: log_result = mochitest.addLogData() michael@0: if result != 0 or log_result != 0: michael@0: mochitest.printDeviceInfo(printLogcat=True) michael@0: mochitest.printScreenshots(screenShotDir) michael@0: # Ensure earlier failures aren't overwritten by success on this run michael@0: if retVal is None or retVal == 0: michael@0: retVal = result michael@0: except: michael@0: log.error("Automation Error: Exception caught while running tests") michael@0: traceback.print_exc() michael@0: mochitest.stopServers() michael@0: try: michael@0: mochitest.cleanup(None, options) michael@0: except devicemanager.DMError: michael@0: # device error cleaning up... oh well! michael@0: pass michael@0: retVal = 1 michael@0: break michael@0: finally: michael@0: # Clean-up added bookmarks michael@0: if test['name'] == "testImportFromAndroid": michael@0: if ("pandaboard" in devOS): michael@0: cmd_del = ['execsu', 'sqlite3', "/data/data/com.android.browser/databases/browser2.db \'delete from bookmarks where _id > 14;\'"] michael@0: else: michael@0: cmd_del = ['execsu', 'sqlite3', "/data/data/com.android.browser/databases/browser.db \'delete from bookmarks where _id > 14;\'"] michael@0: if (options.dm_trans == "sut"): michael@0: dm._runCmds([{"cmd": " ".join(cmd_del)}]) michael@0: if retVal is None: michael@0: log.warn("No tests run. Did you pass an invalid TEST_PATH?") michael@0: retVal = 1 michael@0: else: michael@0: # if we didn't have some kind of error running the tests, make michael@0: # sure the tests actually passed michael@0: print "INFO | runtests.py | Test summary: start." michael@0: overallResult = mochitest.printLog() michael@0: print "INFO | runtests.py | Test summary: end." michael@0: if retVal == 0: michael@0: retVal = overallResult michael@0: else: michael@0: try: michael@0: dm.recordLogcat() michael@0: retVal = mochitest.runTests(options) michael@0: except: michael@0: log.error("Automation Error: Exception caught while running tests") michael@0: traceback.print_exc() michael@0: mochitest.stopServers() michael@0: try: michael@0: mochitest.cleanup(None, options) michael@0: except devicemanager.DMError: michael@0: # device error cleaning up... oh well! michael@0: pass michael@0: retVal = 1 michael@0: michael@0: mochitest.printDeviceInfo(printLogcat=True) michael@0: michael@0: sys.exit(retVal) michael@0: michael@0: if __name__ == "__main__": michael@0: main()